@flow-industries/id 0.26.5 → 0.27.0
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/contracts/v1/sdk-exports.json +85 -0
- package/dist/sdk/client/github-connector.d.ts +19 -0
- package/dist/sdk/client/github-connector.js +128 -0
- package/dist/sdk/client/index.d.ts +3 -0
- package/dist/sdk/client/index.js +2 -0
- package/dist/sdk/client/oauth.d.ts +9 -0
- package/dist/sdk/client/oauth.js +68 -0
- package/dist/sdk/github/operations.d.ts +909 -0
- package/dist/sdk/github/operations.js +543 -0
- package/dist/sdk/github/scopes.d.ts +31 -0
- package/dist/sdk/github/scopes.js +70 -0
- package/dist/sdk/oauth/stores.d.ts +3 -0
- package/dist/sdk/oauth/stores.js +4 -0
- package/dist/sdk/profile.css +1 -1
- package/dist/sdk/types/auth.d.ts +3 -0
- package/dist/sdk/types/github-connector.d.ts +88 -0
- package/dist/sdk/types/github-connector.js +0 -0
- package/dist/sdk/types/index.d.ts +3 -1
- package/dist/sdk/types/landing.d.ts +20 -0
- package/dist/sdk/types/oauth.d.ts +64 -0
- package/dist/sdk/types/oauth.js +0 -0
- package/package.json +1 -1
|
@@ -197,6 +197,11 @@
|
|
|
197
197
|
"type": false,
|
|
198
198
|
"value": true
|
|
199
199
|
},
|
|
200
|
+
{
|
|
201
|
+
"name": "createGitHubConnector",
|
|
202
|
+
"type": false,
|
|
203
|
+
"value": true
|
|
204
|
+
},
|
|
200
205
|
{
|
|
201
206
|
"name": "createNoteTransport",
|
|
202
207
|
"type": false,
|
|
@@ -367,6 +372,86 @@
|
|
|
367
372
|
"type": false,
|
|
368
373
|
"value": true
|
|
369
374
|
},
|
|
375
|
+
{
|
|
376
|
+
"name": "GITHUB_BASELINE_SCOPES",
|
|
377
|
+
"type": false,
|
|
378
|
+
"value": true
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"name": "GITHUB_REQUESTABLE_SCOPES",
|
|
382
|
+
"type": false,
|
|
383
|
+
"value": true
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"name": "GITHUB_SCOPE_DESCRIPTIONS",
|
|
387
|
+
"type": false,
|
|
388
|
+
"value": true
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"name": "GitHubConnector",
|
|
392
|
+
"type": true,
|
|
393
|
+
"value": false
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"name": "GitHubConnectorCallOptions",
|
|
397
|
+
"type": true,
|
|
398
|
+
"value": false
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"name": "GitHubConnectorError",
|
|
402
|
+
"type": true,
|
|
403
|
+
"value": true
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"name": "GitHubConnectorErrorCode",
|
|
407
|
+
"type": true,
|
|
408
|
+
"value": false
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"name": "GitHubConnectorFailure",
|
|
412
|
+
"type": true,
|
|
413
|
+
"value": false
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
"name": "GitHubConnectorOptions",
|
|
417
|
+
"type": true,
|
|
418
|
+
"value": false
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"name": "GitHubConnectorResult",
|
|
422
|
+
"type": true,
|
|
423
|
+
"value": false
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"name": "githubConsentUrl",
|
|
427
|
+
"type": false,
|
|
428
|
+
"value": true
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"name": "GitHubOperationData",
|
|
432
|
+
"type": true,
|
|
433
|
+
"value": false
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"name": "GitHubOperationId",
|
|
437
|
+
"type": true,
|
|
438
|
+
"value": false
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"name": "GitHubOperationInput",
|
|
442
|
+
"type": true,
|
|
443
|
+
"value": false
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"name": "GitHubScope",
|
|
447
|
+
"type": true,
|
|
448
|
+
"value": false
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"name": "hasGitHubScope",
|
|
452
|
+
"type": false,
|
|
453
|
+
"value": true
|
|
454
|
+
},
|
|
370
455
|
{
|
|
371
456
|
"name": "hitLabel",
|
|
372
457
|
"type": false,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type GitHubScope } from "../github/scopes";
|
|
2
|
+
import type { GitHubConnector, GitHubConnectorErrorCode, GitHubConnectorFailure, GitHubConnectorOptions } from "../types/github-connector";
|
|
3
|
+
/** A refused connector call. `consentUrl` is set when the user can fix it in
|
|
4
|
+
* Flow ID: connecting GitHub, reconnecting, or granting `requiredScopes`. */
|
|
5
|
+
export declare class GitHubConnectorError extends Error {
|
|
6
|
+
readonly status: number;
|
|
7
|
+
readonly code: GitHubConnectorErrorCode;
|
|
8
|
+
readonly requiredScopes: GitHubScope[];
|
|
9
|
+
readonly consentUrl?: string;
|
|
10
|
+
readonly retryAfter?: number;
|
|
11
|
+
readonly fields: string[];
|
|
12
|
+
constructor(status: number, failure: GitHubConnectorFailure, options?: ErrorOptions);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Calls GitHub through Flow ID as the signed-in user. Flow ID holds the
|
|
16
|
+
* user's GitHub grant and answers with projected data, so the app never sees
|
|
17
|
+
* a GitHub token. The app's origin must be on Flow ID's connector allow-list.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createGitHubConnector(host: string, getToken: () => Promise<string | null>, getUserId: () => string | null, options?: GitHubConnectorOptions): GitHubConnector;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { githubConsentUrl, isGitHubScope, } from "../github/scopes";
|
|
3
|
+
/** A refused connector call. `consentUrl` is set when the user can fix it in
|
|
4
|
+
* Flow ID: connecting GitHub, reconnecting, or granting `requiredScopes`. */
|
|
5
|
+
export class GitHubConnectorError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
code;
|
|
8
|
+
requiredScopes;
|
|
9
|
+
consentUrl;
|
|
10
|
+
retryAfter;
|
|
11
|
+
fields;
|
|
12
|
+
constructor(status, failure, options) {
|
|
13
|
+
super(failure.code, options);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.name = "GitHubConnectorError";
|
|
16
|
+
this.code = failure.code;
|
|
17
|
+
this.requiredScopes = failure.requiredScopes ?? [];
|
|
18
|
+
this.consentUrl = failure.consentUrl;
|
|
19
|
+
this.retryAfter = failure.retryAfter;
|
|
20
|
+
this.fields = failure.fields ?? [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const failureSchema = z.object({
|
|
24
|
+
code: z.string().max(64),
|
|
25
|
+
requiredScopes: z.array(z.string().max(64)).max(16).optional(),
|
|
26
|
+
consentUrl: z.string().max(2048).optional(),
|
|
27
|
+
retryAfter: z.number().int().min(0).max(3600).optional(),
|
|
28
|
+
fields: z.array(z.string().max(256)).max(20).optional(),
|
|
29
|
+
});
|
|
30
|
+
const knownCodes = new Set([
|
|
31
|
+
"GITHUB_CONNECTOR_DISABLED",
|
|
32
|
+
"ORIGIN_DENIED",
|
|
33
|
+
"SIGN_IN_REQUIRED",
|
|
34
|
+
"ACCOUNT_CHANGED",
|
|
35
|
+
"RATE_LIMITED",
|
|
36
|
+
"UNKNOWN_OPERATION",
|
|
37
|
+
"INVALID_BODY",
|
|
38
|
+
"TOO_LARGE",
|
|
39
|
+
"GITHUB_NOT_CONNECTED",
|
|
40
|
+
"GITHUB_RECONNECT_REQUIRED",
|
|
41
|
+
"GITHUB_SCOPE_REQUIRED",
|
|
42
|
+
"GITHUB_RATE_LIMITED",
|
|
43
|
+
"GITHUB_NOT_FOUND",
|
|
44
|
+
"GITHUB_FORBIDDEN",
|
|
45
|
+
"GITHUB_CONFLICT",
|
|
46
|
+
"GITHUB_VALIDATION_FAILED",
|
|
47
|
+
"GITHUB_UNAVAILABLE",
|
|
48
|
+
"GITHUB_INVALID_RESPONSE",
|
|
49
|
+
]);
|
|
50
|
+
function isKnownCode(code) {
|
|
51
|
+
return knownCodes.has(code);
|
|
52
|
+
}
|
|
53
|
+
const successSchema = z.object({
|
|
54
|
+
data: z.unknown(),
|
|
55
|
+
nextPage: z.number().int().min(1).max(1000).nullable(),
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Calls GitHub through Flow ID as the signed-in user. Flow ID holds the
|
|
59
|
+
* user's GitHub grant and answers with projected data, so the app never sees
|
|
60
|
+
* a GitHub token. The app's origin must be on Flow ID's connector allow-list.
|
|
61
|
+
*/
|
|
62
|
+
export function createGitHubConnector(host, getToken, getUserId, options = {}) {
|
|
63
|
+
// Never call `fetch` as a method of another object: Chrome throws
|
|
64
|
+
// "Illegal invocation" for any receiver other than the window.
|
|
65
|
+
const send = options.fetch ??
|
|
66
|
+
((input, init) => globalThis.fetch(input, init));
|
|
67
|
+
const consentUrl = (scopes = []) => githubConsentUrl(host, scopes);
|
|
68
|
+
return {
|
|
69
|
+
async call(operation, input, callOptions = {}) {
|
|
70
|
+
const userId = getUserId();
|
|
71
|
+
const token = userId ? await getToken() : null;
|
|
72
|
+
if (!userId || !token)
|
|
73
|
+
throw new GitHubConnectorError(401, { code: "SIGN_IN_REQUIRED" });
|
|
74
|
+
let response;
|
|
75
|
+
try {
|
|
76
|
+
response = await send(`${host}/api/connectors/github/${encodeURIComponent(operation)}`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Bearer ${token}`,
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"X-Flow-User-Id": userId,
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify(input),
|
|
84
|
+
signal: callOptions.signal,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
callOptions.signal?.throwIfAborted();
|
|
89
|
+
throw new GitHubConnectorError(0, { code: "CONNECTOR_UNAVAILABLE" }, {
|
|
90
|
+
cause: error,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const payload = await response.json().catch(() => null);
|
|
94
|
+
if (getUserId() !== userId)
|
|
95
|
+
throw new GitHubConnectorError(409, { code: "ACCOUNT_CHANGED" });
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const failure = failureSchema.safeParse(payload);
|
|
98
|
+
throw new GitHubConnectorError(response.status, failure.success && isKnownCode(failure.data.code)
|
|
99
|
+
? {
|
|
100
|
+
...failure.data,
|
|
101
|
+
code: failure.data.code,
|
|
102
|
+
requiredScopes: failure.data.requiredScopes?.filter(isGitHubScope),
|
|
103
|
+
}
|
|
104
|
+
: { code: "CONNECTOR_UNAVAILABLE" });
|
|
105
|
+
}
|
|
106
|
+
const success = successSchema.safeParse(payload);
|
|
107
|
+
if (!success.success)
|
|
108
|
+
throw new GitHubConnectorError(502, {
|
|
109
|
+
code: "GITHUB_INVALID_RESPONSE",
|
|
110
|
+
});
|
|
111
|
+
/* SAFETY: Flow ID validated `data` against this operation's response schema
|
|
112
|
+
before answering; the SDK names that contract rather than re-validating it. */
|
|
113
|
+
return success.data;
|
|
114
|
+
},
|
|
115
|
+
consentUrl,
|
|
116
|
+
requestScopes(scopes = []) {
|
|
117
|
+
if (!("window" in globalThis))
|
|
118
|
+
return null;
|
|
119
|
+
// Open blank first so the opener link is cut before Flow ID loads.
|
|
120
|
+
const opened = window.open("about:blank", "_blank");
|
|
121
|
+
if (!opened)
|
|
122
|
+
return null;
|
|
123
|
+
opened.opener = null;
|
|
124
|
+
opened.location.href = consentUrl(scopes);
|
|
125
|
+
return opened;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export { GITHUB_BASELINE_SCOPES, GITHUB_REQUESTABLE_SCOPES, GITHUB_SCOPE_DESCRIPTIONS, githubConsentUrl, hasGitHubScope, } from "../github/scopes";
|
|
1
2
|
export { defaultIdHost, isLocalHostname } from "../id-host";
|
|
2
3
|
export type { AccessKeyOptions, ActionCatalogEntry, ActionCatalogResponse, ActionCurve, ActionEventRequest, ActionEventResponse, ActionSubjectRef, ActionSurface, ActionsApi, ActionTransitionKind, ActiveAction, ActiveActionResponse, ActiveSessionConflict, AdditionalSession, Address, AtprotoApi, AtprotoResolution, AtprotoResolutionFailure, AtprotoResolveOptions, BrowserAccount, BrowserAccounts, CatalogFieldRef, ClockAnchor, ConnectCapabilities, ConnectResponse, CreateFlowOptions, CreateStudySubjectInput, CurrentAction, DialogHost, FinishActionRequest, FinishActionResponse, FinishedSummary, Flow, FlowCredential, FlowSessionState, FlowState, FlowUser, FlowWidgetHandle, FlowWidgetName, FocusApi, FocusPhase, FocusSessionEvent, FocusSessionState, ListStudySessionsOptions, ListStudySubjectsOptions, LoginOptions, MarkNotificationsReadInput, MethodName, MountFlowWidgetOptions, MountProfileOptions, NotificationCounts, NotificationEntry, NotificationPage, NotificationReadResult, NotificationRefusal, OpenProfileOptions, PasskeyPrfOptions, ProfileButtonHandle, ProfilePosition, RoomMemberEntry, Session, StartActionInput, StartActionResponse, StartActionResult, StartFocusRequest, StudyApi, StudyCatalog, StudyCategory, StudyCategoryTotals, StudyField, StudyRange, StudySearchHit, StudySessionEntry, StudySessionPage, StudySubject, StudySubjectList, StudySubjectRefusal, StudySubjectRemovalResponse, StudySubjectResponse, StudySubjectTotals, StudySummary, SwitchAccountOptions, UpdateStudySubjectInput, UserActionSurface, XpEstimateContext, XpMode, } from "../types";
|
|
4
|
+
export type { GitHubConnector, GitHubConnectorCallOptions, GitHubConnectorErrorCode, GitHubConnectorFailure, GitHubConnectorOptions, GitHubConnectorResult, GitHubOperationData, GitHubOperationId, GitHubOperationInput, GitHubScope, } from "../types/github-connector";
|
|
3
5
|
export type { NoteBlob, NoteCollection, NoteOperation, NoteRecord, NoteResponses, NoteStoredRecord, NoteVaultHeader, } from "../types/note-transport";
|
|
4
6
|
export { consistencyCurve, estimateActionXp, FLOW_SCORE_MAX, flowMultiplier, } from "../xp/curve";
|
|
5
7
|
export { ActionsRequestError, createActionsApi, isActiveSessionConflict, } from "./actions";
|
|
@@ -8,6 +10,7 @@ export { createFlow, getFlow, requireFlow, resetFlow } from "./create-flow";
|
|
|
8
10
|
export { createDialogHost } from "./dialog-host";
|
|
9
11
|
export { createFlowWidget } from "./flow-widget";
|
|
10
12
|
export { actionHeadline, anchorFor, catalogFieldIndex, createFocusApi, elapsedSeconds, fieldsCoveredBySubjects, focusSessionReducer, formatElapsed, hitLabel, INITIAL_FOCUS_STATE, MEDITATION_SOURCE, matchScore, normalizeSearchText, REANCHOR_TOLERANCE_SECONDS, recentSubjects, STUDY_SOURCE, searchStudy, shouldReanchor, } from "./focus";
|
|
13
|
+
export { createGitHubConnector, GitHubConnectorError, } from "./github-connector";
|
|
11
14
|
export { METHODS } from "./methods";
|
|
12
15
|
export { createNoteTransport, NoteTransportError } from "./note-transport";
|
|
13
16
|
export { closeProfile, openProfile } from "./open-profile";
|
package/dist/sdk/client/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { GITHUB_BASELINE_SCOPES, GITHUB_REQUESTABLE_SCOPES, GITHUB_SCOPE_DESCRIPTIONS, githubConsentUrl, hasGitHubScope, } from "../github/scopes";
|
|
1
2
|
export { defaultIdHost, isLocalHostname } from "../id-host";
|
|
2
3
|
export { consistencyCurve, estimateActionXp, FLOW_SCORE_MAX, flowMultiplier, } from "../xp/curve";
|
|
3
4
|
export { ActionsRequestError, createActionsApi, isActiveSessionConflict, } from "./actions";
|
|
@@ -6,6 +7,7 @@ export { createFlow, getFlow, requireFlow, resetFlow } from "./create-flow";
|
|
|
6
7
|
export { createDialogHost } from "./dialog-host";
|
|
7
8
|
export { createFlowWidget } from "./flow-widget";
|
|
8
9
|
export { actionHeadline, anchorFor, catalogFieldIndex, createFocusApi, elapsedSeconds, fieldsCoveredBySubjects, focusSessionReducer, formatElapsed, hitLabel, INITIAL_FOCUS_STATE, MEDITATION_SOURCE, matchScore, normalizeSearchText, REANCHOR_TOLERANCE_SECONDS, recentSubjects, STUDY_SOURCE, searchStudy, shouldReanchor, } from "./focus";
|
|
10
|
+
export { createGitHubConnector, GitHubConnectorError, } from "./github-connector";
|
|
9
11
|
export { METHODS } from "./methods";
|
|
10
12
|
export { createNoteTransport, NoteTransportError } from "./note-transport";
|
|
11
13
|
export { closeProfile, openProfile } from "./open-profile";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OAuthCompletion, OAuthStartRequest, OAuthStatus } from "../types";
|
|
2
|
+
/** The provider a hosted-page or account-page URL is returning from, if any. */
|
|
3
|
+
export declare function oauthReturnProvider(search: string): string | null;
|
|
4
|
+
export declare function getOAuthStatus(provider: string, signal?: AbortSignal): Promise<OAuthStatus>;
|
|
5
|
+
export declare function unlinkOAuth(provider: string, expectedUserId: string): Promise<void>;
|
|
6
|
+
/** Starts authorization and leaves the page for the provider; resolves only on failure paths. */
|
|
7
|
+
export declare function startOAuth(provider: string, request: OAuthStartRequest, signal: AbortSignal): Promise<void>;
|
|
8
|
+
/** Finishes the attempt this tab started; memoized so a re-render cannot spend the token twice. */
|
|
9
|
+
export declare function completeOAuth(provider: string): Promise<OAuthCompletion>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** The provider a hosted-page or account-page URL is returning from, if any. */
|
|
2
|
+
export function oauthReturnProvider(search) {
|
|
3
|
+
return new URLSearchParams(search).get("oauth");
|
|
4
|
+
}
|
|
5
|
+
const endpoint = (provider, action) => `/api/auth/oauth/${encodeURIComponent(provider)}/${action}`;
|
|
6
|
+
const completionKey = (provider) => `flow.oauth.completion.${provider}`;
|
|
7
|
+
async function readResponse(response) {
|
|
8
|
+
const body = await response.json().catch(() => ({}));
|
|
9
|
+
if (!response.ok)
|
|
10
|
+
throw new Error(body.message ?? body.error ?? "Could not connect your account.");
|
|
11
|
+
// SAFETY: the caller names the payload returned by the same-origin Flow endpoint.
|
|
12
|
+
return body;
|
|
13
|
+
}
|
|
14
|
+
export async function getOAuthStatus(provider, signal) {
|
|
15
|
+
return readResponse(await fetch(endpoint(provider, "status"), {
|
|
16
|
+
credentials: "include",
|
|
17
|
+
signal,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
export async function unlinkOAuth(provider, expectedUserId) {
|
|
21
|
+
await readResponse(await fetch(endpoint(provider, "unlink"), {
|
|
22
|
+
method: "POST",
|
|
23
|
+
credentials: "include",
|
|
24
|
+
headers: { "Content-Type": "application/json" },
|
|
25
|
+
body: JSON.stringify({ expectedUserId }),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/** Starts authorization and leaves the page for the provider; resolves only on failure paths. */
|
|
29
|
+
export async function startOAuth(provider, request, signal) {
|
|
30
|
+
signal.throwIfAborted();
|
|
31
|
+
const result = await readResponse(await fetch(endpoint(provider, "start"), {
|
|
32
|
+
method: "POST",
|
|
33
|
+
credentials: "include",
|
|
34
|
+
headers: { "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify(request),
|
|
36
|
+
signal,
|
|
37
|
+
}));
|
|
38
|
+
signal.throwIfAborted();
|
|
39
|
+
sessionStorage.setItem(completionKey(provider), result.completionToken);
|
|
40
|
+
window.location.assign(result.url);
|
|
41
|
+
}
|
|
42
|
+
const completions = new Map();
|
|
43
|
+
/** Finishes the attempt this tab started; memoized so a re-render cannot spend the token twice. */
|
|
44
|
+
export function completeOAuth(provider) {
|
|
45
|
+
const existing = completions.get(provider);
|
|
46
|
+
if (existing)
|
|
47
|
+
return existing;
|
|
48
|
+
const completionToken = sessionStorage.getItem(completionKey(provider));
|
|
49
|
+
if (!completionToken)
|
|
50
|
+
return Promise.reject(new Error("Sign-in expired. Please try again."));
|
|
51
|
+
const completion = fetch(endpoint(provider, "complete"), {
|
|
52
|
+
method: "POST",
|
|
53
|
+
credentials: "include",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({ completionToken }),
|
|
56
|
+
})
|
|
57
|
+
.then(async (response) => {
|
|
58
|
+
if (response.status === 202)
|
|
59
|
+
throw new Error("Authorization is still finishing. Please try again.");
|
|
60
|
+
return readResponse(response);
|
|
61
|
+
})
|
|
62
|
+
.then((result) => {
|
|
63
|
+
sessionStorage.removeItem(completionKey(provider));
|
|
64
|
+
return result;
|
|
65
|
+
});
|
|
66
|
+
completions.set(provider, completion);
|
|
67
|
+
return completion;
|
|
68
|
+
}
|