@mdbase-dev/connect 0.1.0-beta.82 → 0.1.0-beta.84
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 +16 -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 +0 -3
- package/dist/connect.js +44 -45
- package/dist/connection-index.d.ts +3 -0
- package/dist/connection-index.js +20 -0
- package/dist/connection-transport.js +7 -4
- package/dist/connection.d.ts +3 -0
- package/dist/hosted-operation-response.d.ts +5 -0
- package/dist/hosted-operation-response.js +26 -0
- package/dist/internal-types.d.ts +13 -0
- package/dist/popup-authorization.d.ts +31 -0
- package/dist/popup-authorization.js +100 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-l1syvCOespqx2xGAwlKWRSmphqrCw5zoEJLrsvcdCqadK8gQTQcTZJuGTJ7NtIZm
|
package/dist/connect.d.ts
CHANGED
|
@@ -58,8 +58,5 @@ export declare class MdbaseConnectInternals<Frontmatter extends JsonObject> {
|
|
|
58
58
|
defaultApplicationOrigin(): string;
|
|
59
59
|
private pendingKey;
|
|
60
60
|
private storagePrefix;
|
|
61
|
-
private connectionsKey;
|
|
62
|
-
private connectionIds;
|
|
63
|
-
private addConnectionId;
|
|
64
61
|
private emitConnections;
|
|
65
62
|
}
|
package/dist/connect.js
CHANGED
|
@@ -3,10 +3,12 @@ import { abortableDelay } from "./async.js";
|
|
|
3
3
|
import { randomBase64Url } from "./base64.js";
|
|
4
4
|
import { ApplicationIdentityStoreError, IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, applicationIdentity, applicationInstallationId, signApplicationAuthorization } from "./application-identity.js";
|
|
5
5
|
import { MdbaseConnection } from "./connection.js";
|
|
6
|
+
import { addConnectionId, connectionIds, removeConnectionId } from "./connection-index.js";
|
|
6
7
|
import { authorizationAbort, declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
|
|
7
8
|
import { IndexedDbGrantKeyStore, MemoryGrantKeyStore } from "./crypto.js";
|
|
8
9
|
import { connectError, serverConnectError } from "./errors.js";
|
|
9
10
|
import { DEFAULT_OPERATIONS } from "./internal-types.js";
|
|
11
|
+
import { prepareAuthorizationWindow, publishPopupAuthorizationCompletion, waitForPopupAuthorization } from "./popup-authorization.js";
|
|
10
12
|
import { uniqueOperations } from "./operation-helpers.js";
|
|
11
13
|
import { pendingMutationsUseKey, pendingRecoveryTransports, removePendingMutations } from "./pending-recovery-transports.js";
|
|
12
14
|
import { resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
|
|
@@ -146,37 +148,28 @@ export class MdbaseConnectInternals {
|
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
async authorize(options = {}) {
|
|
149
|
-
|
|
151
|
+
const authorizationTimeout = options.presentation === "popup"
|
|
152
|
+
? 10 * 60 * 1_000
|
|
153
|
+
: this.timeouts.requestMs;
|
|
154
|
+
return withRequestBudget(options, authorizationTimeout, (budget) => this.authorizeWithinBudget({ ...options, signal: budget.signal, timeoutMs: null }));
|
|
150
155
|
}
|
|
151
156
|
async authorizeWithinBudget(options) {
|
|
152
|
-
if (typeof location === "undefined" && !this.navigate && !options.openVerification) {
|
|
153
|
-
throw connectError("browser_required", "Authorization navigation requires a browser environment.");
|
|
154
|
-
}
|
|
155
157
|
const portableDeclared = typeof this.manifest !== "string"
|
|
156
158
|
&& this.manifest.distribution === "portable";
|
|
157
|
-
const popup =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
let application;
|
|
164
|
-
try {
|
|
165
|
-
application = await this.registerWithinBudget(options.signal);
|
|
166
|
-
}
|
|
167
|
-
catch (error) {
|
|
168
|
-
popup?.close();
|
|
169
|
-
throw error;
|
|
170
|
-
}
|
|
159
|
+
const { application, popup } = await prepareAuthorizationWindow({
|
|
160
|
+
options,
|
|
161
|
+
portableDeclared,
|
|
162
|
+
navigate: this.navigate,
|
|
163
|
+
register: (signal) => this.registerWithinBudget(signal)
|
|
164
|
+
});
|
|
171
165
|
if (application.distribution === "portable") {
|
|
172
166
|
if (options.openVerification)
|
|
173
167
|
popup?.close();
|
|
174
168
|
return this.authorizePortable(application, options, options.openVerification ? null : popup);
|
|
175
169
|
}
|
|
176
|
-
popup
|
|
177
|
-
return this.authorizeWeb(application, options);
|
|
170
|
+
return this.authorizeWeb(application, options, popup);
|
|
178
171
|
}
|
|
179
|
-
async authorizeWeb(application, options) {
|
|
172
|
+
async authorizeWeb(application, options, popup) {
|
|
180
173
|
const { verifier, challenge } = await createPkce();
|
|
181
174
|
const state = randomBase64Url(24);
|
|
182
175
|
const authorizationId = crypto.randomUUID();
|
|
@@ -231,7 +224,8 @@ export class MdbaseConnectInternals {
|
|
|
231
224
|
keyHandle,
|
|
232
225
|
authorizationId,
|
|
233
226
|
applicationAgreementPublicKey: grantKey.agreementPublicKey,
|
|
234
|
-
applicationSigningPublicKey: grantKey.signingPublicKey
|
|
227
|
+
applicationSigningPublicKey: grantKey.signingPublicKey,
|
|
228
|
+
...(options.presentation === "popup" && popup ? { presentation: "popup" } : {})
|
|
235
229
|
};
|
|
236
230
|
this.storage.setItem(this.pendingKey(state), JSON.stringify(pending));
|
|
237
231
|
try {
|
|
@@ -260,6 +254,20 @@ export class MdbaseConnectInternals {
|
|
|
260
254
|
|| body.expires_in <= 0) {
|
|
261
255
|
throw connectError("invalid_device_authorization_response", "The authorization service returned an invalid authorization response.");
|
|
262
256
|
}
|
|
257
|
+
if (options.presentation === "popup" && popup) {
|
|
258
|
+
popup.location.href = body.authorization_uri;
|
|
259
|
+
return await waitForPopupAuthorization({
|
|
260
|
+
storage: this.storage,
|
|
261
|
+
storagePrefix: this.storagePrefix(),
|
|
262
|
+
state,
|
|
263
|
+
pending,
|
|
264
|
+
popup,
|
|
265
|
+
signal: options.signal,
|
|
266
|
+
keyStore: this.keyStore,
|
|
267
|
+
connection: (collectionId) => this.connection(collectionId)
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
popup?.close();
|
|
263
271
|
if (this.navigate)
|
|
264
272
|
await this.navigate(body.authorization_uri);
|
|
265
273
|
else if (typeof location !== "undefined")
|
|
@@ -270,6 +278,7 @@ export class MdbaseConnectInternals {
|
|
|
270
278
|
return { kind: "redirecting" };
|
|
271
279
|
}
|
|
272
280
|
catch (error) {
|
|
281
|
+
popup?.close();
|
|
273
282
|
this.storage.removeItem(this.pendingKey(state));
|
|
274
283
|
await this.keyStore.delete(keyHandle);
|
|
275
284
|
if (options.signal?.aborted) {
|
|
@@ -477,10 +486,19 @@ export class MdbaseConnectInternals {
|
|
|
477
486
|
if (!state) {
|
|
478
487
|
return Promise.reject(connectError("invalid_callback", "Authorization callback is missing its state."));
|
|
479
488
|
}
|
|
489
|
+
const pending = parseStored(this.storage.getItem(this.pendingKey(state)));
|
|
490
|
+
const popupAttempt = pending?.presentation === "popup";
|
|
480
491
|
const existing = this.completionPromises.get(state);
|
|
481
492
|
if (existing)
|
|
482
493
|
return existing;
|
|
483
|
-
const completion =
|
|
494
|
+
const completion = publishPopupAuthorizationCompletion({
|
|
495
|
+
storage: this.storage,
|
|
496
|
+
storagePrefix: this.storagePrefix(),
|
|
497
|
+
state,
|
|
498
|
+
pending,
|
|
499
|
+
popupAttempt,
|
|
500
|
+
completion: this.performAuthorizationCompletion(callbackUrl, state, signal)
|
|
501
|
+
});
|
|
484
502
|
const shared = completion.finally(() => {
|
|
485
503
|
if (this.completionPromises.get(state) === shared)
|
|
486
504
|
this.completionPromises.delete(state);
|
|
@@ -556,7 +574,7 @@ export class MdbaseConnectInternals {
|
|
|
556
574
|
}
|
|
557
575
|
connections() {
|
|
558
576
|
const connections = [];
|
|
559
|
-
for (const collectionId of this.
|
|
577
|
+
for (const collectionId of connectionIds(this.storage, this.storagePrefix())) {
|
|
560
578
|
const info = this.connection(collectionId)?.info();
|
|
561
579
|
if (info)
|
|
562
580
|
connections.push(info);
|
|
@@ -599,7 +617,7 @@ export class MdbaseConnectInternals {
|
|
|
599
617
|
void this.keyStore.delete(previous.keyHandle).catch(() => undefined);
|
|
600
618
|
}
|
|
601
619
|
this.storage.setItem(this.tokenKey(collectionId), JSON.stringify(token));
|
|
602
|
-
this.
|
|
620
|
+
addConnectionId(this.storage, this.storagePrefix(), collectionId);
|
|
603
621
|
this.invalidatedConnections.delete(collectionId);
|
|
604
622
|
this.connectionCache.delete(collectionId);
|
|
605
623
|
this.emitConnections();
|
|
@@ -643,10 +661,7 @@ export class MdbaseConnectInternals {
|
|
|
643
661
|
for (const transport of ["web_push", "fcm"]) {
|
|
644
662
|
this.storage.removeItem(this.notificationKey(collectionId, transport));
|
|
645
663
|
}
|
|
646
|
-
this.storage
|
|
647
|
-
version: 1,
|
|
648
|
-
collectionIds: this.connectionIds().filter((id) => id !== collectionId)
|
|
649
|
-
}));
|
|
664
|
+
removeConnectionId(this.storage, this.storagePrefix(), collectionId);
|
|
650
665
|
this.connectionCache.delete(collectionId);
|
|
651
666
|
this.emitConnections();
|
|
652
667
|
}
|
|
@@ -671,22 +686,6 @@ export class MdbaseConnectInternals {
|
|
|
671
686
|
storagePrefix() {
|
|
672
687
|
return `mdbase-connect:${this.serverUrl}:${this.manifestSource}`;
|
|
673
688
|
}
|
|
674
|
-
connectionsKey() {
|
|
675
|
-
return `${this.storagePrefix()}:connections`;
|
|
676
|
-
}
|
|
677
|
-
connectionIds() {
|
|
678
|
-
const index = parseStored(this.storage.getItem(this.connectionsKey()));
|
|
679
|
-
return index?.version === 1 && Array.isArray(index.collectionIds)
|
|
680
|
-
&& index.collectionIds.every((collectionId) => typeof collectionId === "string")
|
|
681
|
-
? index.collectionIds
|
|
682
|
-
: [];
|
|
683
|
-
}
|
|
684
|
-
addConnectionId(collectionId) {
|
|
685
|
-
this.storage.setItem(this.connectionsKey(), JSON.stringify({
|
|
686
|
-
version: 1,
|
|
687
|
-
collectionIds: [...new Set([...this.connectionIds(), collectionId])]
|
|
688
|
-
}));
|
|
689
|
-
}
|
|
690
689
|
emitConnections() {
|
|
691
690
|
const connections = this.connections();
|
|
692
691
|
for (const listener of this.listeners)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function connectionIds(storage: Storage, storagePrefix: string): string[];
|
|
2
|
+
export declare function addConnectionId(storage: Storage, storagePrefix: string, collectionId: string): void;
|
|
3
|
+
export declare function removeConnectionId(storage: Storage, storagePrefix: string, collectionId: string): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parseStored } from "./runtime-utils.js";
|
|
2
|
+
export function connectionIds(storage, storagePrefix) {
|
|
3
|
+
const index = parseStored(storage.getItem(`${storagePrefix}:connections`));
|
|
4
|
+
return index?.version === 1 && Array.isArray(index.collectionIds)
|
|
5
|
+
&& index.collectionIds.every((collectionId) => typeof collectionId === "string")
|
|
6
|
+
? index.collectionIds
|
|
7
|
+
: [];
|
|
8
|
+
}
|
|
9
|
+
export function addConnectionId(storage, storagePrefix, collectionId) {
|
|
10
|
+
writeConnectionIds(storage, storagePrefix, [...connectionIds(storage, storagePrefix), collectionId]);
|
|
11
|
+
}
|
|
12
|
+
export function removeConnectionId(storage, storagePrefix, collectionId) {
|
|
13
|
+
writeConnectionIds(storage, storagePrefix, connectionIds(storage, storagePrefix).filter((id) => id !== collectionId));
|
|
14
|
+
}
|
|
15
|
+
function writeConnectionIds(storage, storagePrefix, ids) {
|
|
16
|
+
storage.setItem(`${storagePrefix}:connections`, JSON.stringify({
|
|
17
|
+
version: 1,
|
|
18
|
+
collectionIds: [...new Set(ids)]
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
@@ -12,6 +12,7 @@ import { sendAuthoritySyncRequest } from "./authority-sync-request.js";
|
|
|
12
12
|
import { freshEncryptedRequest } from "./fresh-encrypted-request.js";
|
|
13
13
|
import { isExplicitConnectorBusyResponse, retryExplicitConnectorBusy } from "./transient-retry.js";
|
|
14
14
|
import { probeLoopbackAccess, tokenSupportsDirectAccess } from "./direct-access.js";
|
|
15
|
+
import { decodeHostedOperationResponse } from "./hosted-operation-response.js";
|
|
15
16
|
export class ConnectionTransport {
|
|
16
17
|
serverUrl;
|
|
17
18
|
storage;
|
|
@@ -192,17 +193,19 @@ export class ConnectionTransport {
|
|
|
192
193
|
clearPending: (requestId) => this.clearPendingMutation(requestId),
|
|
193
194
|
retry: (pending, knownRejected) => this.performOperationWithinBudget(operation, input, options, pending, freshReadRetried, connectorBusyRetries + 1, knownRejected)
|
|
194
195
|
});
|
|
195
|
-
let
|
|
196
|
+
let decoded;
|
|
196
197
|
try {
|
|
197
|
-
|
|
198
|
+
decoded = await decodeHostedOperationResponse(response, token.authority !== undefined);
|
|
198
199
|
}
|
|
199
200
|
catch (cause) {
|
|
200
201
|
if (attempt.pendingMutation)
|
|
201
202
|
throw unknownMutationOutcome(attempt.requestId, cause);
|
|
202
|
-
throw
|
|
203
|
+
throw cause;
|
|
203
204
|
}
|
|
205
|
+
const body = decoded.body;
|
|
204
206
|
if (!response.ok) {
|
|
205
|
-
const error =
|
|
207
|
+
const error = decoded.httpError
|
|
208
|
+
?? apiError(body, "operation_failed", "Collection operation failed.", response.status);
|
|
206
209
|
const recovery = await retryBusy(error);
|
|
207
210
|
if (recovery.retried)
|
|
208
211
|
return recovery.result;
|
package/dist/connection.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type MdbaseAuthorizationTarget = {
|
|
|
17
17
|
};
|
|
18
18
|
export interface MdbaseAuthorizeOptions extends ConnectRequestOptions {
|
|
19
19
|
operations?: CollectionOperation[];
|
|
20
|
+
/** Keep a web application loaded while Connect owns the approval ceremony. */
|
|
21
|
+
presentation?: "redirect" | "popup";
|
|
20
22
|
/** Choose any compatible collection, or require one exact collection. */
|
|
21
23
|
target?: MdbaseAuthorizationTarget;
|
|
22
24
|
/** App-local location to restore after the authorization callback. */
|
|
@@ -28,6 +30,7 @@ export interface MdbaseAuthorizeOptions extends ConnectRequestOptions {
|
|
|
28
30
|
}
|
|
29
31
|
export interface MdbaseConnectionAuthorizeOptions extends ConnectRequestOptions {
|
|
30
32
|
operations?: CollectionOperation[];
|
|
33
|
+
presentation?: "redirect" | "popup";
|
|
31
34
|
returnTo?: string;
|
|
32
35
|
onDeviceCode?: (authorization: MdbaseDeviceAuthorization) => void;
|
|
33
36
|
openVerification?: (authorization: MdbaseDeviceAuthorization) => void | Promise<void>;
|
|
@@ -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
|
@@ -24,7 +24,20 @@ export interface StoredAuthorization {
|
|
|
24
24
|
authorizationId?: string;
|
|
25
25
|
applicationAgreementPublicKey?: string;
|
|
26
26
|
applicationSigningPublicKey?: string;
|
|
27
|
+
presentation?: "popup";
|
|
27
28
|
}
|
|
29
|
+
export type StoredAuthorizationCompletion = {
|
|
30
|
+
version: 1;
|
|
31
|
+
status: "connected";
|
|
32
|
+
collectionId: string;
|
|
33
|
+
returnTo?: string;
|
|
34
|
+
} | {
|
|
35
|
+
version: 1;
|
|
36
|
+
status: "failed";
|
|
37
|
+
code: string;
|
|
38
|
+
message: string;
|
|
39
|
+
returnTo?: string;
|
|
40
|
+
};
|
|
28
41
|
export interface StoredToken {
|
|
29
42
|
version: 1;
|
|
30
43
|
accessToken: string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { MdbaseAuthorizeOptions, MdbaseAuthorizationOutcome, MdbaseAuthorizationResult, MdbaseConnection } from "./connection.js";
|
|
3
|
+
import type { GrantKeyStore } from "./crypto.js";
|
|
4
|
+
import type { Application, StoredAuthorization } from "./internal-types.js";
|
|
5
|
+
export declare function prepareAuthorizationWindow(input: {
|
|
6
|
+
options: MdbaseAuthorizeOptions;
|
|
7
|
+
portableDeclared: boolean;
|
|
8
|
+
navigate?: (url: string) => void | Promise<void>;
|
|
9
|
+
register(signal: AbortSignal): Promise<Application>;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
application: Application;
|
|
12
|
+
popup: Window | null;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function waitForPopupAuthorization<Frontmatter extends JsonObject>(input: {
|
|
15
|
+
storage: Storage;
|
|
16
|
+
storagePrefix: string;
|
|
17
|
+
state: string;
|
|
18
|
+
pending: StoredAuthorization;
|
|
19
|
+
popup: Window;
|
|
20
|
+
signal: AbortSignal;
|
|
21
|
+
keyStore: GrantKeyStore;
|
|
22
|
+
connection(collectionId: string): MdbaseConnection<Frontmatter> | null;
|
|
23
|
+
}): Promise<MdbaseAuthorizationOutcome<Frontmatter>>;
|
|
24
|
+
export declare function publishPopupAuthorizationCompletion<Frontmatter extends JsonObject>(input: {
|
|
25
|
+
storage: Storage;
|
|
26
|
+
storagePrefix: string;
|
|
27
|
+
state: string;
|
|
28
|
+
pending: StoredAuthorization | null;
|
|
29
|
+
popupAttempt: boolean;
|
|
30
|
+
completion: Promise<MdbaseAuthorizationResult<Frontmatter>>;
|
|
31
|
+
}): Promise<MdbaseAuthorizationResult<Frontmatter>>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { abortableDelay } from "./async.js";
|
|
2
|
+
import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
|
|
3
|
+
import { parseStored } from "./runtime-utils.js";
|
|
4
|
+
export async function prepareAuthorizationWindow(input) {
|
|
5
|
+
if (typeof location === "undefined" && !input.navigate && !input.options.openVerification) {
|
|
6
|
+
throw connectError("browser_required", "Authorization navigation requires a browser environment.");
|
|
7
|
+
}
|
|
8
|
+
const popupRequested = input.options.presentation === "popup" || input.portableDeclared;
|
|
9
|
+
const popup = popupRequested
|
|
10
|
+
&& !input.options.openVerification
|
|
11
|
+
&& !input.navigate
|
|
12
|
+
&& typeof window !== "undefined"
|
|
13
|
+
? window.open("", "mdbase-connect-authorization", "popup,width=620,height=760")
|
|
14
|
+
: null;
|
|
15
|
+
try {
|
|
16
|
+
return {
|
|
17
|
+
application: await input.register(input.options.signal),
|
|
18
|
+
popup
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
popup?.close();
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function waitForPopupAuthorization(input) {
|
|
27
|
+
const completionKey = authorizationCompletionKey(input.storagePrefix, input.state);
|
|
28
|
+
try {
|
|
29
|
+
while (true) {
|
|
30
|
+
const completion = parseStored(input.storage.getItem(completionKey));
|
|
31
|
+
if (completion?.version === 1) {
|
|
32
|
+
input.storage.removeItem(completionKey);
|
|
33
|
+
if (completion.status === "failed") {
|
|
34
|
+
throw serverConnectError(completion.code, completion.message, {
|
|
35
|
+
details: completion.returnTo ? { return_to: completion.returnTo } : undefined
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const connection = input.connection(completion.collectionId);
|
|
39
|
+
if (!connection) {
|
|
40
|
+
throw connectError("invalid_callback", "Authorization completed without a usable saved connection.");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
kind: "connected",
|
|
44
|
+
connection,
|
|
45
|
+
...(completion.returnTo ? { returnTo: completion.returnTo } : {})
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Cross-origin opener isolation may report a live Connect window as
|
|
49
|
+
// closed. Shared callback state, abort, or the request budget owns exit.
|
|
50
|
+
await abortableDelay(200, input.signal);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
input.popup.close();
|
|
55
|
+
input.storage.removeItem(completionKey);
|
|
56
|
+
const pendingKey = `${input.storagePrefix}:pending:${input.state}`;
|
|
57
|
+
const outstanding = parseStored(input.storage.getItem(pendingKey));
|
|
58
|
+
if (outstanding?.state === input.state) {
|
|
59
|
+
input.storage.removeItem(pendingKey);
|
|
60
|
+
if (input.pending.keyHandle)
|
|
61
|
+
await input.keyStore.delete(input.pending.keyHandle);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function publishPopupAuthorizationCompletion(input) {
|
|
66
|
+
return input.completion.then((result) => {
|
|
67
|
+
if (input.popupAttempt)
|
|
68
|
+
writeCompletion(input, {
|
|
69
|
+
version: 1,
|
|
70
|
+
status: "connected",
|
|
71
|
+
collectionId: result.connection.collectionId,
|
|
72
|
+
...(result.returnTo ? { returnTo: result.returnTo } : {})
|
|
73
|
+
});
|
|
74
|
+
if (input.popupAttempt)
|
|
75
|
+
closeCallbackWindow();
|
|
76
|
+
return result;
|
|
77
|
+
}).catch((error) => {
|
|
78
|
+
if (input.popupAttempt)
|
|
79
|
+
writeCompletion(input, {
|
|
80
|
+
version: 1,
|
|
81
|
+
status: "failed",
|
|
82
|
+
code: error instanceof MdbaseConnectError ? error.code : "invalid_callback",
|
|
83
|
+
message: error instanceof Error ? error.message : "Authorization could not be completed.",
|
|
84
|
+
...(input.pending?.returnTo ? { returnTo: input.pending.returnTo } : {})
|
|
85
|
+
});
|
|
86
|
+
if (input.popupAttempt)
|
|
87
|
+
closeCallbackWindow();
|
|
88
|
+
throw error;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function writeCompletion(input, completion) {
|
|
92
|
+
input.storage.setItem(authorizationCompletionKey(input.storagePrefix, input.state), JSON.stringify(completion));
|
|
93
|
+
}
|
|
94
|
+
function authorizationCompletionKey(storagePrefix, state) {
|
|
95
|
+
return `${storagePrefix}:completion:${state}`;
|
|
96
|
+
}
|
|
97
|
+
function closeCallbackWindow() {
|
|
98
|
+
if (typeof window !== "undefined" && typeof window.close === "function")
|
|
99
|
+
window.close();
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/connect",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.84",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mdbase-dev/connect-protocol": "0.1.0-beta.
|
|
36
|
+
"@mdbase-dev/connect-protocol": "0.1.0-beta.84"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"esbuild": "^0.28.1",
|