sliftutils 1.7.5 → 1.7.7
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/index.d.ts +279 -1
- package/misc/https/persistentLocalStorage.ts +2 -0
- package/package.json +1 -1
- package/render-utils/Input.tsx +2 -0
- package/render-utils/InputLabel.tsx +2 -0
- package/render-utils/colors.tsx +2 -0
- package/storage/BulkDatabase2/syncClient.ts +10 -7
- package/storage/FileFolderAPI.d.ts +1 -0
- package/storage/FileFolderAPI.tsx +36 -1
- package/storage/IArchives.d.ts +37 -0
- package/storage/IArchives.ts +20 -0
- package/storage/backblaze.d.ts +2 -1
- package/storage/backblaze.ts +2 -1
- package/storage/fileSystemPointer.d.ts +1 -0
- package/storage/fileSystemPointer.ts +30 -0
- package/storage/remoteStorage/ArchivesRemote.d.ts +67 -0
- package/storage/remoteStorage/ArchivesRemote.ts +195 -0
- package/storage/remoteStorage/accessPage.d.ts +1 -0
- package/storage/remoteStorage/accessPage.tsx +133 -0
- package/storage/remoteStorage/blobStore.d.ts +56 -0
- package/storage/remoteStorage/blobStore.ts +403 -0
- package/storage/remoteStorage/storageController.d.ts +89 -0
- package/storage/remoteStorage/storageController.ts +398 -0
- package/storage/remoteStorage/storageServer.d.ts +1 -0
- package/storage/remoteStorage/storageServer.ts +132 -0
- package/teststorage/browser.tsx +148 -0
- package/teststorage/server.ts +43 -0
- package/yarn.lock +3 -3
package/index.d.ts
CHANGED
|
@@ -1879,6 +1879,7 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1879
1879
|
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
1880
1880
|
};
|
|
1881
1881
|
export declare function setFileAPIKey(key: string): void;
|
|
1882
|
+
export declare function setStorageRootOverride(handle: FileSystemDirectoryHandle | undefined): void;
|
|
1882
1883
|
export declare class NodeJSFileHandleWrapper implements FileWrapper {
|
|
1883
1884
|
private filePath;
|
|
1884
1885
|
constructor(filePath: string);
|
|
@@ -1968,6 +1969,47 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1968
1969
|
|
|
1969
1970
|
}
|
|
1970
1971
|
|
|
1972
|
+
declare module "sliftutils/storage/IArchives" {
|
|
1973
|
+
/// <reference types="node" />
|
|
1974
|
+
/// <reference types="node" />
|
|
1975
|
+
export type ArchiveFileInfo = {
|
|
1976
|
+
path: string;
|
|
1977
|
+
createTime: number;
|
|
1978
|
+
size: number;
|
|
1979
|
+
};
|
|
1980
|
+
export interface IArchives {
|
|
1981
|
+
getDebugName(): string;
|
|
1982
|
+
get(fileName: string, config?: {
|
|
1983
|
+
range?: {
|
|
1984
|
+
start: number;
|
|
1985
|
+
end: number;
|
|
1986
|
+
};
|
|
1987
|
+
}): Promise<Buffer | undefined>;
|
|
1988
|
+
set(fileName: string, data: Buffer): Promise<void>;
|
|
1989
|
+
del(fileName: string): Promise<void>;
|
|
1990
|
+
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
1991
|
+
setLargeFile(config: {
|
|
1992
|
+
path: string;
|
|
1993
|
+
getNextData(): Promise<Buffer | undefined>;
|
|
1994
|
+
}): Promise<void>;
|
|
1995
|
+
getInfo(fileName: string): Promise<{
|
|
1996
|
+
writeTime: number;
|
|
1997
|
+
size: number;
|
|
1998
|
+
} | undefined>;
|
|
1999
|
+
find(prefix: string, config?: {
|
|
2000
|
+
shallow?: boolean;
|
|
2001
|
+
type: "files" | "folders";
|
|
2002
|
+
}): Promise<string[]>;
|
|
2003
|
+
findInfo(prefix: string, config?: {
|
|
2004
|
+
shallow?: boolean;
|
|
2005
|
+
type: "files" | "folders";
|
|
2006
|
+
}): Promise<ArchiveFileInfo[]>;
|
|
2007
|
+
/** Only works for public buckets (private buckets are API-access only). */
|
|
2008
|
+
getURL(path: string): Promise<string>;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
}
|
|
2012
|
+
|
|
1971
2013
|
declare module "sliftutils/storage/IStorage" {
|
|
1972
2014
|
/// <reference types="node" />
|
|
1973
2015
|
/// <reference types="node" />
|
|
@@ -2224,7 +2266,8 @@ declare module "sliftutils/storage/TransactionStorage" {
|
|
|
2224
2266
|
declare module "sliftutils/storage/backblaze" {
|
|
2225
2267
|
/// <reference types="node" />
|
|
2226
2268
|
/// <reference types="node" />
|
|
2227
|
-
|
|
2269
|
+
import { IArchives } from "./IArchives";
|
|
2270
|
+
export declare class ArchivesBackblaze implements IArchives {
|
|
2228
2271
|
private config;
|
|
2229
2272
|
constructor(config: {
|
|
2230
2273
|
bucketName: string;
|
|
@@ -2380,6 +2423,7 @@ declare module "sliftutils/storage/fileSystemPointer" {
|
|
|
2380
2423
|
handle: FileSystemFileHandle | FileSystemDirectoryHandle;
|
|
2381
2424
|
}): Promise<FileSystemPointer>;
|
|
2382
2425
|
export declare function deleteFileSystemPointer(pointer: FileSystemPointer): Promise<void>;
|
|
2426
|
+
export declare function findGrantedPointerHandle(mode: "read" | "readwrite"): Promise<FileSystemDirectoryHandle | undefined>;
|
|
2383
2427
|
export declare function getFileSystemPointer(config: {
|
|
2384
2428
|
pointer: FileSystemPointer;
|
|
2385
2429
|
}): Promise<{
|
|
@@ -2527,6 +2571,240 @@ declare module "sliftutils/storage/remoteFileStorage" {
|
|
|
2527
2571
|
|
|
2528
2572
|
}
|
|
2529
2573
|
|
|
2574
|
+
declare module "sliftutils/storage/remoteStorage/ArchivesRemote" {
|
|
2575
|
+
/// <reference types="node" />
|
|
2576
|
+
/// <reference types="node" />
|
|
2577
|
+
import { IArchives, ArchiveFileInfo } from "../IArchives";
|
|
2578
|
+
export type ArchivesRemoteConfig = {
|
|
2579
|
+
address: string;
|
|
2580
|
+
port: number;
|
|
2581
|
+
account: string;
|
|
2582
|
+
bucketName: string;
|
|
2583
|
+
public?: boolean;
|
|
2584
|
+
fast?: boolean;
|
|
2585
|
+
writeDelay?: number;
|
|
2586
|
+
};
|
|
2587
|
+
export declare function buildPublicFileURL(config: {
|
|
2588
|
+
address: string;
|
|
2589
|
+
port: number;
|
|
2590
|
+
account: string;
|
|
2591
|
+
bucketName: string;
|
|
2592
|
+
path: string;
|
|
2593
|
+
}): string;
|
|
2594
|
+
export declare function authenticateStorage(config: {
|
|
2595
|
+
address: string;
|
|
2596
|
+
port: number;
|
|
2597
|
+
nodeId: string;
|
|
2598
|
+
}): Promise<{
|
|
2599
|
+
machineId: string;
|
|
2600
|
+
ip: string;
|
|
2601
|
+
}>;
|
|
2602
|
+
export declare class ArchivesRemote implements IArchives {
|
|
2603
|
+
private config;
|
|
2604
|
+
constructor(config: ArchivesRemoteConfig);
|
|
2605
|
+
private nodeId;
|
|
2606
|
+
private controller;
|
|
2607
|
+
private setupDone;
|
|
2608
|
+
private lastDeniedLog;
|
|
2609
|
+
getDebugName(): string;
|
|
2610
|
+
private authenticate;
|
|
2611
|
+
private callAuthed;
|
|
2612
|
+
waitingForAccess(): Promise<string | undefined>;
|
|
2613
|
+
private onAccessDenied;
|
|
2614
|
+
private ensureSetup;
|
|
2615
|
+
private call;
|
|
2616
|
+
get(fileName: string, config?: {
|
|
2617
|
+
range?: {
|
|
2618
|
+
start: number;
|
|
2619
|
+
end: number;
|
|
2620
|
+
};
|
|
2621
|
+
}): Promise<Buffer | undefined>;
|
|
2622
|
+
set(fileName: string, data: Buffer): Promise<void>;
|
|
2623
|
+
del(fileName: string): Promise<void>;
|
|
2624
|
+
getInfo(fileName: string): Promise<{
|
|
2625
|
+
writeTime: number;
|
|
2626
|
+
size: number;
|
|
2627
|
+
} | undefined>;
|
|
2628
|
+
findInfo(prefix: string, config?: {
|
|
2629
|
+
shallow?: boolean;
|
|
2630
|
+
type: "files" | "folders";
|
|
2631
|
+
}): Promise<ArchiveFileInfo[]>;
|
|
2632
|
+
find(prefix: string, config?: {
|
|
2633
|
+
shallow?: boolean;
|
|
2634
|
+
type: "files" | "folders";
|
|
2635
|
+
}): Promise<string[]>;
|
|
2636
|
+
setLargeFile(config: {
|
|
2637
|
+
path: string;
|
|
2638
|
+
getNextData(): Promise<Buffer | undefined>;
|
|
2639
|
+
}): Promise<void>;
|
|
2640
|
+
getURL(path: string): Promise<string>;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
declare module "sliftutils/storage/remoteStorage/accessPage" {
|
|
2646
|
+
export {};
|
|
2647
|
+
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
declare module "sliftutils/storage/remoteStorage/blobStore" {
|
|
2651
|
+
/// <reference types="node" />
|
|
2652
|
+
/// <reference types="node" />
|
|
2653
|
+
import { ArchiveFileInfo } from "../IArchives";
|
|
2654
|
+
export declare const DEFAULT_FAST_WRITE_DELAY: number;
|
|
2655
|
+
export type WriteConfig = {
|
|
2656
|
+
fast?: boolean;
|
|
2657
|
+
writeDelay?: number;
|
|
2658
|
+
};
|
|
2659
|
+
export declare class BlobStore {
|
|
2660
|
+
private folder;
|
|
2661
|
+
constructor(folder: string);
|
|
2662
|
+
private memCache;
|
|
2663
|
+
private overlay;
|
|
2664
|
+
private writeQueue;
|
|
2665
|
+
private openBlobs;
|
|
2666
|
+
private largeUploads;
|
|
2667
|
+
private nextLargeUploadId;
|
|
2668
|
+
private currentBlobNumber;
|
|
2669
|
+
private currentBlobOffset;
|
|
2670
|
+
private currentBlobFd;
|
|
2671
|
+
private index;
|
|
2672
|
+
private deadBytes;
|
|
2673
|
+
private blobsDir;
|
|
2674
|
+
init: {
|
|
2675
|
+
(): Promise<void>;
|
|
2676
|
+
reset(): void;
|
|
2677
|
+
set(newValue: Promise<void>): void;
|
|
2678
|
+
};
|
|
2679
|
+
private blobName;
|
|
2680
|
+
private blobPath;
|
|
2681
|
+
private getBlobHandle;
|
|
2682
|
+
private closeBlobHandle;
|
|
2683
|
+
private addDeadBytes;
|
|
2684
|
+
private appendData;
|
|
2685
|
+
private setIndexEntry;
|
|
2686
|
+
set(key: string, data: Buffer, config?: WriteConfig): Promise<void>;
|
|
2687
|
+
del(key: string, config?: WriteConfig): Promise<void>;
|
|
2688
|
+
get(key: string, range?: {
|
|
2689
|
+
start: number;
|
|
2690
|
+
end: number;
|
|
2691
|
+
}): Promise<Buffer | undefined>;
|
|
2692
|
+
getInfo(key: string): Promise<{
|
|
2693
|
+
writeTime: number;
|
|
2694
|
+
size: number;
|
|
2695
|
+
} | undefined>;
|
|
2696
|
+
findInfo(prefix: string, config?: {
|
|
2697
|
+
shallow?: boolean;
|
|
2698
|
+
type?: "files" | "folders";
|
|
2699
|
+
}): Promise<ArchiveFileInfo[]>;
|
|
2700
|
+
startLargeUpload(): Promise<string>;
|
|
2701
|
+
appendLargeUpload(id: string, data: Buffer): Promise<void>;
|
|
2702
|
+
finishLargeUpload(id: string, key: string): Promise<void>;
|
|
2703
|
+
cancelLargeUpload(id: string): Promise<void>;
|
|
2704
|
+
private flushOverlay;
|
|
2705
|
+
private compact;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
2711
|
+
/// <reference types="node" />
|
|
2712
|
+
/// <reference types="node" />
|
|
2713
|
+
import { ArchiveFileInfo } from "../IArchives";
|
|
2714
|
+
import type { BlobStore } from "./blobStore";
|
|
2715
|
+
import type { IStorage } from "../IStorage";
|
|
2716
|
+
export declare const REMOTE_STORAGE_CLASS_GUID = "RemoteStorageController-b7e42a91";
|
|
2717
|
+
export declare const STORAGE_AUTH_PURPOSE = "remoteStorage-auth-1";
|
|
2718
|
+
export declare const STORAGE_NOT_AUTHENTICATED = "REMOTE_STORAGE_NOT_AUTHENTICATED_cf2f7b1e";
|
|
2719
|
+
export declare const STORAGE_ACCESS_DENIED = "REMOTE_STORAGE_ACCESS_DENIED_9d81a4c0";
|
|
2720
|
+
export type AuthToken = {
|
|
2721
|
+
certPem: string;
|
|
2722
|
+
time: number;
|
|
2723
|
+
signature: string;
|
|
2724
|
+
};
|
|
2725
|
+
export type AccessRequest = {
|
|
2726
|
+
requestId: string;
|
|
2727
|
+
account: string;
|
|
2728
|
+
machineId: string;
|
|
2729
|
+
ip: string;
|
|
2730
|
+
time: number;
|
|
2731
|
+
};
|
|
2732
|
+
export type TrustRecord = {
|
|
2733
|
+
account: string;
|
|
2734
|
+
machineId: string;
|
|
2735
|
+
ip: string;
|
|
2736
|
+
time: number;
|
|
2737
|
+
};
|
|
2738
|
+
export type BucketConfig = {
|
|
2739
|
+
public?: boolean;
|
|
2740
|
+
fast?: boolean;
|
|
2741
|
+
writeDelay?: number;
|
|
2742
|
+
};
|
|
2743
|
+
export type AccessState = {
|
|
2744
|
+
machineId: string;
|
|
2745
|
+
ip: string;
|
|
2746
|
+
hasAccess: boolean;
|
|
2747
|
+
listAccessCommand: string;
|
|
2748
|
+
grantAccessCommand?: string;
|
|
2749
|
+
machines?: (AccessRequest & {
|
|
2750
|
+
trusted: boolean;
|
|
2751
|
+
})[];
|
|
2752
|
+
};
|
|
2753
|
+
export type StorageServerState = {
|
|
2754
|
+
domain: string;
|
|
2755
|
+
port: number;
|
|
2756
|
+
rootDomain: string;
|
|
2757
|
+
sshTarget: string;
|
|
2758
|
+
serverCommand: string;
|
|
2759
|
+
blobStore: BlobStore;
|
|
2760
|
+
trust: IStorage<TrustRecord>;
|
|
2761
|
+
requests: IStorage<AccessRequest[]>;
|
|
2762
|
+
buckets: IStorage<BucketConfig>;
|
|
2763
|
+
};
|
|
2764
|
+
export declare function setStorageServerState(state: StorageServerState): void;
|
|
2765
|
+
export declare const RemoteStorageController: import("socket-function/SocketFunctionTypes").SocketRegistered<{
|
|
2766
|
+
authenticate: (token: AuthToken) => Promise<{
|
|
2767
|
+
machineId: string;
|
|
2768
|
+
ip: string;
|
|
2769
|
+
}>;
|
|
2770
|
+
requestAccess: (account: string) => Promise<{
|
|
2771
|
+
machineId: string;
|
|
2772
|
+
ip: string;
|
|
2773
|
+
requestId: string;
|
|
2774
|
+
grantAccessCommand: string;
|
|
2775
|
+
}>;
|
|
2776
|
+
getAccessState: (account: string) => Promise<AccessState>;
|
|
2777
|
+
adminListRequests: (ip: string) => Promise<AccessRequest[]>;
|
|
2778
|
+
adminGrantAccess: (requestId: string) => Promise<TrustRecord>;
|
|
2779
|
+
ensureBucket: (account: string, bucketName: string, config: BucketConfig) => Promise<void>;
|
|
2780
|
+
get: (account: string, bucketName: string, path: string, range?: {
|
|
2781
|
+
start: number;
|
|
2782
|
+
end: number;
|
|
2783
|
+
}) => Promise<Buffer | undefined>;
|
|
2784
|
+
set: (account: string, bucketName: string, path: string, data: Buffer) => Promise<void>;
|
|
2785
|
+
del: (account: string, bucketName: string, path: string) => Promise<void>;
|
|
2786
|
+
getInfo: (account: string, bucketName: string, path: string) => Promise<{
|
|
2787
|
+
writeTime: number;
|
|
2788
|
+
size: number;
|
|
2789
|
+
} | undefined>;
|
|
2790
|
+
findInfo: (account: string, bucketName: string, prefix: string, config?: {
|
|
2791
|
+
shallow?: boolean;
|
|
2792
|
+
type?: "files" | "folders";
|
|
2793
|
+
}) => Promise<ArchiveFileInfo[]>;
|
|
2794
|
+
startLargeFile: (account: string, bucketName: string, path: string) => Promise<string>;
|
|
2795
|
+
uploadPart: (uploadId: string, data: Buffer) => Promise<void>;
|
|
2796
|
+
finishLargeFile: (uploadId: string) => Promise<void>;
|
|
2797
|
+
cancelLargeFile: (uploadId: string) => Promise<void>;
|
|
2798
|
+
getPublicFile: (account: string, bucketName: string, path: string) => Promise<Buffer>;
|
|
2799
|
+
}>;
|
|
2800
|
+
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
declare module "sliftutils/storage/remoteStorage/storageServer" {
|
|
2804
|
+
import "./accessPage";
|
|
2805
|
+
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2530
2808
|
declare module "sliftutils/storage/storage" {
|
|
2531
2809
|
|
|
2532
2810
|
|
package/package.json
CHANGED
package/render-utils/Input.tsx
CHANGED
package/render-utils/colors.tsx
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { isNode } from "typesafecss";
|
|
2
|
-
|
|
3
1
|
// Browser-side cross-tab write sync for BulkDatabase2, over BroadcastChannel (one channel per
|
|
4
|
-
// collection, same-origin tabs). It does NOT persist anything — each
|
|
5
|
-
// just relays live writes to other open tabs and, when
|
|
6
|
-
// made recently that may not be on disk yet. No-op in Node / where
|
|
7
|
-
// BulkDatabase2 can call these unconditionally. This is an
|
|
2
|
+
// collection, same-origin tabs and workers). It does NOT persist anything — each side writes to disk
|
|
3
|
+
// itself; this just relays live writes to other open tabs/workers and, when one starts up, asks peers
|
|
4
|
+
// for writes they've made recently that may not be on disk yet. No-op in Node / where
|
|
5
|
+
// BroadcastChannel is unavailable, so BulkDatabase2 can call these unconditionally. This is an
|
|
6
|
+
// optional feature, so there is no fallback.
|
|
8
7
|
|
|
9
8
|
export type RemoteWrite = { key: string; time: number; deleted?: boolean; value?: unknown };
|
|
10
9
|
|
|
@@ -24,7 +23,11 @@ type Channel = {
|
|
|
24
23
|
const channels = new Map<string, Channel>();
|
|
25
24
|
|
|
26
25
|
export function isSyncSupported(): boolean {
|
|
27
|
-
|
|
26
|
+
// window (main-thread browser) or WorkerGlobalScope (Web/Service/Shared worker) — but not Node
|
|
27
|
+
// (typesafecss's isNode is `typeof window === "undefined"`, which misclassifies workers)
|
|
28
|
+
let isBrowserOrWorker = typeof window !== "undefined"
|
|
29
|
+
|| "WorkerGlobalScope" in globalThis;
|
|
30
|
+
return isBrowserOrWorker && typeof BroadcastChannel !== "undefined";
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
function pruneRecent(channel: Channel) {
|
|
@@ -66,6 +66,7 @@ export type DirectoryWrapper = {
|
|
|
66
66
|
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
67
67
|
};
|
|
68
68
|
export declare function setFileAPIKey(key: string): void;
|
|
69
|
+
export declare function setStorageRootOverride(handle: FileSystemDirectoryHandle | undefined): void;
|
|
69
70
|
export declare class NodeJSFileHandleWrapper implements FileWrapper {
|
|
70
71
|
private filePath;
|
|
71
72
|
constructor(filePath: string);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import preact from "preact";
|
|
2
|
-
import { getFileSystemPointer, storeFileSystemPointer } from "./fileSystemPointer";
|
|
2
|
+
import { findGrantedPointerHandle, getFileSystemPointer, storeFileSystemPointer } from "./fileSystemPointer";
|
|
3
3
|
import { observable } from "mobx";
|
|
4
4
|
import { observer } from "../render-utils/observer";
|
|
5
5
|
import { cache, lazy } from "socket-function/src/caching";
|
|
@@ -36,6 +36,9 @@ declare global {
|
|
|
36
36
|
// DO NOT enable this is isNode
|
|
37
37
|
const USE_INDEXED_DB = false;
|
|
38
38
|
|
|
39
|
+
// How often a worker rechecks the pointer IndexedDB for a granted handle when none is available yet.
|
|
40
|
+
const WORKER_POLL_INTERVAL_MS = 60 * 1000;
|
|
41
|
+
|
|
39
42
|
// These mirror the subset of the native FileSystemFileHandle / FileSystemDirectoryHandle API we use, so
|
|
40
43
|
// the native browser handles, the Node handles, and the remote handles are all interchangeable — and
|
|
41
44
|
// code written against the native handle (e.g. a recursive walk over `handle.entries()`) works on any of
|
|
@@ -91,6 +94,18 @@ export function setFileAPIKey(key: string) {
|
|
|
91
94
|
fileAPIKey = key;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
// A directory handle to serve as the storage root instead of resolving one via
|
|
98
|
+
// the picker / stored-pointer / remote flow. Set this in a context that can't run
|
|
99
|
+
// the interactive resolution — most importantly a Web Worker, which has no DOM to
|
|
100
|
+
// prompt with and no localStorage pointer. The owning window resolves the handle
|
|
101
|
+
// (with its permission grant) and postMessages it in; the worker calls this before
|
|
102
|
+
// touching any storage. When set, getDirectoryHandle returns it directly and skips
|
|
103
|
+
// every DOM / localStorage branch below.
|
|
104
|
+
let storageRootOverride: FileSystemDirectoryHandle | undefined;
|
|
105
|
+
export function setStorageRootOverride(handle: FileSystemDirectoryHandle | undefined): void {
|
|
106
|
+
storageRootOverride = handle;
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
// ---- remote (server) storage config ----
|
|
95
110
|
// Instead of a local folder, the user can point at a remoteFileServer.js instance (URL + password).
|
|
96
111
|
// When configured, getFileStorageNested2 serves everything from that server. Persisted in localStorage.
|
|
@@ -442,6 +457,26 @@ export function pickPrivateFolder(name: string): void {
|
|
|
442
457
|
// (we actually connect) before use; if it no longer works we re-prompt, just like a local folder whose
|
|
443
458
|
// permission was lost. Blocks until ready (or the user dismisses, which rejects).
|
|
444
459
|
export const getDirectoryHandle = lazy(async function getDirectoryHandle(): Promise<DirectoryWrapper> {
|
|
460
|
+
// An injected root (e.g. a Web Worker handed the handle by its owning window)
|
|
461
|
+
// short-circuits the whole interactive resolution — no picker, no DOM, no
|
|
462
|
+
// localStorage. Must come first so a worker never reaches the branches below.
|
|
463
|
+
if (storageRootOverride) {
|
|
464
|
+
return storageRootOverride as unknown as DirectoryWrapper;
|
|
465
|
+
}
|
|
466
|
+
// A worker can't show the picker (no DOM / no user activation) and can't read localStorage, but
|
|
467
|
+
// it CAN open the pointer IndexedDB the main-thread picker persists to. Poll it for a handle
|
|
468
|
+
// whose permission is already granted; the owning window may also postMessage a handle in
|
|
469
|
+
// mid-poll (checked each iteration). This has to come before the isNode branch: typesafecss's
|
|
470
|
+
// isNode() returns true in workers (window is undefined), so a worker would otherwise fall into
|
|
471
|
+
// the Node branch and try to use fs/path.
|
|
472
|
+
if ("WorkerGlobalScope" in globalThis) {
|
|
473
|
+
while (true) {
|
|
474
|
+
if (storageRootOverride) return storageRootOverride as unknown as DirectoryWrapper;
|
|
475
|
+
let handle = await findGrantedPointerHandle("readwrite");
|
|
476
|
+
if (handle) return handle as unknown as DirectoryWrapper;
|
|
477
|
+
await new Promise(resolve => setTimeout(resolve, WORKER_POLL_INTERVAL_MS));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
445
480
|
if (isNode()) {
|
|
446
481
|
return new NodeJSDirectoryHandleWrapper(path.resolve("./data/"));
|
|
447
482
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
export type ArchiveFileInfo = {
|
|
4
|
+
path: string;
|
|
5
|
+
createTime: number;
|
|
6
|
+
size: number;
|
|
7
|
+
};
|
|
8
|
+
export interface IArchives {
|
|
9
|
+
getDebugName(): string;
|
|
10
|
+
get(fileName: string, config?: {
|
|
11
|
+
range?: {
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
};
|
|
15
|
+
}): Promise<Buffer | undefined>;
|
|
16
|
+
set(fileName: string, data: Buffer): Promise<void>;
|
|
17
|
+
del(fileName: string): Promise<void>;
|
|
18
|
+
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
19
|
+
setLargeFile(config: {
|
|
20
|
+
path: string;
|
|
21
|
+
getNextData(): Promise<Buffer | undefined>;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
getInfo(fileName: string): Promise<{
|
|
24
|
+
writeTime: number;
|
|
25
|
+
size: number;
|
|
26
|
+
} | undefined>;
|
|
27
|
+
find(prefix: string, config?: {
|
|
28
|
+
shallow?: boolean;
|
|
29
|
+
type: "files" | "folders";
|
|
30
|
+
}): Promise<string[]>;
|
|
31
|
+
findInfo(prefix: string, config?: {
|
|
32
|
+
shallow?: boolean;
|
|
33
|
+
type: "files" | "folders";
|
|
34
|
+
}): Promise<ArchiveFileInfo[]>;
|
|
35
|
+
/** Only works for public buckets (private buckets are API-access only). */
|
|
36
|
+
getURL(path: string): Promise<string>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.allowclient = true;
|
|
2
|
+
|
|
3
|
+
// The important operations of an archive bucket (extracted from ArchivesBackblaze), so other
|
|
4
|
+
// backends (e.g. our own remote storage server) can be used interchangeably.
|
|
5
|
+
|
|
6
|
+
export type ArchiveFileInfo = { path: string; createTime: number; size: number };
|
|
7
|
+
|
|
8
|
+
export interface IArchives {
|
|
9
|
+
getDebugName(): string;
|
|
10
|
+
get(fileName: string, config?: { range?: { start: number; end: number } }): Promise<Buffer | undefined>;
|
|
11
|
+
set(fileName: string, data: Buffer): Promise<void>;
|
|
12
|
+
del(fileName: string): Promise<void>;
|
|
13
|
+
/** Streams a file too large to hold in memory. getNextData returns undefined when done. */
|
|
14
|
+
setLargeFile(config: { path: string; getNextData(): Promise<Buffer | undefined> }): Promise<void>;
|
|
15
|
+
getInfo(fileName: string): Promise<{ writeTime: number; size: number } | undefined>;
|
|
16
|
+
find(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<string[]>;
|
|
17
|
+
findInfo(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<ArchiveFileInfo[]>;
|
|
18
|
+
/** Only works for public buckets (private buckets are API-access only). */
|
|
19
|
+
getURL(path: string): Promise<string>;
|
|
20
|
+
}
|
package/storage/backblaze.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
|
|
3
|
+
import { IArchives } from "./IArchives";
|
|
4
|
+
export declare class ArchivesBackblaze implements IArchives {
|
|
4
5
|
private config;
|
|
5
6
|
constructor(config: {
|
|
6
7
|
bucketName: string;
|
package/storage/backblaze.ts
CHANGED
|
@@ -9,6 +9,7 @@ import debugbreak from "debugbreak";
|
|
|
9
9
|
import dns from "dns";
|
|
10
10
|
import { getSecret } from "../misc/getSecret";
|
|
11
11
|
import { httpsRequest } from "socket-function/src/https";
|
|
12
|
+
import { IArchives } from "./IArchives";
|
|
12
13
|
|
|
13
14
|
type BackblazeCreds = {
|
|
14
15
|
applicationKeyId: string;
|
|
@@ -372,7 +373,7 @@ const getAPI = lazy(async () => {
|
|
|
372
373
|
type B2Api = (typeof getAPI) extends () => Promise<infer T> ? T : never;
|
|
373
374
|
|
|
374
375
|
|
|
375
|
-
export class ArchivesBackblaze {
|
|
376
|
+
export class ArchivesBackblaze implements IArchives {
|
|
376
377
|
public constructor(private config: {
|
|
377
378
|
bucketName: string;
|
|
378
379
|
public?: boolean;
|
|
@@ -4,6 +4,7 @@ export declare function storeFileSystemPointer(config: {
|
|
|
4
4
|
handle: FileSystemFileHandle | FileSystemDirectoryHandle;
|
|
5
5
|
}): Promise<FileSystemPointer>;
|
|
6
6
|
export declare function deleteFileSystemPointer(pointer: FileSystemPointer): Promise<void>;
|
|
7
|
+
export declare function findGrantedPointerHandle(mode: "read" | "readwrite"): Promise<FileSystemDirectoryHandle | undefined>;
|
|
7
8
|
export declare function getFileSystemPointer(config: {
|
|
8
9
|
pointer: FileSystemPointer;
|
|
9
10
|
}): Promise<{
|
|
@@ -55,6 +55,36 @@ export async function deleteFileSystemPointer(pointer: FileSystemPointer) {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// Enumerates every stored pointer handle whose permission is already granted for `mode`, newest first
|
|
59
|
+
// (pointer keys start with Date.now(), which sorts lexicographically). Callable from a Web Worker —
|
|
60
|
+
// unlike getFileSystemPointer's onUserActivation flow, this only uses queryPermission, which does
|
|
61
|
+
// NOT require user activation.
|
|
62
|
+
export async function findGrantedPointerHandle(mode: "read" | "readwrite"): Promise<FileSystemDirectoryHandle | undefined> {
|
|
63
|
+
let database = await db();
|
|
64
|
+
if (!database) return undefined;
|
|
65
|
+
let store = database.transaction(objectStoreName, "readonly").objectStore(objectStoreName);
|
|
66
|
+
let keysReq = store.getAllKeys();
|
|
67
|
+
let valuesReq = store.getAll();
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
keysReq.addEventListener("success", resolve);
|
|
70
|
+
keysReq.addEventListener("error", reject);
|
|
71
|
+
});
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
valuesReq.addEventListener("success", resolve);
|
|
74
|
+
valuesReq.addEventListener("error", reject);
|
|
75
|
+
});
|
|
76
|
+
let keys = keysReq.result as string[];
|
|
77
|
+
let values = valuesReq.result as (FileSystemFileHandle | FileSystemDirectoryHandle)[];
|
|
78
|
+
let entries = keys.map((key, i) => ({ key, value: values[i], time: +key.split("_")[0] || 0 }))
|
|
79
|
+
.sort((a, b) => b.time - a.time);
|
|
80
|
+
for (let entry of entries) {
|
|
81
|
+
if (entry.value.kind !== "directory") continue;
|
|
82
|
+
let state = await (entry.value as any).queryPermission({ mode });
|
|
83
|
+
if (state === "granted") return entry.value;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
export async function getFileSystemPointer(config: {
|
|
59
89
|
pointer: FileSystemPointer;
|
|
60
90
|
}): Promise<{
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { IArchives, ArchiveFileInfo } from "../IArchives";
|
|
4
|
+
export type ArchivesRemoteConfig = {
|
|
5
|
+
address: string;
|
|
6
|
+
port: number;
|
|
7
|
+
account: string;
|
|
8
|
+
bucketName: string;
|
|
9
|
+
public?: boolean;
|
|
10
|
+
fast?: boolean;
|
|
11
|
+
writeDelay?: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function buildPublicFileURL(config: {
|
|
14
|
+
address: string;
|
|
15
|
+
port: number;
|
|
16
|
+
account: string;
|
|
17
|
+
bucketName: string;
|
|
18
|
+
path: string;
|
|
19
|
+
}): string;
|
|
20
|
+
export declare function authenticateStorage(config: {
|
|
21
|
+
address: string;
|
|
22
|
+
port: number;
|
|
23
|
+
nodeId: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
machineId: string;
|
|
26
|
+
ip: string;
|
|
27
|
+
}>;
|
|
28
|
+
export declare class ArchivesRemote implements IArchives {
|
|
29
|
+
private config;
|
|
30
|
+
constructor(config: ArchivesRemoteConfig);
|
|
31
|
+
private nodeId;
|
|
32
|
+
private controller;
|
|
33
|
+
private setupDone;
|
|
34
|
+
private lastDeniedLog;
|
|
35
|
+
getDebugName(): string;
|
|
36
|
+
private authenticate;
|
|
37
|
+
private callAuthed;
|
|
38
|
+
waitingForAccess(): Promise<string | undefined>;
|
|
39
|
+
private onAccessDenied;
|
|
40
|
+
private ensureSetup;
|
|
41
|
+
private call;
|
|
42
|
+
get(fileName: string, config?: {
|
|
43
|
+
range?: {
|
|
44
|
+
start: number;
|
|
45
|
+
end: number;
|
|
46
|
+
};
|
|
47
|
+
}): Promise<Buffer | undefined>;
|
|
48
|
+
set(fileName: string, data: Buffer): Promise<void>;
|
|
49
|
+
del(fileName: string): Promise<void>;
|
|
50
|
+
getInfo(fileName: string): Promise<{
|
|
51
|
+
writeTime: number;
|
|
52
|
+
size: number;
|
|
53
|
+
} | undefined>;
|
|
54
|
+
findInfo(prefix: string, config?: {
|
|
55
|
+
shallow?: boolean;
|
|
56
|
+
type: "files" | "folders";
|
|
57
|
+
}): Promise<ArchiveFileInfo[]>;
|
|
58
|
+
find(prefix: string, config?: {
|
|
59
|
+
shallow?: boolean;
|
|
60
|
+
type: "files" | "folders";
|
|
61
|
+
}): Promise<string[]>;
|
|
62
|
+
setLargeFile(config: {
|
|
63
|
+
path: string;
|
|
64
|
+
getNextData(): Promise<Buffer | undefined>;
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
getURL(path: string): Promise<string>;
|
|
67
|
+
}
|