@enbox/browser 0.3.69 → 0.3.71
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/README.md +30 -7
- package/dist/browser.mjs +14 -14
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/blob-url-pool.js +36 -0
- package/dist/esm/blob-url-pool.js.map +1 -0
- package/dist/esm/file-envelope-url-store.js +203 -0
- package/dist/esm/file-envelope-url-store.js.map +1 -0
- package/dist/esm/index.js +12 -12
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/object-url-reference.js +68 -0
- package/dist/esm/object-url-reference.js.map +1 -0
- package/dist/esm/web-features.js +11 -10
- package/dist/esm/web-features.js.map +1 -1
- package/dist/types/blob-url-pool.d.ts +16 -0
- package/dist/types/blob-url-pool.d.ts.map +1 -0
- package/dist/types/file-envelope-url-store.d.ts +30 -0
- package/dist/types/file-envelope-url-store.d.ts.map +1 -0
- package/dist/types/index.d.ts +16 -14
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/object-url-reference.d.ts +20 -0
- package/dist/types/object-url-reference.d.ts.map +1 -0
- package/dist/types/web-features.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/blob-url-pool.ts +55 -0
- package/src/file-envelope-url-store.ts +260 -0
- package/src/index.ts +23 -146
- package/src/object-url-reference.ts +82 -0
- package/src/web-features.ts +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,cAAc,YAAY,CAAC;AAM3B,cAAc,qBAAqB,CAAC;AAOpC,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACvE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAMtE,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACtF,YAAY,EAAE,4BAA4B,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACzG,YAAY,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACjG,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,YAAY,EAAE,iCAAiC,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EACL,6BAA6B,EAC7B,gCAAgC,EAChC,iBAAiB,EACjB,iCAAiC,EACjC,kCAAkC,GACnC,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC/G,YAAY,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,8BAA8B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxF,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1I,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC1G,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC;AActC,OAAO,EAAE,mBAAmB,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AACrF,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ObjectUrlLease = Readonly<{
|
|
2
|
+
url: string;
|
|
3
|
+
/** Release immediately, cancelling any deferred release. Idempotent. */
|
|
4
|
+
release(): void;
|
|
5
|
+
/** Release after a grace period. The first deferred release wins. */
|
|
6
|
+
releaseAfter(delayMs: number): void;
|
|
7
|
+
}>;
|
|
8
|
+
/** Private reference-counted ownership for one object URL. */
|
|
9
|
+
export declare class ObjectUrlReference {
|
|
10
|
+
private readonly _onEmpty;
|
|
11
|
+
readonly url: string;
|
|
12
|
+
private _isRevoked;
|
|
13
|
+
private _references;
|
|
14
|
+
private readonly _releaseTimers;
|
|
15
|
+
constructor(blob: Blob, _onEmpty: (reference: ObjectUrlReference) => void);
|
|
16
|
+
get references(): number;
|
|
17
|
+
acquire(): ObjectUrlLease;
|
|
18
|
+
revoke(): void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=object-url-reference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-url-reference.d.ts","sourceRoot":"","sources":["../../src/object-url-reference.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,OAAO,IAAI,IAAI,CAAC;IAChB,qEAAqE;IACrE,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,CAAC,CAAC;AAUH,8DAA8D;AAC9D,qBAAa,kBAAkB;IAME,OAAO,CAAC,QAAQ,CAAC,QAAQ;IALxD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4C;gBAExD,IAAI,EAAE,IAAI,EAAmB,QAAQ,EAAE,CAAC,SAAS,EAAE,kBAAkB,KAAK,IAAI;IAIjG,IAAW,UAAU,IAAI,MAAM,CAE9B;IAEM,OAAO,IAAI,cAAc;IAuCzB,MAAM,IAAI,IAAI;CAWtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-features.d.ts","sourceRoot":"","sources":["../../src/web-features.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,KAAK,EAAE,UAAU,GAAG,KAAK,EACzB,GAAG,EAAE,MAAM,KACR,gBAAgB,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;AAExF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yDAAyD;IACzD,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"web-features.d.ts","sourceRoot":"","sources":["../../src/web-features.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,KAAK,EAAE,UAAU,GAAG,KAAK,EACzB,GAAG,EAAE,MAAM,KACR,gBAAgB,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;AAExF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yDAAyD;IACzD,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACpC,CAAC;AAiBF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAMlD,iFAAiF;AACjF,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGpE;AAQD,iEAAiE;AACjE,wBAAsB,WAAW,CAC/B,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAC/E,OAAO,CAAC,QAAQ,CAAC,CAsCnB;AAED,6EAA6E;AAC7E,wBAAsB,aAAa,CACjC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EACzD,aAAa,EAAE,KAAK,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,wBAAwB,GACnE,OAAO,CAAC,QAAQ,CAAC,CAgCnB;AAED,mFAAmF;AACnF,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAW7G;AA0XD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,IAAI,CAoB9E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAGtD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enbox/browser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.71",
|
|
4
4
|
"description": "Enbox tools and features to use in the browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/esm/index.js",
|
|
@@ -66,15 +66,15 @@
|
|
|
66
66
|
"access": "public"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@enbox/agent": "0.8.
|
|
70
|
-
"@enbox/api": "0.6.
|
|
71
|
-
"@enbox/auth": "0.6.
|
|
69
|
+
"@enbox/agent": "0.8.40",
|
|
70
|
+
"@enbox/api": "0.6.79",
|
|
71
|
+
"@enbox/auth": "0.6.86",
|
|
72
72
|
"@enbox/common": "0.1.6",
|
|
73
|
-
"@enbox/connect": "0.1.
|
|
73
|
+
"@enbox/connect": "0.1.20",
|
|
74
74
|
"@enbox/crypto": "0.1.9",
|
|
75
|
-
"@enbox/dids": "0.1.
|
|
76
|
-
"@enbox/dwn-sdk-js": "0.4.
|
|
77
|
-
"@enbox/protocols": "0.2.
|
|
75
|
+
"@enbox/dids": "0.1.10",
|
|
76
|
+
"@enbox/dwn-sdk-js": "0.4.24",
|
|
77
|
+
"@enbox/protocols": "0.2.106"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@types/qrcode": "^1.5.6",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ObjectUrlLease } from './object-url-reference.js';
|
|
2
|
+
|
|
3
|
+
import { ObjectUrlReference } from './object-url-reference.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A reference-counted lease for a browser object URL.
|
|
7
|
+
*
|
|
8
|
+
* When replacing a URL already rendered by the DOM, `releaseAfter()` provides
|
|
9
|
+
* a grace period without passing the lease through the component tree.
|
|
10
|
+
*/
|
|
11
|
+
export type BlobUrlLease = ObjectUrlLease;
|
|
12
|
+
|
|
13
|
+
/** Owns object URLs for Blob identities until their last lease is released. */
|
|
14
|
+
export interface BlobUrlPool {
|
|
15
|
+
acquire(blob: Blob): BlobUrlLease;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class DefaultBlobUrlPool implements BlobUrlPool {
|
|
20
|
+
private readonly _references = new Map<Blob, ObjectUrlReference>();
|
|
21
|
+
private _isDisposed = false;
|
|
22
|
+
|
|
23
|
+
public acquire(blob: Blob): BlobUrlLease {
|
|
24
|
+
if (this._isDisposed) {
|
|
25
|
+
throw new Error('BlobUrlPool is disposed.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let reference = this._references.get(blob);
|
|
29
|
+
if (reference === undefined) {
|
|
30
|
+
reference = new ObjectUrlReference(blob, (released): void => {
|
|
31
|
+
this._references.delete(blob);
|
|
32
|
+
released.revoke();
|
|
33
|
+
});
|
|
34
|
+
this._references.set(blob, reference);
|
|
35
|
+
}
|
|
36
|
+
return reference.acquire();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public dispose(): void {
|
|
40
|
+
if (this._isDisposed) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this._isDisposed = true;
|
|
45
|
+
for (const reference of this._references.values()) {
|
|
46
|
+
reference.revoke();
|
|
47
|
+
}
|
|
48
|
+
this._references.clear();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Create a terminally disposable, reference-counted object URL pool. */
|
|
53
|
+
export function createBlobUrlPool(): BlobUrlPool {
|
|
54
|
+
return new DefaultBlobUrlPool();
|
|
55
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { FileEnvelopeData } from '@enbox/api';
|
|
2
|
+
|
|
3
|
+
import type { BlobUrlLease } from './blob-url-pool.js';
|
|
4
|
+
|
|
5
|
+
import { ObjectUrlReference } from './object-url-reference.js';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MAX_BYTES = 128 * 1024 * 1024;
|
|
8
|
+
const DEFAULT_MAX_ENTRIES = 50;
|
|
9
|
+
|
|
10
|
+
type CacheEntry = {
|
|
11
|
+
bytes: number;
|
|
12
|
+
filename: string;
|
|
13
|
+
key: string;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
url: ObjectUrlReference;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type PendingRead = {
|
|
19
|
+
controller: AbortController;
|
|
20
|
+
generation: number;
|
|
21
|
+
promise: Promise<CacheEntry | undefined>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** A cached file envelope represented by a leased browser object URL. */
|
|
25
|
+
export type FileEnvelopeUrlLease = BlobUrlLease & Readonly<{
|
|
26
|
+
filename: string;
|
|
27
|
+
mimeType: string;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export type FileEnvelopeUrlStoreOptions<Request> = Readonly<{
|
|
31
|
+
/** Select the stable cache key for a read request. */
|
|
32
|
+
key(request: Request): string;
|
|
33
|
+
/** Read and decode the file. The signal aborts invalidated work. */
|
|
34
|
+
read(request: Request, signal: AbortSignal): Promise<FileEnvelopeData | undefined>;
|
|
35
|
+
/** Choose the Blob exposed by the URL, retaining MIME/render policy in the caller. */
|
|
36
|
+
blobForUrl(value: FileEnvelopeData): Blob;
|
|
37
|
+
/** Maximum cached entries (default 50). Soft while every eviction candidate is leased. */
|
|
38
|
+
maxEntries?: number;
|
|
39
|
+
/** Maximum cached Blob bytes (default 128 MiB). Soft while every eviction candidate is leased. */
|
|
40
|
+
maxBytes?: number;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
/** A keyed, bounded object URL cache for decoded file envelopes. */
|
|
44
|
+
export interface FileEnvelopeUrlStore<Request> {
|
|
45
|
+
acquire(request: Request): Promise<FileEnvelopeUrlLease | undefined>;
|
|
46
|
+
acquireCached(request: Request): FileEnvelopeUrlLease | undefined;
|
|
47
|
+
clear(): void;
|
|
48
|
+
dispose(): void;
|
|
49
|
+
invalidate(request: Request): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function assertLimit(value: number, name: string): void {
|
|
53
|
+
if (value !== Infinity && (!Number.isSafeInteger(value) || value < 0)) {
|
|
54
|
+
throw new TypeError(`${name} must be a non-negative safe integer or Infinity.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function throwAborted(controller: AbortController): never {
|
|
59
|
+
controller.abort();
|
|
60
|
+
throw controller.signal.reason;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class DefaultFileEnvelopeUrlStore<Request> implements FileEnvelopeUrlStore<Request> {
|
|
64
|
+
private readonly _cache = new Map<string, CacheEntry>();
|
|
65
|
+
private readonly _entries = new Set<CacheEntry>();
|
|
66
|
+
private readonly _maxBytes: number;
|
|
67
|
+
private readonly _maxEntries: number;
|
|
68
|
+
private readonly _pending = new Map<string, PendingRead>();
|
|
69
|
+
private _bytes = 0;
|
|
70
|
+
private _generation = 0;
|
|
71
|
+
private _isDisposed = false;
|
|
72
|
+
|
|
73
|
+
public constructor(private readonly _options: FileEnvelopeUrlStoreOptions<Request>) {
|
|
74
|
+
this._maxBytes = _options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
75
|
+
this._maxEntries = _options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
76
|
+
assertLimit(this._maxBytes, 'maxBytes');
|
|
77
|
+
assertLimit(this._maxEntries, 'maxEntries');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public async acquire(request: Request): Promise<FileEnvelopeUrlLease | undefined> {
|
|
81
|
+
this._assertActive();
|
|
82
|
+
const key = this._options.key(request);
|
|
83
|
+
const cached = this._cache.get(key);
|
|
84
|
+
if (cached !== undefined) {
|
|
85
|
+
return this._acquire(cached);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const pending = this._pending.get(key) ?? this._startRead(request, key);
|
|
89
|
+
const entry = await pending.promise;
|
|
90
|
+
if (this._isDisposed || pending.generation !== this._generation) {
|
|
91
|
+
return throwAborted(pending.controller);
|
|
92
|
+
}
|
|
93
|
+
if (entry === undefined) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
if (this._cache.get(key) !== entry) {
|
|
97
|
+
return throwAborted(pending.controller);
|
|
98
|
+
}
|
|
99
|
+
return this._acquire(entry);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public acquireCached(request: Request): FileEnvelopeUrlLease | undefined {
|
|
103
|
+
this._assertActive();
|
|
104
|
+
const entry = this._cache.get(this._options.key(request));
|
|
105
|
+
return entry === undefined ? undefined : this._acquire(entry);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public invalidate(request: Request): void {
|
|
109
|
+
this._assertActive();
|
|
110
|
+
const key = this._options.key(request);
|
|
111
|
+
const pending = this._pending.get(key);
|
|
112
|
+
pending?.controller.abort();
|
|
113
|
+
this._pending.delete(key);
|
|
114
|
+
const entry = this._cache.get(key);
|
|
115
|
+
if (entry !== undefined) {
|
|
116
|
+
this._remove(entry);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public clear(): void {
|
|
121
|
+
this._assertActive();
|
|
122
|
+
this._reset(false);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public dispose(): void {
|
|
126
|
+
if (this._isDisposed) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this._isDisposed = true;
|
|
131
|
+
this._reset(true);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _abortReads(): void {
|
|
135
|
+
for (const pending of this._pending.values()) {
|
|
136
|
+
pending.controller.abort();
|
|
137
|
+
}
|
|
138
|
+
this._pending.clear();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private _acquire(entry: CacheEntry): FileEnvelopeUrlLease {
|
|
142
|
+
this._cache.delete(entry.key);
|
|
143
|
+
this._cache.set(entry.key, entry);
|
|
144
|
+
const lease = entry.url.acquire();
|
|
145
|
+
this._evict();
|
|
146
|
+
return Object.freeze({
|
|
147
|
+
filename : entry.filename,
|
|
148
|
+
mimeType : entry.mimeType,
|
|
149
|
+
release : lease.release,
|
|
150
|
+
releaseAfter : lease.releaseAfter,
|
|
151
|
+
url : lease.url,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _assertActive(): void {
|
|
156
|
+
if (this._isDisposed) {
|
|
157
|
+
throw new Error('FileEnvelopeUrlStore is disposed.');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _assertCurrent(key: string, controller: AbortController, generation: number): void {
|
|
162
|
+
if (
|
|
163
|
+
this._isDisposed
|
|
164
|
+
|| controller.signal.aborted
|
|
165
|
+
|| this._generation !== generation
|
|
166
|
+
|| this._pending.get(key)?.controller !== controller
|
|
167
|
+
) {
|
|
168
|
+
throwAborted(controller);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private _evict(): void {
|
|
173
|
+
while (this._cache.size > this._maxEntries || this._bytes > this._maxBytes) {
|
|
174
|
+
const entry = [...this._cache.values()].find((candidate): boolean => candidate.url.references === 0);
|
|
175
|
+
if (entry === undefined) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this._remove(entry);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async _load(request: Request, key: string, controller: AbortController, generation: number): Promise<CacheEntry | undefined> {
|
|
183
|
+
const value = await this._options.read(request, controller.signal);
|
|
184
|
+
this._assertCurrent(key, controller, generation);
|
|
185
|
+
if (value === undefined) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const blob = this._options.blobForUrl(value);
|
|
190
|
+
this._assertCurrent(key, controller, generation);
|
|
191
|
+
const url = new ObjectUrlReference(blob, (): void => this._released(entry));
|
|
192
|
+
const entry: CacheEntry = {
|
|
193
|
+
bytes : blob.size,
|
|
194
|
+
filename : value.filename,
|
|
195
|
+
key,
|
|
196
|
+
mimeType : value.mimeType,
|
|
197
|
+
url,
|
|
198
|
+
};
|
|
199
|
+
this._cache.set(key, entry);
|
|
200
|
+
this._bytes += entry.bytes;
|
|
201
|
+
this._entries.add(entry);
|
|
202
|
+
return entry;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private _released(entry: CacheEntry): void {
|
|
206
|
+
if (this._cache.get(entry.key) !== entry) {
|
|
207
|
+
this._revoke(entry);
|
|
208
|
+
} else {
|
|
209
|
+
this._evict();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _remove(entry: CacheEntry): void {
|
|
214
|
+
if (this._cache.get(entry.key) !== entry) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this._cache.delete(entry.key);
|
|
218
|
+
this._bytes -= entry.bytes;
|
|
219
|
+
if (entry.url.references === 0) {
|
|
220
|
+
this._revoke(entry);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private _reset(revokeActive: boolean): void {
|
|
225
|
+
this._generation += 1;
|
|
226
|
+
this._abortReads();
|
|
227
|
+
this._cache.clear();
|
|
228
|
+
this._bytes = 0;
|
|
229
|
+
for (const entry of this._entries) {
|
|
230
|
+
if (revokeActive || entry.url.references === 0) {
|
|
231
|
+
this._revoke(entry);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private _revoke(entry: CacheEntry): void {
|
|
237
|
+
entry.url.revoke();
|
|
238
|
+
this._entries.delete(entry);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private _startRead(request: Request, key: string): PendingRead {
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
const generation = this._generation;
|
|
244
|
+
const promise = this._load(request, key, controller, generation).finally((): void => {
|
|
245
|
+
if (this._pending.get(key)?.controller === controller) {
|
|
246
|
+
this._pending.delete(key);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const pending = { controller, generation, promise };
|
|
250
|
+
this._pending.set(key, pending);
|
|
251
|
+
return pending;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Create a bounded file-envelope URL store with deduplicated reads. */
|
|
256
|
+
export function createFileEnvelopeUrlStore<Request>(
|
|
257
|
+
options: FileEnvelopeUrlStoreOptions<Request>,
|
|
258
|
+
): FileEnvelopeUrlStore<Request> {
|
|
259
|
+
return new DefaultFileEnvelopeUrlStore(options);
|
|
260
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,14 +8,17 @@
|
|
|
8
8
|
* @example Single-import dapp setup
|
|
9
9
|
* ```ts
|
|
10
10
|
* import {
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* BrowserConnectHandler, createConnectionStore,
|
|
12
|
+
* defineApplicationManifest,
|
|
13
13
|
* } from '@enbox/browser';
|
|
14
|
+
* import { MyProtocol } from './my-protocol.js';
|
|
14
15
|
*
|
|
15
|
-
* const
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* const store = createConnectionStore({
|
|
17
|
+
* application : defineApplicationManifest({ protocols: [MyProtocol] }),
|
|
18
|
+
* connectHandler : BrowserConnectHandler({ appName: 'My App' }),
|
|
18
19
|
* });
|
|
20
|
+
* const snapshot = await store.connect();
|
|
21
|
+
* if (snapshot.phase !== 'connected') throw snapshot.error ?? new Error('Connection failed.');
|
|
19
22
|
* ```
|
|
20
23
|
*
|
|
21
24
|
* For non-browser environments (Node.js, CLI, desktop), import from
|
|
@@ -28,137 +31,13 @@
|
|
|
28
31
|
//
|
|
29
32
|
// The high-level Enbox API: DWN records and protocols.
|
|
30
33
|
|
|
31
|
-
export
|
|
32
|
-
AudienceDecryptError,
|
|
33
|
-
createConnectionStore,
|
|
34
|
-
ContextNotReadyError,
|
|
35
|
-
ContextRetiredError,
|
|
36
|
-
defineApplicationManifest,
|
|
37
|
-
DwnResponseError,
|
|
38
|
-
Enbox,
|
|
39
|
-
defineProtocol,
|
|
40
|
-
getApplicationProtocolRequests,
|
|
41
|
-
ProtocolReadinessError,
|
|
42
|
-
RecordConflictError,
|
|
43
|
-
RecordValidationError,
|
|
44
|
-
recordCodecs,
|
|
45
|
-
} from '@enbox/api';
|
|
46
|
-
|
|
47
|
-
export type {
|
|
48
|
-
ApplicationConnectionStore,
|
|
49
|
-
ApplicationConnectionStoreConnectOptions,
|
|
50
|
-
ApplicationConnectionStoreOptions,
|
|
51
|
-
ApplicationConnectionStoreRefreshOptions,
|
|
52
|
-
ApplicationManifest,
|
|
53
|
-
ApplicationManifestProtocol,
|
|
54
|
-
ApplicationManifestProtocolInput,
|
|
55
|
-
AudienceDecryptFailureCause,
|
|
56
|
-
ConnectionPhase,
|
|
57
|
-
ConnectionSnapshot,
|
|
58
|
-
ConnectionSnapshotListener,
|
|
59
|
-
ConnectionStore,
|
|
60
|
-
ConnectionStoreOptions,
|
|
61
|
-
ContextView,
|
|
62
|
-
ContextViewListener,
|
|
63
|
-
ContextViewState,
|
|
64
|
-
ContextMember,
|
|
65
|
-
ContextMembersApi,
|
|
66
|
-
ContextInvitation,
|
|
67
|
-
ContextInvitationPreview,
|
|
68
|
-
ContextInvitationsApi,
|
|
69
|
-
ContextMaterializedRecord,
|
|
70
|
-
ContextRecord,
|
|
71
|
-
ContextRecordDeleteParams,
|
|
72
|
-
ContextRecordsApi,
|
|
73
|
-
ContextRecordUpdateParams,
|
|
74
|
-
ContextRoleGroup,
|
|
75
|
-
ContextRoleGroups,
|
|
76
|
-
ContextRolePath,
|
|
77
|
-
ContextsApi,
|
|
78
|
-
DataForPath,
|
|
79
|
-
DefineApplicationManifestOptions,
|
|
80
|
-
EnboxAnonymousOptions,
|
|
81
|
-
EnboxConnectOptions,
|
|
82
|
-
EnboxConnectResult,
|
|
83
|
-
EnboxParams,
|
|
84
|
-
EnboxSessionParams,
|
|
85
|
-
EncodedRecordData,
|
|
86
|
-
EnsureProtocolsReadyOptions,
|
|
87
|
-
FollowContextOptions,
|
|
88
|
-
JsonRecordCodecOptions,
|
|
89
|
-
MaterializedRecord,
|
|
90
|
-
MemberContext,
|
|
91
|
-
OwnedContext,
|
|
92
|
-
ProtocolContext,
|
|
93
|
-
ProtocolRolePaths,
|
|
94
|
-
RecordCodec,
|
|
95
|
-
RecordCodecContext,
|
|
96
|
-
RecordCodecValue,
|
|
97
|
-
RecordFilter,
|
|
98
|
-
RecordPage,
|
|
99
|
-
RecordPatch,
|
|
100
|
-
RecordPatchInput,
|
|
101
|
-
RecordQuery,
|
|
102
|
-
RecordSubscription,
|
|
103
|
-
RecordSubscriptionEvent,
|
|
104
|
-
RecordSubscriptionListener,
|
|
105
|
-
RecordView,
|
|
106
|
-
RecordViewListener,
|
|
107
|
-
RecordViewState,
|
|
108
|
-
RecordValidationFailure,
|
|
109
|
-
RecordValidator,
|
|
110
|
-
ReplicationCurrentness,
|
|
111
|
-
RoleDeliveryState,
|
|
112
|
-
SyncStatusSnapshot,
|
|
113
|
-
} from '@enbox/api';
|
|
34
|
+
export * from '@enbox/api';
|
|
114
35
|
|
|
115
36
|
// ─── Re-exports from @enbox/auth ────────────────────────────────
|
|
116
37
|
//
|
|
117
38
|
// Authentication lifecycle, session management, connect handlers.
|
|
118
39
|
|
|
119
|
-
export
|
|
120
|
-
AuthManager,
|
|
121
|
-
AuthSession,
|
|
122
|
-
ConnectDeniedError,
|
|
123
|
-
SESSION_EXPIRED_ERROR_CODE,
|
|
124
|
-
SESSION_REVOKED_ERROR_CODE,
|
|
125
|
-
computeConnectionStatus,
|
|
126
|
-
isConnectDeniedError,
|
|
127
|
-
isSessionExpiredError,
|
|
128
|
-
isSessionInvalidError,
|
|
129
|
-
normalizeProtocolRequests,
|
|
130
|
-
WalletConnect,
|
|
131
|
-
} from '@enbox/auth/browser';
|
|
132
|
-
|
|
133
|
-
export type {
|
|
134
|
-
AuthEvent,
|
|
135
|
-
AuthEventHandler,
|
|
136
|
-
AuthManagerOptions,
|
|
137
|
-
AuthState,
|
|
138
|
-
ComputeConnectionStatusOptions,
|
|
139
|
-
ConnectionMonitorOptions,
|
|
140
|
-
ConnectionState,
|
|
141
|
-
ConnectionStatus,
|
|
142
|
-
ConnectionStatusGrant,
|
|
143
|
-
ConnectHandler,
|
|
144
|
-
ConnectOptions,
|
|
145
|
-
ConnectResult,
|
|
146
|
-
DisconnectOptions,
|
|
147
|
-
GetConnectionStatusOptions,
|
|
148
|
-
HandlerConnectOptions,
|
|
149
|
-
ImportFromPortableOptions,
|
|
150
|
-
VaultConnectOptions,
|
|
151
|
-
Permission,
|
|
152
|
-
PortableIdentity,
|
|
153
|
-
ProtocolDefinitionCarrier,
|
|
154
|
-
ProtocolPermissionRequest,
|
|
155
|
-
ProtocolRequest,
|
|
156
|
-
RefreshOptions,
|
|
157
|
-
RegistrationOptions,
|
|
158
|
-
StorageAdapter,
|
|
159
|
-
SyncOption,
|
|
160
|
-
WalletConnectOptions,
|
|
161
|
-
} from '@enbox/auth/browser';
|
|
40
|
+
export * from '@enbox/auth/browser';
|
|
162
41
|
|
|
163
42
|
// ─── Re-exports from @enbox/connect ─────────────────────────────
|
|
164
43
|
//
|
|
@@ -166,7 +45,7 @@ export type {
|
|
|
166
45
|
// transports: opening/sealing envelopes and the deny token.
|
|
167
46
|
|
|
168
47
|
export { CONNECT_DENIED_TOKEN, ConnectProvider } from '@enbox/connect';
|
|
169
|
-
export type { ConnectApproval,
|
|
48
|
+
export type { ConnectApproval, ConnectRequest } from '@enbox/connect';
|
|
170
49
|
|
|
171
50
|
// ─── Browser-specific exports ───────────────────────────────────
|
|
172
51
|
//
|
|
@@ -200,6 +79,14 @@ export { MAX_PIN_ATTEMPTS, RelayConnectCancelledError, runRelayConnect } from '.
|
|
|
200
79
|
export type { RelayConnectOptions } from './relay-connect-runner.js';
|
|
201
80
|
export { encodeQr, qrToSvg } from './ui/qr.js';
|
|
202
81
|
export type { QrCode, QrSvgOptions } from './ui/qr.js';
|
|
82
|
+
export { createBlobUrlPool } from './blob-url-pool.js';
|
|
83
|
+
export type { BlobUrlLease, BlobUrlPool } from './blob-url-pool.js';
|
|
84
|
+
export { createFileEnvelopeUrlStore } from './file-envelope-url-store.js';
|
|
85
|
+
export type {
|
|
86
|
+
FileEnvelopeUrlLease,
|
|
87
|
+
FileEnvelopeUrlStore,
|
|
88
|
+
FileEnvelopeUrlStoreOptions,
|
|
89
|
+
} from './file-envelope-url-store.js';
|
|
203
90
|
|
|
204
91
|
// ─── Convenience re-exports for dapps ───────────────────────────
|
|
205
92
|
//
|
|
@@ -209,14 +96,6 @@ export type { QrCode, QrSvgOptions } from './ui/qr.js';
|
|
|
209
96
|
// more specialized is still available by importing the underlying package
|
|
210
97
|
// directly.
|
|
211
98
|
|
|
212
|
-
// Canonical typed record (from @enbox/api).
|
|
213
|
-
export { Record } from '@enbox/api';
|
|
214
|
-
|
|
215
|
-
// Custom storage adapter + DWN registration provider-auth callback shapes
|
|
216
|
-
// (from @enbox/auth).
|
|
217
|
-
export { BrowserStorage } from '@enbox/auth/browser';
|
|
218
|
-
export type { ProviderAuthParams, ProviderAuthResult } from '@enbox/auth/browser';
|
|
219
|
-
|
|
220
99
|
// Public-profile read/cache layer (from @enbox/protocols): fetch other
|
|
221
100
|
// users' published profiles (displayName + avatar/hero) from their DWNs
|
|
222
101
|
// with caching, retries, negative caching, and refcounted subscriptions.
|
|
@@ -242,12 +121,9 @@ export type {
|
|
|
242
121
|
// DWN interface enum for typing agent requests (from @enbox/agent).
|
|
243
122
|
export { DwnInterface } from '@enbox/agent';
|
|
244
123
|
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
// quota-blocked remote will next be re-probed, via
|
|
249
|
-
// `agent.sync.getRemoteSyncStatus()`; resume immediately with
|
|
250
|
-
// `agent.sync.retryRemoteNow(tenantDid, remoteEndpoint)`.
|
|
124
|
+
// Low-level sync-status types for advanced agent consumers. Dapps using a
|
|
125
|
+
// ConnectionStore receive current advertised rows through `snapshot.sync.remotes`
|
|
126
|
+
// and can call `store.retryRemote(remoteEndpoint)` without wiring agent events.
|
|
251
127
|
export type {
|
|
252
128
|
DeadLetterEntry,
|
|
253
129
|
RemoteSyncState,
|
|
@@ -257,6 +133,7 @@ export type {
|
|
|
257
133
|
SyncEvent,
|
|
258
134
|
SyncEventListener,
|
|
259
135
|
SyncHealthSummary,
|
|
136
|
+
SyncIdentityStatus,
|
|
260
137
|
} from '@enbox/agent';
|
|
261
138
|
|
|
262
139
|
// Common DWN primitives for record queries, permission scopes, and protocol
|