@absolutejs/auth 0.68.2 → 0.69.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 +69 -0
- package/dist/authContext.d.ts +9 -4
- package/dist/cli/migrate.js +37 -8
- package/dist/cli/migrate.js.map +4 -4
- package/dist/client/createAuthClient.d.ts +28 -2
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +542 -8
- package/dist/client/index.js.map +5 -4
- package/dist/client/mobile.d.ts +84 -0
- package/dist/client/mobile.js +572 -0
- package/dist/client/mobile.js.map +10 -0
- package/dist/htmx/configuredRoutes.d.ts +16 -28
- package/dist/htmx/routes.d.ts +16 -28
- package/dist/index.d.ts +221 -34
- package/dist/index.js +1855 -1401
- package/dist/index.js.map +16 -12
- package/dist/oidc/config.d.ts +17 -2
- package/dist/oidc/inMemoryStores.d.ts +2 -1
- package/dist/oidc/index.d.ts +4 -2
- package/dist/oidc/index.js +589 -6
- package/dist/oidc/index.js.map +8 -4
- package/dist/oidc/nativeClients.d.ts +11 -0
- package/dist/oidc/postgresStores.d.ts +145 -1
- package/dist/oidc/socketTicketRoutes.d.ts +7 -0
- package/dist/oidc/socketTickets.d.ts +21 -0
- package/dist/oidc/types.d.ts +20 -0
- package/dist/principal.d.ts +39 -0
- package/dist/routes/protectRoute.d.ts +7 -3
- package/dist/routes/requireAuth.d.ts +4 -1
- package/dist/server.js +1843 -1400
- package/dist/server.js.map +16 -12
- package/package.json +10 -2
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
37
|
+
var __export = (target, all) => {
|
|
38
|
+
for (var name in all)
|
|
39
|
+
__defProp(target, name, {
|
|
40
|
+
get: all[name],
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
|
+
var __require = import.meta.require;
|
|
48
|
+
|
|
49
|
+
// src/client/mobile.ts
|
|
50
|
+
class MobileAuthError extends Error {
|
|
51
|
+
code;
|
|
52
|
+
cause;
|
|
53
|
+
constructor(code, message, options) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = "MobileAuthError";
|
|
56
|
+
this.code = code;
|
|
57
|
+
this.cause = options?.cause;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
var PENDING_KEY = "oidc.pending";
|
|
61
|
+
var REFRESH_KEY = "oidc.refresh";
|
|
62
|
+
var DEFAULT_CLOCK_SKEW_MS = 30000;
|
|
63
|
+
var PENDING_TTL_MS = 10 * 60000;
|
|
64
|
+
var RANDOM_BYTES = 32;
|
|
65
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66
|
+
var base64Url = (value) => {
|
|
67
|
+
let binary = "";
|
|
68
|
+
for (const byte of value)
|
|
69
|
+
binary += String.fromCharCode(byte);
|
|
70
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
71
|
+
};
|
|
72
|
+
var decodeBase64Url = (value) => {
|
|
73
|
+
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
74
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
75
|
+
const binary = atob(padded);
|
|
76
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
77
|
+
};
|
|
78
|
+
var randomValue = () => {
|
|
79
|
+
const value = new Uint8Array(RANDOM_BYTES);
|
|
80
|
+
crypto.getRandomValues(value);
|
|
81
|
+
return base64Url(value);
|
|
82
|
+
};
|
|
83
|
+
var pkceChallenge = async (verifier) => base64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
|
|
84
|
+
var normalizeIssuer = (value) => {
|
|
85
|
+
const issuer = new URL(value);
|
|
86
|
+
const loopback = issuer.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(issuer.hostname);
|
|
87
|
+
if (issuer.protocol !== "https:" && !loopback)
|
|
88
|
+
throw new TypeError("Mobile auth issuer must use HTTPS.");
|
|
89
|
+
if (issuer.username || issuer.password || issuer.search || issuer.hash)
|
|
90
|
+
throw new TypeError("Mobile auth issuer cannot contain credentials, query, or fragment.");
|
|
91
|
+
issuer.pathname = issuer.pathname.replace(/\/$/u, "");
|
|
92
|
+
return issuer.href.replace(/\/$/u, "");
|
|
93
|
+
};
|
|
94
|
+
var exactRedirect = (actualValue, expectedValue) => {
|
|
95
|
+
const actual = new URL(actualValue);
|
|
96
|
+
const expected = new URL(expectedValue);
|
|
97
|
+
return actual.protocol === expected.protocol && actual.host === expected.host && actual.pathname === expected.pathname && actual.username === "" && actual.password === "";
|
|
98
|
+
};
|
|
99
|
+
var parsePending = (value) => {
|
|
100
|
+
if (value === null)
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(value);
|
|
104
|
+
if (!isRecord(parsed) || typeof parsed.createdAt !== "number" || typeof parsed.nonce !== "string" || typeof parsed.state !== "string" || typeof parsed.verifier !== "string")
|
|
105
|
+
return;
|
|
106
|
+
return {
|
|
107
|
+
createdAt: parsed.createdAt,
|
|
108
|
+
nonce: parsed.nonce,
|
|
109
|
+
state: parsed.state,
|
|
110
|
+
verifier: parsed.verifier
|
|
111
|
+
};
|
|
112
|
+
} catch {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var parseTokenResponse = (value) => {
|
|
117
|
+
if (!isRecord(value) || typeof value.access_token !== "string" || typeof value.expires_in !== "number" || !Number.isFinite(value.expires_in) || value.expires_in <= 0 || typeof value.id_token !== "string" || typeof value.refresh_token !== "string" || typeof value.token_type !== "string")
|
|
118
|
+
throw new MobileAuthError("token", "The token response is malformed.");
|
|
119
|
+
if (value.token_type.toLowerCase() !== "bearer")
|
|
120
|
+
throw new MobileAuthError("token", `Unsupported mobile token type ${value.token_type}; DPoP is not enabled for this client.`);
|
|
121
|
+
return {
|
|
122
|
+
access_token: value.access_token,
|
|
123
|
+
expires_in: value.expires_in,
|
|
124
|
+
id_token: value.id_token,
|
|
125
|
+
refresh_token: value.refresh_token,
|
|
126
|
+
scope: typeof value.scope === "string" ? value.scope : undefined,
|
|
127
|
+
token_type: value.token_type
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
var responseBody = async (response) => {
|
|
131
|
+
const text = await response.text();
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(text);
|
|
134
|
+
} catch {
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
var requireEndpoint = (value, name, issuer) => {
|
|
139
|
+
if (typeof value !== "string")
|
|
140
|
+
throw new MobileAuthError("discovery", `OIDC discovery is missing ${name}.`);
|
|
141
|
+
const endpoint = new URL(value);
|
|
142
|
+
if (endpoint.protocol !== "https:" && new URL(issuer).protocol === "https:")
|
|
143
|
+
throw new MobileAuthError("discovery", `OIDC discovery ${name} must use HTTPS.`);
|
|
144
|
+
return endpoint.href;
|
|
145
|
+
};
|
|
146
|
+
var parseDiscovery = (value, issuer) => {
|
|
147
|
+
if (!isRecord(value) || value.issuer !== issuer)
|
|
148
|
+
throw new MobileAuthError("discovery", "OIDC discovery issuer does not match the configured issuer.");
|
|
149
|
+
const methods = Array.isArray(value.code_challenge_methods_supported) ? value.code_challenge_methods_supported.filter((method) => typeof method === "string") : undefined;
|
|
150
|
+
if (!methods?.includes("S256"))
|
|
151
|
+
throw new MobileAuthError("discovery", "OIDC provider does not advertise S256 PKCE.");
|
|
152
|
+
const authMethods = Array.isArray(value.token_endpoint_auth_methods_supported) ? value.token_endpoint_auth_methods_supported.filter((method) => typeof method === "string") : undefined;
|
|
153
|
+
if (!authMethods?.includes("none"))
|
|
154
|
+
throw new MobileAuthError("discovery", "OIDC provider does not accept public clients at the token endpoint.");
|
|
155
|
+
return {
|
|
156
|
+
authorization_endpoint: requireEndpoint(value.authorization_endpoint, "authorization_endpoint", issuer),
|
|
157
|
+
code_challenge_methods_supported: methods,
|
|
158
|
+
issuer,
|
|
159
|
+
jwks_uri: requireEndpoint(value.jwks_uri, "jwks_uri", issuer),
|
|
160
|
+
revocation_endpoint: typeof value.revocation_endpoint === "string" ? requireEndpoint(value.revocation_endpoint, "revocation_endpoint", issuer) : undefined,
|
|
161
|
+
socket_ticket_endpoint: typeof value.socket_ticket_endpoint === "string" ? requireEndpoint(value.socket_ticket_endpoint, "socket_ticket_endpoint", issuer) : undefined,
|
|
162
|
+
token_endpoint: requireEndpoint(value.token_endpoint, "token_endpoint", issuer),
|
|
163
|
+
token_endpoint_auth_methods_supported: authMethods,
|
|
164
|
+
userinfo_endpoint: requireEndpoint(value.userinfo_endpoint, "userinfo_endpoint", issuer)
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
var verifyIdToken = async ({
|
|
168
|
+
clientId,
|
|
169
|
+
fetchImpl,
|
|
170
|
+
idToken,
|
|
171
|
+
issuer,
|
|
172
|
+
jwksUri,
|
|
173
|
+
nonce,
|
|
174
|
+
now
|
|
175
|
+
}) => {
|
|
176
|
+
const [encodedHeader, encodedPayload, encodedSignature, ...extra] = idToken.split(".");
|
|
177
|
+
if (!encodedHeader || !encodedPayload || !encodedSignature || extra.length)
|
|
178
|
+
throw new MobileAuthError("id-token", "The ID token is malformed.");
|
|
179
|
+
let header;
|
|
180
|
+
let payload;
|
|
181
|
+
try {
|
|
182
|
+
header = JSON.parse(new TextDecoder().decode(decodeBase64Url(encodedHeader)));
|
|
183
|
+
payload = JSON.parse(new TextDecoder().decode(decodeBase64Url(encodedPayload)));
|
|
184
|
+
} catch (cause) {
|
|
185
|
+
throw new MobileAuthError("id-token", "The ID token is malformed.", {
|
|
186
|
+
cause
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (!isRecord(header) || header.alg !== "ES256" || typeof header.kid !== "string" || !isRecord(payload))
|
|
190
|
+
throw new MobileAuthError("id-token", "The ID token header or claims are invalid.");
|
|
191
|
+
const response = await fetchImpl(jwksUri, { cache: "no-store" });
|
|
192
|
+
if (!response.ok)
|
|
193
|
+
throw new MobileAuthError("network", "Unable to fetch OIDC signing keys.");
|
|
194
|
+
const body = await response.json();
|
|
195
|
+
const keys = isRecord(body) && Array.isArray(body.keys) ? body.keys : [];
|
|
196
|
+
const jwk = keys.find((candidate) => isRecord(candidate) && candidate.kid === header.kid);
|
|
197
|
+
if (!jwk)
|
|
198
|
+
throw new MobileAuthError("id-token", "The ID token signing key is unknown.");
|
|
199
|
+
const key = await crypto.subtle.importKey("jwk", jwk, { hash: "SHA-256", name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
|
|
200
|
+
const valid = await crypto.subtle.verify({ hash: "SHA-256", name: "ECDSA" }, key, decodeBase64Url(encodedSignature), new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`));
|
|
201
|
+
const audience = payload.aud;
|
|
202
|
+
if (!valid || payload.iss !== issuer || !(audience === clientId || Array.isArray(audience) && audience.includes(clientId)) || payload.nonce !== nonce || typeof payload.exp !== "number" || payload.exp * 1000 <= now)
|
|
203
|
+
throw new MobileAuthError("id-token", "The ID token signature or claims are invalid.");
|
|
204
|
+
};
|
|
205
|
+
var createMobileAuthClient = (config) => {
|
|
206
|
+
const issuer = normalizeIssuer(config.issuer);
|
|
207
|
+
const redirectUri = new URL(config.redirectUri).href;
|
|
208
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
209
|
+
const now = config.now ?? Date.now;
|
|
210
|
+
const clockSkewMs = config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS;
|
|
211
|
+
const scopes = [...new Set(config.scopes ?? ["openid", "profile"])];
|
|
212
|
+
const resource = config.resource ?? issuer;
|
|
213
|
+
if (!scopes.includes("openid"))
|
|
214
|
+
scopes.unshift("openid");
|
|
215
|
+
const allowedOrigins = new Set((config.allowedOrigins ?? [new URL(issuer).origin]).map((value) => new URL(value).origin));
|
|
216
|
+
let discoveryPromise;
|
|
217
|
+
let access;
|
|
218
|
+
let refreshPromise;
|
|
219
|
+
let stopLinks;
|
|
220
|
+
let stopResume;
|
|
221
|
+
let startPromise;
|
|
222
|
+
const pendingSignIns = new Map;
|
|
223
|
+
const fetchDiscovery = async () => {
|
|
224
|
+
const url = new URL("/.well-known/openid-configuration", `${issuer}/`);
|
|
225
|
+
const response = await fetchImpl(url, {
|
|
226
|
+
cache: "no-store",
|
|
227
|
+
headers: { accept: "application/json" }
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok)
|
|
230
|
+
throw new MobileAuthError("discovery", `OIDC discovery failed with HTTP ${response.status}.`);
|
|
231
|
+
return parseDiscovery(await response.json(), issuer);
|
|
232
|
+
};
|
|
233
|
+
const discovery = () => {
|
|
234
|
+
discoveryPromise ??= fetchDiscovery();
|
|
235
|
+
return discoveryPromise;
|
|
236
|
+
};
|
|
237
|
+
const assertSecureStorage = async () => {
|
|
238
|
+
const capability = await config.storage.capability?.();
|
|
239
|
+
if (capability && !capability.available)
|
|
240
|
+
throw new MobileAuthError("secure-storage", capability.message ?? "Native secure credential storage is unavailable.");
|
|
241
|
+
};
|
|
242
|
+
const tokenRequest = async (metadata, body, nonce) => {
|
|
243
|
+
let response;
|
|
244
|
+
try {
|
|
245
|
+
response = await fetchImpl(metadata.token_endpoint, {
|
|
246
|
+
body,
|
|
247
|
+
credentials: "omit",
|
|
248
|
+
headers: {
|
|
249
|
+
accept: "application/json",
|
|
250
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
251
|
+
},
|
|
252
|
+
method: "POST"
|
|
253
|
+
});
|
|
254
|
+
} catch (cause) {
|
|
255
|
+
throw new MobileAuthError("network", "The token request failed.", {
|
|
256
|
+
cause
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const value = await responseBody(response);
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
const oauthCode = isRecord(value) && typeof value.error === "string" ? value.error : `HTTP ${response.status}`;
|
|
262
|
+
throw new MobileAuthError("oauth", `The authorization server rejected the token request (${oauthCode}).`);
|
|
263
|
+
}
|
|
264
|
+
const tokens = parseTokenResponse(value);
|
|
265
|
+
if (nonce)
|
|
266
|
+
await verifyIdToken({
|
|
267
|
+
clientId: config.clientId,
|
|
268
|
+
fetchImpl,
|
|
269
|
+
idToken: tokens.id_token,
|
|
270
|
+
issuer,
|
|
271
|
+
jwksUri: metadata.jwks_uri,
|
|
272
|
+
nonce,
|
|
273
|
+
now: now()
|
|
274
|
+
});
|
|
275
|
+
return tokens;
|
|
276
|
+
};
|
|
277
|
+
const acceptTokens = async (tokens) => {
|
|
278
|
+
try {
|
|
279
|
+
await config.storage.set(REFRESH_KEY, tokens.refresh_token);
|
|
280
|
+
} catch (cause) {
|
|
281
|
+
access = undefined;
|
|
282
|
+
await config.storage.remove(REFRESH_KEY).catch(() => {
|
|
283
|
+
return;
|
|
284
|
+
});
|
|
285
|
+
throw new MobileAuthError("secure-storage", "The rotated refresh credential could not be saved securely.", { cause });
|
|
286
|
+
}
|
|
287
|
+
access = {
|
|
288
|
+
accessToken: tokens.access_token,
|
|
289
|
+
expiresAt: now() + tokens.expires_in * 1000,
|
|
290
|
+
idToken: tokens.id_token,
|
|
291
|
+
scope: tokens.scope?.split(" ").filter(Boolean) ?? scopes,
|
|
292
|
+
tokenType: "Bearer"
|
|
293
|
+
};
|
|
294
|
+
return { ...access, refreshToken: tokens.refresh_token };
|
|
295
|
+
};
|
|
296
|
+
const rotateAccessToken = async () => {
|
|
297
|
+
await assertSecureStorage();
|
|
298
|
+
const refreshToken = await config.storage.get(REFRESH_KEY);
|
|
299
|
+
if (!refreshToken)
|
|
300
|
+
throw new MobileAuthError("token", "This app has no renewable mobile session.");
|
|
301
|
+
const metadata = await discovery();
|
|
302
|
+
const body = new URLSearchParams({
|
|
303
|
+
client_id: config.clientId,
|
|
304
|
+
grant_type: "refresh_token",
|
|
305
|
+
refresh_token: refreshToken
|
|
306
|
+
});
|
|
307
|
+
body.set("resource", resource);
|
|
308
|
+
const tokens = await tokenRequest(metadata, body);
|
|
309
|
+
return (await acceptTokens(tokens)).accessToken;
|
|
310
|
+
};
|
|
311
|
+
const refreshAccessToken = async () => {
|
|
312
|
+
if (access && access.expiresAt - clockSkewMs > now())
|
|
313
|
+
return access.accessToken;
|
|
314
|
+
if (refreshPromise)
|
|
315
|
+
return refreshPromise;
|
|
316
|
+
refreshPromise = rotateAccessToken().finally(() => {
|
|
317
|
+
refreshPromise = undefined;
|
|
318
|
+
});
|
|
319
|
+
return refreshPromise;
|
|
320
|
+
};
|
|
321
|
+
const handleCallback = async (value) => {
|
|
322
|
+
if (!exactRedirect(value, redirectUri))
|
|
323
|
+
throw new MobileAuthError("callback", "The authorization callback does not match this app registration.");
|
|
324
|
+
const url = new URL(value);
|
|
325
|
+
const pending = parsePending(await config.storage.get(PENDING_KEY));
|
|
326
|
+
if (!pending || now() - pending.createdAt > PENDING_TTL_MS) {
|
|
327
|
+
await config.storage.remove(PENDING_KEY).catch(() => {
|
|
328
|
+
return;
|
|
329
|
+
});
|
|
330
|
+
throw new MobileAuthError("callback", "The authorization transaction is missing or expired.");
|
|
331
|
+
}
|
|
332
|
+
const deferred = pendingSignIns.get(pending.state);
|
|
333
|
+
const reject = (error) => {
|
|
334
|
+
pendingSignIns.delete(pending.state);
|
|
335
|
+
config.storage.remove(PENDING_KEY);
|
|
336
|
+
deferred?.reject(error);
|
|
337
|
+
throw error;
|
|
338
|
+
};
|
|
339
|
+
if (url.searchParams.get("state") !== pending.state)
|
|
340
|
+
return reject(new MobileAuthError("callback", "The authorization callback state does not match."));
|
|
341
|
+
if (url.searchParams.get("iss") !== issuer)
|
|
342
|
+
return reject(new MobileAuthError("callback", "The authorization callback issuer does not match."));
|
|
343
|
+
const oauthError = url.searchParams.get("error");
|
|
344
|
+
if (oauthError) {
|
|
345
|
+
await config.storage.remove(PENDING_KEY);
|
|
346
|
+
return reject(new MobileAuthError("oauth", `Authorization failed (${oauthError}).`));
|
|
347
|
+
}
|
|
348
|
+
const code = url.searchParams.get("code");
|
|
349
|
+
if (!code)
|
|
350
|
+
return reject(new MobileAuthError("callback", "The authorization callback contains no code."));
|
|
351
|
+
await config.storage.remove(PENDING_KEY);
|
|
352
|
+
try {
|
|
353
|
+
const metadata = await discovery();
|
|
354
|
+
const body = new URLSearchParams({
|
|
355
|
+
client_id: config.clientId,
|
|
356
|
+
code,
|
|
357
|
+
code_verifier: pending.verifier,
|
|
358
|
+
grant_type: "authorization_code",
|
|
359
|
+
redirect_uri: redirectUri
|
|
360
|
+
});
|
|
361
|
+
body.set("resource", resource);
|
|
362
|
+
const tokens = await acceptTokens(await tokenRequest(metadata, body, pending.nonce));
|
|
363
|
+
pendingSignIns.delete(pending.state);
|
|
364
|
+
deferred?.resolve(tokens);
|
|
365
|
+
return tokens;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
return reject(error);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const initialize = async () => {
|
|
371
|
+
await assertSecureStorage();
|
|
372
|
+
stopLinks = await config.links.onOpen((url) => {
|
|
373
|
+
if (exactRedirect(url, redirectUri))
|
|
374
|
+
handleCallback(url).catch(() => {
|
|
375
|
+
return;
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
if (config.lifecycle?.onResume)
|
|
379
|
+
stopResume = await config.lifecycle.onResume(() => {
|
|
380
|
+
refreshAccessToken().catch(() => {
|
|
381
|
+
return;
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
const launchUrl = await config.links.getLaunchUrl();
|
|
385
|
+
if (launchUrl && exactRedirect(launchUrl, redirectUri))
|
|
386
|
+
await handleCallback(launchUrl);
|
|
387
|
+
};
|
|
388
|
+
const start = () => {
|
|
389
|
+
startPromise ??= initialize();
|
|
390
|
+
return startPromise;
|
|
391
|
+
};
|
|
392
|
+
const signIn = async (options = {}) => {
|
|
393
|
+
await start();
|
|
394
|
+
if (options.signal?.aborted)
|
|
395
|
+
throw new MobileAuthError("aborted", "Authorization was cancelled.");
|
|
396
|
+
const metadata = await discovery();
|
|
397
|
+
const pending = {
|
|
398
|
+
createdAt: now(),
|
|
399
|
+
nonce: randomValue(),
|
|
400
|
+
state: randomValue(),
|
|
401
|
+
verifier: randomValue()
|
|
402
|
+
};
|
|
403
|
+
await config.storage.set(PENDING_KEY, JSON.stringify(pending));
|
|
404
|
+
const url = new URL(metadata.authorization_endpoint);
|
|
405
|
+
url.search = new URLSearchParams({
|
|
406
|
+
client_id: config.clientId,
|
|
407
|
+
code_challenge: await pkceChallenge(pending.verifier),
|
|
408
|
+
code_challenge_method: "S256",
|
|
409
|
+
nonce: pending.nonce,
|
|
410
|
+
redirect_uri: redirectUri,
|
|
411
|
+
response_type: "code",
|
|
412
|
+
scope: scopes.join(" "),
|
|
413
|
+
state: pending.state,
|
|
414
|
+
...options.authorizationParameters
|
|
415
|
+
}).toString();
|
|
416
|
+
url.searchParams.set("resource", resource);
|
|
417
|
+
const result = new Promise((resolve, reject) => {
|
|
418
|
+
pendingSignIns.set(pending.state, { reject, resolve });
|
|
419
|
+
options.signal?.addEventListener("abort", () => {
|
|
420
|
+
pendingSignIns.delete(pending.state);
|
|
421
|
+
config.storage.remove(PENDING_KEY);
|
|
422
|
+
reject(new MobileAuthError("aborted", "Authorization was cancelled."));
|
|
423
|
+
}, { once: true });
|
|
424
|
+
});
|
|
425
|
+
try {
|
|
426
|
+
await config.links.openExternal(url.href);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
pendingSignIns.delete(pending.state);
|
|
429
|
+
await config.storage.remove(PENDING_KEY);
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
return result;
|
|
433
|
+
};
|
|
434
|
+
const authenticatedFetch = async (input, init) => {
|
|
435
|
+
const original = new Request(input, init);
|
|
436
|
+
if (!allowedOrigins.has(new URL(original.url).origin))
|
|
437
|
+
throw new MobileAuthError("origin", "Mobile auth refused to send a credential to an unregistered origin.");
|
|
438
|
+
const send = async (forceRefresh) => {
|
|
439
|
+
if (forceRefresh)
|
|
440
|
+
access = undefined;
|
|
441
|
+
const token = await refreshAccessToken();
|
|
442
|
+
const request = original.clone();
|
|
443
|
+
const headers = new Headers(request.headers);
|
|
444
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
445
|
+
return fetchImpl(new Request(request, { credentials: "omit", headers }));
|
|
446
|
+
};
|
|
447
|
+
const response = await send(false);
|
|
448
|
+
return response.status === 401 ? send(true) : response;
|
|
449
|
+
};
|
|
450
|
+
const optionalAuthenticatedFetch = async (input, init) => {
|
|
451
|
+
const request = new Request(input, init);
|
|
452
|
+
if (!allowedOrigins.has(new URL(request.url).origin))
|
|
453
|
+
throw new MobileAuthError("origin", "Mobile auth refused to send a request outside an allowed origin.");
|
|
454
|
+
const refreshToken = await config.storage.get(REFRESH_KEY);
|
|
455
|
+
if (access || refreshToken)
|
|
456
|
+
return authenticatedFetch(request);
|
|
457
|
+
return fetchImpl(new Request(request, { credentials: "omit" }));
|
|
458
|
+
};
|
|
459
|
+
const status = async () => {
|
|
460
|
+
try {
|
|
461
|
+
const metadata = await discovery();
|
|
462
|
+
const response = await authenticatedFetch(metadata.userinfo_endpoint);
|
|
463
|
+
if (response.status === 401)
|
|
464
|
+
return null;
|
|
465
|
+
if (!response.ok)
|
|
466
|
+
throw new MobileAuthError("network", `User info failed with HTTP ${response.status}.`);
|
|
467
|
+
const user = await response.json();
|
|
468
|
+
if (!isRecord(user) || typeof user.sub !== "string")
|
|
469
|
+
throw new MobileAuthError("token", "The user-info response is malformed.");
|
|
470
|
+
return { ...user, sub: user.sub };
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (error instanceof MobileAuthError && (error.code === "oauth" || error.code === "token"))
|
|
473
|
+
return null;
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
const socketTicket = async (audience = resource) => {
|
|
478
|
+
const metadata = await discovery();
|
|
479
|
+
if (!metadata.socket_ticket_endpoint)
|
|
480
|
+
throw new MobileAuthError("discovery", "The authorization server does not advertise WebSocket tickets.");
|
|
481
|
+
const response = await authenticatedFetch(metadata.socket_ticket_endpoint, {
|
|
482
|
+
body: JSON.stringify({ audience }),
|
|
483
|
+
headers: { "content-type": "application/json" },
|
|
484
|
+
method: "POST"
|
|
485
|
+
});
|
|
486
|
+
const body = await responseBody(response);
|
|
487
|
+
if (!response.ok || !isRecord(body) || typeof body.ticket !== "string")
|
|
488
|
+
throw new MobileAuthError(response.ok ? "token" : "oauth", `WebSocket ticket request failed with HTTP ${response.status}.`);
|
|
489
|
+
return body.ticket;
|
|
490
|
+
};
|
|
491
|
+
const revokeRefreshToken = async (refreshToken) => {
|
|
492
|
+
const metadata = await discovery();
|
|
493
|
+
if (metadata.revocation_endpoint)
|
|
494
|
+
await fetchImpl(metadata.revocation_endpoint, {
|
|
495
|
+
body: new URLSearchParams({
|
|
496
|
+
client_id: config.clientId,
|
|
497
|
+
token: refreshToken,
|
|
498
|
+
token_type_hint: "refresh_token"
|
|
499
|
+
}),
|
|
500
|
+
credentials: "omit",
|
|
501
|
+
headers: {
|
|
502
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
503
|
+
},
|
|
504
|
+
method: "POST"
|
|
505
|
+
});
|
|
506
|
+
};
|
|
507
|
+
const signOut = async () => {
|
|
508
|
+
const refreshToken = await config.storage.get(REFRESH_KEY);
|
|
509
|
+
try {
|
|
510
|
+
if (refreshToken)
|
|
511
|
+
await revokeRefreshToken(refreshToken);
|
|
512
|
+
} finally {
|
|
513
|
+
access = undefined;
|
|
514
|
+
await Promise.all([
|
|
515
|
+
config.storage.remove(PENDING_KEY),
|
|
516
|
+
config.storage.remove(REFRESH_KEY)
|
|
517
|
+
]);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const stop = async () => {
|
|
521
|
+
await Promise.all([stopLinks?.(), stopResume?.()]);
|
|
522
|
+
stopLinks = undefined;
|
|
523
|
+
stopResume = undefined;
|
|
524
|
+
startPromise = undefined;
|
|
525
|
+
};
|
|
526
|
+
return {
|
|
527
|
+
fetch: authenticatedFetch,
|
|
528
|
+
fetchOptional: optionalAuthenticatedFetch,
|
|
529
|
+
handleCallback,
|
|
530
|
+
refresh: refreshAccessToken,
|
|
531
|
+
signIn,
|
|
532
|
+
signOut,
|
|
533
|
+
socketTicket,
|
|
534
|
+
start,
|
|
535
|
+
status,
|
|
536
|
+
stop
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
var createMobileAuthTransport = (client) => ({
|
|
540
|
+
fetch: client.fetch,
|
|
541
|
+
signInEmail: async ({ email }) => {
|
|
542
|
+
await client.signIn({
|
|
543
|
+
authorizationParameters: { login_hint: email }
|
|
544
|
+
});
|
|
545
|
+
return { status: "authenticated" };
|
|
546
|
+
},
|
|
547
|
+
signOut: async () => {
|
|
548
|
+
await client.signOut();
|
|
549
|
+
return null;
|
|
550
|
+
},
|
|
551
|
+
signUpEmail: async ({ email }) => {
|
|
552
|
+
await client.signIn({
|
|
553
|
+
authorizationParameters: {
|
|
554
|
+
login_hint: email,
|
|
555
|
+
screen_hint: "signup"
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
return { status: "authenticated" };
|
|
559
|
+
},
|
|
560
|
+
status: async () => {
|
|
561
|
+
const user = await client.status();
|
|
562
|
+
return { user };
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
export {
|
|
566
|
+
createMobileAuthTransport,
|
|
567
|
+
createMobileAuthClient,
|
|
568
|
+
MobileAuthError
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
//# debugId=44F9E46781902F6C64756E2164756E21
|
|
572
|
+
//# sourceMappingURL=mobile.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/client/mobile.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export type MobileAuthSecureStorage = {\n\tcapability?: () => Promise<{\n\t\tavailable: boolean;\n\t\tmessage?: string;\n\t}>;\n\tget(key: string): Promise<string | null>;\n\tremove(key: string): Promise<void>;\n\tset(key: string, value: string): Promise<void>;\n};\n\nexport type MobileAuthLinks = {\n\tgetLaunchUrl(): Promise<string | null>;\n\tonOpen(\n\t\tlistener: (url: string) => void\n\t): Promise<() => Promise<void> | void>;\n\topenExternal(url: string): Promise<void>;\n};\n\nexport type MobileAuthLifecycle = {\n\tonResume?(listener: () => void): Promise<() => Promise<void> | void>;\n};\n\nexport type MobileAuthDiscovery = {\n\tauthorization_endpoint: string;\n\tcode_challenge_methods_supported?: string[];\n\tissuer: string;\n\tjwks_uri: string;\n\trevocation_endpoint?: string;\n\tsocket_ticket_endpoint?: string;\n\ttoken_endpoint: string;\n\ttoken_endpoint_auth_methods_supported?: string[];\n\tuserinfo_endpoint: string;\n};\n\nexport type MobileAuthClientConfig = {\n\tallowedOrigins?: readonly string[];\n\tclientId: string;\n\tclockSkewMs?: number;\n\tfetch?: typeof globalThis.fetch;\n\tissuer: string;\n\tlifecycle?: MobileAuthLifecycle;\n\tlinks: MobileAuthLinks;\n\tnow?: () => number;\n\tredirectUri: string;\n\tresource?: string;\n\tscopes?: readonly string[];\n\tstorage: MobileAuthSecureStorage;\n};\n\nexport type MobileAuthSignInOptions = {\n\tauthorizationParameters?: Readonly<Record<string, string>>;\n\tsignal?: AbortSignal;\n};\n\nexport type MobileAuthTokens = {\n\taccessToken: string;\n\texpiresAt: number;\n\tidToken: string;\n\trefreshToken: string;\n\tscope: string[];\n\ttokenType: 'Bearer';\n};\n\nexport type MobileAuthUser = Record<string, unknown> & { sub: string };\n\nexport type MobileAuthErrorCode =\n\t| 'aborted'\n\t| 'callback'\n\t| 'discovery'\n\t| 'id-token'\n\t| 'network'\n\t| 'oauth'\n\t| 'origin'\n\t| 'secure-storage'\n\t| 'token';\n\nexport class MobileAuthError extends Error {\n\treadonly code: MobileAuthErrorCode;\n\treadonly cause?: unknown;\n\n\tconstructor(\n\t\tcode: MobileAuthErrorCode,\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown }\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'MobileAuthError';\n\t\tthis.code = code;\n\t\tthis.cause = options?.cause;\n\t}\n}\n\ntype PendingAuthorization = {\n\tcreatedAt: number;\n\tnonce: string;\n\tstate: string;\n\tverifier: string;\n};\n\ntype TokenResponse = {\n\taccess_token: string;\n\texpires_in: number;\n\tid_token: string;\n\trefresh_token: string;\n\tscope?: string;\n\ttoken_type: string;\n};\n\ntype DeferredSignIn = {\n\treject: (error: unknown) => void;\n\tresolve: (tokens: MobileAuthTokens) => void;\n};\n\nconst PENDING_KEY = 'oidc.pending';\nconst REFRESH_KEY = 'oidc.refresh';\nconst DEFAULT_CLOCK_SKEW_MS = 30_000;\nconst PENDING_TTL_MS = 10 * 60_000;\nconst RANDOM_BYTES = 32;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst base64Url = (value: Uint8Array) => {\n\tlet binary = '';\n\tfor (const byte of value) binary += String.fromCharCode(byte);\n\n\treturn btoa(binary)\n\t\t.replaceAll('+', '-')\n\t\t.replaceAll('/', '_')\n\t\t.replace(/=+$/u, '');\n};\n\nconst decodeBase64Url = (value: string) => {\n\tconst normalized = value.replaceAll('-', '+').replaceAll('_', '/');\n\tconst padded = normalized.padEnd(\n\t\tnormalized.length + ((4 - (normalized.length % 4)) % 4),\n\t\t'='\n\t);\n\tconst binary = atob(padded);\n\n\treturn Uint8Array.from(binary, (character) => character.charCodeAt(0));\n};\n\nconst randomValue = () => {\n\tconst value = new Uint8Array(RANDOM_BYTES);\n\tcrypto.getRandomValues(value);\n\n\treturn base64Url(value);\n};\n\nconst pkceChallenge = async (verifier: string) =>\n\tbase64Url(\n\t\tnew Uint8Array(\n\t\t\tawait crypto.subtle.digest(\n\t\t\t\t'SHA-256',\n\t\t\t\tnew TextEncoder().encode(verifier)\n\t\t\t)\n\t\t)\n\t);\n\nconst normalizeIssuer = (value: string) => {\n\tconst issuer = new URL(value);\n\tconst loopback =\n\t\tissuer.protocol === 'http:' &&\n\t\t['127.0.0.1', '[::1]', 'localhost'].includes(issuer.hostname);\n\tif (issuer.protocol !== 'https:' && !loopback)\n\t\tthrow new TypeError('Mobile auth issuer must use HTTPS.');\n\tif (issuer.username || issuer.password || issuer.search || issuer.hash)\n\t\tthrow new TypeError(\n\t\t\t'Mobile auth issuer cannot contain credentials, query, or fragment.'\n\t\t);\n\tissuer.pathname = issuer.pathname.replace(/\\/$/u, '');\n\n\treturn issuer.href.replace(/\\/$/u, '');\n};\n\nconst exactRedirect = (actualValue: string, expectedValue: string) => {\n\tconst actual = new URL(actualValue);\n\tconst expected = new URL(expectedValue);\n\n\treturn (\n\t\tactual.protocol === expected.protocol &&\n\t\tactual.host === expected.host &&\n\t\tactual.pathname === expected.pathname &&\n\t\tactual.username === '' &&\n\t\tactual.password === ''\n\t);\n};\n\nconst parsePending = (value: string | null) => {\n\tif (value === null) return undefined;\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(value);\n\t\tif (\n\t\t\t!isRecord(parsed) ||\n\t\t\ttypeof parsed.createdAt !== 'number' ||\n\t\t\ttypeof parsed.nonce !== 'string' ||\n\t\t\ttypeof parsed.state !== 'string' ||\n\t\t\ttypeof parsed.verifier !== 'string'\n\t\t)\n\t\t\treturn undefined;\n\n\t\treturn {\n\t\t\tcreatedAt: parsed.createdAt,\n\t\t\tnonce: parsed.nonce,\n\t\t\tstate: parsed.state,\n\t\t\tverifier: parsed.verifier\n\t\t} satisfies PendingAuthorization;\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n\nconst parseTokenResponse = (value: unknown) => {\n\tif (\n\t\t!isRecord(value) ||\n\t\ttypeof value.access_token !== 'string' ||\n\t\ttypeof value.expires_in !== 'number' ||\n\t\t!Number.isFinite(value.expires_in) ||\n\t\tvalue.expires_in <= 0 ||\n\t\ttypeof value.id_token !== 'string' ||\n\t\ttypeof value.refresh_token !== 'string' ||\n\t\ttypeof value.token_type !== 'string'\n\t)\n\t\tthrow new MobileAuthError('token', 'The token response is malformed.');\n\tif (value.token_type.toLowerCase() !== 'bearer')\n\t\tthrow new MobileAuthError(\n\t\t\t'token',\n\t\t\t`Unsupported mobile token type ${value.token_type}; DPoP is not enabled for this client.`\n\t\t);\n\n\treturn {\n\t\taccess_token: value.access_token,\n\t\texpires_in: value.expires_in,\n\t\tid_token: value.id_token,\n\t\trefresh_token: value.refresh_token,\n\t\tscope: typeof value.scope === 'string' ? value.scope : undefined,\n\t\ttoken_type: value.token_type\n\t} satisfies TokenResponse;\n};\n\nconst responseBody = async (response: Response) => {\n\tconst text = await response.text();\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch {\n\t\treturn text;\n\t}\n};\n\nconst requireEndpoint = (value: unknown, name: string, issuer: string) => {\n\tif (typeof value !== 'string')\n\t\tthrow new MobileAuthError(\n\t\t\t'discovery',\n\t\t\t`OIDC discovery is missing ${name}.`\n\t\t);\n\tconst endpoint = new URL(value);\n\tif (endpoint.protocol !== 'https:' && new URL(issuer).protocol === 'https:')\n\t\tthrow new MobileAuthError(\n\t\t\t'discovery',\n\t\t\t`OIDC discovery ${name} must use HTTPS.`\n\t\t);\n\n\treturn endpoint.href;\n};\n\nconst parseDiscovery = (\n\tvalue: unknown,\n\tissuer: string\n): MobileAuthDiscovery => {\n\tif (!isRecord(value) || value.issuer !== issuer)\n\t\tthrow new MobileAuthError(\n\t\t\t'discovery',\n\t\t\t'OIDC discovery issuer does not match the configured issuer.'\n\t\t);\n\tconst methods = Array.isArray(value.code_challenge_methods_supported)\n\t\t? value.code_challenge_methods_supported.filter(\n\t\t\t\t(method): method is string => typeof method === 'string'\n\t\t\t)\n\t\t: undefined;\n\tif (!methods?.includes('S256'))\n\t\tthrow new MobileAuthError(\n\t\t\t'discovery',\n\t\t\t'OIDC provider does not advertise S256 PKCE.'\n\t\t);\n\tconst authMethods = Array.isArray(\n\t\tvalue.token_endpoint_auth_methods_supported\n\t)\n\t\t? value.token_endpoint_auth_methods_supported.filter(\n\t\t\t\t(method): method is string => typeof method === 'string'\n\t\t\t)\n\t\t: undefined;\n\tif (!authMethods?.includes('none'))\n\t\tthrow new MobileAuthError(\n\t\t\t'discovery',\n\t\t\t'OIDC provider does not accept public clients at the token endpoint.'\n\t\t);\n\n\treturn {\n\t\tauthorization_endpoint: requireEndpoint(\n\t\t\tvalue.authorization_endpoint,\n\t\t\t'authorization_endpoint',\n\t\t\tissuer\n\t\t),\n\t\tcode_challenge_methods_supported: methods,\n\t\tissuer,\n\t\tjwks_uri: requireEndpoint(value.jwks_uri, 'jwks_uri', issuer),\n\t\trevocation_endpoint:\n\t\t\ttypeof value.revocation_endpoint === 'string'\n\t\t\t\t? requireEndpoint(\n\t\t\t\t\t\tvalue.revocation_endpoint,\n\t\t\t\t\t\t'revocation_endpoint',\n\t\t\t\t\t\tissuer\n\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\tsocket_ticket_endpoint:\n\t\t\ttypeof value.socket_ticket_endpoint === 'string'\n\t\t\t\t? requireEndpoint(\n\t\t\t\t\t\tvalue.socket_ticket_endpoint,\n\t\t\t\t\t\t'socket_ticket_endpoint',\n\t\t\t\t\t\tissuer\n\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\ttoken_endpoint: requireEndpoint(\n\t\t\tvalue.token_endpoint,\n\t\t\t'token_endpoint',\n\t\t\tissuer\n\t\t),\n\t\ttoken_endpoint_auth_methods_supported: authMethods,\n\t\tuserinfo_endpoint: requireEndpoint(\n\t\t\tvalue.userinfo_endpoint,\n\t\t\t'userinfo_endpoint',\n\t\t\tissuer\n\t\t)\n\t};\n};\n\nconst verifyIdToken = async ({\n\tclientId,\n\tfetchImpl,\n\tidToken,\n\tissuer,\n\tjwksUri,\n\tnonce,\n\tnow\n}: {\n\tclientId: string;\n\tfetchImpl: typeof globalThis.fetch;\n\tidToken: string;\n\tissuer: string;\n\tjwksUri: string;\n\tnonce: string;\n\tnow: number;\n}) => {\n\tconst [encodedHeader, encodedPayload, encodedSignature, ...extra] =\n\t\tidToken.split('.');\n\tif (!encodedHeader || !encodedPayload || !encodedSignature || extra.length)\n\t\tthrow new MobileAuthError('id-token', 'The ID token is malformed.');\n\tlet header: unknown;\n\tlet payload: unknown;\n\ttry {\n\t\theader = JSON.parse(\n\t\t\tnew TextDecoder().decode(decodeBase64Url(encodedHeader))\n\t\t);\n\t\tpayload = JSON.parse(\n\t\t\tnew TextDecoder().decode(decodeBase64Url(encodedPayload))\n\t\t);\n\t} catch (cause) {\n\t\tthrow new MobileAuthError('id-token', 'The ID token is malformed.', {\n\t\t\tcause\n\t\t});\n\t}\n\tif (\n\t\t!isRecord(header) ||\n\t\theader.alg !== 'ES256' ||\n\t\ttypeof header.kid !== 'string' ||\n\t\t!isRecord(payload)\n\t)\n\t\tthrow new MobileAuthError(\n\t\t\t'id-token',\n\t\t\t'The ID token header or claims are invalid.'\n\t\t);\n\tconst response = await fetchImpl(jwksUri, { cache: 'no-store' });\n\tif (!response.ok)\n\t\tthrow new MobileAuthError(\n\t\t\t'network',\n\t\t\t'Unable to fetch OIDC signing keys.'\n\t\t);\n\tconst body: unknown = await response.json();\n\tconst keys = isRecord(body) && Array.isArray(body.keys) ? body.keys : [];\n\tconst jwk = keys.find(\n\t\t(candidate): candidate is JsonWebKey & { kid: string } =>\n\t\t\tisRecord(candidate) && candidate.kid === header.kid\n\t);\n\tif (!jwk)\n\t\tthrow new MobileAuthError(\n\t\t\t'id-token',\n\t\t\t'The ID token signing key is unknown.'\n\t\t);\n\tconst key = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjwk,\n\t\t{ hash: 'SHA-256', name: 'ECDSA', namedCurve: 'P-256' },\n\t\tfalse,\n\t\t['verify']\n\t);\n\tconst valid = await crypto.subtle.verify(\n\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\tkey,\n\t\tdecodeBase64Url(encodedSignature),\n\t\tnew TextEncoder().encode(`${encodedHeader}.${encodedPayload}`)\n\t);\n\tconst audience = payload.aud;\n\tif (\n\t\t!valid ||\n\t\tpayload.iss !== issuer ||\n\t\t!(\n\t\t\taudience === clientId ||\n\t\t\t(Array.isArray(audience) && audience.includes(clientId))\n\t\t) ||\n\t\tpayload.nonce !== nonce ||\n\t\ttypeof payload.exp !== 'number' ||\n\t\tpayload.exp * 1000 <= now\n\t)\n\t\tthrow new MobileAuthError(\n\t\t\t'id-token',\n\t\t\t'The ID token signature or claims are invalid.'\n\t\t);\n};\n\nexport const createMobileAuthClient = (config: MobileAuthClientConfig) => {\n\tconst issuer = normalizeIssuer(config.issuer);\n\tconst redirectUri = new URL(config.redirectUri).href;\n\tconst fetchImpl = config.fetch ?? globalThis.fetch;\n\tconst now = config.now ?? Date.now;\n\tconst clockSkewMs = config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS;\n\tconst scopes = [...new Set(config.scopes ?? ['openid', 'profile'])];\n\tconst resource = config.resource ?? issuer;\n\tif (!scopes.includes('openid')) scopes.unshift('openid');\n\tconst allowedOrigins = new Set(\n\t\t(config.allowedOrigins ?? [new URL(issuer).origin]).map(\n\t\t\t(value) => new URL(value).origin\n\t\t)\n\t);\n\tlet discoveryPromise: Promise<MobileAuthDiscovery> | undefined;\n\tlet access: Omit<MobileAuthTokens, 'refreshToken'> | undefined;\n\tlet refreshPromise: Promise<string> | undefined;\n\tlet stopLinks: (() => Promise<void> | void) | undefined;\n\tlet stopResume: (() => Promise<void> | void) | undefined;\n\tlet startPromise: Promise<void> | undefined;\n\tconst pendingSignIns = new Map<string, DeferredSignIn>();\n\n\tconst fetchDiscovery = async () => {\n\t\tconst url = new URL('/.well-known/openid-configuration', `${issuer}/`);\n\t\tconst response = await fetchImpl(url, {\n\t\t\tcache: 'no-store',\n\t\t\theaders: { accept: 'application/json' }\n\t\t});\n\t\tif (!response.ok)\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'discovery',\n\t\t\t\t`OIDC discovery failed with HTTP ${response.status}.`\n\t\t\t);\n\n\t\treturn parseDiscovery(await response.json(), issuer);\n\t};\n\n\tconst discovery = () => {\n\t\tdiscoveryPromise ??= fetchDiscovery();\n\n\t\treturn discoveryPromise;\n\t};\n\n\tconst assertSecureStorage = async () => {\n\t\tconst capability = await config.storage.capability?.();\n\t\tif (capability && !capability.available)\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'secure-storage',\n\t\t\t\tcapability.message ??\n\t\t\t\t\t'Native secure credential storage is unavailable.'\n\t\t\t);\n\t};\n\n\tconst tokenRequest = async (\n\t\tmetadata: MobileAuthDiscovery,\n\t\tbody: URLSearchParams,\n\t\tnonce?: string\n\t) => {\n\t\tlet response: Response;\n\t\ttry {\n\t\t\tresponse = await fetchImpl(metadata.token_endpoint, {\n\t\t\t\tbody,\n\t\t\t\tcredentials: 'omit',\n\t\t\t\theaders: {\n\t\t\t\t\taccept: 'application/json',\n\t\t\t\t\t'content-type': 'application/x-www-form-urlencoded'\n\t\t\t\t},\n\t\t\t\tmethod: 'POST'\n\t\t\t});\n\t\t} catch (cause) {\n\t\t\tthrow new MobileAuthError('network', 'The token request failed.', {\n\t\t\t\tcause\n\t\t\t});\n\t\t}\n\t\tconst value = await responseBody(response);\n\t\tif (!response.ok) {\n\t\t\tconst oauthCode =\n\t\t\t\tisRecord(value) && typeof value.error === 'string'\n\t\t\t\t\t? value.error\n\t\t\t\t\t: `HTTP ${response.status}`;\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'oauth',\n\t\t\t\t`The authorization server rejected the token request (${oauthCode}).`\n\t\t\t);\n\t\t}\n\t\tconst tokens = parseTokenResponse(value);\n\t\tif (nonce)\n\t\t\tawait verifyIdToken({\n\t\t\t\tclientId: config.clientId,\n\t\t\t\tfetchImpl,\n\t\t\t\tidToken: tokens.id_token,\n\t\t\t\tissuer,\n\t\t\t\tjwksUri: metadata.jwks_uri,\n\t\t\t\tnonce,\n\t\t\t\tnow: now()\n\t\t\t});\n\n\t\treturn tokens;\n\t};\n\n\tconst acceptTokens = async (\n\t\ttokens: TokenResponse\n\t): Promise<MobileAuthTokens> => {\n\t\ttry {\n\t\t\tawait config.storage.set(REFRESH_KEY, tokens.refresh_token);\n\t\t} catch (cause) {\n\t\t\taccess = undefined;\n\t\t\tawait config.storage.remove(REFRESH_KEY).catch(() => undefined);\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'secure-storage',\n\t\t\t\t'The rotated refresh credential could not be saved securely.',\n\t\t\t\t{ cause }\n\t\t\t);\n\t\t}\n\t\taccess = {\n\t\t\taccessToken: tokens.access_token,\n\t\t\texpiresAt: now() + tokens.expires_in * 1000,\n\t\t\tidToken: tokens.id_token,\n\t\t\tscope: tokens.scope?.split(' ').filter(Boolean) ?? scopes,\n\t\t\ttokenType: 'Bearer'\n\t\t};\n\n\t\treturn { ...access, refreshToken: tokens.refresh_token };\n\t};\n\n\tconst rotateAccessToken = async () => {\n\t\tawait assertSecureStorage();\n\t\tconst refreshToken = await config.storage.get(REFRESH_KEY);\n\t\tif (!refreshToken)\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'token',\n\t\t\t\t'This app has no renewable mobile session.'\n\t\t\t);\n\t\tconst metadata = await discovery();\n\t\tconst body = new URLSearchParams({\n\t\t\tclient_id: config.clientId,\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken\n\t\t});\n\t\tbody.set('resource', resource);\n\t\tconst tokens = await tokenRequest(metadata, body);\n\n\t\treturn (await acceptTokens(tokens)).accessToken;\n\t};\n\n\tconst refreshAccessToken = async () => {\n\t\tif (access && access.expiresAt - clockSkewMs > now())\n\t\t\treturn access.accessToken;\n\t\tif (refreshPromise) return refreshPromise;\n\t\trefreshPromise = rotateAccessToken().finally(() => {\n\t\t\trefreshPromise = undefined;\n\t\t});\n\n\t\treturn refreshPromise;\n\t};\n\n\tconst handleCallback = async (value: string) => {\n\t\tif (!exactRedirect(value, redirectUri))\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'callback',\n\t\t\t\t'The authorization callback does not match this app registration.'\n\t\t\t);\n\t\tconst url = new URL(value);\n\t\tconst pending = parsePending(await config.storage.get(PENDING_KEY));\n\t\tif (!pending || now() - pending.createdAt > PENDING_TTL_MS) {\n\t\t\tawait config.storage.remove(PENDING_KEY).catch(() => undefined);\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'callback',\n\t\t\t\t'The authorization transaction is missing or expired.'\n\t\t\t);\n\t\t}\n\t\tconst deferred = pendingSignIns.get(pending.state);\n\t\tconst reject = (error: unknown) => {\n\t\t\tpendingSignIns.delete(pending.state);\n\t\t\tvoid config.storage.remove(PENDING_KEY);\n\t\t\tdeferred?.reject(error);\n\t\t\tthrow error;\n\t\t};\n\t\tif (url.searchParams.get('state') !== pending.state)\n\t\t\treturn reject(\n\t\t\t\tnew MobileAuthError(\n\t\t\t\t\t'callback',\n\t\t\t\t\t'The authorization callback state does not match.'\n\t\t\t\t)\n\t\t\t);\n\t\tif (url.searchParams.get('iss') !== issuer)\n\t\t\treturn reject(\n\t\t\t\tnew MobileAuthError(\n\t\t\t\t\t'callback',\n\t\t\t\t\t'The authorization callback issuer does not match.'\n\t\t\t\t)\n\t\t\t);\n\t\tconst oauthError = url.searchParams.get('error');\n\t\tif (oauthError) {\n\t\t\tawait config.storage.remove(PENDING_KEY);\n\n\t\t\treturn reject(\n\t\t\t\tnew MobileAuthError(\n\t\t\t\t\t'oauth',\n\t\t\t\t\t`Authorization failed (${oauthError}).`\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\tconst code = url.searchParams.get('code');\n\t\tif (!code)\n\t\t\treturn reject(\n\t\t\t\tnew MobileAuthError(\n\t\t\t\t\t'callback',\n\t\t\t\t\t'The authorization callback contains no code.'\n\t\t\t\t)\n\t\t\t);\n\t\tawait config.storage.remove(PENDING_KEY);\n\t\ttry {\n\t\t\tconst metadata = await discovery();\n\t\t\tconst body = new URLSearchParams({\n\t\t\t\tclient_id: config.clientId,\n\t\t\t\tcode,\n\t\t\t\tcode_verifier: pending.verifier,\n\t\t\t\tgrant_type: 'authorization_code',\n\t\t\t\tredirect_uri: redirectUri\n\t\t\t});\n\t\t\tbody.set('resource', resource);\n\t\t\tconst tokens = await acceptTokens(\n\t\t\t\tawait tokenRequest(metadata, body, pending.nonce)\n\t\t\t);\n\t\t\tpendingSignIns.delete(pending.state);\n\t\t\tdeferred?.resolve(tokens);\n\n\t\t\treturn tokens;\n\t\t} catch (error) {\n\t\t\treturn reject(error);\n\t\t}\n\t};\n\n\tconst initialize = async () => {\n\t\tawait assertSecureStorage();\n\t\tstopLinks = await config.links.onOpen((url) => {\n\t\t\tif (exactRedirect(url, redirectUri))\n\t\t\t\tvoid handleCallback(url).catch(() => undefined);\n\t\t});\n\t\tif (config.lifecycle?.onResume)\n\t\t\tstopResume = await config.lifecycle.onResume(() => {\n\t\t\t\tvoid refreshAccessToken().catch(() => undefined);\n\t\t\t});\n\t\tconst launchUrl = await config.links.getLaunchUrl();\n\t\tif (launchUrl && exactRedirect(launchUrl, redirectUri))\n\t\t\tawait handleCallback(launchUrl);\n\t};\n\n\tconst start = () => {\n\t\tstartPromise ??= initialize();\n\n\t\treturn startPromise;\n\t};\n\n\tconst signIn = async (options: MobileAuthSignInOptions = {}) => {\n\t\tawait start();\n\t\tif (options.signal?.aborted)\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'aborted',\n\t\t\t\t'Authorization was cancelled.'\n\t\t\t);\n\t\tconst metadata = await discovery();\n\t\tconst pending: PendingAuthorization = {\n\t\t\tcreatedAt: now(),\n\t\t\tnonce: randomValue(),\n\t\t\tstate: randomValue(),\n\t\t\tverifier: randomValue()\n\t\t};\n\t\tawait config.storage.set(PENDING_KEY, JSON.stringify(pending));\n\t\tconst url = new URL(metadata.authorization_endpoint);\n\t\turl.search = new URLSearchParams({\n\t\t\tclient_id: config.clientId,\n\t\t\tcode_challenge: await pkceChallenge(pending.verifier),\n\t\t\tcode_challenge_method: 'S256',\n\t\t\tnonce: pending.nonce,\n\t\t\tredirect_uri: redirectUri,\n\t\t\tresponse_type: 'code',\n\t\t\tscope: scopes.join(' '),\n\t\t\tstate: pending.state,\n\t\t\t...options.authorizationParameters\n\t\t}).toString();\n\t\turl.searchParams.set('resource', resource);\n\t\tconst result = new Promise<MobileAuthTokens>((resolve, reject) => {\n\t\t\tpendingSignIns.set(pending.state, { reject, resolve });\n\t\t\toptions.signal?.addEventListener(\n\t\t\t\t'abort',\n\t\t\t\t() => {\n\t\t\t\t\tpendingSignIns.delete(pending.state);\n\t\t\t\t\tvoid config.storage.remove(PENDING_KEY);\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew MobileAuthError(\n\t\t\t\t\t\t\t'aborted',\n\t\t\t\t\t\t\t'Authorization was cancelled.'\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\t{ once: true }\n\t\t\t);\n\t\t});\n\t\ttry {\n\t\t\tawait config.links.openExternal(url.href);\n\t\t} catch (error) {\n\t\t\tpendingSignIns.delete(pending.state);\n\t\t\tawait config.storage.remove(PENDING_KEY);\n\t\t\tthrow error;\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tconst authenticatedFetch = async (\n\t\tinput: RequestInfo | URL,\n\t\tinit?: RequestInit\n\t) => {\n\t\tconst original = new Request(input, init);\n\t\tif (!allowedOrigins.has(new URL(original.url).origin))\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'origin',\n\t\t\t\t'Mobile auth refused to send a credential to an unregistered origin.'\n\t\t\t);\n\t\tconst send = async (forceRefresh: boolean) => {\n\t\t\tif (forceRefresh) access = undefined;\n\t\t\tconst token = await refreshAccessToken();\n\t\t\tconst request = original.clone();\n\t\t\tconst headers = new Headers(request.headers);\n\t\t\theaders.set('authorization', `Bearer ${token}`);\n\n\t\t\treturn fetchImpl(\n\t\t\t\tnew Request(request, { credentials: 'omit', headers })\n\t\t\t);\n\t\t};\n\t\tconst response = await send(false);\n\n\t\treturn response.status === 401 ? send(true) : response;\n\t};\n\n\tconst optionalAuthenticatedFetch = async (\n\t\tinput: RequestInfo | URL,\n\t\tinit?: RequestInit\n\t) => {\n\t\tconst request = new Request(input, init);\n\t\tif (!allowedOrigins.has(new URL(request.url).origin))\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'origin',\n\t\t\t\t'Mobile auth refused to send a request outside an allowed origin.'\n\t\t\t);\n\t\tconst refreshToken = await config.storage.get(REFRESH_KEY);\n\t\tif (access || refreshToken) return authenticatedFetch(request);\n\n\t\treturn fetchImpl(new Request(request, { credentials: 'omit' }));\n\t};\n\n\tconst status = async () => {\n\t\ttry {\n\t\t\tconst metadata = await discovery();\n\t\t\tconst response = await authenticatedFetch(\n\t\t\t\tmetadata.userinfo_endpoint\n\t\t\t);\n\t\t\tif (response.status === 401) return null;\n\t\t\tif (!response.ok)\n\t\t\t\tthrow new MobileAuthError(\n\t\t\t\t\t'network',\n\t\t\t\t\t`User info failed with HTTP ${response.status}.`\n\t\t\t\t);\n\t\t\tconst user: unknown = await response.json();\n\t\t\tif (!isRecord(user) || typeof user.sub !== 'string')\n\t\t\t\tthrow new MobileAuthError(\n\t\t\t\t\t'token',\n\t\t\t\t\t'The user-info response is malformed.'\n\t\t\t\t);\n\n\t\t\treturn { ...user, sub: user.sub } satisfies MobileAuthUser;\n\t\t} catch (error) {\n\t\t\tif (\n\t\t\t\terror instanceof MobileAuthError &&\n\t\t\t\t(error.code === 'oauth' || error.code === 'token')\n\t\t\t)\n\t\t\t\treturn null;\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tconst socketTicket = async (audience = resource) => {\n\t\tconst metadata = await discovery();\n\t\tif (!metadata.socket_ticket_endpoint)\n\t\t\tthrow new MobileAuthError(\n\t\t\t\t'discovery',\n\t\t\t\t'The authorization server does not advertise WebSocket tickets.'\n\t\t\t);\n\t\tconst response = await authenticatedFetch(\n\t\t\tmetadata.socket_ticket_endpoint,\n\t\t\t{\n\t\t\t\tbody: JSON.stringify({ audience }),\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tmethod: 'POST'\n\t\t\t}\n\t\t);\n\t\tconst body: unknown = await responseBody(response);\n\t\tif (!response.ok || !isRecord(body) || typeof body.ticket !== 'string')\n\t\t\tthrow new MobileAuthError(\n\t\t\t\tresponse.ok ? 'token' : 'oauth',\n\t\t\t\t`WebSocket ticket request failed with HTTP ${response.status}.`\n\t\t\t);\n\n\t\treturn body.ticket;\n\t};\n\n\tconst revokeRefreshToken = async (refreshToken: string) => {\n\t\tconst metadata = await discovery();\n\t\tif (metadata.revocation_endpoint)\n\t\t\tawait fetchImpl(metadata.revocation_endpoint, {\n\t\t\t\tbody: new URLSearchParams({\n\t\t\t\t\tclient_id: config.clientId,\n\t\t\t\t\ttoken: refreshToken,\n\t\t\t\t\ttoken_type_hint: 'refresh_token'\n\t\t\t\t}),\n\t\t\t\tcredentials: 'omit',\n\t\t\t\theaders: {\n\t\t\t\t\t'content-type': 'application/x-www-form-urlencoded'\n\t\t\t\t},\n\t\t\t\tmethod: 'POST'\n\t\t\t});\n\t};\n\n\tconst signOut = async () => {\n\t\tconst refreshToken = await config.storage.get(REFRESH_KEY);\n\t\ttry {\n\t\t\tif (refreshToken) await revokeRefreshToken(refreshToken);\n\t\t} finally {\n\t\t\taccess = undefined;\n\t\t\tawait Promise.all([\n\t\t\t\tconfig.storage.remove(PENDING_KEY),\n\t\t\t\tconfig.storage.remove(REFRESH_KEY)\n\t\t\t]);\n\t\t}\n\t};\n\n\tconst stop = async () => {\n\t\tawait Promise.all([stopLinks?.(), stopResume?.()]);\n\t\tstopLinks = undefined;\n\t\tstopResume = undefined;\n\t\tstartPromise = undefined;\n\t};\n\n\treturn {\n\t\tfetch: authenticatedFetch,\n\t\tfetchOptional: optionalAuthenticatedFetch,\n\t\thandleCallback,\n\t\trefresh: refreshAccessToken,\n\t\tsignIn,\n\t\tsignOut,\n\t\tsocketTicket,\n\t\tstart,\n\t\tstatus,\n\t\tstop\n\t};\n};\n\nexport type MobileAuthClient = ReturnType<typeof createMobileAuthClient>;\n\n/** Adapts the native OAuth lifecycle behind the same createAuthClient API used\n * by web applications. Passwords stay out of the WebView request path; the\n * external authorization UI owns credential entry and MFA. */\nexport const createMobileAuthTransport = (\n\tclient: MobileAuthClient\n): import('./createAuthClient').AuthClientTransport => ({\n\tfetch: client.fetch,\n\tsignInEmail: async ({ email }) => {\n\t\tawait client.signIn({\n\t\t\tauthorizationParameters: { login_hint: email }\n\t\t});\n\n\t\treturn { status: 'authenticated' };\n\t},\n\tsignOut: async () => {\n\t\tawait client.signOut();\n\n\t\treturn null;\n\t},\n\tsignUpEmail: async ({ email }) => {\n\t\tawait client.signIn({\n\t\t\tauthorizationParameters: {\n\t\t\t\tlogin_hint: email,\n\t\t\t\tscreen_hint: 'signup'\n\t\t\t}\n\t\t});\n\n\t\treturn { status: 'authenticated' };\n\t},\n\tstatus: async () => {\n\t\tconst user = await client.status();\n\n\t\treturn { user };\n\t}\n});\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EO,MAAM,wBAAwB,MAAM;AAAA,EACjC;AAAA,EACA;AAAA,EAET,WAAW,CACV,MACA,SACA,SACC;AAAA,IACD,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ,KAAK,OAAO;AAAA,IACZ,KAAK,QAAQ,SAAS;AAAA;AAExB;AAuBA,IAAM,cAAc;AACpB,IAAM,cAAc;AACpB,IAAM,wBAAwB;AAC9B,IAAM,iBAAiB,KAAK;AAC5B,IAAM,eAAe;AAErB,IAAM,WAAW,CAAC,UACjB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAEpE,IAAM,YAAY,CAAC,UAAsB;AAAA,EACxC,IAAI,SAAS;AAAA,EACb,WAAW,QAAQ;AAAA,IAAO,UAAU,OAAO,aAAa,IAAI;AAAA,EAE5D,OAAO,KAAK,MAAM,EAChB,WAAW,KAAK,GAAG,EACnB,WAAW,KAAK,GAAG,EACnB,QAAQ,QAAQ,EAAE;AAAA;AAGrB,IAAM,kBAAkB,CAAC,UAAkB;AAAA,EAC1C,MAAM,aAAa,MAAM,WAAW,KAAK,GAAG,EAAE,WAAW,KAAK,GAAG;AAAA,EACjE,MAAM,SAAS,WAAW,OACzB,WAAW,UAAW,IAAK,WAAW,SAAS,KAAM,GACrD,GACD;AAAA,EACA,MAAM,SAAS,KAAK,MAAM;AAAA,EAE1B,OAAO,WAAW,KAAK,QAAQ,CAAC,cAAc,UAAU,WAAW,CAAC,CAAC;AAAA;AAGtE,IAAM,cAAc,MAAM;AAAA,EACzB,MAAM,QAAQ,IAAI,WAAW,YAAY;AAAA,EACzC,OAAO,gBAAgB,KAAK;AAAA,EAE5B,OAAO,UAAU,KAAK;AAAA;AAGvB,IAAM,gBAAgB,OAAO,aAC5B,UACC,IAAI,WACH,MAAM,OAAO,OAAO,OACnB,WACA,IAAI,YAAY,EAAE,OAAO,QAAQ,CAClC,CACD,CACD;AAED,IAAM,kBAAkB,CAAC,UAAkB;AAAA,EAC1C,MAAM,SAAS,IAAI,IAAI,KAAK;AAAA,EAC5B,MAAM,WACL,OAAO,aAAa,WACpB,CAAC,aAAa,SAAS,WAAW,EAAE,SAAS,OAAO,QAAQ;AAAA,EAC7D,IAAI,OAAO,aAAa,YAAY,CAAC;AAAA,IACpC,MAAM,IAAI,UAAU,oCAAoC;AAAA,EACzD,IAAI,OAAO,YAAY,OAAO,YAAY,OAAO,UAAU,OAAO;AAAA,IACjE,MAAM,IAAI,UACT,oEACD;AAAA,EACD,OAAO,WAAW,OAAO,SAAS,QAAQ,QAAQ,EAAE;AAAA,EAEpD,OAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAAA;AAGtC,IAAM,gBAAgB,CAAC,aAAqB,kBAA0B;AAAA,EACrE,MAAM,SAAS,IAAI,IAAI,WAAW;AAAA,EAClC,MAAM,WAAW,IAAI,IAAI,aAAa;AAAA,EAEtC,OACC,OAAO,aAAa,SAAS,YAC7B,OAAO,SAAS,SAAS,QACzB,OAAO,aAAa,SAAS,YAC7B,OAAO,aAAa,MACpB,OAAO,aAAa;AAAA;AAItB,IAAM,eAAe,CAAC,UAAyB;AAAA,EAC9C,IAAI,UAAU;AAAA,IAAM;AAAA,EACpB,IAAI;AAAA,IACH,MAAM,SAAkB,KAAK,MAAM,KAAK;AAAA,IACxC,IACC,CAAC,SAAS,MAAM,KAChB,OAAO,OAAO,cAAc,YAC5B,OAAO,OAAO,UAAU,YACxB,OAAO,OAAO,UAAU,YACxB,OAAO,OAAO,aAAa;AAAA,MAE3B;AAAA,IAED,OAAO;AAAA,MACN,WAAW,OAAO;AAAA,MAClB,OAAO,OAAO;AAAA,MACd,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,IAClB;AAAA,IACC,MAAM;AAAA,IACP;AAAA;AAAA;AAIF,IAAM,qBAAqB,CAAC,UAAmB;AAAA,EAC9C,IACC,CAAC,SAAS,KAAK,KACf,OAAO,MAAM,iBAAiB,YAC9B,OAAO,MAAM,eAAe,YAC5B,CAAC,OAAO,SAAS,MAAM,UAAU,KACjC,MAAM,cAAc,KACpB,OAAO,MAAM,aAAa,YAC1B,OAAO,MAAM,kBAAkB,YAC/B,OAAO,MAAM,eAAe;AAAA,IAE5B,MAAM,IAAI,gBAAgB,SAAS,kCAAkC;AAAA,EACtE,IAAI,MAAM,WAAW,YAAY,MAAM;AAAA,IACtC,MAAM,IAAI,gBACT,SACA,iCAAiC,MAAM,kDACxC;AAAA,EAED,OAAO;AAAA,IACN,cAAc,MAAM;AAAA,IACpB,YAAY,MAAM;AAAA,IAClB,UAAU,MAAM;AAAA,IAChB,eAAe,MAAM;AAAA,IACrB,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;AAAA,IACvD,YAAY,MAAM;AAAA,EACnB;AAAA;AAGD,IAAM,eAAe,OAAO,aAAuB;AAAA,EAClD,MAAM,OAAO,MAAM,SAAS,KAAK;AAAA,EACjC,IAAI;AAAA,IACH,OAAO,KAAK,MAAM,IAAI;AAAA,IACrB,MAAM;AAAA,IACP,OAAO;AAAA;AAAA;AAIT,IAAM,kBAAkB,CAAC,OAAgB,MAAc,WAAmB;AAAA,EACzE,IAAI,OAAO,UAAU;AAAA,IACpB,MAAM,IAAI,gBACT,aACA,6BAA6B,OAC9B;AAAA,EACD,MAAM,WAAW,IAAI,IAAI,KAAK;AAAA,EAC9B,IAAI,SAAS,aAAa,YAAY,IAAI,IAAI,MAAM,EAAE,aAAa;AAAA,IAClE,MAAM,IAAI,gBACT,aACA,kBAAkB,sBACnB;AAAA,EAED,OAAO,SAAS;AAAA;AAGjB,IAAM,iBAAiB,CACtB,OACA,WACyB;AAAA,EACzB,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,WAAW;AAAA,IACxC,MAAM,IAAI,gBACT,aACA,6DACD;AAAA,EACD,MAAM,UAAU,MAAM,QAAQ,MAAM,gCAAgC,IACjE,MAAM,iCAAiC,OACvC,CAAC,WAA6B,OAAO,WAAW,QACjD,IACC;AAAA,EACH,IAAI,CAAC,SAAS,SAAS,MAAM;AAAA,IAC5B,MAAM,IAAI,gBACT,aACA,6CACD;AAAA,EACD,MAAM,cAAc,MAAM,QACzB,MAAM,qCACP,IACG,MAAM,sCAAsC,OAC5C,CAAC,WAA6B,OAAO,WAAW,QACjD,IACC;AAAA,EACH,IAAI,CAAC,aAAa,SAAS,MAAM;AAAA,IAChC,MAAM,IAAI,gBACT,aACA,qEACD;AAAA,EAED,OAAO;AAAA,IACN,wBAAwB,gBACvB,MAAM,wBACN,0BACA,MACD;AAAA,IACA,kCAAkC;AAAA,IAClC;AAAA,IACA,UAAU,gBAAgB,MAAM,UAAU,YAAY,MAAM;AAAA,IAC5D,qBACC,OAAO,MAAM,wBAAwB,WAClC,gBACA,MAAM,qBACN,uBACA,MACD,IACC;AAAA,IACJ,wBACC,OAAO,MAAM,2BAA2B,WACrC,gBACA,MAAM,wBACN,0BACA,MACD,IACC;AAAA,IACJ,gBAAgB,gBACf,MAAM,gBACN,kBACA,MACD;AAAA,IACA,uCAAuC;AAAA,IACvC,mBAAmB,gBAClB,MAAM,mBACN,qBACA,MACD;AAAA,EACD;AAAA;AAGD,IAAM,gBAAgB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,MASK;AAAA,EACL,OAAO,eAAe,gBAAgB,qBAAqB,SAC1D,QAAQ,MAAM,GAAG;AAAA,EAClB,IAAI,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,oBAAoB,MAAM;AAAA,IACnE,MAAM,IAAI,gBAAgB,YAAY,4BAA4B;AAAA,EACnE,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,IACH,SAAS,KAAK,MACb,IAAI,YAAY,EAAE,OAAO,gBAAgB,aAAa,CAAC,CACxD;AAAA,IACA,UAAU,KAAK,MACd,IAAI,YAAY,EAAE,OAAO,gBAAgB,cAAc,CAAC,CACzD;AAAA,IACC,OAAO,OAAO;AAAA,IACf,MAAM,IAAI,gBAAgB,YAAY,8BAA8B;AAAA,MACnE;AAAA,IACD,CAAC;AAAA;AAAA,EAEF,IACC,CAAC,SAAS,MAAM,KAChB,OAAO,QAAQ,WACf,OAAO,OAAO,QAAQ,YACtB,CAAC,SAAS,OAAO;AAAA,IAEjB,MAAM,IAAI,gBACT,YACA,4CACD;AAAA,EACD,MAAM,WAAW,MAAM,UAAU,SAAS,EAAE,OAAO,WAAW,CAAC;AAAA,EAC/D,IAAI,CAAC,SAAS;AAAA,IACb,MAAM,IAAI,gBACT,WACA,oCACD;AAAA,EACD,MAAM,OAAgB,MAAM,SAAS,KAAK;AAAA,EAC1C,MAAM,OAAO,SAAS,IAAI,KAAK,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC;AAAA,EACvE,MAAM,MAAM,KAAK,KAChB,CAAC,cACA,SAAS,SAAS,KAAK,UAAU,QAAQ,OAAO,GAClD;AAAA,EACA,IAAI,CAAC;AAAA,IACJ,MAAM,IAAI,gBACT,YACA,sCACD;AAAA,EACD,MAAM,MAAM,MAAM,OAAO,OAAO,UAC/B,OACA,KACA,EAAE,MAAM,WAAW,MAAM,SAAS,YAAY,QAAQ,GACtD,OACA,CAAC,QAAQ,CACV;AAAA,EACA,MAAM,QAAQ,MAAM,OAAO,OAAO,OACjC,EAAE,MAAM,WAAW,MAAM,QAAQ,GACjC,KACA,gBAAgB,gBAAgB,GAChC,IAAI,YAAY,EAAE,OAAO,GAAG,iBAAiB,gBAAgB,CAC9D;AAAA,EACA,MAAM,WAAW,QAAQ;AAAA,EACzB,IACC,CAAC,SACD,QAAQ,QAAQ,UAChB,EACC,aAAa,YACZ,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,QAAQ,MAEvD,QAAQ,UAAU,SAClB,OAAO,QAAQ,QAAQ,YACvB,QAAQ,MAAM,QAAQ;AAAA,IAEtB,MAAM,IAAI,gBACT,YACA,+CACD;AAAA;AAGK,IAAM,yBAAyB,CAAC,WAAmC;AAAA,EACzE,MAAM,SAAS,gBAAgB,OAAO,MAAM;AAAA,EAC5C,MAAM,cAAc,IAAI,IAAI,OAAO,WAAW,EAAE;AAAA,EAChD,MAAM,YAAY,OAAO,SAAS,WAAW;AAAA,EAC7C,MAAM,MAAM,OAAO,OAAO,KAAK;AAAA,EAC/B,MAAM,cAAc,OAAO,eAAe;AAAA,EAC1C,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,UAAU,CAAC,UAAU,SAAS,CAAC,CAAC;AAAA,EAClE,MAAM,WAAW,OAAO,YAAY;AAAA,EACpC,IAAI,CAAC,OAAO,SAAS,QAAQ;AAAA,IAAG,OAAO,QAAQ,QAAQ;AAAA,EACvD,MAAM,iBAAiB,IAAI,KACzB,OAAO,kBAAkB,CAAC,IAAI,IAAI,MAAM,EAAE,MAAM,GAAG,IACnD,CAAC,UAAU,IAAI,IAAI,KAAK,EAAE,MAC3B,CACD;AAAA,EACA,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,MAAM,iBAAiB,IAAI;AAAA,EAE3B,MAAM,iBAAiB,YAAY;AAAA,IAClC,MAAM,MAAM,IAAI,IAAI,qCAAqC,GAAG,SAAS;AAAA,IACrE,MAAM,WAAW,MAAM,UAAU,KAAK;AAAA,MACrC,OAAO;AAAA,MACP,SAAS,EAAE,QAAQ,mBAAmB;AAAA,IACvC,CAAC;AAAA,IACD,IAAI,CAAC,SAAS;AAAA,MACb,MAAM,IAAI,gBACT,aACA,mCAAmC,SAAS,SAC7C;AAAA,IAED,OAAO,eAAe,MAAM,SAAS,KAAK,GAAG,MAAM;AAAA;AAAA,EAGpD,MAAM,YAAY,MAAM;AAAA,IACvB,qBAAqB,eAAe;AAAA,IAEpC,OAAO;AAAA;AAAA,EAGR,MAAM,sBAAsB,YAAY;AAAA,IACvC,MAAM,aAAa,MAAM,OAAO,QAAQ,aAAa;AAAA,IACrD,IAAI,cAAc,CAAC,WAAW;AAAA,MAC7B,MAAM,IAAI,gBACT,kBACA,WAAW,WACV,kDACF;AAAA;AAAA,EAGF,MAAM,eAAe,OACpB,UACA,MACA,UACI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,MACH,WAAW,MAAM,UAAU,SAAS,gBAAgB;AAAA,QACnD;AAAA,QACA,aAAa;AAAA,QACb,SAAS;AAAA,UACR,QAAQ;AAAA,UACR,gBAAgB;AAAA,QACjB;AAAA,QACA,QAAQ;AAAA,MACT,CAAC;AAAA,MACA,OAAO,OAAO;AAAA,MACf,MAAM,IAAI,gBAAgB,WAAW,6BAA6B;AAAA,QACjE;AAAA,MACD,CAAC;AAAA;AAAA,IAEF,MAAM,QAAQ,MAAM,aAAa,QAAQ;AAAA,IACzC,IAAI,CAAC,SAAS,IAAI;AAAA,MACjB,MAAM,YACL,SAAS,KAAK,KAAK,OAAO,MAAM,UAAU,WACvC,MAAM,QACN,QAAQ,SAAS;AAAA,MACrB,MAAM,IAAI,gBACT,SACA,wDAAwD,aACzD;AAAA,IACD;AAAA,IACA,MAAM,SAAS,mBAAmB,KAAK;AAAA,IACvC,IAAI;AAAA,MACH,MAAM,cAAc;AAAA,QACnB,UAAU,OAAO;AAAA,QACjB;AAAA,QACA,SAAS,OAAO;AAAA,QAChB;AAAA,QACA,SAAS,SAAS;AAAA,QAClB;AAAA,QACA,KAAK,IAAI;AAAA,MACV,CAAC;AAAA,IAEF,OAAO;AAAA;AAAA,EAGR,MAAM,eAAe,OACpB,WAC+B;AAAA,IAC/B,IAAI;AAAA,MACH,MAAM,OAAO,QAAQ,IAAI,aAAa,OAAO,aAAa;AAAA,MACzD,OAAO,OAAO;AAAA,MACf,SAAS;AAAA,MACT,MAAM,OAAO,QAAQ,OAAO,WAAW,EAAE,MAAM,MAAG;AAAA,QAAG;AAAA,OAAS;AAAA,MAC9D,MAAM,IAAI,gBACT,kBACA,+DACA,EAAE,MAAM,CACT;AAAA;AAAA,IAED,SAAS;AAAA,MACR,aAAa,OAAO;AAAA,MACpB,WAAW,IAAI,IAAI,OAAO,aAAa;AAAA,MACvC,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO,KAAK;AAAA,MACnD,WAAW;AAAA,IACZ;AAAA,IAEA,OAAO,KAAK,QAAQ,cAAc,OAAO,cAAc;AAAA;AAAA,EAGxD,MAAM,oBAAoB,YAAY;AAAA,IACrC,MAAM,oBAAoB;AAAA,IAC1B,MAAM,eAAe,MAAM,OAAO,QAAQ,IAAI,WAAW;AAAA,IACzD,IAAI,CAAC;AAAA,MACJ,MAAM,IAAI,gBACT,SACA,2CACD;AAAA,IACD,MAAM,WAAW,MAAM,UAAU;AAAA,IACjC,MAAM,OAAO,IAAI,gBAAgB;AAAA,MAChC,WAAW,OAAO;AAAA,MAClB,YAAY;AAAA,MACZ,eAAe;AAAA,IAChB,CAAC;AAAA,IACD,KAAK,IAAI,YAAY,QAAQ;AAAA,IAC7B,MAAM,SAAS,MAAM,aAAa,UAAU,IAAI;AAAA,IAEhD,QAAQ,MAAM,aAAa,MAAM,GAAG;AAAA;AAAA,EAGrC,MAAM,qBAAqB,YAAY;AAAA,IACtC,IAAI,UAAU,OAAO,YAAY,cAAc,IAAI;AAAA,MAClD,OAAO,OAAO;AAAA,IACf,IAAI;AAAA,MAAgB,OAAO;AAAA,IAC3B,iBAAiB,kBAAkB,EAAE,QAAQ,MAAM;AAAA,MAClD,iBAAiB;AAAA,KACjB;AAAA,IAED,OAAO;AAAA;AAAA,EAGR,MAAM,iBAAiB,OAAO,UAAkB;AAAA,IAC/C,IAAI,CAAC,cAAc,OAAO,WAAW;AAAA,MACpC,MAAM,IAAI,gBACT,YACA,kEACD;AAAA,IACD,MAAM,MAAM,IAAI,IAAI,KAAK;AAAA,IACzB,MAAM,UAAU,aAAa,MAAM,OAAO,QAAQ,IAAI,WAAW,CAAC;AAAA,IAClE,IAAI,CAAC,WAAW,IAAI,IAAI,QAAQ,YAAY,gBAAgB;AAAA,MAC3D,MAAM,OAAO,QAAQ,OAAO,WAAW,EAAE,MAAM,MAAG;AAAA,QAAG;AAAA,OAAS;AAAA,MAC9D,MAAM,IAAI,gBACT,YACA,sDACD;AAAA,IACD;AAAA,IACA,MAAM,WAAW,eAAe,IAAI,QAAQ,KAAK;AAAA,IACjD,MAAM,SAAS,CAAC,UAAmB;AAAA,MAClC,eAAe,OAAO,QAAQ,KAAK;AAAA,MAC9B,OAAO,QAAQ,OAAO,WAAW;AAAA,MACtC,UAAU,OAAO,KAAK;AAAA,MACtB,MAAM;AAAA;AAAA,IAEP,IAAI,IAAI,aAAa,IAAI,OAAO,MAAM,QAAQ;AAAA,MAC7C,OAAO,OACN,IAAI,gBACH,YACA,kDACD,CACD;AAAA,IACD,IAAI,IAAI,aAAa,IAAI,KAAK,MAAM;AAAA,MACnC,OAAO,OACN,IAAI,gBACH,YACA,mDACD,CACD;AAAA,IACD,MAAM,aAAa,IAAI,aAAa,IAAI,OAAO;AAAA,IAC/C,IAAI,YAAY;AAAA,MACf,MAAM,OAAO,QAAQ,OAAO,WAAW;AAAA,MAEvC,OAAO,OACN,IAAI,gBACH,SACA,yBAAyB,cAC1B,CACD;AAAA,IACD;AAAA,IACA,MAAM,OAAO,IAAI,aAAa,IAAI,MAAM;AAAA,IACxC,IAAI,CAAC;AAAA,MACJ,OAAO,OACN,IAAI,gBACH,YACA,8CACD,CACD;AAAA,IACD,MAAM,OAAO,QAAQ,OAAO,WAAW;AAAA,IACvC,IAAI;AAAA,MACH,MAAM,WAAW,MAAM,UAAU;AAAA,MACjC,MAAM,OAAO,IAAI,gBAAgB;AAAA,QAChC,WAAW,OAAO;AAAA,QAClB;AAAA,QACA,eAAe,QAAQ;AAAA,QACvB,YAAY;AAAA,QACZ,cAAc;AAAA,MACf,CAAC;AAAA,MACD,KAAK,IAAI,YAAY,QAAQ;AAAA,MAC7B,MAAM,SAAS,MAAM,aACpB,MAAM,aAAa,UAAU,MAAM,QAAQ,KAAK,CACjD;AAAA,MACA,eAAe,OAAO,QAAQ,KAAK;AAAA,MACnC,UAAU,QAAQ,MAAM;AAAA,MAExB,OAAO;AAAA,MACN,OAAO,OAAO;AAAA,MACf,OAAO,OAAO,KAAK;AAAA;AAAA;AAAA,EAIrB,MAAM,aAAa,YAAY;AAAA,IAC9B,MAAM,oBAAoB;AAAA,IAC1B,YAAY,MAAM,OAAO,MAAM,OAAO,CAAC,QAAQ;AAAA,MAC9C,IAAI,cAAc,KAAK,WAAW;AAAA,QAC5B,eAAe,GAAG,EAAE,MAAM,MAAG;AAAA,UAAG;AAAA,SAAS;AAAA,KAC/C;AAAA,IACD,IAAI,OAAO,WAAW;AAAA,MACrB,aAAa,MAAM,OAAO,UAAU,SAAS,MAAM;AAAA,QAC7C,mBAAmB,EAAE,MAAM,MAAG;AAAA,UAAG;AAAA,SAAS;AAAA,OAC/C;AAAA,IACF,MAAM,YAAY,MAAM,OAAO,MAAM,aAAa;AAAA,IAClD,IAAI,aAAa,cAAc,WAAW,WAAW;AAAA,MACpD,MAAM,eAAe,SAAS;AAAA;AAAA,EAGhC,MAAM,QAAQ,MAAM;AAAA,IACnB,iBAAiB,WAAW;AAAA,IAE5B,OAAO;AAAA;AAAA,EAGR,MAAM,SAAS,OAAO,UAAmC,CAAC,MAAM;AAAA,IAC/D,MAAM,MAAM;AAAA,IACZ,IAAI,QAAQ,QAAQ;AAAA,MACnB,MAAM,IAAI,gBACT,WACA,8BACD;AAAA,IACD,MAAM,WAAW,MAAM,UAAU;AAAA,IACjC,MAAM,UAAgC;AAAA,MACrC,WAAW,IAAI;AAAA,MACf,OAAO,YAAY;AAAA,MACnB,OAAO,YAAY;AAAA,MACnB,UAAU,YAAY;AAAA,IACvB;AAAA,IACA,MAAM,OAAO,QAAQ,IAAI,aAAa,KAAK,UAAU,OAAO,CAAC;AAAA,IAC7D,MAAM,MAAM,IAAI,IAAI,SAAS,sBAAsB;AAAA,IACnD,IAAI,SAAS,IAAI,gBAAgB;AAAA,MAChC,WAAW,OAAO;AAAA,MAClB,gBAAgB,MAAM,cAAc,QAAQ,QAAQ;AAAA,MACpD,uBAAuB;AAAA,MACvB,OAAO,QAAQ;AAAA,MACf,cAAc;AAAA,MACd,eAAe;AAAA,MACf,OAAO,OAAO,KAAK,GAAG;AAAA,MACtB,OAAO,QAAQ;AAAA,SACZ,QAAQ;AAAA,IACZ,CAAC,EAAE,SAAS;AAAA,IACZ,IAAI,aAAa,IAAI,YAAY,QAAQ;AAAA,IACzC,MAAM,SAAS,IAAI,QAA0B,CAAC,SAAS,WAAW;AAAA,MACjE,eAAe,IAAI,QAAQ,OAAO,EAAE,QAAQ,QAAQ,CAAC;AAAA,MACrD,QAAQ,QAAQ,iBACf,SACA,MAAM;AAAA,QACL,eAAe,OAAO,QAAQ,KAAK;AAAA,QAC9B,OAAO,QAAQ,OAAO,WAAW;AAAA,QACtC,OACC,IAAI,gBACH,WACA,8BACD,CACD;AAAA,SAED,EAAE,MAAM,KAAK,CACd;AAAA,KACA;AAAA,IACD,IAAI;AAAA,MACH,MAAM,OAAO,MAAM,aAAa,IAAI,IAAI;AAAA,MACvC,OAAO,OAAO;AAAA,MACf,eAAe,OAAO,QAAQ,KAAK;AAAA,MACnC,MAAM,OAAO,QAAQ,OAAO,WAAW;AAAA,MACvC,MAAM;AAAA;AAAA,IAGP,OAAO;AAAA;AAAA,EAGR,MAAM,qBAAqB,OAC1B,OACA,SACI;AAAA,IACJ,MAAM,WAAW,IAAI,QAAQ,OAAO,IAAI;AAAA,IACxC,IAAI,CAAC,eAAe,IAAI,IAAI,IAAI,SAAS,GAAG,EAAE,MAAM;AAAA,MACnD,MAAM,IAAI,gBACT,UACA,qEACD;AAAA,IACD,MAAM,OAAO,OAAO,iBAA0B;AAAA,MAC7C,IAAI;AAAA,QAAc,SAAS;AAAA,MAC3B,MAAM,QAAQ,MAAM,mBAAmB;AAAA,MACvC,MAAM,UAAU,SAAS,MAAM;AAAA,MAC/B,MAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;AAAA,MAC3C,QAAQ,IAAI,iBAAiB,UAAU,OAAO;AAAA,MAE9C,OAAO,UACN,IAAI,QAAQ,SAAS,EAAE,aAAa,QAAQ,QAAQ,CAAC,CACtD;AAAA;AAAA,IAED,MAAM,WAAW,MAAM,KAAK,KAAK;AAAA,IAEjC,OAAO,SAAS,WAAW,MAAM,KAAK,IAAI,IAAI;AAAA;AAAA,EAG/C,MAAM,6BAA6B,OAClC,OACA,SACI;AAAA,IACJ,MAAM,UAAU,IAAI,QAAQ,OAAO,IAAI;AAAA,IACvC,IAAI,CAAC,eAAe,IAAI,IAAI,IAAI,QAAQ,GAAG,EAAE,MAAM;AAAA,MAClD,MAAM,IAAI,gBACT,UACA,kEACD;AAAA,IACD,MAAM,eAAe,MAAM,OAAO,QAAQ,IAAI,WAAW;AAAA,IACzD,IAAI,UAAU;AAAA,MAAc,OAAO,mBAAmB,OAAO;AAAA,IAE7D,OAAO,UAAU,IAAI,QAAQ,SAAS,EAAE,aAAa,OAAO,CAAC,CAAC;AAAA;AAAA,EAG/D,MAAM,SAAS,YAAY;AAAA,IAC1B,IAAI;AAAA,MACH,MAAM,WAAW,MAAM,UAAU;AAAA,MACjC,MAAM,WAAW,MAAM,mBACtB,SAAS,iBACV;AAAA,MACA,IAAI,SAAS,WAAW;AAAA,QAAK,OAAO;AAAA,MACpC,IAAI,CAAC,SAAS;AAAA,QACb,MAAM,IAAI,gBACT,WACA,8BAA8B,SAAS,SACxC;AAAA,MACD,MAAM,OAAgB,MAAM,SAAS,KAAK;AAAA,MAC1C,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,QAAQ;AAAA,QAC1C,MAAM,IAAI,gBACT,SACA,sCACD;AAAA,MAED,OAAO,KAAK,MAAM,KAAK,KAAK,IAAI;AAAA,MAC/B,OAAO,OAAO;AAAA,MACf,IACC,iBAAiB,oBAChB,MAAM,SAAS,WAAW,MAAM,SAAS;AAAA,QAE1C,OAAO;AAAA,MACR,MAAM;AAAA;AAAA;AAAA,EAIR,MAAM,eAAe,OAAO,WAAW,aAAa;AAAA,IACnD,MAAM,WAAW,MAAM,UAAU;AAAA,IACjC,IAAI,CAAC,SAAS;AAAA,MACb,MAAM,IAAI,gBACT,aACA,gEACD;AAAA,IACD,MAAM,WAAW,MAAM,mBACtB,SAAS,wBACT;AAAA,MACC,MAAM,KAAK,UAAU,EAAE,SAAS,CAAC;AAAA,MACjC,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,QAAQ;AAAA,IACT,CACD;AAAA,IACA,MAAM,OAAgB,MAAM,aAAa,QAAQ;AAAA,IACjD,IAAI,CAAC,SAAS,MAAM,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,WAAW;AAAA,MAC7D,MAAM,IAAI,gBACT,SAAS,KAAK,UAAU,SACxB,6CAA6C,SAAS,SACvD;AAAA,IAED,OAAO,KAAK;AAAA;AAAA,EAGb,MAAM,qBAAqB,OAAO,iBAAyB;AAAA,IAC1D,MAAM,WAAW,MAAM,UAAU;AAAA,IACjC,IAAI,SAAS;AAAA,MACZ,MAAM,UAAU,SAAS,qBAAqB;AAAA,QAC7C,MAAM,IAAI,gBAAgB;AAAA,UACzB,WAAW,OAAO;AAAA,UAClB,OAAO;AAAA,UACP,iBAAiB;AAAA,QAClB,CAAC;AAAA,QACD,aAAa;AAAA,QACb,SAAS;AAAA,UACR,gBAAgB;AAAA,QACjB;AAAA,QACA,QAAQ;AAAA,MACT,CAAC;AAAA;AAAA,EAGH,MAAM,UAAU,YAAY;AAAA,IAC3B,MAAM,eAAe,MAAM,OAAO,QAAQ,IAAI,WAAW;AAAA,IACzD,IAAI;AAAA,MACH,IAAI;AAAA,QAAc,MAAM,mBAAmB,YAAY;AAAA,cACtD;AAAA,MACD,SAAS;AAAA,MACT,MAAM,QAAQ,IAAI;AAAA,QACjB,OAAO,QAAQ,OAAO,WAAW;AAAA,QACjC,OAAO,QAAQ,OAAO,WAAW;AAAA,MAClC,CAAC;AAAA;AAAA;AAAA,EAIH,MAAM,OAAO,YAAY;AAAA,IACxB,MAAM,QAAQ,IAAI,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC;AAAA,IACjD,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA;AAAA,EAGhB,OAAO;AAAA,IACN,OAAO;AAAA,IACP,eAAe;AAAA,IACf;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAAA;AAQM,IAAM,4BAA4B,CACxC,YACuD;AAAA,EACvD,OAAO,OAAO;AAAA,EACd,aAAa,SAAS,YAAY;AAAA,IACjC,MAAM,OAAO,OAAO;AAAA,MACnB,yBAAyB,EAAE,YAAY,MAAM;AAAA,IAC9C,CAAC;AAAA,IAED,OAAO,EAAE,QAAQ,gBAAgB;AAAA;AAAA,EAElC,SAAS,YAAY;AAAA,IACpB,MAAM,OAAO,QAAQ;AAAA,IAErB,OAAO;AAAA;AAAA,EAER,aAAa,SAAS,YAAY;AAAA,IACjC,MAAM,OAAO,OAAO;AAAA,MACnB,yBAAyB;AAAA,QACxB,YAAY;AAAA,QACZ,aAAa;AAAA,MACd;AAAA,IACD,CAAC;AAAA,IAED,OAAO,EAAE,QAAQ,gBAAgB;AAAA;AAAA,EAElC,QAAQ,YAAY;AAAA,IACnB,MAAM,OAAO,MAAM,OAAO,OAAO;AAAA,IAEjC,OAAO,EAAE,KAAK;AAAA;AAEhB;",
|
|
8
|
+
"debugId": "44F9E46781902F6C64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|