@apifuse/connector-sdk 2.0.0-beta.1
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 +44 -0
- package/bin/apifuse-check.ts +408 -0
- package/bin/apifuse-dev.ts +222 -0
- package/bin/apifuse-init.ts +390 -0
- package/bin/apifuse-perf.ts +1101 -0
- package/bin/apifuse-record.ts +446 -0
- package/bin/apifuse-test.ts +688 -0
- package/bin/apifuse.ts +51 -0
- package/package.json +64 -0
- package/src/__tests__/auth.test.ts +396 -0
- package/src/__tests__/browser-auth.test.ts +180 -0
- package/src/__tests__/browser.test.ts +632 -0
- package/src/__tests__/connectors-yaml.test.ts +135 -0
- package/src/__tests__/define.test.ts +225 -0
- package/src/__tests__/errors.test.ts +69 -0
- package/src/__tests__/executor.test.ts +214 -0
- package/src/__tests__/http.test.ts +238 -0
- package/src/__tests__/insights.test.ts +210 -0
- package/src/__tests__/instrumentation.test.ts +290 -0
- package/src/__tests__/otlp.test.ts +141 -0
- package/src/__tests__/perf.test.ts +60 -0
- package/src/__tests__/proxy.test.ts +359 -0
- package/src/__tests__/recipes.test.ts +36 -0
- package/src/__tests__/serve.test.ts +233 -0
- package/src/__tests__/session.test.ts +231 -0
- package/src/__tests__/state.test.ts +100 -0
- package/src/__tests__/stealth.test.ts +57 -0
- package/src/__tests__/testing.test.ts +97 -0
- package/src/__tests__/tls.test.ts +345 -0
- package/src/__tests__/types.test.ts +142 -0
- package/src/__tests__/utils.test.ts +62 -0
- package/src/__tests__/waterfall.test.ts +270 -0
- package/src/config/connectors-yaml.ts +373 -0
- package/src/config/loader.ts +122 -0
- package/src/define.ts +137 -0
- package/src/dev.ts +38 -0
- package/src/errors.ts +68 -0
- package/src/index.test.ts +1 -0
- package/src/index.ts +100 -0
- package/src/protocol.ts +183 -0
- package/src/recipes/gov-api.ts +97 -0
- package/src/recipes/rest-api.ts +152 -0
- package/src/runtime/auth.ts +245 -0
- package/src/runtime/browser.ts +724 -0
- package/src/runtime/connector.ts +20 -0
- package/src/runtime/executor.ts +51 -0
- package/src/runtime/http.ts +248 -0
- package/src/runtime/insights.ts +456 -0
- package/src/runtime/instrumentation.ts +424 -0
- package/src/runtime/otlp.ts +171 -0
- package/src/runtime/perf.ts +73 -0
- package/src/runtime/session.ts +573 -0
- package/src/runtime/state.ts +124 -0
- package/src/runtime/tls.ts +410 -0
- package/src/runtime/trace.ts +261 -0
- package/src/runtime/waterfall.ts +245 -0
- package/src/serve.ts +665 -0
- package/src/stealth/profiles.ts +391 -0
- package/src/testing/helpers.ts +144 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/run.ts +88 -0
- package/src/types/playwright-stealth.d.ts +9 -0
- package/src/types.ts +243 -0
- package/src/utils/date.ts +163 -0
- package/src/utils/parse.ts +66 -0
- package/src/utils/text.ts +20 -0
- package/src/utils/transform.ts +62 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { unwrapEnvelope } from "../utils/parse";
|
|
2
|
+
|
|
3
|
+
function getResultCode(raw: unknown): string | undefined {
|
|
4
|
+
if (!raw || typeof raw !== "object") {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const record = raw as Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
if (typeof record.resultCode === "string") {
|
|
11
|
+
return record.resultCode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const response = record.response as Record<string, unknown> | undefined;
|
|
15
|
+
const header = response?.header as Record<string, unknown> | undefined;
|
|
16
|
+
|
|
17
|
+
return typeof header?.resultCode === "string" ? header.resultCode : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check Korean government API result code
|
|
22
|
+
* Returns true if resultCode matches successCodes (default: ['00', '000', '0000'])
|
|
23
|
+
*/
|
|
24
|
+
export function checkResultCode(
|
|
25
|
+
raw: unknown,
|
|
26
|
+
successCodes: string[] = ["00", "000", "0000"],
|
|
27
|
+
): boolean {
|
|
28
|
+
const code = getResultCode(raw);
|
|
29
|
+
return code ? successCodes.includes(code) : false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Replace placeholder values with null
|
|
34
|
+
* e.g., nullIfPlaceholder('해당없음', ['해당없음', '-', '']) → null
|
|
35
|
+
*/
|
|
36
|
+
export function nullIfPlaceholder(
|
|
37
|
+
v: unknown,
|
|
38
|
+
patterns: string[],
|
|
39
|
+
): unknown | null {
|
|
40
|
+
if (v === null || v === undefined) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof v !== "string") {
|
|
45
|
+
return v;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const normalized = v.trim();
|
|
49
|
+
return patterns.some((pattern) => normalized === pattern.trim()) ? null : v;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Unwrap Korean government API envelope
|
|
54
|
+
* Handles: {response: {body: {items: {item: ...}}}} nested structure
|
|
55
|
+
*/
|
|
56
|
+
export function unwrapGovEnvelope(raw: unknown): unknown {
|
|
57
|
+
const item = unwrapEnvelope(raw, "response.body.items.item");
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(item)) {
|
|
60
|
+
return item;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (item !== undefined && item !== null) {
|
|
64
|
+
return [item];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const items = unwrapEnvelope(raw, "response.body.items");
|
|
68
|
+
if (Array.isArray(items)) {
|
|
69
|
+
return items;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (items !== undefined && items !== null) {
|
|
73
|
+
return items;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return raw;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if Korean government API returned empty result
|
|
81
|
+
*/
|
|
82
|
+
export function isEmptyResult(raw: unknown): boolean {
|
|
83
|
+
if (getResultCode(raw) === "03") {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const items = unwrapGovEnvelope(raw);
|
|
88
|
+
if (Array.isArray(items)) {
|
|
89
|
+
return items.length === 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (items && typeof items === "object") {
|
|
93
|
+
return Object.keys(items as Record<string, unknown>).length === 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export interface PaginationInfo {
|
|
2
|
+
page: number;
|
|
3
|
+
perPage: number;
|
|
4
|
+
total: number;
|
|
5
|
+
totalPages: number;
|
|
6
|
+
hasNext: boolean;
|
|
7
|
+
hasPrev: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function toPositiveInteger(value: unknown): number | null {
|
|
11
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
12
|
+
return Math.trunc(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
16
|
+
const parsed = Number.parseInt(value, 10);
|
|
17
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readPath(raw: unknown, path: string): unknown {
|
|
24
|
+
if (!raw || typeof raw !== "object") {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let current: unknown = raw;
|
|
29
|
+
for (const segment of path.split(".")) {
|
|
30
|
+
if (!current || typeof current !== "object") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
current = (current as Record<string, unknown>)[segment];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return current;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract pagination info from various REST API response shapes.
|
|
42
|
+
* Handles: {page, per_page, total}, {meta: {pagination: {...}}}, {currentPage, totalCount, pageSize}
|
|
43
|
+
*/
|
|
44
|
+
export function extractPagination(raw: unknown): PaginationInfo | null {
|
|
45
|
+
const candidates = [
|
|
46
|
+
{
|
|
47
|
+
page: readPath(raw, "page"),
|
|
48
|
+
perPage: readPath(raw, "per_page"),
|
|
49
|
+
total: readPath(raw, "total"),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
page: readPath(raw, "meta.pagination.page"),
|
|
53
|
+
perPage: readPath(raw, "meta.pagination.per_page"),
|
|
54
|
+
total: readPath(raw, "meta.pagination.total"),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
page: readPath(raw, "meta.pagination.currentPage"),
|
|
58
|
+
perPage: readPath(raw, "meta.pagination.pageSize"),
|
|
59
|
+
total: readPath(raw, "meta.pagination.totalCount"),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
page: readPath(raw, "currentPage"),
|
|
63
|
+
perPage: readPath(raw, "pageSize"),
|
|
64
|
+
total: readPath(raw, "totalCount"),
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const candidate of candidates) {
|
|
69
|
+
const page = toPositiveInteger(candidate.page);
|
|
70
|
+
const perPage = toPositiveInteger(candidate.perPage);
|
|
71
|
+
const total = toPositiveInteger(candidate.total);
|
|
72
|
+
|
|
73
|
+
if (page === null || perPage === null || total === null) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const totalPages = perPage > 0 ? Math.ceil(total / perPage) : 0;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
page,
|
|
81
|
+
perPage,
|
|
82
|
+
total,
|
|
83
|
+
totalPages,
|
|
84
|
+
hasNext: page < totalPages,
|
|
85
|
+
hasPrev: page > 1,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function extractMessage(value: unknown): string | null {
|
|
93
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!value || typeof value !== "object") {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const record = value as Record<string, unknown>;
|
|
102
|
+
|
|
103
|
+
if (typeof record.message === "string" && record.message.trim() !== "") {
|
|
104
|
+
return record.message;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (typeof record.error === "string" && record.error.trim() !== "") {
|
|
108
|
+
return record.error;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Normalize error response to a standard format.
|
|
116
|
+
* Handles: {error: string}, {message: string}, {errors: []}, {error: {message: string}}
|
|
117
|
+
*/
|
|
118
|
+
export function normalizeErrorResponse(
|
|
119
|
+
raw: unknown,
|
|
120
|
+
): { message: string; code?: string } | null {
|
|
121
|
+
if (!raw || typeof raw !== "object") {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const record = raw as Record<string, unknown>;
|
|
126
|
+
|
|
127
|
+
const directMessage =
|
|
128
|
+
extractMessage(record.error) ?? extractMessage(record.message);
|
|
129
|
+
if (directMessage) {
|
|
130
|
+
const code =
|
|
131
|
+
typeof record.code === "string" && record.code.trim() !== ""
|
|
132
|
+
? record.code
|
|
133
|
+
: undefined;
|
|
134
|
+
return code ? { message: directMessage, code } : { message: directMessage };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const errors = record.errors;
|
|
138
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
139
|
+
for (const item of errors) {
|
|
140
|
+
const message = extractMessage(item);
|
|
141
|
+
if (message) {
|
|
142
|
+
const code =
|
|
143
|
+
typeof record.code === "string" && record.code.trim() !== ""
|
|
144
|
+
? record.code
|
|
145
|
+
: undefined;
|
|
146
|
+
return code ? { message, code } : { message };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { AuthError, TransportError } from "../errors";
|
|
2
|
+
import type {
|
|
3
|
+
AuthConfig,
|
|
4
|
+
AuthContext,
|
|
5
|
+
AuthField,
|
|
6
|
+
ConnectorContext,
|
|
7
|
+
SessionStore,
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
const AUTH_SESSION_KEY = "__auth__";
|
|
11
|
+
const REQUEST_FIELD_TIMEOUT_MS = 60_000;
|
|
12
|
+
|
|
13
|
+
function createAuthSessionMarker(
|
|
14
|
+
marker: Record<string, boolean | number>,
|
|
15
|
+
): string {
|
|
16
|
+
return JSON.stringify(marker);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type PendingFieldRequest = {
|
|
20
|
+
promise: Promise<string>;
|
|
21
|
+
resolve: (value: string) => void;
|
|
22
|
+
reject: (error: AuthError) => void;
|
|
23
|
+
timeoutId: ReturnType<typeof setTimeout>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface AuthManager {
|
|
27
|
+
exchange(
|
|
28
|
+
ctx: ConnectorContext,
|
|
29
|
+
credentials: Record<string, string>,
|
|
30
|
+
): Promise<void>;
|
|
31
|
+
refresh(ctx: ConnectorContext): Promise<void>;
|
|
32
|
+
disconnect(ctx: ConnectorContext): Promise<void>;
|
|
33
|
+
wrapWithAutoRefresh<T>(
|
|
34
|
+
ctx: ConnectorContext,
|
|
35
|
+
operation: () => Promise<T>,
|
|
36
|
+
): Promise<T>;
|
|
37
|
+
getFields(): AuthField[];
|
|
38
|
+
createAuthContext(): AuthContext;
|
|
39
|
+
resolveField(name: string, value: string): void;
|
|
40
|
+
getPendingFields(): string[];
|
|
41
|
+
isAuthNone(): boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toAuthError(error: unknown): AuthError {
|
|
45
|
+
if (error instanceof AuthError) {
|
|
46
|
+
return error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
return new AuthError(error.message, { cause: error });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new AuthError("Auth exchange failed");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function is401or403(error: unknown): error is TransportError {
|
|
57
|
+
return (
|
|
58
|
+
error instanceof TransportError &&
|
|
59
|
+
(error.status === 401 || error.status === 403)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createAuthManager(
|
|
64
|
+
config: AuthConfig | undefined,
|
|
65
|
+
session: SessionStore,
|
|
66
|
+
): AuthManager {
|
|
67
|
+
const pendingFieldRequests = new Map<string, PendingFieldRequest>();
|
|
68
|
+
const resolvedFieldValues = new Map<string, string>();
|
|
69
|
+
let refreshPromise: Promise<void> | null = null;
|
|
70
|
+
|
|
71
|
+
function createTimedFieldRequest(name: string): Promise<string> {
|
|
72
|
+
const existingRequest = pendingFieldRequests.get(name);
|
|
73
|
+
if (existingRequest) {
|
|
74
|
+
return existingRequest.promise;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const preResolvedValue = resolvedFieldValues.get(name);
|
|
78
|
+
if (preResolvedValue !== undefined) {
|
|
79
|
+
resolvedFieldValues.delete(name);
|
|
80
|
+
return Promise.resolve(preResolvedValue);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let resolvePromise!: (value: string) => void;
|
|
84
|
+
let rejectPromise!: (error: AuthError) => void;
|
|
85
|
+
|
|
86
|
+
const promise = new Promise<string>((resolve, reject) => {
|
|
87
|
+
resolvePromise = resolve;
|
|
88
|
+
rejectPromise = reject;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const timeoutId = setTimeout(() => {
|
|
92
|
+
pendingFieldRequests.delete(name);
|
|
93
|
+
rejectPromise(new AuthError(`Auth field request timed out: ${name}`));
|
|
94
|
+
}, REQUEST_FIELD_TIMEOUT_MS);
|
|
95
|
+
|
|
96
|
+
pendingFieldRequests.set(name, {
|
|
97
|
+
promise,
|
|
98
|
+
resolve: (value) => {
|
|
99
|
+
clearTimeout(timeoutId);
|
|
100
|
+
pendingFieldRequests.delete(name);
|
|
101
|
+
resolvePromise(value);
|
|
102
|
+
},
|
|
103
|
+
reject: (error) => {
|
|
104
|
+
clearTimeout(timeoutId);
|
|
105
|
+
pendingFieldRequests.delete(name);
|
|
106
|
+
rejectPromise(error);
|
|
107
|
+
},
|
|
108
|
+
timeoutId,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return promise;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const authContext: AuthContext = {
|
|
115
|
+
requestField(name) {
|
|
116
|
+
return createTimedFieldRequest(name);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const manager: AuthManager = {
|
|
121
|
+
async exchange(ctx, credentials) {
|
|
122
|
+
if (!config || config.mode === "none") {
|
|
123
|
+
throw new AuthError("No auth configured");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!config.exchange) {
|
|
127
|
+
throw new AuthError("Auth exchange is not configured");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
ctx.auth = authContext;
|
|
131
|
+
|
|
132
|
+
await ctx.trace.span("auth.exchange", async () => {
|
|
133
|
+
try {
|
|
134
|
+
await config.exchange?.(ctx, credentials);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw toAuthError(error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await session.set(
|
|
140
|
+
AUTH_SESSION_KEY,
|
|
141
|
+
createAuthSessionMarker({
|
|
142
|
+
authenticated: true,
|
|
143
|
+
timestamp: Date.now(),
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
async refresh(ctx) {
|
|
149
|
+
if (refreshPromise) {
|
|
150
|
+
return refreshPromise;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!config?.refresh) {
|
|
154
|
+
throw new AuthError("No refresh configured");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const doRefresh = async () => {
|
|
158
|
+
await ctx.trace.span("auth.refresh", async () => {
|
|
159
|
+
try {
|
|
160
|
+
await config.refresh?.(ctx);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
throw toAuthError(error);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await session.set(
|
|
166
|
+
AUTH_SESSION_KEY,
|
|
167
|
+
createAuthSessionMarker({
|
|
168
|
+
authenticated: true,
|
|
169
|
+
refreshed: true,
|
|
170
|
+
timestamp: Date.now(),
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
refreshPromise = doRefresh().finally(() => {
|
|
177
|
+
refreshPromise = null;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return refreshPromise;
|
|
181
|
+
},
|
|
182
|
+
async disconnect(ctx) {
|
|
183
|
+
await ctx.trace.span("auth.disconnect", async () => {
|
|
184
|
+
let disconnectError: AuthError | null = null;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
if (config?.disconnect) {
|
|
188
|
+
await config.disconnect(ctx);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
disconnectError = toAuthError(error);
|
|
192
|
+
} finally {
|
|
193
|
+
await session.delete(AUTH_SESSION_KEY);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (disconnectError) {
|
|
197
|
+
throw disconnectError;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
async wrapWithAutoRefresh(ctx, operation) {
|
|
202
|
+
try {
|
|
203
|
+
return await operation();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (is401or403(error) && config?.refresh) {
|
|
206
|
+
const originalError = error;
|
|
207
|
+
await manager.refresh(ctx);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
return await operation();
|
|
211
|
+
} catch {
|
|
212
|
+
throw originalError;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
getFields() {
|
|
220
|
+
return config?.fields ?? [];
|
|
221
|
+
},
|
|
222
|
+
createAuthContext() {
|
|
223
|
+
return authContext;
|
|
224
|
+
},
|
|
225
|
+
resolveField(name, value) {
|
|
226
|
+
const pendingRequest = pendingFieldRequests.get(name);
|
|
227
|
+
|
|
228
|
+
if (!pendingRequest) {
|
|
229
|
+
resolvedFieldValues.set(name, value);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
clearTimeout(pendingRequest.timeoutId);
|
|
234
|
+
pendingRequest.resolve(value);
|
|
235
|
+
},
|
|
236
|
+
getPendingFields() {
|
|
237
|
+
return Array.from(pendingFieldRequests.keys());
|
|
238
|
+
},
|
|
239
|
+
isAuthNone() {
|
|
240
|
+
return !config || config.mode === "none";
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
return manager;
|
|
245
|
+
}
|