@telecrypt-io/storage 0.5.27 → 0.5.29
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 +22 -8
- package/dist/TeleCryptIOStorage.d.ts +8 -10
- package/dist/TeleCryptIOStorage.js +244 -211
- package/dist/core/errors.d.ts +9 -9
- package/dist/core/errors.js +18 -18
- package/dist/core/http.d.ts +20 -14
- package/dist/core/http.js +165 -91
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/oidc.d.ts +15 -0
- package/dist/core/oidc.js +533 -215
- package/dist/core/operations.js +187 -267
- package/dist/core/poll.d.ts +3 -0
- package/dist/core/poll.js +7 -1
- package/dist/core/validation.d.ts +2 -1
- package/dist/core/validation.js +8 -5
- package/package.json +1 -1
package/dist/core/errors.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* conditions (bad login, wrong recovery key, missing file, vault, or folder, not
|
|
4
4
|
* logged in, ...). Platform-agnostic: no Node/CLI dependencies. */
|
|
5
5
|
export declare class StorageError extends Error {
|
|
6
|
-
constructor(message: string);
|
|
6
|
+
constructor(message: string, options?: ErrorOptions);
|
|
7
7
|
}
|
|
8
8
|
/** Setup would replace account recovery state, which this SDK never does. */
|
|
9
9
|
export declare class RecoveryAlreadyConfiguredError extends StorageError {
|
|
@@ -13,35 +13,35 @@ export declare class RecoveryAlreadyConfiguredError extends StorageError {
|
|
|
13
13
|
/** Restore failed without exposing provider or crypto-library error details. */
|
|
14
14
|
export declare class RecoveryRestoreError extends StorageError {
|
|
15
15
|
readonly code: "RECOVERY_RESTORE_FAILED";
|
|
16
|
-
constructor();
|
|
16
|
+
constructor(cause?: unknown);
|
|
17
17
|
}
|
|
18
18
|
/** Setup failed before the generated Recovery Key could be safely returned. */
|
|
19
19
|
export declare class RecoverySetupError extends StorageError {
|
|
20
20
|
readonly code: "RECOVERY_SETUP_FAILED";
|
|
21
|
-
constructor();
|
|
21
|
+
constructor(cause?: unknown);
|
|
22
22
|
}
|
|
23
23
|
/** The recovery setup transport result is ambiguous; state must be checked before retrying. */
|
|
24
24
|
export declare class RecoverySetupAmbiguousError extends StorageError {
|
|
25
25
|
readonly code: "RECOVERY_SETUP_AMBIGUOUS";
|
|
26
|
-
constructor();
|
|
26
|
+
constructor(cause?: unknown);
|
|
27
27
|
}
|
|
28
28
|
/** Recovery restore was cancelled after the provider may have changed state. */
|
|
29
29
|
export declare class RecoveryRestoreAmbiguousError extends StorageError {
|
|
30
30
|
readonly code: "RECOVERY_RESTORE_AMBIGUOUS";
|
|
31
|
-
constructor();
|
|
31
|
+
constructor(cause?: unknown);
|
|
32
32
|
}
|
|
33
33
|
/** A Matrix mutation was cancelled after its request started. */
|
|
34
34
|
export declare class MutationOutcomeUnknownError extends StorageError {
|
|
35
35
|
readonly code: "MUTATION_OUTCOME_UNKNOWN";
|
|
36
36
|
readonly operation: string;
|
|
37
|
-
constructor(operation?: string);
|
|
37
|
+
constructor(operation?: string, options?: ErrorOptions);
|
|
38
38
|
}
|
|
39
39
|
/** A multi-room mutation committed some work before a later step failed. */
|
|
40
40
|
export declare class MutationPartialError extends StorageError {
|
|
41
41
|
readonly code: "MUTATION_PARTIAL";
|
|
42
42
|
readonly operation: string;
|
|
43
43
|
readonly completedIds: readonly string[];
|
|
44
|
-
constructor(operation: string, completedIds: readonly string[], detail?: string);
|
|
44
|
+
constructor(operation: string, completedIds: readonly string[], detail?: string, options?: ErrorOptions);
|
|
45
45
|
}
|
|
46
46
|
/** A vault or folder still contains files or child folders. */
|
|
47
47
|
export declare class NonEmptyTreeError extends StorageError {
|
|
@@ -53,13 +53,13 @@ export declare class NonEmptyTreeError extends StorageError {
|
|
|
53
53
|
export declare class RoomCreationAmbiguousError extends StorageError {
|
|
54
54
|
readonly code: "ROOM_CREATION_AMBIGUOUS";
|
|
55
55
|
readonly operation: string;
|
|
56
|
-
constructor(operation?: string);
|
|
56
|
+
constructor(operation?: string, cause?: unknown);
|
|
57
57
|
}
|
|
58
58
|
/** A newly-created room could not be fully rolled back. */
|
|
59
59
|
export declare class RoomCleanupIncompleteError extends StorageError {
|
|
60
60
|
readonly code: "ROOM_CLEANUP_INCOMPLETE";
|
|
61
61
|
readonly roomId: string;
|
|
62
|
-
constructor(roomId: string);
|
|
62
|
+
constructor(roomId: string, cause?: unknown);
|
|
63
63
|
}
|
|
64
64
|
/** The SDK's single product file-size limit was exceeded. */
|
|
65
65
|
export declare class FileTooLargeError extends StorageError {
|
package/dist/core/errors.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* conditions (bad login, wrong recovery key, missing file, vault, or folder, not
|
|
4
4
|
* logged in, ...). Platform-agnostic: no Node/CLI dependencies. */
|
|
5
5
|
export class StorageError extends Error {
|
|
6
|
-
constructor(message) {
|
|
7
|
-
super(message);
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message, options);
|
|
8
8
|
this.name = "StorageError";
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -19,32 +19,32 @@ export class RecoveryAlreadyConfiguredError extends StorageError {
|
|
|
19
19
|
/** Restore failed without exposing provider or crypto-library error details. */
|
|
20
20
|
export class RecoveryRestoreError extends StorageError {
|
|
21
21
|
code = "RECOVERY_RESTORE_FAILED";
|
|
22
|
-
constructor() {
|
|
23
|
-
super("recovery restore failed; verify the Recovery Key and account");
|
|
22
|
+
constructor(cause) {
|
|
23
|
+
super("recovery restore failed; verify the Recovery Key and account", cause === undefined ? undefined : { cause });
|
|
24
24
|
this.name = "RecoveryRestoreError";
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
/** Setup failed before the generated Recovery Key could be safely returned. */
|
|
28
28
|
export class RecoverySetupError extends StorageError {
|
|
29
29
|
code = "RECOVERY_SETUP_FAILED";
|
|
30
|
-
constructor() {
|
|
31
|
-
super("recovery setup did not complete; check recovery status before retrying");
|
|
30
|
+
constructor(cause) {
|
|
31
|
+
super("recovery setup did not complete; check recovery status before retrying", cause === undefined ? undefined : { cause });
|
|
32
32
|
this.name = "RecoverySetupError";
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
/** The recovery setup transport result is ambiguous; state must be checked before retrying. */
|
|
36
36
|
export class RecoverySetupAmbiguousError extends StorageError {
|
|
37
37
|
code = "RECOVERY_SETUP_AMBIGUOUS";
|
|
38
|
-
constructor() {
|
|
39
|
-
super("recovery setup may have completed; check recovery status before retrying");
|
|
38
|
+
constructor(cause) {
|
|
39
|
+
super("recovery setup may have completed; check recovery status before retrying", cause === undefined ? undefined : { cause });
|
|
40
40
|
this.name = "RecoverySetupAmbiguousError";
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
/** Recovery restore was cancelled after the provider may have changed state. */
|
|
44
44
|
export class RecoveryRestoreAmbiguousError extends StorageError {
|
|
45
45
|
code = "RECOVERY_RESTORE_AMBIGUOUS";
|
|
46
|
-
constructor() {
|
|
47
|
-
super("recovery restore may have completed; check recovery status before retrying");
|
|
46
|
+
constructor(cause) {
|
|
47
|
+
super("recovery restore may have completed; check recovery status before retrying", cause === undefined ? undefined : { cause });
|
|
48
48
|
this.name = "RecoveryRestoreAmbiguousError";
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -52,8 +52,8 @@ export class RecoveryRestoreAmbiguousError extends StorageError {
|
|
|
52
52
|
export class MutationOutcomeUnknownError extends StorageError {
|
|
53
53
|
code = "MUTATION_OUTCOME_UNKNOWN";
|
|
54
54
|
operation;
|
|
55
|
-
constructor(operation = "operation") {
|
|
56
|
-
super(`${operation} outcome is unknown; reconcile current state before retrying
|
|
55
|
+
constructor(operation = "operation", options) {
|
|
56
|
+
super(`${operation} outcome is unknown; reconcile current state before retrying`, options);
|
|
57
57
|
this.name = "MutationOutcomeUnknownError";
|
|
58
58
|
this.operation = operation;
|
|
59
59
|
}
|
|
@@ -63,8 +63,8 @@ export class MutationPartialError extends StorageError {
|
|
|
63
63
|
code = "MUTATION_PARTIAL";
|
|
64
64
|
operation;
|
|
65
65
|
completedIds;
|
|
66
|
-
constructor(operation, completedIds, detail) {
|
|
67
|
-
super(`${operation} failed after partial completion${detail ? `: ${detail}` : ""}; retry to reconcile current state
|
|
66
|
+
constructor(operation, completedIds, detail, options) {
|
|
67
|
+
super(`${operation} failed after partial completion${detail ? `: ${detail}` : ""}; retry to reconcile current state`, options);
|
|
68
68
|
this.name = "MutationPartialError";
|
|
69
69
|
this.operation = operation;
|
|
70
70
|
this.completedIds = [...completedIds];
|
|
@@ -84,8 +84,8 @@ export class NonEmptyTreeError extends StorageError {
|
|
|
84
84
|
export class RoomCreationAmbiguousError extends StorageError {
|
|
85
85
|
code = "ROOM_CREATION_AMBIGUOUS";
|
|
86
86
|
operation;
|
|
87
|
-
constructor(operation = "room creation") {
|
|
88
|
-
super(`${operation} outcome is unknown; reconcile newly-created rooms before retrying
|
|
87
|
+
constructor(operation = "room creation", cause) {
|
|
88
|
+
super(`${operation} outcome is unknown; reconcile newly-created rooms before retrying`, cause === undefined ? undefined : { cause });
|
|
89
89
|
this.name = "RoomCreationAmbiguousError";
|
|
90
90
|
this.operation = operation;
|
|
91
91
|
}
|
|
@@ -94,8 +94,8 @@ export class RoomCreationAmbiguousError extends StorageError {
|
|
|
94
94
|
export class RoomCleanupIncompleteError extends StorageError {
|
|
95
95
|
code = "ROOM_CLEANUP_INCOMPLETE";
|
|
96
96
|
roomId;
|
|
97
|
-
constructor(roomId) {
|
|
98
|
-
super("created room cleanup is incomplete; retry cleanup for the created room");
|
|
97
|
+
constructor(roomId, cause) {
|
|
98
|
+
super("created room cleanup is incomplete; retry cleanup for the created room", cause === undefined ? undefined : { cause });
|
|
99
99
|
this.name = "RoomCleanupIncompleteError";
|
|
100
100
|
this.roomId = roomId;
|
|
101
101
|
}
|
package/dist/core/http.d.ts
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
|
+
/** Read a complete response body while preserving the caller's cancellation contract. */
|
|
2
|
+
export interface ResponseBody {
|
|
3
|
+
bytes: Uint8Array<ArrayBuffer>;
|
|
4
|
+
}
|
|
1
5
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
6
|
+
* A response stream failed after producing some bytes. The bytes are kept for
|
|
7
|
+
* diagnostic callers; product-media callers must unwrap only the cause and
|
|
8
|
+
* never expose this raw buffer in a public error.
|
|
5
9
|
*/
|
|
6
|
-
export
|
|
7
|
-
bytes: Uint8Array<ArrayBuffer>;
|
|
8
|
-
|
|
10
|
+
export declare class ResponseBodyReadError extends Error {
|
|
11
|
+
readonly bytes: Uint8Array<ArrayBuffer>;
|
|
12
|
+
constructor(bytes: Uint8Array<ArrayBuffer>, cause: unknown);
|
|
9
13
|
}
|
|
10
|
-
export interface
|
|
14
|
+
export interface ResponseBodyOptions {
|
|
11
15
|
/** Error returned when the body signal aborts. */
|
|
12
16
|
abortError?: () => Error;
|
|
13
|
-
/** Keep a stream reader locked until its asynchronous cancellation settles. */
|
|
14
|
-
deferReaderRelease?: boolean;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Reject a caller-facing operation as soon as a signal aborts. The underlying
|
|
18
20
|
* operation is deliberately not awaited after that point: browser/network
|
|
19
|
-
* implementations are allowed to ignore AbortSignal, but
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* implementations are allowed to ignore AbortSignal, but the SDK API must
|
|
22
|
+
* still settle at its deadline. A deadline rejection is the complete public
|
|
23
|
+
* outcome; the late operation has no second result channel.
|
|
22
24
|
*/
|
|
23
|
-
export declare function raceWithAbort<T>(operation: PromiseLike<T>, signal: AbortSignal,
|
|
24
|
-
|
|
25
|
+
export declare function raceWithAbort<T>(operation: PromiseLike<T>, signal: AbortSignal, error: () => Error): Promise<T>;
|
|
26
|
+
/** Await cleanup for a response that is being rejected before its body is read. */
|
|
27
|
+
export declare function cancelResponseBody(response: Response, operation: string, primaryError?: unknown): Promise<void>;
|
|
28
|
+
export declare function readResponseBody(response: Response, signal?: AbortSignal, options?: ResponseBodyOptions): Promise<ResponseBody>;
|
|
29
|
+
/** Read successful product media with the SDK's single plaintext-size ceiling. */
|
|
30
|
+
export declare function readMediaResponseBody(response: Response, maxBytes: number, signal?: AbortSignal, options?: ResponseBodyOptions): Promise<ResponseBody>;
|
package/dist/core/http.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
|
+
import { FileTooLargeError } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* A response stream failed after producing some bytes. The bytes are kept for
|
|
4
|
+
* diagnostic callers; product-media callers must unwrap only the cause and
|
|
5
|
+
* never expose this raw buffer in a public error.
|
|
6
|
+
*/
|
|
7
|
+
export class ResponseBodyReadError extends Error {
|
|
8
|
+
bytes;
|
|
9
|
+
constructor(bytes, cause) {
|
|
10
|
+
super("response body read failed", { cause });
|
|
11
|
+
this.name = "ResponseBodyReadError";
|
|
12
|
+
this.bytes = bytes;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const RESPONSE_CLEANUP_TIMEOUT_MS = 5_000;
|
|
1
16
|
function abortError() {
|
|
2
17
|
return new DOMException("The operation was aborted", "AbortError");
|
|
3
18
|
}
|
|
4
19
|
/**
|
|
5
20
|
* Reject a caller-facing operation as soon as a signal aborts. The underlying
|
|
6
21
|
* operation is deliberately not awaited after that point: browser/network
|
|
7
|
-
* implementations are allowed to ignore AbortSignal, but
|
|
8
|
-
*
|
|
9
|
-
*
|
|
22
|
+
* implementations are allowed to ignore AbortSignal, but the SDK API must
|
|
23
|
+
* still settle at its deadline. A deadline rejection is the complete public
|
|
24
|
+
* outcome; the late operation has no second result channel.
|
|
10
25
|
*/
|
|
11
|
-
export function raceWithAbort(operation, signal,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
// Cleanup is best effort; the caller-facing abort error remains stable.
|
|
18
|
-
}
|
|
19
|
-
// The operation may already have been started by the caller. Observe its
|
|
20
|
-
// eventual rejection even though the public operation is already aborted.
|
|
21
|
-
void Promise.resolve(operation).catch(() => undefined);
|
|
22
|
-
return Promise.reject(error());
|
|
23
|
-
}
|
|
26
|
+
export function raceWithAbort(operation, signal, error) {
|
|
27
|
+
// Attach the rejection observer before checking an already-aborted signal;
|
|
28
|
+
// the caller may have started this operation before entering the race.
|
|
29
|
+
const observedOperation = Promise.resolve(operation);
|
|
24
30
|
return new Promise((resolve, reject) => {
|
|
25
31
|
let settled = false;
|
|
26
32
|
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
@@ -32,98 +38,127 @@ export function raceWithAbort(operation, signal, onAbort, error) {
|
|
|
32
38
|
callback();
|
|
33
39
|
};
|
|
34
40
|
const abort = () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// operation hand its response to the bounded body reader, which can then
|
|
38
|
-
// cancel that reader instead of losing it in the fetch race.
|
|
39
|
-
queueMicrotask(() => {
|
|
40
|
-
finish(() => {
|
|
41
|
-
try {
|
|
42
|
-
onAbort();
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Cleanup is best effort; the caller-facing abort error remains stable.
|
|
46
|
-
}
|
|
41
|
+
finish(() => {
|
|
42
|
+
try {
|
|
47
43
|
reject(error());
|
|
48
|
-
}
|
|
44
|
+
}
|
|
45
|
+
catch (abortError) {
|
|
46
|
+
reject(abortError);
|
|
47
|
+
}
|
|
49
48
|
});
|
|
50
49
|
};
|
|
51
50
|
signal.addEventListener("abort", abort, { once: true });
|
|
52
|
-
|
|
51
|
+
observedOperation.then((value) => finish(() => resolve(value)), (reason) => finish(() => reject(reason)));
|
|
53
52
|
if (signal.aborted)
|
|
54
53
|
abort();
|
|
55
54
|
});
|
|
56
55
|
}
|
|
57
|
-
function
|
|
56
|
+
function combineCleanupFailures(primaryError, cleanupFailures, message) {
|
|
57
|
+
const errors = primaryError === undefined ? [...cleanupFailures] : [primaryError, ...cleanupFailures];
|
|
58
|
+
return new AggregateError(errors, message, {
|
|
59
|
+
cause: primaryError ?? cleanupFailures[0],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function runBoundedCleanup(cleanup, operation) {
|
|
63
|
+
const pending = Promise.resolve().then(cleanup);
|
|
64
|
+
let timer;
|
|
65
|
+
const deadline = new Promise((_, reject) => {
|
|
66
|
+
timer = setTimeout(() => {
|
|
67
|
+
reject(new Error(`${operation} cleanup timed out`));
|
|
68
|
+
}, RESPONSE_CLEANUP_TIMEOUT_MS);
|
|
69
|
+
});
|
|
70
|
+
try {
|
|
71
|
+
await Promise.race([pending, deadline]);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (timer !== undefined)
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Await cleanup for a response that is being rejected before its body is read. */
|
|
79
|
+
export async function cancelResponseBody(response, operation, primaryError) {
|
|
80
|
+
if (!response.body)
|
|
81
|
+
return;
|
|
58
82
|
try {
|
|
59
|
-
|
|
83
|
+
await runBoundedCleanup(() => response.body.cancel(), `${operation} response`);
|
|
60
84
|
}
|
|
61
|
-
catch {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw combineCleanupFailures(primaryError, [error], primaryError === undefined
|
|
87
|
+
? `${operation} response cleanup was incomplete`
|
|
88
|
+
: `${operation} failed and response cleanup was incomplete`);
|
|
65
89
|
}
|
|
66
90
|
}
|
|
67
|
-
function
|
|
91
|
+
async function cancelReaderAndRelease(reader, operation, primaryError) {
|
|
92
|
+
const cleanupFailures = [];
|
|
93
|
+
try {
|
|
94
|
+
await runBoundedCleanup(() => reader.cancel(), `${operation} reader cancellation`);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
cleanupFailures.push(error);
|
|
98
|
+
}
|
|
99
|
+
// Cancellation is awaited before releasing the reader, so cleanup failures
|
|
100
|
+
// stay part of the current operation instead of mutating a later error.
|
|
68
101
|
try {
|
|
69
102
|
reader.releaseLock();
|
|
70
103
|
}
|
|
71
|
-
catch {
|
|
72
|
-
|
|
73
|
-
// caller must retain its abort/size result rather than waiting for or
|
|
74
|
-
// replacing it with stream cleanup failure.
|
|
104
|
+
catch (error) {
|
|
105
|
+
cleanupFailures.push(error);
|
|
75
106
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
if (cleanupFailures.length > 0) {
|
|
108
|
+
throw combineCleanupFailures(primaryError, cleanupFailures, primaryError === undefined
|
|
109
|
+
? `${operation} reader cleanup was incomplete`
|
|
110
|
+
: `${operation} failed and reader cleanup was incomplete`);
|
|
80
111
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const cancelAbortedResponse = () => {
|
|
86
|
-
if (!response.body) {
|
|
87
|
-
cancelBody();
|
|
112
|
+
}
|
|
113
|
+
async function readResponseBodyInternal(response, signal, options = {}, maxBytes) {
|
|
114
|
+
const cancelAbortedResponse = async (failure) => {
|
|
115
|
+
if (!response.body)
|
|
88
116
|
return;
|
|
89
|
-
}
|
|
90
117
|
let reader;
|
|
91
118
|
try {
|
|
92
119
|
reader = response.body.getReader();
|
|
93
120
|
}
|
|
94
|
-
catch {
|
|
95
|
-
|
|
96
|
-
return;
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw combineCleanupFailures(failure, [error], "response reader setup failed after abort");
|
|
97
123
|
}
|
|
98
|
-
|
|
99
|
-
if (options.deferReaderRelease)
|
|
100
|
-
void cancellation.then(() => releaseReaderWithoutThrowing(reader));
|
|
101
|
-
else
|
|
102
|
-
releaseReaderWithoutThrowing(reader);
|
|
124
|
+
await cancelReaderAndRelease(reader, "response body", failure);
|
|
103
125
|
};
|
|
104
126
|
if (signal?.aborted) {
|
|
105
|
-
|
|
106
|
-
|
|
127
|
+
const failure = options.abortError?.() ?? abortError();
|
|
128
|
+
await cancelAbortedResponse(failure);
|
|
129
|
+
throw failure;
|
|
107
130
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
if (maxBytes !== undefined &&
|
|
132
|
+
(!Number.isSafeInteger(maxBytes) || maxBytes < 0)) {
|
|
133
|
+
throw new Error("invalid media response body limit");
|
|
134
|
+
}
|
|
135
|
+
if (maxBytes !== undefined) {
|
|
136
|
+
const contentLength = response.headers.get("content-length");
|
|
137
|
+
if (contentLength !== null && /^\d+$/u.test(contentLength) && Number(contentLength) > maxBytes) {
|
|
138
|
+
const tooLarge = new FileTooLargeError();
|
|
139
|
+
await cancelResponseBody(response, "media response", tooLarge);
|
|
140
|
+
throw tooLarge;
|
|
141
|
+
}
|
|
112
142
|
}
|
|
113
143
|
if (!response.body)
|
|
114
|
-
return { bytes: new Uint8Array()
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
return { bytes: new Uint8Array() };
|
|
145
|
+
let reader;
|
|
146
|
+
try {
|
|
147
|
+
reader = response.body.getReader();
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
let failure = error;
|
|
151
|
+
try {
|
|
152
|
+
await cancelResponseBody(response, "response body reader setup", error);
|
|
123
153
|
}
|
|
124
|
-
|
|
125
|
-
|
|
154
|
+
catch (cleanupError) {
|
|
155
|
+
failure = cleanupError;
|
|
126
156
|
}
|
|
157
|
+
throw new ResponseBodyReadError(new Uint8Array(), failure);
|
|
158
|
+
}
|
|
159
|
+
let abortCancellation;
|
|
160
|
+
const cancelReader = () => {
|
|
161
|
+
abortCancellation ??= runBoundedCleanup(() => reader.cancel(), "response body reader");
|
|
127
162
|
};
|
|
128
163
|
const chunks = [];
|
|
129
164
|
let bytesRead = 0;
|
|
@@ -166,16 +201,8 @@ export async function readBoundedResponseBody(response, maxBytes, signal, option
|
|
|
166
201
|
const { done, value } = await readChunk();
|
|
167
202
|
if (done)
|
|
168
203
|
break;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (remaining > 0) {
|
|
172
|
-
const chunk = new Uint8Array(remaining);
|
|
173
|
-
chunk.set(value.subarray(0, remaining));
|
|
174
|
-
chunks.push(chunk);
|
|
175
|
-
bytesRead += remaining;
|
|
176
|
-
}
|
|
177
|
-
cancelReader();
|
|
178
|
-
return { bytes: joinBytes(chunks, bytesRead), truncated: true };
|
|
204
|
+
if (maxBytes !== undefined && value.byteLength > maxBytes - bytesRead) {
|
|
205
|
+
throw new FileTooLargeError();
|
|
179
206
|
}
|
|
180
207
|
const chunk = new Uint8Array(value.byteLength);
|
|
181
208
|
chunk.set(value);
|
|
@@ -183,10 +210,57 @@ export async function readBoundedResponseBody(response, maxBytes, signal, option
|
|
|
183
210
|
bytesRead += value.byteLength;
|
|
184
211
|
}
|
|
185
212
|
}
|
|
186
|
-
|
|
187
|
-
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (signal?.aborted) {
|
|
215
|
+
const failure = new ResponseBodyReadError(joinBytes(chunks, bytesRead), error);
|
|
216
|
+
const cleanupFailures = [];
|
|
217
|
+
if (abortCancellation) {
|
|
218
|
+
try {
|
|
219
|
+
await abortCancellation;
|
|
220
|
+
}
|
|
221
|
+
catch (cleanupError) {
|
|
222
|
+
cleanupFailures.push(cleanupError);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
reader.releaseLock();
|
|
227
|
+
}
|
|
228
|
+
catch (cleanupError) {
|
|
229
|
+
cleanupFailures.push(cleanupError);
|
|
230
|
+
}
|
|
231
|
+
if (cleanupFailures.length > 0) {
|
|
232
|
+
throw combineCleanupFailures(failure, cleanupFailures, "response body read failed and reader cleanup was incomplete");
|
|
233
|
+
}
|
|
234
|
+
throw failure;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await cancelReaderAndRelease(reader, "response body", error);
|
|
238
|
+
}
|
|
239
|
+
catch (cleanupError) {
|
|
240
|
+
if (error instanceof FileTooLargeError)
|
|
241
|
+
throw cleanupError;
|
|
242
|
+
throw new ResponseBodyReadError(joinBytes(chunks, bytesRead), cleanupError);
|
|
243
|
+
}
|
|
244
|
+
if (error instanceof FileTooLargeError)
|
|
245
|
+
throw error;
|
|
246
|
+
throw new ResponseBodyReadError(joinBytes(chunks, bytesRead), error);
|
|
188
247
|
}
|
|
189
|
-
|
|
248
|
+
// A settled normal read has no pending read request, so Web Streams
|
|
249
|
+
// guarantees that this synchronous release cannot throw.
|
|
250
|
+
try {
|
|
251
|
+
reader.releaseLock();
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
throw new ResponseBodyReadError(joinBytes(chunks, bytesRead), error);
|
|
255
|
+
}
|
|
256
|
+
return { bytes: joinBytes(chunks, bytesRead) };
|
|
257
|
+
}
|
|
258
|
+
export function readResponseBody(response, signal, options = {}) {
|
|
259
|
+
return readResponseBodyInternal(response, signal, options);
|
|
260
|
+
}
|
|
261
|
+
/** Read successful product media with the SDK's single plaintext-size ceiling. */
|
|
262
|
+
export function readMediaResponseBody(response, maxBytes, signal, options = {}) {
|
|
263
|
+
return readResponseBodyInternal(response, signal, options, maxBytes);
|
|
190
264
|
}
|
|
191
265
|
function joinBytes(chunks, total) {
|
|
192
266
|
const bytes = new Uint8Array(total);
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
package/dist/core/oidc.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared OIDC/MAS login building blocks — used by BOTH the CLI (device-code
|
|
3
|
+
* grant) and the web UI (authorization-code + PKCE). Browser-safe (no
|
|
4
|
+
* node:fs/path/v8/process/commander/fake-indexeddb): browser state is only
|
|
5
|
+
* touched when the PKCE functions are called by the web adapter. Importing
|
|
6
|
+
* this module under Node is safe; the CLI uses the device-code functions,
|
|
7
|
+
* which are plain fetch calls.
|
|
8
|
+
*/
|
|
1
9
|
import { type DeviceAuthorizationResponse, type DeviceAccessTokenResponse, type DeviceAccessTokenError, type BearerTokenResponse as OAuthBearerTokenResponse, type ValidatedAuthMetadata } from "matrix-js-sdk/lib/oauth/index.js";
|
|
2
10
|
import type { AccessTokens, TokenRefreshFunction } from "matrix-js-sdk/lib/http-api/index.js";
|
|
3
11
|
/** Matrix 1.18 OAuth metadata returned by `/auth_metadata`. */
|
|
@@ -22,6 +30,13 @@ export interface OidcRegistrationClientMetadata {
|
|
|
22
30
|
}
|
|
23
31
|
/** The issuer-bound endpoints accepted by the token refresh adapter. */
|
|
24
32
|
export type OidcTokenEndpointMetadata = Pick<OidcClientConfig, "issuer" | "token_endpoint" | "revocation_endpoint">;
|
|
33
|
+
/** Keep provider diagnostics useful without allowing credentials, customer
|
|
34
|
+
* identifiers, or control characters from an untrusted response to cross the
|
|
35
|
+
* SDK boundary. */
|
|
36
|
+
export declare function sanitizeDiagnosticText(value: string): string;
|
|
37
|
+
/** Copy a transport error's useful details across the SDK boundary after the
|
|
38
|
+
* same secret/control-character sanitization used for provider responses. */
|
|
39
|
+
export declare function diagnosticCause(error: unknown, seen?: WeakSet<object>): Error;
|
|
25
40
|
/**
|
|
26
41
|
* Discovers the OIDC/MAS issuer and endpoints for a homeserver via the
|
|
27
42
|
* Matrix client's stable `/auth_metadata` endpoint. Matrix 42 performs this
|