@interop/was-client 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Collection.d.ts +17 -26
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +60 -59
- package/dist/Collection.js.map +1 -1
- package/dist/Resource.d.ts +27 -3
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +60 -22
- package/dist/Resource.js.map +1 -1
- package/dist/Space.d.ts +26 -2
- package/dist/Space.d.ts.map +1 -1
- package/dist/Space.js +66 -18
- package/dist/Space.js.map +1 -1
- package/dist/WasClient.d.ts +6 -0
- package/dist/WasClient.d.ts.map +1 -1
- package/dist/WasClient.js +29 -20
- package/dist/WasClient.js.map +1 -1
- package/dist/edv/EdvCodec.d.ts +11 -2
- package/dist/edv/EdvCodec.d.ts.map +1 -1
- package/dist/edv/EdvCodec.js +79 -67
- package/dist/edv/EdvCodec.js.map +1 -1
- package/dist/edv/WasTransport.d.ts +23 -44
- package/dist/edv/WasTransport.d.ts.map +1 -1
- package/dist/edv/WasTransport.js +109 -155
- package/dist/edv/WasTransport.js.map +1 -1
- package/dist/edv/constants.d.ts +0 -14
- package/dist/edv/constants.d.ts.map +1 -1
- package/dist/edv/constants.js +4 -8
- package/dist/edv/constants.js.map +1 -1
- package/dist/edv/epochCrypto.d.ts +1 -1
- package/dist/edv/epochCrypto.d.ts.map +1 -1
- package/dist/edv/epochCrypto.js +33 -4
- package/dist/edv/epochCrypto.js.map +1 -1
- package/dist/edv/epochKeys.js +11 -1
- package/dist/edv/epochKeys.js.map +1 -1
- package/dist/edv/recipients.d.ts +5 -1
- package/dist/edv/recipients.d.ts.map +1 -1
- package/dist/edv/recipients.js +38 -16
- package/dist/edv/recipients.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/codec.d.ts +19 -0
- package/dist/internal/codec.d.ts.map +1 -1
- package/dist/internal/codec.js +26 -8
- package/dist/internal/codec.js.map +1 -1
- package/dist/internal/conditional.d.ts +11 -0
- package/dist/internal/conditional.d.ts.map +1 -1
- package/dist/internal/conditional.js +12 -0
- package/dist/internal/conditional.js.map +1 -1
- package/dist/internal/content.d.ts +7 -0
- package/dist/internal/content.d.ts.map +1 -1
- package/dist/internal/content.js +5 -3
- package/dist/internal/content.js.map +1 -1
- package/dist/internal/describe.d.ts +49 -1
- package/dist/internal/describe.d.ts.map +1 -1
- package/dist/internal/describe.js +47 -6
- package/dist/internal/describe.js.map +1 -1
- package/dist/internal/features.d.ts +73 -0
- package/dist/internal/features.d.ts.map +1 -0
- package/dist/internal/features.js +129 -0
- package/dist/internal/features.js.map +1 -0
- package/dist/internal/grant.d.ts.map +1 -1
- package/dist/internal/grant.js +2 -1
- package/dist/internal/grant.js.map +1 -1
- package/dist/internal/pagination.d.ts +58 -14
- package/dist/internal/pagination.d.ts.map +1 -1
- package/dist/internal/pagination.js +49 -6
- package/dist/internal/pagination.js.map +1 -1
- package/dist/internal/paths.d.ts.map +1 -1
- package/dist/internal/paths.js +38 -10
- package/dist/internal/paths.js.map +1 -1
- package/dist/internal/policy.d.ts +28 -0
- package/dist/internal/policy.d.ts.map +1 -1
- package/dist/internal/policy.js +31 -0
- package/dist/internal/policy.js.map +1 -1
- package/dist/internal/revoke.d.ts +11 -0
- package/dist/internal/revoke.d.ts.map +1 -1
- package/dist/internal/revoke.js +14 -1
- package/dist/internal/revoke.js.map +1 -1
- package/dist/internal/write.d.ts +10 -1
- package/dist/internal/write.d.ts.map +1 -1
- package/dist/internal/write.js +29 -4
- package/dist/internal/write.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/edv/WasTransport.js
CHANGED
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
* `/{id}/chunks/{n}` sub-segment (the server's `chunked-streams` affordance),
|
|
37
37
|
* storing the chunk object as an opaque JSON body -- so `EdvClientCore.insert({
|
|
38
38
|
* stream })` / `getStream` drive chunked encrypted blobs over a WAS server
|
|
39
|
-
* unchanged.
|
|
39
|
+
* unchanged. Both chunk methods are gated on the backend advertising
|
|
40
|
+
* `chunked-streams` (throwing `NotSupportedError` when it is absent), like
|
|
41
|
+
* `find` is on `blinded-index-query`.
|
|
40
42
|
* `insert` uses an atomic `If-None-Match: *` create when the backend
|
|
41
43
|
* advertises the optional `conditional-writes` feature; otherwise (and for
|
|
42
44
|
* `update`) writes are advisory -- the EDV `sequence` is not enforced
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
*/
|
|
45
47
|
import { Transport } from '@interop/edv-client';
|
|
46
48
|
import { httpStatus } from '../errors.js';
|
|
49
|
+
import { BackendFeatures } from '../internal/features.js';
|
|
47
50
|
import { readJsonData } from '../internal/content.js';
|
|
48
51
|
import { collectionBackend, collectionQuery, resourceChunkPath, resourcePath } from '../internal/paths.js';
|
|
49
52
|
import { DEFAULT_CONTENT_TYPE, envelopeBytes, JOSE_CONTENT_TYPE } from './constants.js';
|
|
@@ -59,16 +62,12 @@ export { JOSE_CONTENT_TYPE };
|
|
|
59
62
|
*/
|
|
60
63
|
const CHUNK_CONTENT_TYPE = 'application/octet-stream';
|
|
61
64
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* (not implemented). These are a definitive "this server advertises no
|
|
66
|
-
* backend features" answer and are safe to cache -- every affordance gate
|
|
67
|
-
* then falls closed against a server that has no backend descriptors. Any
|
|
68
|
-
* other failure (network error, timeout, `401`, `429`, other `5xx`) is
|
|
69
|
-
* transient/ambiguous and is re-probed instead of cached.
|
|
65
|
+
* The message for the EDV unique-attribute collision (HTTP 409): a
|
|
66
|
+
* `unique: true` blinded attribute value already held by another document.
|
|
67
|
+
* Shared by every write method that can trip it.
|
|
70
68
|
*/
|
|
71
|
-
const
|
|
69
|
+
const DUPLICATE_ATTRIBUTE_MESSAGE = 'A unique indexed attribute value is already held by another document in ' +
|
|
70
|
+
'this collection.';
|
|
72
71
|
/**
|
|
73
72
|
* Builds an `Error` carrying the `name` that `EdvClientCore` (and the reference
|
|
74
73
|
* `HttpsTransport`) dispatch on -- `DuplicateError`, `InvalidStateError`,
|
|
@@ -88,12 +87,31 @@ function namedError({ name, message, cause }) {
|
|
|
88
87
|
}
|
|
89
88
|
return err;
|
|
90
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Rethrows a caught transport error as the named error `EdvClientCore`
|
|
92
|
+
* dispatches on, selected by the error's HTTP status from `mapping`; an
|
|
93
|
+
* unmapped status (or a non-HTTP error) is rethrown verbatim. The single
|
|
94
|
+
* status-to-named-error funnel every transport method's catch goes through.
|
|
95
|
+
*
|
|
96
|
+
* @param err {unknown} the caught error
|
|
97
|
+
* @param mapping {object} HTTP status to `{ name, message }` of the named
|
|
98
|
+
* error to throw in its place (the original error becomes its `cause`)
|
|
99
|
+
* @returns {never}
|
|
100
|
+
*/
|
|
101
|
+
function mapTransportError(err, mapping) {
|
|
102
|
+
const status = httpStatus(err);
|
|
103
|
+
const entry = status === undefined ? undefined : mapping[status];
|
|
104
|
+
if (entry) {
|
|
105
|
+
throw namedError({ ...entry, cause: err });
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
91
109
|
export class WasTransport extends Transport {
|
|
92
110
|
spaceId;
|
|
93
111
|
collectionId;
|
|
94
112
|
contentType;
|
|
95
113
|
_was;
|
|
96
|
-
|
|
114
|
+
_features;
|
|
97
115
|
/**
|
|
98
116
|
* @param options {object}
|
|
99
117
|
* @param options.was {WasClient} a WAS client holding the signer
|
|
@@ -110,6 +128,16 @@ export class WasTransport extends Transport {
|
|
|
110
128
|
this.spaceId = spaceId;
|
|
111
129
|
this.collectionId = collectionId;
|
|
112
130
|
this.contentType = contentType;
|
|
131
|
+
// The shared memoizing feature probe (see `BackendFeatures` for the
|
|
132
|
+
// definitive-vs-transient caching rules), reading this collection's
|
|
133
|
+
// "Collection Backend Selected" descriptor with a signed GET.
|
|
134
|
+
this._features = new BackendFeatures(async () => {
|
|
135
|
+
const response = await this._was.request({
|
|
136
|
+
path: collectionBackend(this.spaceId, this.collectionId),
|
|
137
|
+
method: 'GET'
|
|
138
|
+
});
|
|
139
|
+
return readJsonData(response);
|
|
140
|
+
});
|
|
113
141
|
}
|
|
114
142
|
/**
|
|
115
143
|
* The WAS resource path for a document id, delegating to was-client's
|
|
@@ -143,68 +171,6 @@ export class WasTransport extends Transport {
|
|
|
143
171
|
headers: { 'content-type': this.contentType, ...headers }
|
|
144
172
|
});
|
|
145
173
|
}
|
|
146
|
-
/**
|
|
147
|
-
* The feature tokens the collection's backend advertises in its "Collection
|
|
148
|
-
* Backend Selected" descriptor (e.g. `conditional-writes`,
|
|
149
|
-
* `blinded-index-query`). Memoized once it produces a definitive answer: a
|
|
150
|
-
* successful read (including one that lists no features) and a definitive
|
|
151
|
-
* "endpoint absent" (`404` / `405` / `501`) both resolve to a cached feature
|
|
152
|
-
* list, so every affordance gate falls closed against a server that has no
|
|
153
|
-
* backend descriptors.
|
|
154
|
-
*
|
|
155
|
-
* A transient/ambiguous failure (network error, timeout, `401`, `429`, other
|
|
156
|
-
* `5xx`) is NOT cached: the memo is cleared so the next call re-probes, and
|
|
157
|
-
* the error is rethrown so the caller fails loud rather than silently
|
|
158
|
-
* degrading atomicity against a server that may well be capable. (A single
|
|
159
|
-
* transient failure must not poison the transport for its lifetime.)
|
|
160
|
-
*
|
|
161
|
-
* @returns {Promise<string[]>}
|
|
162
|
-
*/
|
|
163
|
-
_backendFeatures() {
|
|
164
|
-
this._backendFeaturesPromise ??= this._probeBackendFeatures();
|
|
165
|
-
return this._backendFeaturesPromise;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Reads and parses the backend descriptor once. On a definitive answer
|
|
169
|
-
* (success, or a `404` / `405` / `501` that means the endpoint is legitimately
|
|
170
|
-
* absent) resolves the feature list, which `_backendFeatures` then caches. On
|
|
171
|
-
* a transient failure, clears the memo (so the next call re-probes) and
|
|
172
|
-
* rethrows.
|
|
173
|
-
*
|
|
174
|
-
* @returns {Promise<string[]>}
|
|
175
|
-
*/
|
|
176
|
-
async _probeBackendFeatures() {
|
|
177
|
-
try {
|
|
178
|
-
const response = await this._was.request({
|
|
179
|
-
path: collectionBackend(this.spaceId, this.collectionId),
|
|
180
|
-
method: 'GET'
|
|
181
|
-
});
|
|
182
|
-
const descriptor = (await readJsonData(response));
|
|
183
|
-
return Array.isArray(descriptor?.features)
|
|
184
|
-
? descriptor.features.filter((feature) => typeof feature === 'string')
|
|
185
|
-
: [];
|
|
186
|
-
}
|
|
187
|
-
catch (err) {
|
|
188
|
-
const status = httpStatus(err);
|
|
189
|
-
if (status !== undefined && DESCRIPTOR_ABSENT_STATUSES.has(status)) {
|
|
190
|
-
return [];
|
|
191
|
-
}
|
|
192
|
-
// Transient/ambiguous: do not cache this failure -- drop the memo so the
|
|
193
|
-
// next call re-probes -- and rethrow.
|
|
194
|
-
this._backendFeaturesPromise = undefined;
|
|
195
|
-
throw err;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Whether the collection's backend advertises the optional
|
|
200
|
-
* `conditional-writes` feature, so `insert` can use an atomic
|
|
201
|
-
* `If-None-Match: *` create instead of the advisory existence-check path.
|
|
202
|
-
*
|
|
203
|
-
* @returns {Promise<boolean>}
|
|
204
|
-
*/
|
|
205
|
-
async _conditionalWrites() {
|
|
206
|
-
return (await this._backendFeatures()).includes('conditional-writes');
|
|
207
|
-
}
|
|
208
174
|
/**
|
|
209
175
|
* @inheritdoc
|
|
210
176
|
*
|
|
@@ -226,27 +192,21 @@ export class WasTransport extends Transport {
|
|
|
226
192
|
if (!encrypted) {
|
|
227
193
|
throw new TypeError('"encrypted" is required.');
|
|
228
194
|
}
|
|
229
|
-
if (await this.
|
|
195
|
+
if (await this._features.has('conditional-writes')) {
|
|
230
196
|
try {
|
|
231
197
|
await this._put(encrypted.id, encrypted, { 'if-none-match': '*' });
|
|
232
198
|
}
|
|
233
199
|
catch (err) {
|
|
234
|
-
|
|
235
|
-
|
|
200
|
+
mapTransportError(err, {
|
|
201
|
+
412: {
|
|
236
202
|
name: 'DuplicateError',
|
|
237
|
-
message: `A document with id "${encrypted.id}" already exists
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
if (httpStatus(err) === 409) {
|
|
242
|
-
throw namedError({
|
|
203
|
+
message: `A document with id "${encrypted.id}" already exists.`
|
|
204
|
+
},
|
|
205
|
+
409: {
|
|
243
206
|
name: 'DuplicateError',
|
|
244
|
-
message:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
throw err;
|
|
207
|
+
message: DUPLICATE_ATTRIBUTE_MESSAGE
|
|
208
|
+
}
|
|
209
|
+
});
|
|
250
210
|
}
|
|
251
211
|
return;
|
|
252
212
|
}
|
|
@@ -260,15 +220,9 @@ export class WasTransport extends Transport {
|
|
|
260
220
|
await this._put(encrypted.id, encrypted);
|
|
261
221
|
}
|
|
262
222
|
catch (err) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
message: 'A unique indexed attribute value is already held by another ' +
|
|
267
|
-
'document in this collection.',
|
|
268
|
-
cause: err
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
throw err;
|
|
223
|
+
mapTransportError(err, {
|
|
224
|
+
409: { name: 'DuplicateError', message: DUPLICATE_ATTRIBUTE_MESSAGE }
|
|
225
|
+
});
|
|
272
226
|
}
|
|
273
227
|
}
|
|
274
228
|
/**
|
|
@@ -298,23 +252,14 @@ export class WasTransport extends Transport {
|
|
|
298
252
|
await this._put(encrypted.id, encrypted);
|
|
299
253
|
}
|
|
300
254
|
catch (err) {
|
|
301
|
-
|
|
302
|
-
|
|
255
|
+
mapTransportError(err, {
|
|
256
|
+
412: {
|
|
303
257
|
name: 'InvalidStateError',
|
|
304
258
|
message: 'Document update conflict: the stored document changed since it ' +
|
|
305
|
-
'was read. Re-fetch the current document and retry.'
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
if (httpStatus(err) === 409) {
|
|
310
|
-
throw namedError({
|
|
311
|
-
name: 'DuplicateError',
|
|
312
|
-
message: 'A unique indexed attribute value is already held by another ' +
|
|
313
|
-
'document in this collection.',
|
|
314
|
-
cause: err
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
throw err;
|
|
259
|
+
'was read. Re-fetch the current document and retry.'
|
|
260
|
+
},
|
|
261
|
+
409: { name: 'DuplicateError', message: DUPLICATE_ATTRIBUTE_MESSAGE }
|
|
262
|
+
});
|
|
318
263
|
}
|
|
319
264
|
}
|
|
320
265
|
/**
|
|
@@ -339,17 +284,30 @@ export class WasTransport extends Transport {
|
|
|
339
284
|
});
|
|
340
285
|
}
|
|
341
286
|
catch (err) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
message: 'Document not found.',
|
|
346
|
-
cause: err
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
throw err;
|
|
287
|
+
mapTransportError(err, {
|
|
288
|
+
404: { name: 'NotFoundError', message: 'Document not found.' }
|
|
289
|
+
});
|
|
350
290
|
}
|
|
351
291
|
return (await readJsonData(response));
|
|
352
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Throws a `NotSupportedError` (the name `EdvClientCore` dispatches on)
|
|
295
|
+
* unless the collection's backend advertises the given affordance token --
|
|
296
|
+
* the shared gate in front of every optional-feature operation.
|
|
297
|
+
*
|
|
298
|
+
* @param feature {string} the affordance token (e.g. `chunked-streams`)
|
|
299
|
+
* @param what {string} the operation name, for the message
|
|
300
|
+
* @returns {Promise<void>}
|
|
301
|
+
*/
|
|
302
|
+
async _requireFeature(feature, what) {
|
|
303
|
+
if (!(await this._features.has(feature))) {
|
|
304
|
+
throw namedError({
|
|
305
|
+
name: 'NotSupportedError',
|
|
306
|
+
message: `${what} is not supported: the collection's backend ` +
|
|
307
|
+
`does not advertise the "${feature}" affordance.`
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
353
311
|
/**
|
|
354
312
|
* Resolves to `true` if a resource exists at the document's path, via a
|
|
355
313
|
* bodiless `HEAD` (the stored envelope is not needed, only its existence). A
|
|
@@ -395,13 +353,7 @@ export class WasTransport extends Transport {
|
|
|
395
353
|
if (!query) {
|
|
396
354
|
throw new TypeError('"query" is required.');
|
|
397
355
|
}
|
|
398
|
-
|
|
399
|
-
throw namedError({
|
|
400
|
-
name: 'NotSupportedError',
|
|
401
|
-
message: "Blinded-index query is not supported: the collection's backend " +
|
|
402
|
-
'does not advertise the "blinded-index-query" affordance.'
|
|
403
|
-
});
|
|
404
|
-
}
|
|
356
|
+
await this._requireFeature('blinded-index-query', 'Blinded-index query');
|
|
405
357
|
// `returnDocuments` is a first-class `IEDVQuery` field, but the WAS profile
|
|
406
358
|
// has no ids-only mode, so it is dropped (whatever its value) and full
|
|
407
359
|
// documents come back -- the best-effort degradation `EdvClientCore.find`
|
|
@@ -416,14 +368,9 @@ export class WasTransport extends Transport {
|
|
|
416
368
|
});
|
|
417
369
|
}
|
|
418
370
|
catch (err) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
message: 'Collection not found.',
|
|
423
|
-
cause: err
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
throw err;
|
|
371
|
+
mapTransportError(err, {
|
|
372
|
+
404: { name: 'NotFoundError', message: 'Collection not found.' }
|
|
373
|
+
});
|
|
427
374
|
}
|
|
428
375
|
const result = await readJsonData(response);
|
|
429
376
|
if (result === null || typeof result !== 'object') {
|
|
@@ -469,9 +416,11 @@ export class WasTransport extends Transport {
|
|
|
469
416
|
* `PUT` as an opaque binary body ({@link CHUNK_CONTENT_TYPE}) to the chunk's
|
|
470
417
|
* own URL (`.../chunks/{index}`), signed like every other write. The server
|
|
471
418
|
* stores the bytes verbatim -- it never parses the chunk -- so any
|
|
472
|
-
* client-side crypto framing is transparent to it.
|
|
473
|
-
*
|
|
474
|
-
*
|
|
419
|
+
* client-side crypto framing is transparent to it. Requires the backend's
|
|
420
|
+
* `chunked-streams` affordance (throws `NotSupportedError` when it is
|
|
421
|
+
* absent). The parent Resource must already exist
|
|
422
|
+
* (`EdvClientCore.insert`/`update` writes the document envelope before
|
|
423
|
+
* draining the stream), so a 404 here surfaces as a `NotFoundError`.
|
|
475
424
|
*
|
|
476
425
|
* @param options {object}
|
|
477
426
|
* @param options.docId {string} the owning document id (= WAS resource id)
|
|
@@ -485,6 +434,10 @@ export class WasTransport extends Transport {
|
|
|
485
434
|
if (!chunk) {
|
|
486
435
|
throw new TypeError('"chunk" is required.');
|
|
487
436
|
}
|
|
437
|
+
// Gate on the affordance before diagnosing a 404: against a server with no
|
|
438
|
+
// `/chunks/{n}` route at all, the 404 would otherwise be misreported as a
|
|
439
|
+
// missing parent document.
|
|
440
|
+
await this._requireFeature('chunked-streams', 'Chunked encrypted storage');
|
|
488
441
|
try {
|
|
489
442
|
await this._was.request({
|
|
490
443
|
path: this._chunkPath(docId, chunk.index),
|
|
@@ -494,15 +447,13 @@ export class WasTransport extends Transport {
|
|
|
494
447
|
});
|
|
495
448
|
}
|
|
496
449
|
catch (err) {
|
|
497
|
-
|
|
498
|
-
|
|
450
|
+
mapTransportError(err, {
|
|
451
|
+
404: {
|
|
499
452
|
name: 'NotFoundError',
|
|
500
453
|
message: `Cannot store chunk ${chunk.index}: the parent document ` +
|
|
501
|
-
`"${docId}" does not exist. Write the document before its chunks
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
throw err;
|
|
454
|
+
`"${docId}" does not exist. Write the document before its chunks.`
|
|
455
|
+
}
|
|
456
|
+
});
|
|
506
457
|
}
|
|
507
458
|
}
|
|
508
459
|
/**
|
|
@@ -510,9 +461,10 @@ export class WasTransport extends Transport {
|
|
|
510
461
|
*
|
|
511
462
|
* Reads one encrypted chunk back by index, `GET`ting the chunk's own URL and
|
|
512
463
|
* parsing the opaque body (stored as raw bytes, so parsed client-side) back
|
|
513
|
-
* into the EDV chunk object the decrypt stream consumes.
|
|
514
|
-
*
|
|
515
|
-
*
|
|
464
|
+
* into the EDV chunk object the decrypt stream consumes. Requires the
|
|
465
|
+
* backend's `chunked-streams` affordance (throws `NotSupportedError` when it
|
|
466
|
+
* is absent). A missing chunk (404) surfaces as a `NotFoundError` (the name
|
|
467
|
+
* `EdvClientCore` expects), so a reassembling reader can distinguish it.
|
|
516
468
|
*
|
|
517
469
|
* @param options {object}
|
|
518
470
|
* @param options.docId {string} the owning document id
|
|
@@ -526,6 +478,10 @@ export class WasTransport extends Transport {
|
|
|
526
478
|
if (chunkIndex === undefined) {
|
|
527
479
|
throw new TypeError('"chunkIndex" is required.');
|
|
528
480
|
}
|
|
481
|
+
// Gate on the affordance before diagnosing a 404: against a server with no
|
|
482
|
+
// `/chunks/{n}` route at all, the 404 would otherwise surface as a spurious
|
|
483
|
+
// missing-chunk (data corruption) report.
|
|
484
|
+
await this._requireFeature('chunked-streams', 'Chunked encrypted storage');
|
|
529
485
|
let response;
|
|
530
486
|
try {
|
|
531
487
|
response = await this._was.request({
|
|
@@ -534,14 +490,12 @@ export class WasTransport extends Transport {
|
|
|
534
490
|
});
|
|
535
491
|
}
|
|
536
492
|
catch (err) {
|
|
537
|
-
|
|
538
|
-
|
|
493
|
+
mapTransportError(err, {
|
|
494
|
+
404: {
|
|
539
495
|
name: 'NotFoundError',
|
|
540
|
-
message: `Chunk ${chunkIndex} of document "${docId}" not found
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
throw err;
|
|
496
|
+
message: `Chunk ${chunkIndex} of document "${docId}" not found.`
|
|
497
|
+
}
|
|
498
|
+
});
|
|
545
499
|
}
|
|
546
500
|
// The chunk was stored as opaque bytes, so the http-client did not
|
|
547
501
|
// pre-parse it: decode the body text and parse the EDV chunk object back.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasTransport.js","sourceRoot":"","sources":["../../src/edv/WasTransport.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"WasTransport.js","sourceRoot":"","sources":["../../src/edv/WasTransport.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAQ/C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,YAAY,EACb,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EAClB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAE5B;;;;;;;;GAQG;AACH,MAAM,kBAAkB,GAAG,0BAA0B,CAAA;AAErD;;;;GAIG;AACH,MAAM,2BAA2B,GAC/B,0EAA0E;IAC1E,kBAAkB,CAAA;AAQpB;;;;;;;;;;GAUG;AACH,SAAS,UAAU,CAAC,EAClB,IAAI,EACJ,OAAO,EACP,KAAK,EAKN;IACC,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC9B,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;IACf,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,GAAG,CAAC,KAAK,GAAG,KAAK,CAAA;IACnB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,iBAAiB,CACxB,GAAY,EACZ,OAA0D;IAE1D,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAChE,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,UAAU,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAC5C,CAAC;IACD,MAAM,GAAG,CAAA;AACX,CAAC;AAED,MAAM,OAAO,YAAa,SAAQ,SAAS;IAChC,OAAO,CAAQ;IACf,YAAY,CAAQ;IACpB,WAAW,CAAQ;IAEX,IAAI,CAAc;IAClB,SAAS,CAAiB;IAE3C;;;;;;;;;OASG;IACH,YAAY,EACV,GAAG,EACH,OAAO,EACP,YAAY,EACZ,WAAW,GAAG,oBAAoB,EAMnC;QACC,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;QAChC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,oEAAoE;QACpE,oEAAoE;QACpE,8DAA8D;QAC9D,IAAI,CAAC,SAAS,GAAG,IAAI,eAAe,CAAC,KAAK,IAAI,EAAE;YAC9C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;gBACvC,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC;gBACxD,MAAM,EAAE,KAAK;aACd,CAAC,CAAA;YACF,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAA;QAC/B,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;;;;;;OAQG;IACK,aAAa,CAAC,EAAU;QAC9B,OAAO,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;IAC1D,CAAC;IAED;;;;;;;;;;OAUG;IACK,KAAK,CAAC,IAAI,CAChB,EAAU,EACV,SAA6B,EAC7B,UAAkC,EAAE;QAEpC,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAA;QACrC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YACvB,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAC5B,MAAM,EAAE,KAAK;YACb,IAAI;YACJ,OAAO,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,EAAE;SAC1D,CAAC,CAAA;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACM,KAAK,CAAC,MAAM,CAAC,EACpB,SAAS,KAC6B,EAAE;QACxC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,SAAS,CAAC,0BAA0B,CAAC,CAAA;QACjD,CAAC;QACD,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,CAAA;YACpE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,iBAAiB,CAAC,GAAG,EAAE;oBACrB,GAAG,EAAE;wBACH,IAAI,EAAE,gBAAgB;wBACtB,OAAO,EAAE,uBAAuB,SAAS,CAAC,EAAE,mBAAmB;qBAChE;oBACD,GAAG,EAAE;wBACH,IAAI,EAAE,gBAAgB;wBACtB,OAAO,EAAE,2BAA2B;qBACrC;iBACF,CAAC,CAAA;YACJ,CAAC;YACD,OAAM;QACR,CAAC;QACD,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;YACrC,MAAM,UAAU,CAAC;gBACf,IAAI,EAAE,gBAAgB;gBACtB,OAAO,EAAE,uBAAuB,SAAS,CAAC,EAAE,mBAAmB;aAChE,CAAC,CAAA;QACJ,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAG,EAAE;gBACrB,GAAG,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,2BAA2B,EAAE;aACtE,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACM,KAAK,CAAC,MAAM,CAAC,EACpB,SAAS,KAC6B,EAAE;QACxC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,SAAS,CAAC,0BAA0B,CAAC,CAAA;QACjD,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAG,EAAE;gBACrB,GAAG,EAAE;oBACH,IAAI,EAAE,mBAAmB;oBACzB,OAAO,EACL,iEAAiE;wBACjE,oDAAoD;iBACvD;gBACD,GAAG,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,2BAA2B,EAAE;aACtE,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;;OASG;IACM,KAAK,CAAC,GAAG,CAAC,EACjB,EAAE,KACiB,EAAE;QACrB,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,IAAI,SAAS,CAAC,mBAAmB,CAAC,CAAA;QAC1C,CAAC;QACD,IAAI,QAAsB,CAAA;QAC1B,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;gBACjC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC5B,MAAM,EAAE,KAAK;aACd,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAG,EAAE;gBACrB,GAAG,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,qBAAqB,EAAE;aAC/D,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,CAAC,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAuB,CAAA;IAC7D,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,eAAe,CAAC,OAAe,EAAE,IAAY;QACzD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACzC,MAAM,UAAU,CAAC;gBACf,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EACL,GAAG,IAAI,8CAA8C;oBACrD,2BAA2B,OAAO,eAAe;aACpD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACK,KAAK,CAAC,OAAO,CAAC,EAAU;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YACzE,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC5B,OAAO,KAAK,CAAA;YACd,CAAC;YACD,MAAM,GAAG,CAAA;QACX,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACM,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,KAA4B,EAAE;QACvD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC7C,CAAC;QACD,MAAM,IAAI,CAAC,eAAe,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,CAAA;QACxE,4EAA4E;QAC5E,uEAAuE;QACvE,0EAA0E;QAC1E,6BAA6B;QAC7B,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE,GAAG,KAAK,CAAA;QACpE,IAAI,QAAsB,CAAA;QAC1B,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;gBACjC,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC;gBACtD,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,YAAY,EAAE;aACpD,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAG,EAAE;gBACrB,GAAG,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,uBAAuB,EAAE;aACjE,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAA;QAC3C,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;QAC5D,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IAED;;;;;;;;;OASG;IACM,KAAK,CAAC,WAAW;QACxB,MAAM,UAAU,CAAC;YACf,IAAI,EAAE,mBAAmB;YACzB,OAAO,EACL,oEAAoE;gBACpE,kEAAkE;gBAClE,4DAA4D;SAC/D,CAAC,CAAA;IACJ,CAAC;IAED;;;;;;;;OAQG;IACK,UAAU,CAAC,KAAa,EAAE,UAAkB;QAClD,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,CAAA;IAC9E,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACM,KAAK,CAAC,UAAU,CAAC,EACxB,KAAK,EACL,KAAK,KACoC,EAAE;QAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC7C,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC7C,CAAC;QACD,2EAA2E;QAC3E,0EAA0E;QAC1E,2BAA2B;QAC3B,MAAM,IAAI,CAAC,eAAe,CAAC,iBAAiB,EAAE,2BAA2B,CAAC,CAAA;QAC1E,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;gBACtB,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;gBACzC,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC;gBAC1B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;aAChD,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAG,EAAE;gBACrB,GAAG,EAAE;oBACH,IAAI,EAAE,eAAe;oBACrB,OAAO,EACL,sBAAsB,KAAK,CAAC,KAAK,wBAAwB;wBACzD,IAAI,KAAK,yDAAyD;iBACrE;aACF,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACM,KAAK,CAAC,QAAQ,CAAC,EACtB,KAAK,EACL,UAAU,KACiC,EAAE;QAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC7C,CAAC;QACD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,SAAS,CAAC,2BAA2B,CAAC,CAAA;QAClD,CAAC;QACD,2EAA2E;QAC3E,4EAA4E;QAC5E,0CAA0C;QAC1C,MAAM,IAAI,CAAC,eAAe,CAAC,iBAAiB,EAAE,2BAA2B,CAAC,CAAA;QAC1E,IAAI,QAAsB,CAAA;QAC1B,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;gBACjC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC;gBACxC,MAAM,EAAE,KAAK;aACd,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAG,EAAE;gBACrB,GAAG,EAAE;oBACH,IAAI,EAAE,eAAe;oBACrB,OAAO,EAAE,SAAS,UAAU,iBAAiB,KAAK,cAAc;iBACjE;aACF,CAAC,CAAA;QACJ,CAAC;QACD,mEAAmE;QACnE,0EAA0E;QAC1E,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAc,CAAA;IACvD,CAAC;CACF"}
|
package/dist/edv/constants.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Shared EDV-over-WAS constants: the stored-envelope content types (the portable
|
|
6
|
-
* default and the preferred JOSE marker) and the `TextEncoder` used to serialize
|
|
7
|
-
* envelopes to bytes. Kept in one place so `WasTransport` and `EdvCodec` stay in
|
|
8
|
-
* lockstep instead of each declaring their own copy.
|
|
9
|
-
*/
|
|
10
1
|
/**
|
|
11
2
|
* The content type used by default: plain JSON, which an unmodified WAS server
|
|
12
3
|
* accepts. The stored envelope is still self-identifying by its `jwe` field.
|
|
@@ -22,11 +13,6 @@ export declare const DEFAULT_CONTENT_TYPE = "application/json";
|
|
|
22
13
|
* `contentType` option).
|
|
23
14
|
*/
|
|
24
15
|
export declare const JOSE_CONTENT_TYPE = "application/jose+json";
|
|
25
|
-
/**
|
|
26
|
-
* A shared `TextEncoder` for serializing envelope bytes (stateless, so one
|
|
27
|
-
* instance is reused across every write).
|
|
28
|
-
*/
|
|
29
|
-
export declare const ENCODER: TextEncoder;
|
|
30
16
|
/**
|
|
31
17
|
* Serializes an encrypted envelope to its wire bytes -- the single source of
|
|
32
18
|
* the envelope encoding shared by `EdvCodec` and `WasTransport`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/edv/constants.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/edv/constants.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,qBAAqB,CAAA;AAEtD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,0BAA0B,CAAA;AAExD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAE1D"}
|
package/dist/edv/constants.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* Shared EDV-over-WAS constants: the stored-envelope content types (the portable
|
|
6
|
-
* default and the preferred JOSE marker) and the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* default and the preferred JOSE marker) and the envelope byte serialization.
|
|
7
|
+
* Kept in one place so `WasTransport` and `EdvCodec` stay in lockstep instead
|
|
8
|
+
* of each declaring their own copy.
|
|
9
9
|
*/
|
|
10
|
+
import { ENCODER } from '../internal/content.js';
|
|
10
11
|
/**
|
|
11
12
|
* The content type used by default: plain JSON, which an unmodified WAS server
|
|
12
13
|
* accepts. The stored envelope is still self-identifying by its `jwe` field.
|
|
@@ -22,11 +23,6 @@ export const DEFAULT_CONTENT_TYPE = 'application/json';
|
|
|
22
23
|
* `contentType` option).
|
|
23
24
|
*/
|
|
24
25
|
export const JOSE_CONTENT_TYPE = 'application/jose+json';
|
|
25
|
-
/**
|
|
26
|
-
* A shared `TextEncoder` for serializing envelope bytes (stateless, so one
|
|
27
|
-
* instance is reused across every write).
|
|
28
|
-
*/
|
|
29
|
-
export const ENCODER = new TextEncoder();
|
|
30
26
|
/**
|
|
31
27
|
* Serializes an encrypted envelope to its wire bytes -- the single source of
|
|
32
28
|
* the envelope encoding shared by `EdvCodec` and `WasTransport`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/edv/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/edv/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAEhD;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAA;AAEtD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAA;AACjD,CAAC"}
|
|
@@ -85,7 +85,7 @@ export declare function epochKeyIdFor(epochId: string): string;
|
|
|
85
85
|
* A `did:key` key resolver for X25519 key-agreement keys: resolves a
|
|
86
86
|
* `did:key:z...#z...` id (an epoch key, or any self-describing X25519 key) to
|
|
87
87
|
* its public verification method. The public key is the fragment, so no network
|
|
88
|
-
* or registry lookup is needed.
|
|
88
|
+
* or registry lookup is needed. Memoized per id.
|
|
89
89
|
*
|
|
90
90
|
* @param options {object}
|
|
91
91
|
* @param options.id {string} the key id to resolve
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epochCrypto.d.ts","sourceRoot":"","sources":["../../src/edv/epochCrypto.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;AAEhE;;GAEG;AACH,eAAO,MAAM,YAAY,mBAAmB,CAAA;AAS5C;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,kBAAkB,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,EACpC,WAAW,EACX,SAAS,EACV,EAAE;IACD,WAAW,EAAE,UAAU,CAAA;IACvB,SAAS,EAAE,kBAAkB,CAAA;CAC9B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CA4BzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,KAAK,EACL,eAAe,EAChB,EAAE;IACD,KAAK,EAAE,6BAA6B,CAAA;IACpC,eAAe,EAAE,gBAAgB,CAAA;CAClC,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"epochCrypto.d.ts","sourceRoot":"","sources":["../../src/edv/epochCrypto.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;AAEhE;;GAEG;AACH,eAAO,MAAM,YAAY,mBAAmB,CAAA;AAS5C;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,kBAAkB,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,EACpC,WAAW,EACX,SAAS,EACV,EAAE;IACD,WAAW,EAAE,UAAU,CAAA;IACvB,SAAS,EAAE,kBAAkB,CAAA;CAC9B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CA4BzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,KAAK,EACL,eAAe,EAChB,EAAE;IACD,KAAK,EAAE,6BAA6B,CAAA;IACpC,eAAe,EAAE,gBAAgB,CAAA;CAClC,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAuC7B;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,UAAU,CAAA;CACnB,CAAC,CAQD;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,MAAM,EACP,EAAE;IACD,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,UAAU,CAAA;CACnB,GAAG,gBAAgB,CASnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AA6BD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,EACnC,EAAE,EACH,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAAC,CA0BpE"}
|
package/dist/edv/epochCrypto.js
CHANGED
|
@@ -96,7 +96,15 @@ export async function unwrapEpochSecret({ entry, keyAgreementKey }) {
|
|
|
96
96
|
if (!epk || typeof epk.x !== 'string') {
|
|
97
97
|
return null;
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
let ephemeralPublicKey;
|
|
100
|
+
try {
|
|
101
|
+
ephemeralPublicKey = base64urlnopad.decode(epk.x);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// A malformed (non-base64url) `epk.x` is the same corrupt-entry class as a
|
|
105
|
+
// failed unwrap: honor the documented null contract.
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
100
108
|
let secret;
|
|
101
109
|
try {
|
|
102
110
|
secret = await keyAgreementKey.deriveSecret({
|
|
@@ -113,7 +121,15 @@ export async function unwrapEpochSecret({ entry, keyAgreementKey }) {
|
|
|
113
121
|
const consumerInfo = TEXT_ENCODER.encode(keyAgreementKey.id);
|
|
114
122
|
const keyData = await deriveKey({ secret, producerInfo, consumerInfo });
|
|
115
123
|
const kek = await createKek({ keyData });
|
|
116
|
-
|
|
124
|
+
try {
|
|
125
|
+
return await kek.unwrapKey({ wrappedKey: entry.encrypted_key });
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// `unwrapKey` returns null on an AES integrity failure but throws on a
|
|
129
|
+
// malformed (non-base64url) `encrypted_key`; both are the same "corrupt
|
|
130
|
+
// entry" class to a caller, so honor the documented null contract here.
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
117
133
|
}
|
|
118
134
|
/**
|
|
119
135
|
* Mints a fresh key epoch: a new random X25519 key pair whose `did:key` is the
|
|
@@ -177,11 +193,18 @@ function publicKeyMultibaseFromEpochId(epochId) {
|
|
|
177
193
|
}
|
|
178
194
|
return epochId.slice(prefix.length);
|
|
179
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Memo of already-resolved key ids: the resolution is a pure deterministic
|
|
198
|
+
* function of the id, and the write path resolves the same epoch key on every
|
|
199
|
+
* encode, so each distinct id pays the fingerprint reconstruction once. Grows
|
|
200
|
+
* by one small entry per distinct epoch/recipient key seen in the process.
|
|
201
|
+
*/
|
|
202
|
+
const RESOLVED_KEYS = new Map();
|
|
180
203
|
/**
|
|
181
204
|
* A `did:key` key resolver for X25519 key-agreement keys: resolves a
|
|
182
205
|
* `did:key:z...#z...` id (an epoch key, or any self-describing X25519 key) to
|
|
183
206
|
* its public verification method. The public key is the fragment, so no network
|
|
184
|
-
* or registry lookup is needed.
|
|
207
|
+
* or registry lookup is needed. Memoized per id.
|
|
185
208
|
*
|
|
186
209
|
* @param options {object}
|
|
187
210
|
* @param options.id {string} the key id to resolve
|
|
@@ -191,6 +214,10 @@ export async function didKeyResolver({ id }) {
|
|
|
191
214
|
if (!id) {
|
|
192
215
|
throw new Error('A key id is required to resolve.');
|
|
193
216
|
}
|
|
217
|
+
const cached = RESOLVED_KEYS.get(id);
|
|
218
|
+
if (cached) {
|
|
219
|
+
return cached;
|
|
220
|
+
}
|
|
194
221
|
const hash = id.indexOf('#');
|
|
195
222
|
const fragment = hash === -1 ? '' : id.slice(hash + 1);
|
|
196
223
|
if (!fragment.startsWith('z')) {
|
|
@@ -202,10 +229,12 @@ export async function didKeyResolver({ id }) {
|
|
|
202
229
|
fingerprint: fragment
|
|
203
230
|
});
|
|
204
231
|
keyPair.verifyFingerprint({ fingerprint: fragment });
|
|
205
|
-
|
|
232
|
+
const resolved = {
|
|
206
233
|
id,
|
|
207
234
|
type: X25519_TYPE,
|
|
208
235
|
publicKeyMultibase: keyPair.publicKeyMultibase
|
|
209
236
|
};
|
|
237
|
+
RESOLVED_KEYS.set(id, resolved);
|
|
238
|
+
return resolved;
|
|
210
239
|
}
|
|
211
240
|
//# sourceMappingURL=epochCrypto.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epochCrypto.js","sourceRoot":"","sources":["../../src/edv/epochCrypto.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EACL,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,4BAA4B,EAC7B,MAAM,mCAAmC,CAAA;AAI1C;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAA;AAE5C;;GAEG;AACH,MAAM,WAAW,GAAG,2BAA2B,CAAA;AAE/C,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAA;AAatC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EACpC,WAAW,EACX,SAAS,EAIV;IACC,MAAM,SAAS,GAAG,MAAM,yBAAyB,CAAC,QAAQ,EAAE,CAAA;IAC5D,MAAM,kBAAkB,GAAG,eAAe,CACxC,4BAA4B,EAC5B,SAAS,CAAC,kBAAkB,CAC7B,CAAA;IACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC;QAC1C,SAAS,EAAE,EAAE,kBAAkB,EAAE,SAAS,CAAC,kBAAkB,EAAE;KAChE,CAAC,CAAA;IACF,MAAM,YAAY,GAAG,kBAAkB,CAAA;IACvC,MAAM,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;IACtD,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC,CAAA;IACvE,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IACxC,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAA;IACrE,OAAO;QACL,MAAM,EAAE;YACN,GAAG,EAAE,SAAS,CAAC,EAAE;YACjB,GAAG,EAAE,YAAY;YACjB,GAAG,EAAE;gBACH,GAAG,EAAE,KAAK;gBACV,GAAG,EAAE,QAAQ;gBACb,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,kBAAkB,CAAC;aAC7C;YACD,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC;YACxC,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC;SACzC;QACD,aAAa,EAAE,YAAY;KAC5B,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACtC,KAAK,EACL,eAAe,EAIhB;IACC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,GAAkC,CAAA;IAC3D,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,IAAI,CAAA;IACb,CAAC;IACD,
|
|
1
|
+
{"version":3,"file":"epochCrypto.js","sourceRoot":"","sources":["../../src/edv/epochCrypto.ts"],"names":[],"mappings":"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"}
|