@enbox/browser 0.3.70 → 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/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/package.json +7 -7
- package/src/blob-url-pool.ts +55 -0
- package/src/file-envelope-url-store.ts +260 -0
- package/src/index.ts +23 -159
- package/src/object-url-reference.ts +82 -0
|
@@ -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,150 +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
|
-
RecordParentNotFoundError,
|
|
44
|
-
RecordSquashBackstopError,
|
|
45
|
-
RecordValidationError,
|
|
46
|
-
protocolContextKey,
|
|
47
|
-
recordCodecs,
|
|
48
|
-
ServiceConfigProtocol,
|
|
49
|
-
} from '@enbox/api';
|
|
50
|
-
|
|
51
|
-
export type {
|
|
52
|
-
ApplicationConnectionStore,
|
|
53
|
-
ApplicationConnectionStoreConnectOptions,
|
|
54
|
-
ApplicationConnectionStoreOptions,
|
|
55
|
-
ApplicationConnectionStoreRefreshOptions,
|
|
56
|
-
ApplicationManifest,
|
|
57
|
-
ApplicationManifestProtocol,
|
|
58
|
-
ApplicationManifestProtocolInput,
|
|
59
|
-
AudienceDecryptFailureCause,
|
|
60
|
-
ConnectionPhase,
|
|
61
|
-
ConnectionSnapshot,
|
|
62
|
-
ConnectionSnapshotListener,
|
|
63
|
-
ConnectionStore,
|
|
64
|
-
ConnectionStoreOptions,
|
|
65
|
-
ContextView,
|
|
66
|
-
ContextViewListener,
|
|
67
|
-
ContextViewState,
|
|
68
|
-
ContextMember,
|
|
69
|
-
ContextMembersApi,
|
|
70
|
-
ContextInvitation,
|
|
71
|
-
ContextInvitationPreview,
|
|
72
|
-
ContextInvitationsApi,
|
|
73
|
-
ContextMaterializedRecord,
|
|
74
|
-
ContextRecord,
|
|
75
|
-
ContextRecordDeleteParams,
|
|
76
|
-
ContextRecordsApi,
|
|
77
|
-
ContextRecordUpdateParams,
|
|
78
|
-
ContextRoleGroup,
|
|
79
|
-
ContextRoleGroups,
|
|
80
|
-
ContextRolePath,
|
|
81
|
-
ContextsApi,
|
|
82
|
-
DataForPath,
|
|
83
|
-
DefineApplicationManifestOptions,
|
|
84
|
-
DwnEndpointResolution,
|
|
85
|
-
EnboxAnonymousOptions,
|
|
86
|
-
EnboxConnectOptions,
|
|
87
|
-
EnboxConnectResult,
|
|
88
|
-
EnboxParams,
|
|
89
|
-
EnboxSessionParams,
|
|
90
|
-
EncodedRecordData,
|
|
91
|
-
EnsureProtocolsReadyOptions,
|
|
92
|
-
ExpandableRecordView,
|
|
93
|
-
FileEnvelopeCodec,
|
|
94
|
-
FileEnvelopeCodecOptions,
|
|
95
|
-
FileEnvelopeData,
|
|
96
|
-
FollowContextOptions,
|
|
97
|
-
JsonRecordCodecOptions,
|
|
98
|
-
MaterializedMemberContext,
|
|
99
|
-
MaterializedRecord,
|
|
100
|
-
MemberContext,
|
|
101
|
-
OwnedContext,
|
|
102
|
-
ProtocolContext,
|
|
103
|
-
ProtocolRolePaths,
|
|
104
|
-
SquashProtocolPaths,
|
|
105
|
-
RecordCodec,
|
|
106
|
-
RecordCodecContext,
|
|
107
|
-
RecordCodecValue,
|
|
108
|
-
RecordFilter,
|
|
109
|
-
RecordPage,
|
|
110
|
-
RecordPatch,
|
|
111
|
-
RecordPatchInput,
|
|
112
|
-
RecordQuery,
|
|
113
|
-
RecordSubscription,
|
|
114
|
-
RecordSubscriptionEvent,
|
|
115
|
-
RecordSubscriptionListener,
|
|
116
|
-
RecordView,
|
|
117
|
-
RecordViewListener,
|
|
118
|
-
RecordViewState,
|
|
119
|
-
RecordValidationFailure,
|
|
120
|
-
RecordValidator,
|
|
121
|
-
ReplicationCurrentness,
|
|
122
|
-
RoleDeliveryState,
|
|
123
|
-
SyncStatusSnapshot,
|
|
124
|
-
TypedMaterializedRecord,
|
|
125
|
-
TypedRecord,
|
|
126
|
-
} from '@enbox/api';
|
|
34
|
+
export * from '@enbox/api';
|
|
127
35
|
|
|
128
36
|
// ─── Re-exports from @enbox/auth ────────────────────────────────
|
|
129
37
|
//
|
|
130
38
|
// Authentication lifecycle, session management, connect handlers.
|
|
131
39
|
|
|
132
|
-
export
|
|
133
|
-
AuthManager,
|
|
134
|
-
AuthSession,
|
|
135
|
-
ConnectDeniedError,
|
|
136
|
-
SESSION_EXPIRED_ERROR_CODE,
|
|
137
|
-
SESSION_REVOKED_ERROR_CODE,
|
|
138
|
-
computeConnectionStatus,
|
|
139
|
-
isConnectDeniedError,
|
|
140
|
-
isSessionExpiredError,
|
|
141
|
-
isSessionInvalidError,
|
|
142
|
-
normalizeProtocolRequests,
|
|
143
|
-
WalletConnect,
|
|
144
|
-
} from '@enbox/auth/browser';
|
|
145
|
-
|
|
146
|
-
export type {
|
|
147
|
-
AuthEvent,
|
|
148
|
-
AuthEventHandler,
|
|
149
|
-
AuthManagerOptions,
|
|
150
|
-
AuthState,
|
|
151
|
-
ComputeConnectionStatusOptions,
|
|
152
|
-
ConnectionMonitorOptions,
|
|
153
|
-
ConnectionState,
|
|
154
|
-
ConnectionStatus,
|
|
155
|
-
ConnectionStatusGrant,
|
|
156
|
-
ConnectHandler,
|
|
157
|
-
ConnectOptions,
|
|
158
|
-
ConnectResult,
|
|
159
|
-
DisconnectOptions,
|
|
160
|
-
GetConnectionStatusOptions,
|
|
161
|
-
HandlerConnectOptions,
|
|
162
|
-
ImportFromPortableOptions,
|
|
163
|
-
VaultConnectOptions,
|
|
164
|
-
Permission,
|
|
165
|
-
PortableIdentity,
|
|
166
|
-
ProtocolDefinitionCarrier,
|
|
167
|
-
ProtocolPermissionRequest,
|
|
168
|
-
ProtocolRequest,
|
|
169
|
-
RefreshOptions,
|
|
170
|
-
RegistrationOptions,
|
|
171
|
-
StorageAdapter,
|
|
172
|
-
SyncOption,
|
|
173
|
-
WalletConnectOptions,
|
|
174
|
-
} from '@enbox/auth/browser';
|
|
40
|
+
export * from '@enbox/auth/browser';
|
|
175
41
|
|
|
176
42
|
// ─── Re-exports from @enbox/connect ─────────────────────────────
|
|
177
43
|
//
|
|
@@ -179,7 +45,7 @@ export type {
|
|
|
179
45
|
// transports: opening/sealing envelopes and the deny token.
|
|
180
46
|
|
|
181
47
|
export { CONNECT_DENIED_TOKEN, ConnectProvider } from '@enbox/connect';
|
|
182
|
-
export type { ConnectApproval,
|
|
48
|
+
export type { ConnectApproval, ConnectRequest } from '@enbox/connect';
|
|
183
49
|
|
|
184
50
|
// ─── Browser-specific exports ───────────────────────────────────
|
|
185
51
|
//
|
|
@@ -213,6 +79,14 @@ export { MAX_PIN_ATTEMPTS, RelayConnectCancelledError, runRelayConnect } from '.
|
|
|
213
79
|
export type { RelayConnectOptions } from './relay-connect-runner.js';
|
|
214
80
|
export { encodeQr, qrToSvg } from './ui/qr.js';
|
|
215
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';
|
|
216
90
|
|
|
217
91
|
// ─── Convenience re-exports for dapps ───────────────────────────
|
|
218
92
|
//
|
|
@@ -222,14 +96,6 @@ export type { QrCode, QrSvgOptions } from './ui/qr.js';
|
|
|
222
96
|
// more specialized is still available by importing the underlying package
|
|
223
97
|
// directly.
|
|
224
98
|
|
|
225
|
-
// Canonical typed record (from @enbox/api).
|
|
226
|
-
export { Record } from '@enbox/api';
|
|
227
|
-
|
|
228
|
-
// Custom storage adapter + DWN registration provider-auth callback shapes
|
|
229
|
-
// (from @enbox/auth).
|
|
230
|
-
export { BrowserStorage } from '@enbox/auth/browser';
|
|
231
|
-
export type { ProviderAuthParams, ProviderAuthResult } from '@enbox/auth/browser';
|
|
232
|
-
|
|
233
99
|
// Public-profile read/cache layer (from @enbox/protocols): fetch other
|
|
234
100
|
// users' published profiles (displayName + avatar/hero) from their DWNs
|
|
235
101
|
// with caching, retries, negative caching, and refcounted subscriptions.
|
|
@@ -255,12 +121,9 @@ export type {
|
|
|
255
121
|
// DWN interface enum for typing agent requests (from @enbox/agent).
|
|
256
122
|
export { DwnInterface } from '@enbox/agent';
|
|
257
123
|
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
// quota-blocked remote will next be re-probed, via
|
|
262
|
-
// `agent.sync.getRemoteSyncStatus()`; resume immediately with
|
|
263
|
-
// `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.
|
|
264
127
|
export type {
|
|
265
128
|
DeadLetterEntry,
|
|
266
129
|
RemoteSyncState,
|
|
@@ -270,6 +133,7 @@ export type {
|
|
|
270
133
|
SyncEvent,
|
|
271
134
|
SyncEventListener,
|
|
272
135
|
SyncHealthSummary,
|
|
136
|
+
SyncIdentityStatus,
|
|
273
137
|
} from '@enbox/agent';
|
|
274
138
|
|
|
275
139
|
// Common DWN primitives for record queries, permission scopes, and protocol
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
|
|
9
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
10
|
+
|
|
11
|
+
function assertReleaseDelay(delayMs: number): void {
|
|
12
|
+
if (!Number.isSafeInteger(delayMs) || delayMs < 0 || delayMs > MAX_TIMER_DELAY_MS) {
|
|
13
|
+
throw new TypeError(`delayMs must be an integer between 0 and ${MAX_TIMER_DELAY_MS}.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Private reference-counted ownership for one object URL. */
|
|
18
|
+
export class ObjectUrlReference {
|
|
19
|
+
public readonly url: string;
|
|
20
|
+
private _isRevoked = false;
|
|
21
|
+
private _references = 0;
|
|
22
|
+
private readonly _releaseTimers = new Set<ReturnType<typeof setTimeout>>();
|
|
23
|
+
|
|
24
|
+
public constructor(blob: Blob, private readonly _onEmpty: (reference: ObjectUrlReference) => void) {
|
|
25
|
+
this.url = globalThis.URL.createObjectURL(blob);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public get references(): number {
|
|
29
|
+
return this._references;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public acquire(): ObjectUrlLease {
|
|
33
|
+
if (this._isRevoked) {
|
|
34
|
+
throw new Error('Object URL has been revoked.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this._references += 1;
|
|
38
|
+
let released = false;
|
|
39
|
+
let releaseTimer: ReturnType<typeof setTimeout> | undefined;
|
|
40
|
+
const release = (): void => {
|
|
41
|
+
if (released) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
released = true;
|
|
45
|
+
if (releaseTimer !== undefined) {
|
|
46
|
+
clearTimeout(releaseTimer);
|
|
47
|
+
this._releaseTimers.delete(releaseTimer);
|
|
48
|
+
}
|
|
49
|
+
if (this._isRevoked) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this._references -= 1;
|
|
53
|
+
if (this._references === 0) {
|
|
54
|
+
this._onEmpty(this);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
url : this.url,
|
|
59
|
+
release,
|
|
60
|
+
releaseAfter : (delayMs: number): void => {
|
|
61
|
+
assertReleaseDelay(delayMs);
|
|
62
|
+
if (released || this._isRevoked || releaseTimer !== undefined) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
releaseTimer = setTimeout(release, delayMs);
|
|
66
|
+
this._releaseTimers.add(releaseTimer);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public revoke(): void {
|
|
72
|
+
if (!this._isRevoked) {
|
|
73
|
+
this._isRevoked = true;
|
|
74
|
+
for (const timer of this._releaseTimers) {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
this._releaseTimers.clear();
|
|
78
|
+
this._references = 0;
|
|
79
|
+
globalThis.URL.revokeObjectURL(this.url);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|