@mdbase-dev/connect 0.1.0-beta.83 → 0.1.0-beta.85
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 +33 -6
- package/dist/advanced.d.ts +1 -1
- package/dist/application-session.d.ts +31 -10
- package/dist/application-session.js +278 -64
- package/dist/authorization-refresh.d.ts +14 -0
- package/dist/authorization-refresh.js +41 -0
- package/dist/authorization-url.d.ts +1 -0
- package/dist/authorization-url.js +9 -0
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +3 -3
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/connect.d.ts +4 -1
- package/dist/connect.js +33 -26
- package/dist/connection-file-transport.d.ts +3 -0
- package/dist/connection-file-transport.js +59 -16
- package/dist/connection-transport-internals.d.ts +2 -0
- package/dist/connection-transport.d.ts +3 -1
- package/dist/connection-transport.js +90 -82
- package/dist/grant-key-leases.d.ts +24 -0
- package/dist/grant-key-leases.js +144 -0
- package/dist/hosted-file-request.d.ts +2 -2
- package/dist/hosted-file-request.js +4 -4
- package/dist/hosted-operation-response.d.ts +5 -0
- package/dist/hosted-operation-response.js +26 -0
- package/dist/internal-types.d.ts +2 -0
- package/dist/local-file-transport.d.ts +8 -2
- package/dist/local-file-transport.js +71 -20
- package/dist/mdbase-connect.d.ts +1 -0
- package/dist/mdbase-connect.js +3 -0
- package/dist/outcomes.d.ts +12 -12
- package/dist/outcomes.js +6 -0
- package/dist/pending-recovery-transports.d.ts +1 -0
- package/dist/pending-recovery-transports.js +7 -3
- package/dist/retired-grant-keys.d.ts +9 -0
- package/dist/retired-grant-keys.js +56 -0
- package/dist/session.d.ts +36 -7
- package/dist/session.js +287 -34
- package/dist/shared-operation.d.ts +7 -0
- package/dist/shared-operation.js +41 -0
- package/dist/token-response.d.ts +2 -1
- package/dist/token-response.js +2 -1
- package/package.json +2 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export class GrantKeyLeaseRegistry {
|
|
2
|
+
namespace;
|
|
3
|
+
onFinalRelease;
|
|
4
|
+
leases = new Map();
|
|
5
|
+
locks = webLockManager();
|
|
6
|
+
retainAutomaticallyRetiredKeys = this.locks === null;
|
|
7
|
+
constructor(namespace, onFinalRelease) {
|
|
8
|
+
this.namespace = namespace;
|
|
9
|
+
this.onFinalRelease = onFinalRelease;
|
|
10
|
+
}
|
|
11
|
+
async acquire(collectionId, keyHandle, signal) {
|
|
12
|
+
const releaseLocal = this.acquireLocal(collectionId, keyHandle);
|
|
13
|
+
if (!this.locks)
|
|
14
|
+
return releaseLocal;
|
|
15
|
+
try {
|
|
16
|
+
const releaseWeb = await acquireSharedWebLock(this.locks, this.lockName(collectionId, keyHandle), signal);
|
|
17
|
+
return once(() => {
|
|
18
|
+
releaseWeb();
|
|
19
|
+
releaseLocal();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
releaseLocal();
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
deleteExclusive(collectionId, keyHandle, action) {
|
|
28
|
+
if (this.retainAutomaticallyRetiredKeys)
|
|
29
|
+
return;
|
|
30
|
+
if (this.locks) {
|
|
31
|
+
void this.locks.request(this.lockName(collectionId, keyHandle), { mode: "exclusive" }, action).catch(() => undefined);
|
|
32
|
+
}
|
|
33
|
+
else if (!this.has(collectionId, keyHandle)) {
|
|
34
|
+
void Promise.resolve().then(action).catch(() => undefined);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
has(collectionId, keyHandle) {
|
|
38
|
+
return this.leases.get(collectionId)?.has(keyHandle) ?? false;
|
|
39
|
+
}
|
|
40
|
+
handles(collectionId) {
|
|
41
|
+
return this.leases.get(collectionId)?.keys() ?? [];
|
|
42
|
+
}
|
|
43
|
+
acquireLocal(collectionId, keyHandle) {
|
|
44
|
+
const collection = this.leases.get(collectionId) ?? new Map();
|
|
45
|
+
collection.set(keyHandle, (collection.get(keyHandle) ?? 0) + 1);
|
|
46
|
+
this.leases.set(collectionId, collection);
|
|
47
|
+
return once(() => {
|
|
48
|
+
const remaining = (collection.get(keyHandle) ?? 1) - 1;
|
|
49
|
+
if (remaining > 0)
|
|
50
|
+
collection.set(keyHandle, remaining);
|
|
51
|
+
else {
|
|
52
|
+
collection.delete(keyHandle);
|
|
53
|
+
if (collection.size === 0)
|
|
54
|
+
this.leases.delete(collectionId);
|
|
55
|
+
this.onFinalRelease(collectionId, keyHandle);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
lockName(collectionId, keyHandle) {
|
|
60
|
+
return `mdbase-connect:grant-key:${this.namespace}:${collectionId}:${keyHandle}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class GrantKeyLeaseSet {
|
|
64
|
+
collectionId;
|
|
65
|
+
acquire;
|
|
66
|
+
releases = new Map();
|
|
67
|
+
constructor(collectionId, acquire) {
|
|
68
|
+
this.collectionId = collectionId;
|
|
69
|
+
this.acquire = acquire;
|
|
70
|
+
}
|
|
71
|
+
async retain(tokenOrHandle, signal) {
|
|
72
|
+
const handle = typeof tokenOrHandle === "string" ? tokenOrHandle : tokenOrHandle?.keyHandle;
|
|
73
|
+
if (!handle)
|
|
74
|
+
return;
|
|
75
|
+
let lease = this.releases.get(handle);
|
|
76
|
+
if (!lease) {
|
|
77
|
+
lease = this.acquire(this.collectionId, handle, signal);
|
|
78
|
+
this.releases.set(handle, lease);
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await lease;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (this.releases.get(handle) === lease)
|
|
85
|
+
this.releases.delete(handle);
|
|
86
|
+
this.release();
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
release() {
|
|
91
|
+
for (const lease of this.releases.values())
|
|
92
|
+
void lease.then((release) => release());
|
|
93
|
+
this.releases.clear();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export async function retainCurrentGrantToken(readCurrent, leases, signal) {
|
|
97
|
+
while (true) {
|
|
98
|
+
const snapshot = readCurrent();
|
|
99
|
+
if (!snapshot)
|
|
100
|
+
return null;
|
|
101
|
+
await leases.retain(snapshot, signal);
|
|
102
|
+
const current = readCurrent();
|
|
103
|
+
if (!current)
|
|
104
|
+
return null;
|
|
105
|
+
if (current.keyHandle === snapshot.keyHandle)
|
|
106
|
+
return current;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function webLockManager() {
|
|
110
|
+
const locks = typeof navigator === "undefined"
|
|
111
|
+
? undefined
|
|
112
|
+
: navigator.locks;
|
|
113
|
+
if (locks)
|
|
114
|
+
return locks;
|
|
115
|
+
const browser = typeof window !== "undefined"
|
|
116
|
+
|| typeof globalThis.importScripts === "function";
|
|
117
|
+
return browser ? null : undefined;
|
|
118
|
+
}
|
|
119
|
+
async function acquireSharedWebLock(locks, name, signal) {
|
|
120
|
+
let acquired;
|
|
121
|
+
let rejectAcquisition;
|
|
122
|
+
let release;
|
|
123
|
+
const ready = new Promise((resolve, reject) => {
|
|
124
|
+
acquired = resolve;
|
|
125
|
+
rejectAcquisition = reject;
|
|
126
|
+
});
|
|
127
|
+
const held = new Promise((resolve) => { release = resolve; });
|
|
128
|
+
const request = locks.request(name, { mode: "shared", ...(signal ? { signal } : {}) }, async () => {
|
|
129
|
+
acquired();
|
|
130
|
+
await held;
|
|
131
|
+
});
|
|
132
|
+
void request.catch(rejectAcquisition);
|
|
133
|
+
await ready;
|
|
134
|
+
return once(release);
|
|
135
|
+
}
|
|
136
|
+
function once(action) {
|
|
137
|
+
let called = false;
|
|
138
|
+
return () => {
|
|
139
|
+
if (called)
|
|
140
|
+
return;
|
|
141
|
+
called = true;
|
|
142
|
+
action();
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StoredToken } from "./internal-types.js";
|
|
2
2
|
type ProofHeaders = (token: StoredToken, method: "GET" | "POST" | "DELETE", url: string, body: string | undefined, credential: string) => Promise<Record<string, string>>;
|
|
3
3
|
export declare function sendHostedFileRequest(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal: AbortSignal | undefined, proofHeaders: ProofHeaders): Promise<Response>;
|
|
4
|
-
export declare function performHostedFileRequest<Result>(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal: AbortSignal | undefined, refresh: () => Promise<StoredToken>, proofHeaders: ProofHeaders): Promise<Result>;
|
|
5
|
-
export declare function performHostedFilePartRequest(token: StoredToken, path: string, expectedLength: number, signal: AbortSignal | undefined, refresh: () => Promise<StoredToken>, proofHeaders: ProofHeaders): Promise<ReadableStream<Uint8Array>>;
|
|
4
|
+
export declare function performHostedFileRequest<Result>(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal: AbortSignal | undefined, refresh: (signal?: AbortSignal) => Promise<StoredToken>, proofHeaders: ProofHeaders, retain?: (token: StoredToken) => Promise<StoredToken>): Promise<Result>;
|
|
5
|
+
export declare function performHostedFilePartRequest(token: StoredToken, path: string, expectedLength: number, signal: AbortSignal | undefined, refresh: (signal?: AbortSignal) => Promise<StoredToken>, proofHeaders: ProofHeaders, retain?: (token: StoredToken) => Promise<StoredToken>): Promise<ReadableStream<Uint8Array>>;
|
|
6
6
|
export {};
|
|
@@ -20,11 +20,11 @@ export async function sendHostedFileRequest(token, method, path, input, signal,
|
|
|
20
20
|
...(signal ? { signal } : {})
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
export async function performHostedFileRequest(token, method, path, input, signal, refresh, proofHeaders) {
|
|
23
|
+
export async function performHostedFileRequest(token, method, path, input, signal, refresh, proofHeaders, retain = async (token) => token) {
|
|
24
24
|
let active = token;
|
|
25
25
|
let response = await sendHostedFileRequest(active, method, path, input, signal, proofHeaders);
|
|
26
26
|
if (response.status === 401 && active.refreshToken) {
|
|
27
|
-
active = await refresh();
|
|
27
|
+
active = await retain(await refresh(signal));
|
|
28
28
|
if (!active.authority || !active.fileCapability) {
|
|
29
29
|
throw connectError("authority_authorization_changed", "Reconnect this collection before accessing its files.");
|
|
30
30
|
}
|
|
@@ -36,11 +36,11 @@ export async function performHostedFileRequest(token, method, path, input, signa
|
|
|
36
36
|
}
|
|
37
37
|
return body;
|
|
38
38
|
}
|
|
39
|
-
export async function performHostedFilePartRequest(token, path, expectedLength, signal, refresh, proofHeaders) {
|
|
39
|
+
export async function performHostedFilePartRequest(token, path, expectedLength, signal, refresh, proofHeaders, retain = async (token) => token) {
|
|
40
40
|
let active = token;
|
|
41
41
|
let response = await sendHostedFileRequest(active, "GET", path, undefined, signal, proofHeaders);
|
|
42
42
|
if (response.status === 401 && active.refreshToken) {
|
|
43
|
-
active = await refresh();
|
|
43
|
+
active = await retain(await refresh(signal));
|
|
44
44
|
if (!active.authority || !active.fileCapability) {
|
|
45
45
|
throw connectError("authority_authorization_changed", "Reconnect this collection before accessing its files.");
|
|
46
46
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { connectError, serverConnectError } from "./errors.js";
|
|
2
|
+
import { decodeJsonResponse } from "./runtime-utils.js";
|
|
3
|
+
export async function decodeHostedOperationResponse(response, hosted) {
|
|
4
|
+
try {
|
|
5
|
+
return {
|
|
6
|
+
body: await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned a response that is not valid JSON.")
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
catch (cause) {
|
|
10
|
+
if (!response.ok && hosted && isDefinitiveHostedHttpRejection(response.status)) {
|
|
11
|
+
return {
|
|
12
|
+
body: undefined,
|
|
13
|
+
httpError: serverConnectError("operation_failed", "Collection operation failed.", {
|
|
14
|
+
status: response.status,
|
|
15
|
+
operationOutcome: "rejected"
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
throw connectError("invalid_operation_response", "The collection authority returned a response that is not valid JSON.", { cause });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isDefinitiveHostedHttpRejection(status) {
|
|
23
|
+
return status >= 400
|
|
24
|
+
&& status < 500
|
|
25
|
+
&& ![408, 425, 429].includes(status);
|
|
26
|
+
}
|
package/dist/internal-types.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export interface StoredToken {
|
|
|
54
54
|
fileCapability?: FileCapability;
|
|
55
55
|
applicationOrigin?: string;
|
|
56
56
|
keyHandle?: string;
|
|
57
|
+
/** Previous grant keys retained for durable pending-mutation recovery. */
|
|
58
|
+
retiredKeyHandles?: string[];
|
|
57
59
|
savedAt: number;
|
|
58
60
|
authority?: {
|
|
59
61
|
operationsUrl: string;
|
|
@@ -5,12 +5,14 @@ interface LocalFileTransportOptions {
|
|
|
5
5
|
keyStore: GrantKeyStore;
|
|
6
6
|
serverUrl: string;
|
|
7
7
|
loopbackUrl: string;
|
|
8
|
+
currentToken?(): StoredToken | null;
|
|
8
9
|
authorizedToken(signal?: AbortSignal): Promise<StoredToken | null>;
|
|
9
10
|
refreshAuthorization(signal?: AbortSignal): Promise<StoredToken>;
|
|
10
11
|
shouldAttemptDirect(token: StoredToken): Promise<boolean>;
|
|
11
12
|
onDirectAvailable(): void;
|
|
12
13
|
onDirectUnavailable(): void;
|
|
13
14
|
onRelayAvailable(): void;
|
|
15
|
+
acquireGrantKeyLease?(collectionId: string, keyHandle: string, signal?: AbortSignal): Promise<() => void>;
|
|
14
16
|
}
|
|
15
17
|
/** Keeps grant-bound file crypto and loopback framing out of the operation transport. */
|
|
16
18
|
export declare class LocalFileTransport {
|
|
@@ -19,13 +21,17 @@ export declare class LocalFileTransport {
|
|
|
19
21
|
control<Result>(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal?: AbortSignal): Promise<Result>;
|
|
20
22
|
private controlAttempt;
|
|
21
23
|
private decryptControlResponse;
|
|
22
|
-
uploadChunk(session: FileTransferSession, chunkIndex: number, bytes: Uint8Array, signal?: AbortSignal): Promise<void>;
|
|
23
|
-
|
|
24
|
+
uploadChunk(session: FileTransferSession, chunkIndex: number, bytes: Uint8Array, signal?: AbortSignal, authorizedToken?: StoredToken): Promise<void>;
|
|
25
|
+
private uploadChunkWithLease;
|
|
26
|
+
downloadChunk(session: FileTransferSession, chunkIndex: number, signal?: AbortSignal, authorizedToken?: StoredToken): Promise<Uint8Array>;
|
|
27
|
+
private downloadChunkWithLease;
|
|
24
28
|
private decryptDownloadResponse;
|
|
25
29
|
private encryptControl;
|
|
26
30
|
private sendRelayControl;
|
|
27
31
|
private sendRelayUpload;
|
|
28
32
|
private sendRelayDownload;
|
|
29
33
|
private localFileToken;
|
|
34
|
+
private grantKeyLeases;
|
|
35
|
+
private retainEntryToken;
|
|
30
36
|
}
|
|
31
37
|
export {};
|
|
@@ -2,6 +2,7 @@ import { decodeFileFrame, FILE_PROTOCOL_VERSION, FILE_TRANSFER_PROTOCOL_VERSION
|
|
|
2
2
|
import { decryptRelayResponse, encryptRelayRequest } from "./crypto.js";
|
|
3
3
|
import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
|
|
4
4
|
import { GrantFileTransferCipher } from "./file-crypto.js";
|
|
5
|
+
import { GrantKeyLeaseSet, retainCurrentGrantToken } from "./grant-key-leases.js";
|
|
5
6
|
import { directFallbackStatus, loopbackRequest } from "./operation-helpers.js";
|
|
6
7
|
import { apiError, decodeJsonResponse } from "./runtime-utils.js";
|
|
7
8
|
import { isConnectorBusy, isExplicitConnectorBusyResponse, MAX_CONNECTOR_BUSY_RETRIES, waitForConnectorAvailability } from "./transient-retry.js";
|
|
@@ -12,9 +13,16 @@ export class LocalFileTransport {
|
|
|
12
13
|
this.options = options;
|
|
13
14
|
}
|
|
14
15
|
async control(token, method, path, input, signal) {
|
|
15
|
-
|
|
16
|
+
const leases = this.grantKeyLeases(token.collectionId);
|
|
17
|
+
try {
|
|
18
|
+
token = await this.retainEntryToken(token, leases, signal);
|
|
19
|
+
return await this.controlAttempt(token, method, path, input, signal, false, 0, leases);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
leases.release();
|
|
23
|
+
}
|
|
16
24
|
}
|
|
17
|
-
async controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries) {
|
|
25
|
+
async controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries, leases) {
|
|
18
26
|
requireEncryption(token);
|
|
19
27
|
const controlInput = localFileControlInput(method, path, input);
|
|
20
28
|
let encryptedRequest = await this.encryptControl(token, controlInput);
|
|
@@ -38,7 +46,7 @@ export class LocalFileTransport {
|
|
|
38
46
|
|| await isExplicitConnectorBusyResponse(response))) {
|
|
39
47
|
if (response.ok)
|
|
40
48
|
this.options.onDirectAvailable();
|
|
41
|
-
return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries);
|
|
49
|
+
return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries, leases);
|
|
42
50
|
}
|
|
43
51
|
if (response)
|
|
44
52
|
this.options.onDirectUnavailable();
|
|
@@ -47,33 +55,35 @@ export class LocalFileTransport {
|
|
|
47
55
|
token = token.expiresAt > Date.now() + 30_000
|
|
48
56
|
? token
|
|
49
57
|
: await this.options.refreshAuthorization(signal);
|
|
58
|
+
token = await this.retainEntryToken(token, leases, signal);
|
|
50
59
|
requireLocalFileToken(token);
|
|
51
60
|
encryptedRequest = await this.encryptControl(token, controlInput);
|
|
52
61
|
}
|
|
53
62
|
response = await this.sendRelayControl(token, encryptedRequest, signal);
|
|
54
63
|
if (await refreshableBindingFailure(response, token)) {
|
|
55
64
|
token = await this.options.refreshAuthorization(signal);
|
|
65
|
+
token = await this.retainEntryToken(token, leases, signal);
|
|
56
66
|
requireLocalFileToken(token);
|
|
57
67
|
encryptedRequest = await this.encryptControl(token, controlInput);
|
|
58
68
|
response = await this.sendRelayControl(token, encryptedRequest, signal);
|
|
59
69
|
}
|
|
60
70
|
if (response.ok)
|
|
61
71
|
this.options.onRelayAvailable();
|
|
62
|
-
return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries);
|
|
72
|
+
return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries, leases);
|
|
63
73
|
}
|
|
64
|
-
async decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries) {
|
|
74
|
+
async decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries, leases) {
|
|
65
75
|
const body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned an invalid file control response.");
|
|
66
76
|
if (!response.ok) {
|
|
67
77
|
const error = apiError(body, "operation_failed", "Collection file request failed.", response.status);
|
|
68
78
|
if (isConnectorBusy(error)
|
|
69
79
|
&& connectorBusyRetries < MAX_CONNECTOR_BUSY_RETRIES) {
|
|
70
80
|
await waitForConnectorAvailability(connectorBusyRetries, signal);
|
|
71
|
-
return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1);
|
|
81
|
+
return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1, leases);
|
|
72
82
|
}
|
|
73
83
|
if (error.code === "fresh_request_required"
|
|
74
84
|
&& method === "GET"
|
|
75
85
|
&& !freshReadRetried) {
|
|
76
|
-
return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries);
|
|
86
|
+
return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries, leases);
|
|
77
87
|
}
|
|
78
88
|
throw error;
|
|
79
89
|
}
|
|
@@ -86,21 +96,32 @@ export class LocalFileTransport {
|
|
|
86
96
|
&& decrypted.problem.code === "fresh_request_required"
|
|
87
97
|
&& method === "GET"
|
|
88
98
|
&& !freshReadRetried) {
|
|
89
|
-
return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries);
|
|
99
|
+
return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries, leases);
|
|
90
100
|
}
|
|
91
101
|
if (!decrypted.ok) {
|
|
92
102
|
const error = encryptedProblem(decrypted.problem);
|
|
93
103
|
if (isConnectorBusy(error)
|
|
94
104
|
&& connectorBusyRetries < MAX_CONNECTOR_BUSY_RETRIES) {
|
|
95
105
|
await waitForConnectorAvailability(connectorBusyRetries, signal);
|
|
96
|
-
return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1);
|
|
106
|
+
return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1, leases);
|
|
97
107
|
}
|
|
98
108
|
throw error;
|
|
99
109
|
}
|
|
100
110
|
return decrypted.result;
|
|
101
111
|
}
|
|
102
|
-
async uploadChunk(session, chunkIndex, bytes, signal) {
|
|
103
|
-
const token = await this.localFileToken(session, "upload", signal);
|
|
112
|
+
async uploadChunk(session, chunkIndex, bytes, signal, authorizedToken) {
|
|
113
|
+
const token = authorizedToken ?? await this.localFileToken(session, "upload", signal);
|
|
114
|
+
const leases = this.grantKeyLeases(token.collectionId);
|
|
115
|
+
try {
|
|
116
|
+
const current = await this.retainEntryToken(token, leases, signal);
|
|
117
|
+
validateLocalFileToken(current, session, "upload");
|
|
118
|
+
return await this.uploadChunkWithLease(current, leases, session, chunkIndex, bytes, signal);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
leases.release();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async uploadChunkWithLease(token, leases, session, chunkIndex, bytes, signal) {
|
|
104
125
|
let deliveryToken = token;
|
|
105
126
|
let encoded = await encryptedUploadChunk(this.options.keyStore, deliveryToken, session, chunkIndex, bytes);
|
|
106
127
|
let response;
|
|
@@ -125,6 +146,7 @@ export class LocalFileTransport {
|
|
|
125
146
|
response = await this.sendRelayUpload(deliveryToken, encoded, signal);
|
|
126
147
|
if (response.status === 401 && deliveryToken.refreshToken) {
|
|
127
148
|
deliveryToken = await this.options.refreshAuthorization(signal);
|
|
149
|
+
deliveryToken = await this.retainEntryToken(deliveryToken, leases, signal);
|
|
128
150
|
requireLocalFileToken(deliveryToken);
|
|
129
151
|
encoded = await encryptedUploadChunk(this.options.keyStore, deliveryToken, session, chunkIndex, bytes);
|
|
130
152
|
response = await this.sendRelayUpload(deliveryToken, encoded, signal);
|
|
@@ -135,8 +157,19 @@ export class LocalFileTransport {
|
|
|
135
157
|
}
|
|
136
158
|
this.options.onRelayAvailable();
|
|
137
159
|
}
|
|
138
|
-
async downloadChunk(session, chunkIndex, signal) {
|
|
139
|
-
let token = await this.localFileToken(session, "download", signal);
|
|
160
|
+
async downloadChunk(session, chunkIndex, signal, authorizedToken) {
|
|
161
|
+
let token = authorizedToken ?? await this.localFileToken(session, "download", signal);
|
|
162
|
+
const leases = this.grantKeyLeases(token.collectionId);
|
|
163
|
+
try {
|
|
164
|
+
token = await this.retainEntryToken(token, leases, signal);
|
|
165
|
+
validateLocalFileToken(token, session, "download");
|
|
166
|
+
return await this.downloadChunkWithLease(token, leases, session, chunkIndex, signal);
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
leases.release();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async downloadChunkWithLease(token, leases, session, chunkIndex, signal) {
|
|
140
173
|
let response;
|
|
141
174
|
if (await this.options.shouldAttemptDirect(token)) {
|
|
142
175
|
try {
|
|
@@ -159,6 +192,7 @@ export class LocalFileTransport {
|
|
|
159
192
|
response = await this.sendRelayDownload(token, session, chunkIndex, signal);
|
|
160
193
|
if (response.status === 401 && token.refreshToken) {
|
|
161
194
|
token = await this.options.refreshAuthorization(signal);
|
|
195
|
+
token = await this.retainEntryToken(token, leases, signal);
|
|
162
196
|
requireLocalFileToken(token);
|
|
163
197
|
response = await this.sendRelayDownload(token, session, chunkIndex, signal);
|
|
164
198
|
}
|
|
@@ -225,15 +259,32 @@ export class LocalFileTransport {
|
|
|
225
259
|
}
|
|
226
260
|
async localFileToken(session, direction, signal) {
|
|
227
261
|
const token = await this.options.authorizedToken(signal);
|
|
228
|
-
|
|
229
|
-
|| !token.grantId || !token.keyHandle
|
|
230
|
-
|| session.direction !== direction
|
|
231
|
-
|| session.protection !== "grant_aead_v1") {
|
|
232
|
-
throw connectError("not_authorized", "This connection cannot use encrypted local file delivery.");
|
|
233
|
-
}
|
|
234
|
-
framedChunkSize(session);
|
|
262
|
+
validateLocalFileToken(token, session, direction);
|
|
235
263
|
return token;
|
|
236
264
|
}
|
|
265
|
+
grantKeyLeases(collectionId) {
|
|
266
|
+
return new GrantKeyLeaseSet(collectionId, this.options.acquireGrantKeyLease
|
|
267
|
+
?? (() => Promise.resolve(() => undefined)));
|
|
268
|
+
}
|
|
269
|
+
async retainEntryToken(snapshot, leases, signal) {
|
|
270
|
+
if (!this.options.currentToken) {
|
|
271
|
+
await leases.retain(snapshot, signal);
|
|
272
|
+
return snapshot;
|
|
273
|
+
}
|
|
274
|
+
const current = await retainCurrentGrantToken(this.options.currentToken, leases, signal);
|
|
275
|
+
if (current)
|
|
276
|
+
return current;
|
|
277
|
+
throw connectError("not_authorized", "Reconnect this application to continue.");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function validateLocalFileToken(token, session, direction) {
|
|
281
|
+
if (!token || token.authority || !token.fileCapability || !token.encryption
|
|
282
|
+
|| !token.grantId || !token.keyHandle
|
|
283
|
+
|| session.direction !== direction
|
|
284
|
+
|| session.protection !== "grant_aead_v1") {
|
|
285
|
+
throw connectError("not_authorized", "This connection cannot use encrypted local file delivery.");
|
|
286
|
+
}
|
|
287
|
+
framedChunkSize(session);
|
|
237
288
|
}
|
|
238
289
|
function requireLocalFileToken(token) {
|
|
239
290
|
if (token.authority || !token.fileCapability) {
|
package/dist/mdbase-connect.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare class MdbaseConnect<Frontmatter extends JsonObject = JsonObject>
|
|
|
20
20
|
connections(): MdbaseConnectionInfo[];
|
|
21
21
|
connection(collectionId: string): MdbaseConnection<Frontmatter> | null;
|
|
22
22
|
unavailableReason(collectionId: string): MdbaseUnavailableReason | null;
|
|
23
|
+
connectionApplicationId(collectionId: string): string | null;
|
|
23
24
|
onConnectionsChange(listener: (connections: MdbaseConnectionInfo[]) => void): () => void;
|
|
24
25
|
forgetAll(): void;
|
|
25
26
|
/** Forget a public TOFU pin after independently verifying a replaced connector. */
|
package/dist/mdbase-connect.js
CHANGED
|
@@ -35,6 +35,9 @@ export class MdbaseConnect {
|
|
|
35
35
|
unavailableReason(collectionId) {
|
|
36
36
|
return this.internals.unavailableReason(collectionId);
|
|
37
37
|
}
|
|
38
|
+
connectionApplicationId(collectionId) {
|
|
39
|
+
return this.internals.connectionApplicationId(collectionId);
|
|
40
|
+
}
|
|
38
41
|
onConnectionsChange(listener) {
|
|
39
42
|
return this.internals.onConnectionsChange(listener);
|
|
40
43
|
}
|
package/dist/outcomes.d.ts
CHANGED
|
@@ -13,19 +13,19 @@ export type ConnectFailure<Code extends ConnectProblemCode = ConnectProblemCode>
|
|
|
13
13
|
* for programming errors and broken SDK invariants.
|
|
14
14
|
*/
|
|
15
15
|
export type ConnectOutcome<Value, Code extends ConnectProblemCode = ConnectProblemCode> = ConnectSuccess<Value> | ConnectFailure<Code>;
|
|
16
|
-
export type CommonOperationProblemCode = "access_denied" | "access_paused" | "authorization_binding_incompatible" | "authorization_expired" | "capability_contract_incompatible" | "collection_access_denied" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "direct_operation_rejected" | "durable_mutation_unsupported" | "operation_outcome_unknown" | "encryption_required" | "encrypted_relay_rejected" | "hosted_provider_unavailable" | "insufficient_access" | "invalid_encrypted_response" | "invalid_operation_response" | "invalid_request" | "missing_grant_key" | "mutation_recovery_expired" | "mutation_request_conflict" | "not_authorized" | "operation_cancelled" | "operation_failed" | "rate_limited" | "relay_authorization_expired" | "relay_unavailable" | "temporarily_unavailable" | "timeout" | "transport_protocol_incompatible" | "unsupported_operation";
|
|
17
|
-
export declare const COMMON_OPERATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation"];
|
|
16
|
+
export type CommonOperationProblemCode = "access_denied" | "access_paused" | "application_declaration_mismatch" | "authorization_binding_incompatible" | "authorization_expired" | "capability_contract_incompatible" | "collection_access_denied" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "direct_operation_rejected" | "durable_mutation_unsupported" | "operation_outcome_unknown" | "encryption_required" | "encrypted_relay_rejected" | "hosted_provider_unavailable" | "insufficient_access" | "invalid_encrypted_response" | "invalid_operation_response" | "invalid_request" | "missing_grant_key" | "mutation_recovery_expired" | "mutation_request_conflict" | "not_authorized" | "operation_cancelled" | "operation_failed" | "rate_limited" | "relay_authorization_expired" | "relay_unavailable" | "temporarily_unavailable" | "timeout" | "transport_protocol_incompatible" | "unsupported_operation";
|
|
17
|
+
export declare const COMMON_OPERATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation"];
|
|
18
18
|
export declare const ALL_CONNECT_PROBLEM_CODES: ConnectProblemCode[];
|
|
19
19
|
export type RegistrationProblemCode = "discovery_failed" | "invalid_operation_response" | "invalid_application_manifest" | "invalid_request" | "manifest_load_failed" | "rate_limited" | "temporarily_unavailable" | "timeout";
|
|
20
20
|
export declare const REGISTRATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout"];
|
|
21
|
-
export type AuthorizationProblemCode = RegistrationProblemCode | "access_denied" | "application_identity_unavailable" | "approval_window_blocked" | "authorization_binding_incompatible" | "authorization_cancelled" | "authorization_replayed" | "browser_required" | "capability_contract_incompatible" | "collection_access_denied" | "collection_configuration_invalid" | "collection_contracts_missing" | "collection_incompatible" | "collection_invalid" | "collection_kind_unsupported" | "collection_mismatch" | "collection_not_found" | "collection_type_registry_invalid" | "collection_version_unsupported" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "device_authorization_failed" | "durable_mutation_unsupported" | "encryption_required" | "expired_token" | "invalid_application_authorization" | "invalid_callback" | "invalid_device_authorization_response" | "invalid_token_response" | "scope_denied" | "reconnect_required" | "token_exchange_failed" | "transport_protocol_incompatible";
|
|
22
|
-
export declare const AUTHORIZATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible"];
|
|
21
|
+
export type AuthorizationProblemCode = RegistrationProblemCode | "access_denied" | "application_declaration_mismatch" | "application_identity_unavailable" | "approval_window_blocked" | "authorization_binding_incompatible" | "authorization_cancelled" | "authorization_replayed" | "browser_required" | "capability_contract_incompatible" | "collection_access_denied" | "collection_configuration_invalid" | "collection_contracts_missing" | "collection_incompatible" | "collection_invalid" | "collection_kind_unsupported" | "collection_mismatch" | "collection_not_found" | "collection_type_registry_invalid" | "collection_version_unsupported" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "device_authorization_failed" | "durable_mutation_unsupported" | "encryption_required" | "expired_token" | "invalid_application_authorization" | "invalid_callback" | "invalid_device_authorization_response" | "invalid_token_response" | "scope_denied" | "reconnect_required" | "token_exchange_failed" | "transport_protocol_incompatible";
|
|
22
|
+
export declare const AUTHORIZATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible"];
|
|
23
23
|
export type NotificationProblemCode = RegistrationProblemCode | "authorization_expired" | "invalid_operation_response" | "invalid_push_subscription" | "managed_fcm_not_declared" | "not_authorized" | "notification_criterion_not_declared" | "notification_reauthorization_required" | "notification_registration_failed" | "notification_unregistration_failed" | "notifications_not_declared" | "notifications_unavailable";
|
|
24
24
|
export declare const NOTIFICATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "authorization_expired", "invalid_operation_response", "invalid_push_subscription", "managed_fcm_not_declared", "not_authorized", "notification_criterion_not_declared", "notification_reauthorization_required", "notification_registration_failed", "notification_unregistration_failed", "notifications_not_declared", "notifications_unavailable"];
|
|
25
25
|
export type DirectAccessProblemCode = "connector_offline" | "connector_upgrade_required" | "invalid_operation_response" | "not_authorized" | "operation_failed" | "temporarily_unavailable" | "timeout";
|
|
26
26
|
export declare const DIRECT_ACCESS_PROBLEM_CODES: readonly ["connector_offline", "connector_upgrade_required", "invalid_operation_response", "not_authorized", "operation_failed", "temporarily_unavailable", "timeout"];
|
|
27
|
-
export type SessionProblemCode = AuthorizationProblemCode | "collection_not_ready" | "collection_not_selected" | "unknown_collection";
|
|
28
|
-
export declare const SESSION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible", "collection_not_ready", "collection_not_selected", "unknown_collection"];
|
|
27
|
+
export type SessionProblemCode = AuthorizationProblemCode | "collection_not_ready" | "collection_not_selected" | "operation_cancelled" | "session_destroyed" | "session_not_started" | "session_starting" | "unknown_collection";
|
|
28
|
+
export declare const SESSION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible", "collection_not_ready", "collection_not_selected", "operation_cancelled", "session_destroyed", "session_not_started", "session_starting", "unknown_collection"];
|
|
29
29
|
export type CollectionSetupProblemCode = "collection_configuration_invalid" | "collection_invalid" | "collection_type_registry_invalid" | "collection_version_unsupported";
|
|
30
30
|
export type CollectionDescriptionProblemCode = CommonOperationProblemCode | CollectionSetupProblemCode;
|
|
31
31
|
export type CollectionReadProblemCode = CollectionDescriptionProblemCode | "invalid_path" | "operation_invalid";
|
|
@@ -34,12 +34,12 @@ export type CollectionMutationProblemCode = CollectionReadProblemCode | "concurr
|
|
|
34
34
|
export type CollectionChangesProblemCode = CollectionDescriptionProblemCode | "change_cursor_reset";
|
|
35
35
|
export type CollectionTypeProblemCode = CollectionMutationProblemCode | "type_pack_provision_failed";
|
|
36
36
|
export declare const COLLECTION_SETUP_PROBLEM_CODES: readonly ["collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
|
|
37
|
-
export declare const COLLECTION_DESCRIPTION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
|
|
38
|
-
export declare const COLLECTION_READ_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid"];
|
|
39
|
-
export declare const COLLECTION_QUERY_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "cursor_capacity_exhausted", "generation_expired", "invalid_read_cursor", "query_snapshot_changed", "sandbox_unsupported"];
|
|
40
|
-
export declare const COLLECTION_MUTATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved"];
|
|
41
|
-
export declare const COLLECTION_CHANGES_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "change_cursor_reset"];
|
|
42
|
-
export declare const COLLECTION_TYPE_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved", "type_pack_provision_failed"];
|
|
37
|
+
export declare const COLLECTION_DESCRIPTION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
|
|
38
|
+
export declare const COLLECTION_READ_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid"];
|
|
39
|
+
export declare const COLLECTION_QUERY_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "cursor_capacity_exhausted", "generation_expired", "invalid_read_cursor", "query_snapshot_changed", "sandbox_unsupported"];
|
|
40
|
+
export declare const COLLECTION_MUTATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved"];
|
|
41
|
+
export declare const COLLECTION_CHANGES_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "change_cursor_reset"];
|
|
42
|
+
export declare const COLLECTION_TYPE_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved", "type_pack_provision_failed"];
|
|
43
43
|
export declare function connectSuccess<Value>(value: Value, diagnostics?: MdbaseDiagnostic[]): ConnectSuccess<Value>;
|
|
44
44
|
export declare function connectFailure<Code extends ConnectProblemCode>(problem: ConnectProblem<Code>): ConnectFailure<Code>;
|
|
45
45
|
export declare function captureConnectOutcome<Value, Code extends ConnectProblemCode>(operation: () => Promise<Value>, allowedCodes: readonly Code[]): Promise<ConnectOutcome<Value, Code>>;
|
package/dist/outcomes.js
CHANGED
|
@@ -3,6 +3,7 @@ import { MdbaseConnectError, unknownConnectProblem } from "./errors.js";
|
|
|
3
3
|
export const COMMON_OPERATION_PROBLEM_CODES = [
|
|
4
4
|
"access_denied",
|
|
5
5
|
"access_paused",
|
|
6
|
+
"application_declaration_mismatch",
|
|
6
7
|
"authorization_binding_incompatible",
|
|
7
8
|
"authorization_expired",
|
|
8
9
|
"capability_contract_incompatible",
|
|
@@ -48,6 +49,7 @@ export const REGISTRATION_PROBLEM_CODES = [
|
|
|
48
49
|
export const AUTHORIZATION_PROBLEM_CODES = [
|
|
49
50
|
...REGISTRATION_PROBLEM_CODES,
|
|
50
51
|
"access_denied",
|
|
52
|
+
"application_declaration_mismatch",
|
|
51
53
|
"application_identity_unavailable",
|
|
52
54
|
"approval_window_blocked",
|
|
53
55
|
"authorization_binding_incompatible",
|
|
@@ -108,6 +110,10 @@ export const SESSION_PROBLEM_CODES = [
|
|
|
108
110
|
...AUTHORIZATION_PROBLEM_CODES,
|
|
109
111
|
"collection_not_ready",
|
|
110
112
|
"collection_not_selected",
|
|
113
|
+
"operation_cancelled",
|
|
114
|
+
"session_destroyed",
|
|
115
|
+
"session_not_started",
|
|
116
|
+
"session_starting",
|
|
111
117
|
"unknown_collection"
|
|
112
118
|
];
|
|
113
119
|
export const COLLECTION_SETUP_PROBLEM_CODES = [
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CollectionOperation, OperationTransportProtocolVersion } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
export declare function pendingRecoveryTransports(storage: Storage, storagePrefix: string, operations: readonly CollectionOperation[], collectionId?: string): OperationTransportProtocolVersion[];
|
|
3
3
|
export declare function pendingMutationsUseKey(storage: Storage, baseKey: string, keyHandle: string): boolean;
|
|
4
|
+
export declare function pendingMutationKeyHandles(storage: Storage, baseKey: string): Set<string>;
|
|
4
5
|
export declare function removePendingMutations(storage: Storage, baseKey: string, removeKey: (keyHandle: string) => void): void;
|