@mdbase-dev/connect 0.1.0-beta.21
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/LICENSE +21 -0
- package/README.md +405 -0
- package/dist/async.d.ts +1 -0
- package/dist/async.js +13 -0
- package/dist/authorization-types.d.ts +7 -0
- package/dist/authorization-types.js +1 -0
- package/dist/authorization-url.d.ts +3 -0
- package/dist/authorization-url.js +27 -0
- package/dist/base64.d.ts +3 -0
- package/dist/base64.js +21 -0
- package/dist/browser/integrity.json +4 -0
- package/dist/browser/mdbase-connect.min.js +3 -0
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -0
- package/dist/collection-client.d.ts +57 -0
- package/dist/collection-client.js +214 -0
- package/dist/connect.d.ts +39 -0
- package/dist/connect.js +620 -0
- package/dist/connection-notifications.d.ts +25 -0
- package/dist/connection-notifications.js +179 -0
- package/dist/connection-transport.d.ts +80 -0
- package/dist/connection-transport.js +670 -0
- package/dist/connection-types.d.ts +29 -0
- package/dist/connection-types.js +1 -0
- package/dist/connection.d.ts +154 -0
- package/dist/connection.js +371 -0
- package/dist/crypto.d.ts +64 -0
- package/dist/crypto.js +391 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +83 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/internal-types.d.ts +70 -0
- package/dist/internal-types.js +1 -0
- package/dist/notifications.d.ts +47 -0
- package/dist/notifications.js +52 -0
- package/dist/operation-helpers.d.ts +24 -0
- package/dist/operation-helpers.js +129 -0
- package/dist/operation-types.d.ts +254 -0
- package/dist/operation-types.js +1 -0
- package/dist/runtime-utils.d.ts +33 -0
- package/dist/runtime-utils.js +224 -0
- package/dist/selection.d.ts +48 -0
- package/dist/selection.js +112 -0
- package/dist/session.d.ts +76 -0
- package/dist/session.js +271 -0
- package/package.json +41 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { validateGrantEncryption } from "./crypto.js";
|
|
2
|
+
import { MdbaseConnectError } from "./errors.js";
|
|
3
|
+
import { canonicalJson } from "./operation-helpers.js";
|
|
4
|
+
import { bytesToBase64Url, randomBase64Url } from "./base64.js";
|
|
5
|
+
export function canonicalLoopbackUrl(value) {
|
|
6
|
+
const url = new URL(value);
|
|
7
|
+
if (url.protocol !== "http:"
|
|
8
|
+
|| !["127.0.0.1", "[::1]"].includes(url.hostname)
|
|
9
|
+
|| url.username
|
|
10
|
+
|| url.password
|
|
11
|
+
|| url.pathname !== "/"
|
|
12
|
+
|| url.search
|
|
13
|
+
|| url.hash) {
|
|
14
|
+
throw new MdbaseConnectError("invalid_loopback_url", "loopbackUrl must be an HTTP origin on 127.0.0.1 or ::1.");
|
|
15
|
+
}
|
|
16
|
+
return url.origin;
|
|
17
|
+
}
|
|
18
|
+
export async function createPkce() {
|
|
19
|
+
const verifier = randomBase64Url(32);
|
|
20
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
21
|
+
return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };
|
|
22
|
+
}
|
|
23
|
+
export function apiError(body, fallbackCode, fallbackMessage, status) {
|
|
24
|
+
return new MdbaseConnectError(oauthErrorCode(body) ?? body?.error?.code ?? fallbackCode, body?.error_description ?? body?.error?.message ?? fallbackMessage, { status, details: body?.error?.details });
|
|
25
|
+
}
|
|
26
|
+
export function oauthErrorCode(body) {
|
|
27
|
+
return typeof body?.error === "string" ? body.error : undefined;
|
|
28
|
+
}
|
|
29
|
+
export function validAuthorityTokenResponse(value, collectionId) {
|
|
30
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
31
|
+
return false;
|
|
32
|
+
if (typeof collectionId !== "string")
|
|
33
|
+
return false;
|
|
34
|
+
const authority = value;
|
|
35
|
+
if (typeof authority.operations_url !== "string"
|
|
36
|
+
|| typeof authority.sync_url !== "string"
|
|
37
|
+
|| typeof authority.replica_id !== "string"
|
|
38
|
+
|| authority.replica_id.length === 0
|
|
39
|
+
|| typeof authority.access_token !== "string"
|
|
40
|
+
|| authority.access_token.length === 0
|
|
41
|
+
|| (authority.proof_public_key !== undefined
|
|
42
|
+
&& (typeof authority.proof_public_key !== "string"
|
|
43
|
+
|| authority.proof_public_key.length === 0)))
|
|
44
|
+
return false;
|
|
45
|
+
try {
|
|
46
|
+
const operations = new URL(authority.operations_url);
|
|
47
|
+
const sync = new URL(authority.sync_url);
|
|
48
|
+
return [operations, sync].every((url) => (url.protocol === "https:"
|
|
49
|
+
|| (url.protocol === "http:"
|
|
50
|
+
&& ["localhost", "127.0.0.1", "[::1]", "::1"].includes(url.hostname)))
|
|
51
|
+
&& !url.username
|
|
52
|
+
&& !url.password
|
|
53
|
+
&& !url.search
|
|
54
|
+
&& !url.hash)
|
|
55
|
+
&& /^\/v1\/authorities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/operations$/i.test(operations.pathname)
|
|
56
|
+
&& /^\/v1\/authorities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/sync$/i.test(sync.pathname)
|
|
57
|
+
&& operations.origin === sync.origin
|
|
58
|
+
&& operations.pathname.split("/")[3] === collectionId
|
|
59
|
+
&& sync.pathname.split("/")[3] === collectionId;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function validStoredAuthority(authority, collectionId) {
|
|
66
|
+
return validAuthorityTokenResponse({
|
|
67
|
+
operations_url: authority?.operationsUrl,
|
|
68
|
+
sync_url: authority?.syncUrl,
|
|
69
|
+
replica_id: authority?.replicaId,
|
|
70
|
+
access_token: authority?.accessToken,
|
|
71
|
+
proof_public_key: authority?.proofPublicKey
|
|
72
|
+
}, collectionId);
|
|
73
|
+
}
|
|
74
|
+
export function validStoredEncryption(encryption, collectionId) {
|
|
75
|
+
if (!encryption || typeof encryption !== "object" || Array.isArray(encryption)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (encryption.collection_id !== collectionId)
|
|
79
|
+
return false;
|
|
80
|
+
try {
|
|
81
|
+
validateGrantEncryption(encryption);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function parseDeviceAuthorization(body) {
|
|
89
|
+
if (typeof body?.device_code !== "string"
|
|
90
|
+
|| typeof body?.user_code !== "string"
|
|
91
|
+
|| typeof body?.verification_uri !== "string"
|
|
92
|
+
|| typeof body?.verification_uri_complete !== "string"
|
|
93
|
+
|| !Number.isFinite(body?.expires_in)
|
|
94
|
+
|| !Number.isFinite(body?.interval)) {
|
|
95
|
+
throw new MdbaseConnectError("invalid_device_authorization_response", "Connect returned an invalid downloaded application authorization response.");
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
userCode: body.user_code,
|
|
99
|
+
verificationUri: body.verification_uri,
|
|
100
|
+
verificationUriComplete: body.verification_uri_complete,
|
|
101
|
+
expiresAt: Date.now() + Math.max(1, body.expires_in) * 1_000,
|
|
102
|
+
intervalSeconds: Math.max(1, body.interval)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function parseStored(value) {
|
|
106
|
+
if (!value)
|
|
107
|
+
return null;
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(value);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function parseGrantScope(value) {
|
|
116
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
117
|
+
return null;
|
|
118
|
+
const scope = value;
|
|
119
|
+
if (scope.access !== "contract" && scope.access !== "full_collection")
|
|
120
|
+
return null;
|
|
121
|
+
if (!Array.isArray(scope.contracts))
|
|
122
|
+
return null;
|
|
123
|
+
if (scope.contracts.some((contract) => !contract
|
|
124
|
+
|| typeof contract !== "object"
|
|
125
|
+
|| contract.contract_type !== "record"
|
|
126
|
+
|| typeof contract.id !== "string"
|
|
127
|
+
|| typeof contract.version !== "string"
|
|
128
|
+
|| !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(contract.version)
|
|
129
|
+
|| !/^sha256:[0-9a-f]{64}$/.test(contract.digest)
|
|
130
|
+
|| !contract.schema
|
|
131
|
+
|| typeof contract.schema !== "object"
|
|
132
|
+
|| Array.isArray(contract.schema)
|
|
133
|
+
|| !Array.isArray(contract.implementations)
|
|
134
|
+
|| contract.implementations.some((implementation) => !implementation
|
|
135
|
+
|| typeof implementation !== "object"
|
|
136
|
+
|| typeof implementation.type_name !== "string"
|
|
137
|
+
|| !Number.isInteger(implementation.type_version)
|
|
138
|
+
|| !/^sha256:[0-9a-f]{64}$/.test(implementation.digest)
|
|
139
|
+
|| !implementation.fields
|
|
140
|
+
|| typeof implementation.fields !== "object"
|
|
141
|
+
|| Array.isArray(implementation.fields)
|
|
142
|
+
|| Object.values(implementation.fields).some((field) => typeof field !== "string"))))
|
|
143
|
+
return null;
|
|
144
|
+
return scope;
|
|
145
|
+
}
|
|
146
|
+
export function stripTrailingSlash(value) {
|
|
147
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
148
|
+
}
|
|
149
|
+
export function defaultManifestSource() {
|
|
150
|
+
if (typeof location === "undefined") {
|
|
151
|
+
throw new MdbaseConnectError("manifest_required", "manifest is required outside a browser environment.");
|
|
152
|
+
}
|
|
153
|
+
if (location.origin === "null") {
|
|
154
|
+
throw new MdbaseConnectError("manifest_required", "Downloaded applications must provide their v1 portable manifest inline.");
|
|
155
|
+
}
|
|
156
|
+
return new URL("/.well-known/mdbase-app.json", location.origin).href;
|
|
157
|
+
}
|
|
158
|
+
export function defaultRedirectUri() {
|
|
159
|
+
if (typeof location === "undefined") {
|
|
160
|
+
throw new MdbaseConnectError("redirect_uri_required", "redirectUri is required outside a browser environment.");
|
|
161
|
+
}
|
|
162
|
+
return location.href.split(/[?#]/)[0];
|
|
163
|
+
}
|
|
164
|
+
export function defaultCallbackUrl() {
|
|
165
|
+
if (typeof location === "undefined") {
|
|
166
|
+
throw new MdbaseConnectError("callback_url_required", "callbackUrl is required outside a browser environment.");
|
|
167
|
+
}
|
|
168
|
+
return location.href;
|
|
169
|
+
}
|
|
170
|
+
export function defaultStorage(memoryOnly) {
|
|
171
|
+
if (memoryOnly)
|
|
172
|
+
return new MemoryStorage();
|
|
173
|
+
if (typeof localStorage === "undefined") {
|
|
174
|
+
throw new MdbaseConnectError("storage_required", "storage is required outside a browser environment.");
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const probe = `mdbase-connect:probe:${randomBase64Url(8)}`;
|
|
178
|
+
localStorage.setItem(probe, "1");
|
|
179
|
+
localStorage.removeItem(probe);
|
|
180
|
+
return localStorage;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return new MemoryStorage();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export class MemoryStorage {
|
|
187
|
+
values = new Map();
|
|
188
|
+
get length() {
|
|
189
|
+
return this.values.size;
|
|
190
|
+
}
|
|
191
|
+
clear() {
|
|
192
|
+
this.values.clear();
|
|
193
|
+
}
|
|
194
|
+
getItem(key) {
|
|
195
|
+
return this.values.get(key) ?? null;
|
|
196
|
+
}
|
|
197
|
+
key(index) {
|
|
198
|
+
return [...this.values.keys()][index] ?? null;
|
|
199
|
+
}
|
|
200
|
+
removeItem(key) {
|
|
201
|
+
this.values.delete(key);
|
|
202
|
+
}
|
|
203
|
+
setItem(key, value) {
|
|
204
|
+
this.values.set(String(key), String(value));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export function isOpaquePortableManifest(manifest) {
|
|
208
|
+
if (typeof manifest === "string" || manifest.distribution !== "portable")
|
|
209
|
+
return false;
|
|
210
|
+
return typeof location === "undefined"
|
|
211
|
+
|| location.origin === "null"
|
|
212
|
+
|| !["http:", "https:"].includes(location.protocol);
|
|
213
|
+
}
|
|
214
|
+
export function manifestStorageFingerprint(manifest) {
|
|
215
|
+
const canonical = canonicalJson(manifest);
|
|
216
|
+
let first = 0x811c9dc5;
|
|
217
|
+
let second = 0x9e3779b9;
|
|
218
|
+
for (let index = 0; index < canonical.length; index += 1) {
|
|
219
|
+
const code = canonical.charCodeAt(index);
|
|
220
|
+
first = Math.imul(first ^ code, 0x01000193);
|
|
221
|
+
second = Math.imul(second ^ code, 0x85ebca6b);
|
|
222
|
+
}
|
|
223
|
+
return `${(first >>> 0).toString(16).padStart(8, "0")}${(second >>> 0).toString(16).padStart(8, "0")}`;
|
|
224
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type MdbaseSelectionHistory = "push" | "replace";
|
|
2
|
+
export interface MdbaseSessionSelection {
|
|
3
|
+
selectedCollectionId(): string | null;
|
|
4
|
+
select(collectionId: string | null, options?: {
|
|
5
|
+
history?: MdbaseSelectionHistory;
|
|
6
|
+
}): void;
|
|
7
|
+
authorizationReturnTo(): string | undefined;
|
|
8
|
+
authorizationCallback(): string | null;
|
|
9
|
+
finishAuthorization(returnTo: string | undefined, collectionId: string): void;
|
|
10
|
+
clearAuthorizationCallback(returnTo?: string): void;
|
|
11
|
+
subscribe(listener: () => void): () => void;
|
|
12
|
+
}
|
|
13
|
+
export interface MdbaseBrowserSelectionOptions {
|
|
14
|
+
collectionParameter?: string;
|
|
15
|
+
fallbackPath?: string;
|
|
16
|
+
}
|
|
17
|
+
/** URL-backed collection selection for browser and browser-shell applications. */
|
|
18
|
+
export declare class MdbaseBrowserSelection implements MdbaseSessionSelection {
|
|
19
|
+
private readonly collectionParameter;
|
|
20
|
+
private readonly fallbackPath;
|
|
21
|
+
private readonly listeners;
|
|
22
|
+
private readonly handlePopState;
|
|
23
|
+
constructor(options?: MdbaseBrowserSelectionOptions);
|
|
24
|
+
selectedCollectionId(): string | null;
|
|
25
|
+
select(collectionId: string | null, options?: {
|
|
26
|
+
history?: MdbaseSelectionHistory;
|
|
27
|
+
}): void;
|
|
28
|
+
authorizationReturnTo(): string;
|
|
29
|
+
authorizationCallback(): string | null;
|
|
30
|
+
finishAuthorization(returnTo: string | undefined, collectionId: string): void;
|
|
31
|
+
clearAuthorizationCallback(returnTo?: string): void;
|
|
32
|
+
subscribe(listener: () => void): () => void;
|
|
33
|
+
private emit;
|
|
34
|
+
private safeAppUrl;
|
|
35
|
+
private currentUrl;
|
|
36
|
+
private browserHistory;
|
|
37
|
+
}
|
|
38
|
+
export declare class MdbaseMemorySelection implements MdbaseSessionSelection {
|
|
39
|
+
private collectionId;
|
|
40
|
+
private readonly listeners;
|
|
41
|
+
selectedCollectionId(): string | null;
|
|
42
|
+
select(collectionId: string | null): void;
|
|
43
|
+
authorizationReturnTo(): undefined;
|
|
44
|
+
authorizationCallback(): null;
|
|
45
|
+
finishAuthorization(_returnTo: string | undefined, collectionId: string): void;
|
|
46
|
+
clearAuthorizationCallback(): void;
|
|
47
|
+
subscribe(listener: () => void): () => void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { cleanAuthorizationParameters, isAuthorizationCallbackUrl } from "./authorization-url.js";
|
|
2
|
+
import { MdbaseConnectError } from "./errors.js";
|
|
3
|
+
/** URL-backed collection selection for browser and browser-shell applications. */
|
|
4
|
+
export class MdbaseBrowserSelection {
|
|
5
|
+
collectionParameter;
|
|
6
|
+
fallbackPath;
|
|
7
|
+
listeners = new Set();
|
|
8
|
+
handlePopState = () => this.emit();
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.collectionParameter = options.collectionParameter ?? "collection";
|
|
11
|
+
this.fallbackPath = options.fallbackPath ?? "/";
|
|
12
|
+
}
|
|
13
|
+
selectedCollectionId() {
|
|
14
|
+
return this.currentUrl().searchParams.get(this.collectionParameter);
|
|
15
|
+
}
|
|
16
|
+
select(collectionId, options = {}) {
|
|
17
|
+
const url = cleanAuthorizationParameters(this.currentUrl());
|
|
18
|
+
if (collectionId)
|
|
19
|
+
url.searchParams.set(this.collectionParameter, collectionId);
|
|
20
|
+
else
|
|
21
|
+
url.searchParams.delete(this.collectionParameter);
|
|
22
|
+
const browserHistory = this.browserHistory();
|
|
23
|
+
browserHistory[options.history === "push" ? "pushState" : "replaceState"](browserHistory.state, "", url);
|
|
24
|
+
this.emit();
|
|
25
|
+
}
|
|
26
|
+
authorizationReturnTo() {
|
|
27
|
+
const url = cleanAuthorizationParameters(this.currentUrl());
|
|
28
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
29
|
+
}
|
|
30
|
+
authorizationCallback() {
|
|
31
|
+
const value = this.currentUrl().href;
|
|
32
|
+
return isAuthorizationCallbackUrl(value) ? value : null;
|
|
33
|
+
}
|
|
34
|
+
finishAuthorization(returnTo, collectionId) {
|
|
35
|
+
const url = this.safeAppUrl(returnTo ?? this.fallbackPath);
|
|
36
|
+
cleanAuthorizationParameters(url);
|
|
37
|
+
url.searchParams.set(this.collectionParameter, collectionId);
|
|
38
|
+
const browserHistory = this.browserHistory();
|
|
39
|
+
browserHistory.replaceState(null, "", url);
|
|
40
|
+
this.emit();
|
|
41
|
+
}
|
|
42
|
+
clearAuthorizationCallback(returnTo) {
|
|
43
|
+
const url = returnTo ? this.safeAppUrl(returnTo) : this.currentUrl();
|
|
44
|
+
const browserHistory = this.browserHistory();
|
|
45
|
+
browserHistory.replaceState(browserHistory.state, "", cleanAuthorizationParameters(url));
|
|
46
|
+
this.emit();
|
|
47
|
+
}
|
|
48
|
+
subscribe(listener) {
|
|
49
|
+
const first = this.listeners.size === 0;
|
|
50
|
+
this.listeners.add(listener);
|
|
51
|
+
if (first && typeof window !== "undefined") {
|
|
52
|
+
window.addEventListener("popstate", this.handlePopState);
|
|
53
|
+
}
|
|
54
|
+
return () => {
|
|
55
|
+
this.listeners.delete(listener);
|
|
56
|
+
if (this.listeners.size === 0 && typeof window !== "undefined") {
|
|
57
|
+
window.removeEventListener("popstate", this.handlePopState);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
emit() {
|
|
62
|
+
for (const listener of this.listeners)
|
|
63
|
+
listener();
|
|
64
|
+
}
|
|
65
|
+
safeAppUrl(value) {
|
|
66
|
+
const current = this.currentUrl();
|
|
67
|
+
const candidate = new URL(value, current.origin);
|
|
68
|
+
return candidate.origin === current.origin
|
|
69
|
+
? candidate
|
|
70
|
+
: new URL(this.fallbackPath, current.origin);
|
|
71
|
+
}
|
|
72
|
+
currentUrl() {
|
|
73
|
+
if (typeof location === "undefined") {
|
|
74
|
+
throw new MdbaseConnectError("browser_required", "Browser selection requires a browser environment.");
|
|
75
|
+
}
|
|
76
|
+
return new URL(location.href);
|
|
77
|
+
}
|
|
78
|
+
browserHistory() {
|
|
79
|
+
if (typeof history === "undefined") {
|
|
80
|
+
throw new MdbaseConnectError("browser_required", "Browser selection requires a browser environment.");
|
|
81
|
+
}
|
|
82
|
+
return history;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export class MdbaseMemorySelection {
|
|
86
|
+
collectionId = null;
|
|
87
|
+
listeners = new Set();
|
|
88
|
+
selectedCollectionId() {
|
|
89
|
+
return this.collectionId;
|
|
90
|
+
}
|
|
91
|
+
select(collectionId) {
|
|
92
|
+
if (collectionId === this.collectionId)
|
|
93
|
+
return;
|
|
94
|
+
this.collectionId = collectionId;
|
|
95
|
+
for (const listener of this.listeners)
|
|
96
|
+
listener();
|
|
97
|
+
}
|
|
98
|
+
authorizationReturnTo() {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
authorizationCallback() {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
finishAuthorization(_returnTo, collectionId) {
|
|
105
|
+
this.select(collectionId);
|
|
106
|
+
}
|
|
107
|
+
clearAuthorizationCallback() { }
|
|
108
|
+
subscribe(listener) {
|
|
109
|
+
this.listeners.add(listener);
|
|
110
|
+
return () => this.listeners.delete(listener);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { CollectionOperation, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { MdbaseConnection, type MdbaseAuthorizationOutcome, type MdbaseAuthorizationResult, type MdbaseAuthorizeOptions } from "./connection.js";
|
|
3
|
+
import type { MdbaseAuthorizationCapabilities, MdbaseConnectionInfo } from "./connection-types.js";
|
|
4
|
+
import type { MdbaseSelectionHistory, MdbaseSessionSelection } from "./selection.js";
|
|
5
|
+
export interface MdbaseSessionConnect<Frontmatter extends JsonObject> {
|
|
6
|
+
authorize(options?: MdbaseAuthorizeOptions): Promise<MdbaseAuthorizationOutcome<Frontmatter>>;
|
|
7
|
+
completeAuthorization(callbackUrl: string): Promise<MdbaseAuthorizationResult<Frontmatter>>;
|
|
8
|
+
connections(): MdbaseConnectionInfo[];
|
|
9
|
+
connection(collectionId: string): MdbaseConnection<Frontmatter> | null;
|
|
10
|
+
unavailableReason(collectionId: string): MdbaseUnavailableReason | null;
|
|
11
|
+
onConnectionsChange(listener: (connections: MdbaseConnectionInfo[]) => void): () => void;
|
|
12
|
+
}
|
|
13
|
+
export interface MdbaseSessionOptions {
|
|
14
|
+
selection: MdbaseSessionSelection;
|
|
15
|
+
operations?: CollectionOperation[];
|
|
16
|
+
autoSelect?: "only" | "never";
|
|
17
|
+
}
|
|
18
|
+
export type MdbaseUnavailableReason = "not_authorized" | "authorization_lost" | "invalid_stored_grant";
|
|
19
|
+
export type MdbaseSessionSnapshot<Frontmatter extends JsonObject = JsonObject> = {
|
|
20
|
+
status: "unselected";
|
|
21
|
+
connections: MdbaseConnectionInfo[];
|
|
22
|
+
} | {
|
|
23
|
+
status: "ready";
|
|
24
|
+
collectionId: string;
|
|
25
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
26
|
+
info: MdbaseConnectionInfo;
|
|
27
|
+
access: MdbaseAuthorizationCapabilities;
|
|
28
|
+
connections: MdbaseConnectionInfo[];
|
|
29
|
+
} | {
|
|
30
|
+
status: "unavailable";
|
|
31
|
+
collectionId: string;
|
|
32
|
+
reason: MdbaseUnavailableReason;
|
|
33
|
+
connections: MdbaseConnectionInfo[];
|
|
34
|
+
};
|
|
35
|
+
export declare class MdbaseSession<Frontmatter extends JsonObject = JsonObject> {
|
|
36
|
+
private readonly connect;
|
|
37
|
+
private readonly operations;
|
|
38
|
+
private readonly selection;
|
|
39
|
+
private readonly autoSelect;
|
|
40
|
+
private readonly listeners;
|
|
41
|
+
private snapshot;
|
|
42
|
+
private snapshotKey;
|
|
43
|
+
private stopConnections?;
|
|
44
|
+
private stopSelection?;
|
|
45
|
+
private stopActiveConnection?;
|
|
46
|
+
private activeCollectionId;
|
|
47
|
+
private unavailableReason;
|
|
48
|
+
private started;
|
|
49
|
+
private refreshing;
|
|
50
|
+
private refreshQueued;
|
|
51
|
+
private transactionDepth;
|
|
52
|
+
private callbackPromise;
|
|
53
|
+
constructor(connect: MdbaseSessionConnect<Frontmatter>, options: MdbaseSessionOptions);
|
|
54
|
+
start(): Promise<MdbaseSessionSnapshot<Frontmatter>>;
|
|
55
|
+
destroy(): void;
|
|
56
|
+
getSnapshot(): MdbaseSessionSnapshot<Frontmatter>;
|
|
57
|
+
subscribe(listener: () => void): () => void;
|
|
58
|
+
select(collectionId: string, options?: {
|
|
59
|
+
history?: MdbaseSelectionHistory;
|
|
60
|
+
}): MdbaseConnection<Frontmatter>;
|
|
61
|
+
clearSelection(options?: {
|
|
62
|
+
history?: MdbaseSelectionHistory;
|
|
63
|
+
}): void;
|
|
64
|
+
forget(collectionId: string): void;
|
|
65
|
+
authorize(target: "choose" | "selected" | {
|
|
66
|
+
collectionId: string;
|
|
67
|
+
}, options?: Omit<MdbaseAuthorizeOptions, "operations" | "returnTo" | "target">): Promise<MdbaseAuthorizationOutcome<Frontmatter>>;
|
|
68
|
+
ensureOperations(requiredOperations: CollectionOperation[]): Promise<MdbaseAuthorizationOutcome<Frontmatter> | {
|
|
69
|
+
kind: "unchanged";
|
|
70
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
71
|
+
}>;
|
|
72
|
+
handleAuthorizationCallback(callbackUrl: string): Promise<MdbaseConnection<Frontmatter>>;
|
|
73
|
+
private autoSelectOnlyConnection;
|
|
74
|
+
private refresh;
|
|
75
|
+
private refreshOnce;
|
|
76
|
+
}
|