@mdbase-dev/connect 0.1.0-beta.94 → 0.1.0-beta.96
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 +48 -13
- package/dist/application-contract.d.ts +22 -0
- package/dist/application-contract.js +75 -0
- package/dist/application-session.d.ts +10 -10
- package/dist/application-session.js +74 -52
- 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/capabilities.d.ts +4 -3
- package/dist/capabilities.js +15 -31
- package/dist/connect-authorization-helpers.d.ts +1 -0
- package/dist/connect-authorization-helpers.js +12 -0
- package/dist/connect-options.d.ts +2 -1
- package/dist/connect.d.ts +2 -1
- package/dist/connect.js +25 -25
- package/dist/connection.d.ts +11 -0
- package/dist/connection.js +38 -11
- package/dist/index.d.ts +3 -1
- package/dist/internal-types.d.ts +4 -2
- package/dist/internal-types.js +1 -1
- package/dist/mdbase-connect.d.ts +1 -1
- package/dist/operation-types.d.ts +4 -0
- package/dist/query-pagination.js +6 -3
- package/dist/request-coordinator.js +1 -0
- package/dist/runtime-utils.d.ts +8 -1
- package/dist/runtime-utils.js +13 -0
- package/dist/session.d.ts +11 -4
- package/dist/session.js +41 -8
- package/dist/structured-readiness.d.ts +77 -0
- package/dist/structured-readiness.js +126 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-qa5e6QDDjQFkjqKf4HstaDjyvJmQnvu4PkubKmMs2QIDou3qqMn+uYWQG2Z0ImA2
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ApplicationCapabilityRequirements, type LegacyApplicationCapabilityRequirements } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { MdbaseApplicationCapabilityId as ApplicationCapabilityId, MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
|
|
2
3
|
import type { MdbaseConnectionInfo } from "./connection-types.js";
|
|
3
4
|
export type MdbaseCapabilityState = "available" | "requires_authorization" | "requires_setup" | "temporarily_unavailable" | "unsupported";
|
|
4
5
|
export interface MdbaseCapabilityEvidence {
|
|
@@ -16,8 +17,8 @@ export interface MdbaseEffectiveCapability {
|
|
|
16
17
|
details?: Record<string, unknown>;
|
|
17
18
|
}
|
|
18
19
|
export interface MdbaseEffectiveCapabilities {
|
|
19
|
-
contractVersion: 1;
|
|
20
|
+
contractVersion: 1 | 2;
|
|
20
21
|
values: Partial<Record<ApplicationCapabilityId, MdbaseEffectiveCapability>>;
|
|
21
22
|
requiredAvailable: boolean;
|
|
22
23
|
}
|
|
23
|
-
export declare function effectiveCapabilities(requirements: ApplicationCapabilityRequirements, manifest: MdbaseAppManifest, connection: MdbaseConnectionInfo): MdbaseEffectiveCapabilities;
|
|
24
|
+
export declare function effectiveCapabilities(requirements: ApplicationCapabilityRequirements | LegacyApplicationCapabilityRequirements, manifest: MdbaseAppManifest, connection: MdbaseConnectionInfo): MdbaseEffectiveCapabilities;
|
package/dist/capabilities.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { capabilityOperationsForContractVersion } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
export function effectiveCapabilities(requirements, manifest, connection) {
|
|
3
3
|
const required = new Set(requirements.required);
|
|
4
4
|
const declared = [...new Set([...requirements.required, ...(requirements.optional ?? [])])];
|
|
5
5
|
const values = {};
|
|
6
6
|
for (const id of declared) {
|
|
7
|
-
values[id] = effectiveCapability(id, required.has(id),
|
|
7
|
+
values[id] = effectiveCapability(id, required.has(id), connection, requirements.contract_version, manifest);
|
|
8
8
|
}
|
|
9
9
|
return {
|
|
10
|
-
contractVersion:
|
|
10
|
+
contractVersion: requirements.contract_version,
|
|
11
11
|
values,
|
|
12
12
|
requiredAvailable: requirements.required.every((id) => values[id]?.state === "available")
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
function effectiveCapability(id, required,
|
|
16
|
-
const operations =
|
|
15
|
+
function effectiveCapability(id, required, connection, version, manifest) {
|
|
16
|
+
const operations = capabilityOperationsForContractVersion(version, id) ?? [];
|
|
17
17
|
const missingOperations = operations.filter((operation) => !connection.operations.includes(operation));
|
|
18
18
|
const base = {
|
|
19
19
|
id,
|
|
@@ -22,7 +22,7 @@ function effectiveCapability(id, required, manifest, connection) {
|
|
|
22
22
|
missingOperations,
|
|
23
23
|
evidence: [{
|
|
24
24
|
source: "application",
|
|
25
|
-
fact: `${required ? "Required" : "Optional"} in capability contract
|
|
25
|
+
fact: `${required ? "Required" : "Optional"} in capability contract v${version}.`
|
|
26
26
|
}]
|
|
27
27
|
};
|
|
28
28
|
if (missingOperations.length > 0) {
|
|
@@ -47,35 +47,20 @@ function effectiveCapability(id, required, manifest, connection) {
|
|
|
47
47
|
}]
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// Compatibility readiness is deliberately limited to v1 aliases. V2 needs
|
|
51
|
+
// structured file/notification/runtime evidence, not these legacy shortcuts.
|
|
52
|
+
if (version === 1 && id.startsWith("files.")) {
|
|
51
53
|
const action = id.slice("files.".length);
|
|
52
54
|
if (!connection.fileCapability?.actions.includes(action)) {
|
|
53
55
|
return {
|
|
54
56
|
...base,
|
|
55
57
|
state: connection.fileCapability ? "requires_authorization" : "unsupported",
|
|
56
|
-
reason: connection.fileCapability
|
|
57
|
-
? `The current file grant does not include ${action}.`
|
|
58
|
-
: "This connection has no file capability.",
|
|
59
|
-
evidence: [...base.evidence, {
|
|
60
|
-
source: "authorization",
|
|
61
|
-
fact: connection.fileCapability
|
|
62
|
-
? `Granted file actions: ${connection.fileCapability.actions.join(", ")}.`
|
|
63
|
-
: "No file capability was granted."
|
|
64
|
-
}]
|
|
58
|
+
reason: connection.fileCapability ? `The current file grant does not include ${action}.` : "This connection has no file capability."
|
|
65
59
|
};
|
|
66
60
|
}
|
|
67
61
|
}
|
|
68
|
-
if (id === "notifications.background-delivery"
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
...base,
|
|
72
|
-
state: "requires_setup",
|
|
73
|
-
reason: "The application manifest declares no notification criteria.",
|
|
74
|
-
evidence: [...base.evidence, {
|
|
75
|
-
source: "application",
|
|
76
|
-
fact: "No authority notification criteria are declared."
|
|
77
|
-
}]
|
|
78
|
-
};
|
|
62
|
+
if (version === 1 && id === "notifications.background-delivery" && (manifest.notifications?.criteria.length ?? 0) === 0) {
|
|
63
|
+
return { ...base, state: "requires_setup", reason: "The application manifest declares no notification criteria." };
|
|
79
64
|
}
|
|
80
65
|
return {
|
|
81
66
|
...base,
|
|
@@ -91,7 +76,7 @@ function effectiveCapability(id, required, manifest, connection) {
|
|
|
91
76
|
? "Backed by a durable hosted mdbase authority."
|
|
92
77
|
: "Backed by a user-operated mdbase connector."
|
|
93
78
|
}],
|
|
94
|
-
...(id === "sync.offline-replica"
|
|
79
|
+
...(id === "offline.replica" || (version === 1 && id === "sync.offline-replica")
|
|
95
80
|
? {
|
|
96
81
|
details: {
|
|
97
82
|
durability: "device",
|
|
@@ -100,8 +85,7 @@ function effectiveCapability(id, required, manifest, connection) {
|
|
|
100
85
|
}
|
|
101
86
|
}
|
|
102
87
|
: {}),
|
|
103
|
-
...(id === "notifications.background-delivery"
|
|
104
|
-
? { details: { delivery: "authority", payload: "opaque" } }
|
|
105
|
-
: {})
|
|
88
|
+
...(version === 1 && id === "notifications.background-delivery"
|
|
89
|
+
? { details: { delivery: "authority", payload: "opaque" } } : {})
|
|
106
90
|
};
|
|
107
91
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { MdbaseConnectError } from "./errors.js";
|
|
2
|
+
export declare function assertFreshV2AuthorizationSupport(serverUrl: string, signal?: AbortSignal): Promise<void>;
|
|
2
3
|
export declare function declarationIdFromFamilyIdentity(familyIdentity: string): string;
|
|
3
4
|
export declare function authorizationAbort(signal: AbortSignal, message: string, cause?: unknown): MdbaseConnectError;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import { APPLICATION_AUTHORIZATION_V2_ISSUANCE_CAPABILITY } from "@mdbase-dev/connect-protocol";
|
|
1
2
|
import { MdbaseConnectError, connectError } from "./errors.js";
|
|
3
|
+
export async function assertFreshV2AuthorizationSupport(serverUrl, signal) {
|
|
4
|
+
const response = await fetch(`${serverUrl}/health`, { signal, cache: "no-store" });
|
|
5
|
+
const health = await response.json().catch(() => null);
|
|
6
|
+
if (!response.ok || !Array.isArray(health?.capabilities)
|
|
7
|
+
|| !health.capabilities.every((value) => typeof value === "string")
|
|
8
|
+
|| !health.capabilities.includes(APPLICATION_AUTHORIZATION_V2_ISSUANCE_CAPABILITY)) {
|
|
9
|
+
throw connectError("capability_contract_incompatible", "This server does not support new version-2 application authorizations.", {
|
|
10
|
+
details: { contract: "fresh_application_authorization", required: [2], supported: [], peer: "server" }
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
2
14
|
export function declarationIdFromFamilyIdentity(familyIdentity) {
|
|
3
15
|
const prefix = "bundle:";
|
|
4
16
|
if (!familyIdentity.startsWith(prefix) || familyIdentity.length === prefix.length) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
|
|
2
|
+
import type { JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
3
|
import type { GrantKeyStore } from "./crypto.js";
|
|
3
4
|
import type { ApplicationIdentityStore } from "./application-identity.js";
|
|
4
5
|
export interface MdbaseConnectTimeouts {
|
package/dist/connect.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { JsonObject
|
|
1
|
+
import type { JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { type MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
|
|
2
3
|
import { type ApplicationIdentityStore } from "./application-identity.js";
|
|
3
4
|
import { MdbaseConnection, type MdbaseAuthorizationOutcome, type MdbaseAuthorizationResult, type MdbaseAuthorizeOptions, type MdbaseConnectEnvironment } from "./connection.js";
|
|
4
5
|
import type { MdbaseConnectOptions } from "./connect-options.js";
|
package/dist/connect.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import { APPLICATION_AUTHORIZATION_PROTOCOL_VERSION, authorizationContractRequirements, DEFAULT_LOOPBACK_PORT, GRANT_ENCRYPTION_PROTOCOL_VERSION, RELAY_ENCRYPTION_SUITE } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { authorizationOperations, authorizationFiles, semanticContractVersion, validateAuthorizationSelection } from "./application-contract.js";
|
|
2
3
|
import { abortableDelay } from "./async.js";
|
|
3
4
|
import { authorizationCallbackState } from "./authorization-url.js";
|
|
4
5
|
import { randomBase64Url } from "./base64.js";
|
|
5
6
|
import { ApplicationIdentityStoreError, IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, applicationIdentity, applicationInstallationId, signApplicationAuthorization } from "./application-identity.js";
|
|
6
7
|
import { MdbaseConnection } from "./connection.js";
|
|
7
8
|
import { addConnectionId, connectionIds, removeConnectionId } from "./connection-index.js";
|
|
8
|
-
import { authorizationAbort, declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
|
|
9
|
+
import { assertFreshV2AuthorizationSupport, authorizationAbort, declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
|
|
9
10
|
import { IndexedDbGrantKeyStore, MemoryGrantKeyStore } from "./crypto.js";
|
|
10
11
|
import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
|
|
11
|
-
import { DEFAULT_OPERATIONS } from "./internal-types.js";
|
|
12
12
|
import { prepareAuthorizationWindow, publishPopupAuthorizationCompletion, waitForPopupAuthorization } from "./popup-authorization.js";
|
|
13
|
-
import { uniqueOperations } from "./operation-helpers.js";
|
|
14
13
|
import { pendingRecoveryTransports, removePendingMutations } from "./pending-recovery-transports.js";
|
|
15
14
|
import { GrantKeyLeaseRegistry } from "./grant-key-leases.js";
|
|
16
15
|
import { resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
|
|
17
|
-
import { MemoryStorage, apiError, applicationStorageOrigin, canonicalLoopbackUrl, collectionIdFromTokenKey, connectFetch, createPkce,
|
|
16
|
+
import { MemoryStorage, apiError, applicationStorageOrigin, canonicalLoopbackUrl, collectionIdFromTokenKey, connectFetch, createPkce, defaultStorage, decodeJsonResponse, isOpaquePortableManifest, resolveManifestSource, oauthErrorCode, parseDeviceAuthorization, parseStored, stripTrailingSlash, validAuthorityTokenResponse } from "./runtime-utils.js";
|
|
18
17
|
import { storedTokenFromResponse } from "./token-response.js";
|
|
19
18
|
import { deleteUnusedGrantKey, planRetiredGrantKeys, storedTokenKeyHandles } from "./retired-grant-keys.js";
|
|
20
19
|
import { SharedOperationMap } from "./shared-operation.js";
|
|
@@ -41,18 +40,11 @@ export class MdbaseConnectInternals {
|
|
|
41
40
|
invalidatedConnections = new Map();
|
|
42
41
|
constructor(options) {
|
|
43
42
|
this.serverUrl = stripTrailingSlash(String(options.serverUrl));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.manifestSource = typeof this.manifest === "string"
|
|
48
|
-
? this.manifest
|
|
49
|
-
: this.manifest.distribution === "portable"
|
|
50
|
-
? `bundle:${this.manifest.id}:${manifestStorageFingerprint(this.manifest)}`
|
|
51
|
-
: `bundle:${this.manifest.id}`;
|
|
43
|
+
const declaration = resolveManifestSource(options.manifest, options.redirectUri);
|
|
44
|
+
this.manifest = declaration.manifest;
|
|
45
|
+
this.manifestSource = declaration.manifestSource;
|
|
52
46
|
const opaquePortable = isOpaquePortableManifest(this.manifest);
|
|
53
|
-
this.redirectUri =
|
|
54
|
-
? ""
|
|
55
|
-
: defaultRedirectUri()) : String(options.redirectUri);
|
|
47
|
+
this.redirectUri = declaration.redirectUri;
|
|
56
48
|
this.storage = options.storage ?? defaultStorage(opaquePortable);
|
|
57
49
|
this.relayEncryption = options.relayEncryption ?? "required";
|
|
58
50
|
this.keyStore = options.keyStore ?? (opaquePortable ? new MemoryGrantKeyStore() : new IndexedDbGrantKeyStore());
|
|
@@ -168,6 +160,16 @@ export class MdbaseConnectInternals {
|
|
|
168
160
|
navigate: this.navigate,
|
|
169
161
|
register: (signal) => this.registerWithinBudget(signal)
|
|
170
162
|
});
|
|
163
|
+
try {
|
|
164
|
+
validateAuthorizationSelection(application.requirements, options);
|
|
165
|
+
if (application.requirements.capabilities?.contract_version === 2) {
|
|
166
|
+
await assertFreshV2AuthorizationSupport(this.serverUrl, options.signal);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
popup?.close();
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
171
173
|
if (application.distribution === "portable") {
|
|
172
174
|
if (options.openVerification)
|
|
173
175
|
popup?.close();
|
|
@@ -192,7 +194,8 @@ export class MdbaseConnectInternals {
|
|
|
192
194
|
await this.keyStore.delete(keyHandle);
|
|
193
195
|
throw error;
|
|
194
196
|
}
|
|
195
|
-
const operations =
|
|
197
|
+
const operations = authorizationOperations(application, options);
|
|
198
|
+
const requestedFiles = authorizationFiles(application.requirements);
|
|
196
199
|
const issuedAt = new Date();
|
|
197
200
|
const proof = await signApplicationAuthorization({
|
|
198
201
|
protocol_version: APPLICATION_AUTHORIZATION_PROTOCOL_VERSION,
|
|
@@ -211,11 +214,9 @@ export class MdbaseConnectInternals {
|
|
|
211
214
|
redirect_uri: this.redirectUri,
|
|
212
215
|
state,
|
|
213
216
|
code_challenge: challenge,
|
|
214
|
-
contracts: authorizationContractRequirements(operations,
|
|
217
|
+
contracts: authorizationContractRequirements(operations, requestedFiles, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, targetCollectionId), semanticContractVersion(application.requirements)),
|
|
215
218
|
requested_operations: operations,
|
|
216
|
-
...(
|
|
217
|
-
? { requested_files: application.requirements.files }
|
|
218
|
-
: {}),
|
|
219
|
+
...(requestedFiles ? { requested_files: requestedFiles } : {}),
|
|
219
220
|
...(targetCollectionId ? { collection_id: targetCollectionId } : {})
|
|
220
221
|
}, installation);
|
|
221
222
|
const pending = {
|
|
@@ -310,7 +311,8 @@ export class MdbaseConnectInternals {
|
|
|
310
311
|
await this.keyStore.delete(keyHandle);
|
|
311
312
|
throw error;
|
|
312
313
|
}
|
|
313
|
-
const operations =
|
|
314
|
+
const operations = authorizationOperations(application, options);
|
|
315
|
+
const requestedFiles = authorizationFiles(application.requirements);
|
|
314
316
|
const authorizationId = crypto.randomUUID();
|
|
315
317
|
const issuedAt = new Date();
|
|
316
318
|
const proof = await signApplicationAuthorization({
|
|
@@ -328,11 +330,9 @@ export class MdbaseConnectInternals {
|
|
|
328
330
|
issued_at: issuedAt.toISOString(),
|
|
329
331
|
expires_at: new Date(issuedAt.getTime() + 10 * 60 * 1_000).toISOString(),
|
|
330
332
|
code_challenge: challenge,
|
|
331
|
-
contracts: authorizationContractRequirements(operations,
|
|
333
|
+
contracts: authorizationContractRequirements(operations, requestedFiles, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, options.target?.kind === "collection" ? options.target.collectionId : undefined), semanticContractVersion(application.requirements)),
|
|
332
334
|
requested_operations: operations,
|
|
333
|
-
...(
|
|
334
|
-
? { requested_files: application.requirements.files }
|
|
335
|
-
: {}),
|
|
335
|
+
...(requestedFiles ? { requested_files: requestedFiles } : {}),
|
|
336
336
|
...(options.target?.kind === "collection"
|
|
337
337
|
? { collection_id: options.target.collectionId }
|
|
338
338
|
: {})
|
package/dist/connection.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CollectionOperation, CollectionTypeDocument, FileCapability, GrantScope, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { type MdbaseApplicationCapabilityId as ApplicationCapabilityId } from "./application-contract.js";
|
|
2
3
|
import { type ConnectionTransportInternals } from "./connection-transport.js";
|
|
3
4
|
import type { DirectAccessStatus, MdbaseAuthorizationCapabilities, MdbaseConnectionInfo, MdbaseConnectionRoute, MdbaseSyncConnection } from "./connection-types.js";
|
|
4
5
|
import type { GrantKeyStore } from "./crypto.js";
|
|
@@ -16,7 +17,10 @@ export type MdbaseAuthorizationTarget = {
|
|
|
16
17
|
collectionId: string;
|
|
17
18
|
};
|
|
18
19
|
export interface MdbaseAuthorizeOptions extends ConnectRequestOptions {
|
|
20
|
+
/** Exact independent operations, for legacy v1 declarations only. Cannot be mixed with capabilities. */
|
|
19
21
|
operations?: CollectionOperation[];
|
|
22
|
+
/** Capability groups to request. Required groups are always included; optional groups default to all. */
|
|
23
|
+
capabilities?: ApplicationCapabilityId[];
|
|
20
24
|
/** Keep a web application loaded while Connect owns the approval ceremony. */
|
|
21
25
|
presentation?: "redirect" | "popup";
|
|
22
26
|
/** Choose any compatible collection, or require one exact collection. */
|
|
@@ -29,7 +33,10 @@ export interface MdbaseAuthorizeOptions extends ConnectRequestOptions {
|
|
|
29
33
|
openVerification?: (authorization: MdbaseDeviceAuthorization) => void | Promise<void>;
|
|
30
34
|
}
|
|
31
35
|
export interface MdbaseConnectionAuthorizeOptions extends ConnectRequestOptions {
|
|
36
|
+
/** Exact independent operations, for legacy v1 declarations only. */
|
|
32
37
|
operations?: CollectionOperation[];
|
|
38
|
+
/** Capability groups to request. Required groups are always included; optional groups default to all. */
|
|
39
|
+
capabilities?: ApplicationCapabilityId[];
|
|
33
40
|
presentation?: "redirect" | "popup";
|
|
34
41
|
returnTo?: string;
|
|
35
42
|
onDeviceCode?: (authorization: MdbaseDeviceAuthorization) => void;
|
|
@@ -80,6 +87,10 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
|
|
|
80
87
|
authorizationCapabilities(requiredOperations?: CollectionOperation[]): MdbaseAuthorizationCapabilities;
|
|
81
88
|
hasOperations(requiredOperations: CollectionOperation[]): boolean;
|
|
82
89
|
authorize(options?: MdbaseConnectionAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
|
|
90
|
+
requestCapabilities(capabilities: ApplicationCapabilityId[], options?: MdbaseConnectionAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
|
|
91
|
+
kind: "unchanged";
|
|
92
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
93
|
+
}, AuthorizationProblemCode>>;
|
|
83
94
|
requestOperations(requiredOperations: CollectionOperation[], options?: Omit<MdbaseConnectionAuthorizeOptions, "operations">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
|
|
84
95
|
kind: "unchanged";
|
|
85
96
|
connection: MdbaseConnection<Frontmatter>;
|
package/dist/connection.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { capabilityOperationsForContractVersion } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { retainedCapabilities, semanticContractVersion, validateAuthorizationSelection } from "./application-contract.js";
|
|
1
3
|
import { MdbaseCollectionClient } from "./collection-client.js";
|
|
2
4
|
import { ConnectionNotifications } from "./connection-notifications.js";
|
|
3
5
|
import { ConnectionTransport } from "./connection-transport.js";
|
|
@@ -106,18 +108,43 @@ export class MdbaseConnection {
|
|
|
106
108
|
target: { kind: "collection", collectionId: this.collectionId }
|
|
107
109
|
}), AUTHORIZATION_PROBLEM_CODES);
|
|
108
110
|
}
|
|
111
|
+
async requestCapabilities(capabilities, options = {}) {
|
|
112
|
+
return captureConnectOutcome(async () => {
|
|
113
|
+
const application = await this.internals.register(options);
|
|
114
|
+
validateAuthorizationSelection(application.requirements, { ...options, capabilities });
|
|
115
|
+
const sufficient = this.info() !== null && capabilities.every((capability) => (capabilityOperationsForContractVersion(semanticContractVersion(application.requirements), capability) ?? [])
|
|
116
|
+
.every((operation) => this.operations.includes(operation)));
|
|
117
|
+
if (sufficient)
|
|
118
|
+
return { kind: "unchanged", connection: this };
|
|
119
|
+
if (semanticContractVersion(application.requirements) === 1) {
|
|
120
|
+
const { capabilities: _selection, ...legacyOptions } = options;
|
|
121
|
+
return this.internals.authorize({
|
|
122
|
+
...legacyOptions,
|
|
123
|
+
target: { kind: "collection", collectionId: this.collectionId },
|
|
124
|
+
operations: [...new Set([...this.operations, ...capabilities.flatMap((id) => capabilityOperationsForContractVersion(1, id) ?? [])])]
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const retained = retainedCapabilities(application.requirements, this.operations);
|
|
128
|
+
return this.internals.authorize({
|
|
129
|
+
...options,
|
|
130
|
+
target: { kind: "collection", collectionId: this.collectionId },
|
|
131
|
+
capabilities: [...new Set([...retained, ...capabilities])]
|
|
132
|
+
});
|
|
133
|
+
}, AUTHORIZATION_PROBLEM_CODES);
|
|
134
|
+
}
|
|
109
135
|
async requestOperations(requiredOperations, options = {}) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
...
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
return captureConnectOutcome(async () => {
|
|
137
|
+
const application = await this.internals.register(options);
|
|
138
|
+
validateAuthorizationSelection(application.requirements, { ...options, operations: requiredOperations });
|
|
139
|
+
const authorization = this.authorizationCapabilities(requiredOperations);
|
|
140
|
+
if (authorization.sufficient)
|
|
141
|
+
return { kind: "unchanged", connection: this };
|
|
142
|
+
return this.internals.authorize({
|
|
143
|
+
...options,
|
|
144
|
+
target: { kind: "collection", collectionId: this.collectionId },
|
|
145
|
+
operations: [...new Set([...authorization.grantedOperations, ...requiredOperations])]
|
|
146
|
+
});
|
|
147
|
+
}, AUTHORIZATION_PROBLEM_CODES);
|
|
121
148
|
}
|
|
122
149
|
onConnectionChange(listener) {
|
|
123
150
|
this.connectionListeners.add(listener);
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { MdbaseConnection } from "./connection.js";
|
|
|
7
7
|
export type { MdbaseAuthorizationTarget, MdbaseAuthorizeOptions, MdbaseConnectionAuthorizeOptions, MdbaseConnectEnvironment, MdbaseAuthorizationResult, MdbaseAuthorizationOutcome } from "./connection.js";
|
|
8
8
|
export type { MdbaseConnectionRoute, DirectAccessStatus, MdbaseConnectionInfo, MdbaseAuthorizationCapabilities, MdbaseSyncTransport, MdbaseSyncConnection } from "./connection-types.js";
|
|
9
9
|
export { effectiveCapabilities } from "./capabilities.js";
|
|
10
|
+
export type { MdbaseStructuredReadiness } from "./structured-readiness.js";
|
|
10
11
|
export type { MdbaseCapabilityState, MdbaseCapabilityEvidence, MdbaseEffectiveCapability, MdbaseEffectiveCapabilities } from "./capabilities.js";
|
|
11
12
|
export { MdbaseConnectError, isRetryableConnectError } from "./errors.js";
|
|
12
13
|
export type { ConnectErrorContext } from "./errors.js";
|
|
@@ -22,4 +23,5 @@ export { MdbaseBrowserSelection, MdbaseMemorySelection } from "./selection.js";
|
|
|
22
23
|
export type { MdbaseSelectionHistory, MdbaseApplicationSelection, MdbaseBrowserSelectionOptions } from "./selection.js";
|
|
23
24
|
export type { MdbaseUnavailableReason } from "./session.js";
|
|
24
25
|
export { MDBASE_RECORD_CREATED_CONTRACT, MDBASE_TIMER_FIRED_CONTRACT, operationsForApplicationCapabilities } from "@mdbase-dev/connect-protocol";
|
|
25
|
-
export type {
|
|
26
|
+
export type { MdbaseApplicationManifest, MdbaseApplicationManifest as MdbaseAppManifest, MdbaseApplicationRequirements, MdbaseApplicationCapabilityId as ApplicationCapabilityId, MdbaseApplicationCapabilityRequirements as ApplicationCapabilityRequirements } from "./application-contract.js";
|
|
27
|
+
export type { ApplicationProvisions, ApplicationRequirements, LegacyApplicationRequirements, LegacyApplicationCapabilityId, LegacyApplicationCapabilityRequirements, LegacyMdbaseAppManifest, ApplicationNotifications, ApplicationAuthorizationBinding, ApplicationAuthorizationProof, FileAction, FileCapability, CollectionOperation as MdbaseOperation, CollectionTypeDocument, ConnectOperationOutcome, ConnectProblem, ConnectProblemCategory, ConnectProblemCode, ConnectProblemDetailsByCode, ConnectRecoveryAction, ContractRequirement, GrantScope, JsonObject, NotificationCriterion, MdbaseDiagnostic, MdbaseOperationEnvelope, KnownConnectProblem, TypePackManifest, TypePackManifestResource, TypePackProvision, TypePackSourceResource, UnknownConnectProblem } from "@mdbase-dev/connect-protocol";
|
package/dist/internal-types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ApplicationNotifications,
|
|
1
|
+
import type { ApplicationNotifications, ApplicationProvisions, CollectionOperation, EncryptedRelayOperationRequest, GrantEncryption, FileCapability, GrantScope, MdbaseOperationRequest, MutationOperationIdentifier } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { MdbaseApplicationRequirements as ApplicationRequirements } from "./application-contract.js";
|
|
2
3
|
import { encryptRelayRequest } from "./crypto.js";
|
|
3
4
|
export interface Application {
|
|
4
5
|
id: string;
|
|
@@ -10,6 +11,7 @@ export interface Application {
|
|
|
10
11
|
project_url?: string;
|
|
11
12
|
notifications?: ApplicationNotifications;
|
|
12
13
|
requirements: ApplicationRequirements;
|
|
14
|
+
provisions?: ApplicationProvisions;
|
|
13
15
|
}
|
|
14
16
|
export interface StoredAuthorization {
|
|
15
17
|
version: 1;
|
|
@@ -95,4 +97,4 @@ export interface OperationAttempt {
|
|
|
95
97
|
pendingMutationRecord?: PendingMutation;
|
|
96
98
|
resumingMutation?: boolean;
|
|
97
99
|
}
|
|
98
|
-
export
|
|
100
|
+
export { DEFAULT_OPERATIONS } from "./application-contract.js";
|
package/dist/internal-types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { DEFAULT_OPERATIONS } from "./application-contract.js";
|
package/dist/mdbase-connect.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare class MdbaseConnect<Frontmatter extends JsonObject = JsonObject>
|
|
|
14
14
|
authorize(options?: MdbaseAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
|
|
15
15
|
/** The ordinary application lifecycle entry point. */
|
|
16
16
|
application(options: MdbaseApplicationSessionOptions): MdbaseApplicationSession<Frontmatter>;
|
|
17
|
-
manifest(options?: ConnectRequestOptions): Promise<ConnectOutcome<import("
|
|
17
|
+
manifest(options?: ConnectRequestOptions): Promise<ConnectOutcome<import("./application-contract.js").MdbaseApplicationManifest, RegistrationProblemCode>>;
|
|
18
18
|
environment(): MdbaseConnectEnvironment;
|
|
19
19
|
completeAuthorization(callbackUrl?: string | URL, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
|
|
20
20
|
connections(): MdbaseConnectionInfo[];
|
|
@@ -187,7 +187,9 @@ export interface QueryResult<Record extends JsonObject = JsonObject> {
|
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
export interface QueryPagesOptions<Record extends JsonObject = JsonObject> {
|
|
190
|
+
/** Initial page size. Cursor authorities pin this size for the query lifetime. */
|
|
190
191
|
firstPageSize?: number;
|
|
192
|
+
/** Also sets the initial size unless firstPageSize is explicit; authorities may cap it. */
|
|
191
193
|
pageSize?: number;
|
|
192
194
|
signal?: AbortSignal;
|
|
193
195
|
/** Independent budget for each page requested by this caller-driven iterator. */
|
|
@@ -196,7 +198,9 @@ export interface QueryPagesOptions<Record extends JsonObject = JsonObject> {
|
|
|
196
198
|
onProgress?: (page: QueryPage<Record>) => void;
|
|
197
199
|
}
|
|
198
200
|
export interface QueryAllOptions<Record extends JsonObject = JsonObject> extends ConnectRequestOptions {
|
|
201
|
+
/** Initial page size. Cursor authorities pin this size for the query lifetime. */
|
|
199
202
|
firstPageSize?: number;
|
|
203
|
+
/** Also sets the initial size unless firstPageSize is explicit; authorities may cap it. */
|
|
200
204
|
pageSize?: number;
|
|
201
205
|
onProgress?: (page: QueryPage<Record>) => void;
|
|
202
206
|
}
|
package/dist/query-pagination.js
CHANGED
|
@@ -3,7 +3,7 @@ import { connectFailure, connectSuccess } from "./outcomes.js";
|
|
|
3
3
|
export async function* coordinatedQueryPages(query, releaseQueryCursor, input = {}, options = {}) {
|
|
4
4
|
const { offset: requestedOffset, limit: requestedLimit, cursor: requestedCursor, pagination: requestedPagination, snapshot: requestedSnapshot, ...criteria } = input;
|
|
5
5
|
let offset = nonNegativeInteger(requestedOffset, 0);
|
|
6
|
-
const firstPageSize = positiveInteger(options.firstPageSize ?? requestedLimit, 200);
|
|
6
|
+
const firstPageSize = positiveInteger(options.firstPageSize ?? options.pageSize ?? requestedLimit, 200);
|
|
7
7
|
const pageSize = positiveInteger(options.pageSize ?? requestedLimit, 1_000);
|
|
8
8
|
let cursor = requestedCursor;
|
|
9
9
|
let cursorMode = requestedCursor !== undefined;
|
|
@@ -25,7 +25,8 @@ export async function* coordinatedQueryPages(query, releaseQueryCursor, input =
|
|
|
25
25
|
&& requestedSnapshot === undefined;
|
|
26
26
|
let queried = await query({
|
|
27
27
|
...criteria,
|
|
28
|
-
|
|
28
|
+
// Continuations use the page size pinned when the cursor was opened.
|
|
29
|
+
...(!cursorMode ? { limit: pageNumber === 0 ? firstPageSize : pageSize } : {}),
|
|
29
30
|
...(cursorMode
|
|
30
31
|
? (pageCursor ? { cursor: pageCursor } : { pagination: "cursor" })
|
|
31
32
|
: {
|
|
@@ -100,7 +101,9 @@ export async function* coordinatedQueryPages(query, releaseQueryCursor, input =
|
|
|
100
101
|
}
|
|
101
102
|
finally {
|
|
102
103
|
if (cursorMode && cursorToRelease) {
|
|
103
|
-
|
|
104
|
+
// Cleanup has its own bounded request. It must not delay delivery of
|
|
105
|
+
// completed pages or consume the caller's data deadline.
|
|
106
|
+
void releaseQueryCursor(cursorToRelease).catch(() => undefined);
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
}
|
|
@@ -79,6 +79,7 @@ export class CollectionRequestCoordinator {
|
|
|
79
79
|
if (queued.started || queued.settled)
|
|
80
80
|
return;
|
|
81
81
|
queued.settled = true;
|
|
82
|
+
queue.splice(queue.indexOf(queued), 1);
|
|
82
83
|
queued.reject(requestAbortReason(combined.signal));
|
|
83
84
|
combined.dispose();
|
|
84
85
|
this.releaseLatest(coordination.family, replacement);
|
package/dist/runtime-utils.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import type { FileCapability, GrantEncryption, GrantScope
|
|
1
|
+
import type { FileCapability, GrantEncryption, GrantScope } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
|
|
2
3
|
import { MdbaseConnectError } from "./errors.js";
|
|
3
4
|
import type { StoredToken } from "./internal-types.js";
|
|
4
5
|
import type { MdbaseDeviceAuthorization } from "./authorization-types.js";
|
|
6
|
+
/** Resolve bundled declarations and their stable storage identity without loading URLs. */
|
|
7
|
+
export declare function resolveManifestSource(input?: MdbaseAppManifest | string | URL, redirect?: string | URL): {
|
|
8
|
+
manifest: MdbaseAppManifest | string;
|
|
9
|
+
manifestSource: string;
|
|
10
|
+
redirectUri: string;
|
|
11
|
+
};
|
|
5
12
|
type NetworkProblemCode = "notification_registration_failed" | "notification_unregistration_failed" | "notifications_unavailable" | "temporarily_unavailable";
|
|
6
13
|
/** Normalize fetch rejection at the exact I/O boundary. */
|
|
7
14
|
export declare function connectFetch(input: RequestInfo | URL, init: RequestInit | undefined, code: NetworkProblemCode, message: string): Promise<Response>;
|
package/dist/runtime-utils.js
CHANGED
|
@@ -2,6 +2,19 @@ import { validateGrantEncryption } from "./crypto.js";
|
|
|
2
2
|
import { connectError, serverConnectError } from "./errors.js";
|
|
3
3
|
import { canonicalJson } from "./operation-helpers.js";
|
|
4
4
|
import { bytesToBase64Url, randomBase64Url } from "./base64.js";
|
|
5
|
+
/** Resolve bundled declarations and their stable storage identity without loading URLs. */
|
|
6
|
+
export function resolveManifestSource(input, redirect) {
|
|
7
|
+
const manifest = input instanceof URL ? input.href : input ?? defaultManifestSource();
|
|
8
|
+
const manifestSource = typeof manifest === "string"
|
|
9
|
+
? manifest
|
|
10
|
+
: manifest.distribution === "portable"
|
|
11
|
+
? `bundle:${manifest.id}:${manifestStorageFingerprint(manifest)}`
|
|
12
|
+
: `bundle:${manifest.id}`;
|
|
13
|
+
const redirectUri = redirect === undefined ? (typeof manifest !== "string" && manifest.distribution === "portable"
|
|
14
|
+
? ""
|
|
15
|
+
: defaultRedirectUri()) : String(redirect);
|
|
16
|
+
return { manifest, manifestSource, redirectUri };
|
|
17
|
+
}
|
|
5
18
|
/** Normalize fetch rejection at the exact I/O boundary. */
|
|
6
19
|
export async function connectFetch(input, init, code, message) {
|
|
7
20
|
try {
|
package/dist/session.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { CollectionOperation, ConnectProblem, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { type MdbaseApplicationManifest, type MdbaseApplicationCapabilityId as ApplicationCapabilityId } from "./application-contract.js";
|
|
2
3
|
import { MdbaseConnection, type MdbaseAuthorizationOutcome, type MdbaseAuthorizationResult, type MdbaseAuthorizeOptions } from "./connection.js";
|
|
3
4
|
import type { MdbaseAuthorizationCapabilities, MdbaseConnectionInfo } from "./connection-types.js";
|
|
4
|
-
import { type AuthorizationProblemCode, type ConnectOutcome, type SessionProblemCode } from "./outcomes.js";
|
|
5
|
+
import { type AuthorizationProblemCode, type RegistrationProblemCode, type ConnectOutcome, type SessionProblemCode } from "./outcomes.js";
|
|
5
6
|
import type { MdbaseSelectionHistory, MdbaseApplicationSelection } from "./selection.js";
|
|
6
7
|
import type { ConnectRequestOptions } from "./operation-types.js";
|
|
7
8
|
export interface MdbaseSessionConnect<Frontmatter extends JsonObject> {
|
|
9
|
+
manifest?(options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseApplicationManifest, RegistrationProblemCode>>;
|
|
8
10
|
authorize(options?: MdbaseAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
|
|
9
11
|
completeAuthorization(callbackUrl: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
|
|
10
12
|
connections(): MdbaseConnectionInfo[];
|
|
@@ -87,11 +89,16 @@ export declare class MdbaseSession<Frontmatter extends JsonObject = JsonObject>
|
|
|
87
89
|
forget(collectionId: string): ConnectOutcome<void, SessionProblemCode>;
|
|
88
90
|
authorize(target: "choose" | "selected" | {
|
|
89
91
|
collectionId: string;
|
|
90
|
-
}, options?: Omit<MdbaseAuthorizeOptions, "
|
|
91
|
-
ensureOperations(requiredOperations: CollectionOperation[], options?: ConnectRequestOptions): Promise<ConnectOutcome<
|
|
92
|
+
}, options?: Omit<MdbaseAuthorizeOptions, "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
|
|
93
|
+
ensureOperations(requiredOperations: CollectionOperation[], options?: ConnectRequestOptions): Promise<ConnectOutcome<{
|
|
92
94
|
kind: "unchanged";
|
|
93
95
|
connection: MdbaseConnection<Frontmatter>;
|
|
94
|
-
}
|
|
96
|
+
} | MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
|
|
97
|
+
ensureCapabilities(requiredCapabilities: ApplicationCapabilityId[], options?: ConnectRequestOptions): Promise<ConnectOutcome<{
|
|
98
|
+
kind: "unchanged";
|
|
99
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
100
|
+
} | MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
|
|
101
|
+
private ensureAuthorization;
|
|
95
102
|
handleAuthorizationCallback(callbackUrl: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, SessionProblemCode>>;
|
|
96
103
|
private completeAuthorizationCallback;
|
|
97
104
|
private beginCallbackCompletion;
|