forked from baron/baron-sso
테넌트 목록 조회 cursor기반으로 재구성. 사용자 metadata 미사용 필드 제거
This commit is contained in:
@@ -11,6 +11,13 @@ export type CommonOidcConfigOptions<TUserStore = unknown> = {
|
||||
userStore: TUserStore;
|
||||
};
|
||||
|
||||
export type LoginRedirectGuardParams = {
|
||||
pathname: string;
|
||||
isRedirecting: boolean;
|
||||
loginPath?: string;
|
||||
callbackPath?: string;
|
||||
};
|
||||
|
||||
type CommonOidcRuntimeConfig<TUserStore> = {
|
||||
authority: string;
|
||||
client_id: string;
|
||||
@@ -61,3 +68,20 @@ export function buildCommonUserManagerSettings<
|
||||
redirect_uri: config.redirect_uri || "",
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldStartLoginRedirect({
|
||||
pathname,
|
||||
isRedirecting,
|
||||
loginPath = "/login",
|
||||
callbackPath = DEFAULT_OIDC_REDIRECT_PATH,
|
||||
}: LoginRedirectGuardParams) {
|
||||
if (isRedirecting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pathname === loginPath || pathname.startsWith(callbackPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
82
common/core/pagination/cursorFetch.ts
Normal file
82
common/core/pagination/cursorFetch.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { CursorFetchRequest, CursorPageResponse } from "./cursorFetchCore";
|
||||
import { fetchAllCursorPagesMainThread } from "./cursorFetchCore";
|
||||
|
||||
type CursorWorkerResponseMessage<TItem> =
|
||||
| {
|
||||
id: string;
|
||||
ok: true;
|
||||
response: CursorPageResponse<TItem>;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
ok: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
function createRequestId() {
|
||||
if (globalThis.crypto?.randomUUID) {
|
||||
return globalThis.crypto.randomUUID();
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function shouldUseWorker(useWorker: boolean | undefined) {
|
||||
if (useWorker === false || typeof Worker === "undefined") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maybeWindow = globalThis as typeof globalThis & {
|
||||
window?: Window & typeof globalThis & { _IS_TEST_MODE?: boolean };
|
||||
};
|
||||
return maybeWindow.window?._IS_TEST_MODE !== true;
|
||||
}
|
||||
|
||||
async function fetchAllCursorPagesInWorker<TItem>(
|
||||
request: CursorFetchRequest,
|
||||
): Promise<CursorPageResponse<TItem>> {
|
||||
const worker = new Worker(new URL("./cursorFetch.worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
const id = createRequestId();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
worker.onmessage = (
|
||||
event: MessageEvent<CursorWorkerResponseMessage<TItem>>,
|
||||
) => {
|
||||
if (event.data.id !== id) {
|
||||
return;
|
||||
}
|
||||
worker.terminate();
|
||||
|
||||
if (event.data.ok) {
|
||||
resolve(event.data.response);
|
||||
} else {
|
||||
reject(new Error(event.data.error));
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (event) => {
|
||||
worker.terminate();
|
||||
reject(new Error(event.message || "Cursor worker failed"));
|
||||
};
|
||||
|
||||
worker.postMessage({ id, request });
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchAllCursorPages<TItem>(
|
||||
request: CursorFetchRequest & { useWorker?: boolean },
|
||||
): Promise<CursorPageResponse<TItem>> {
|
||||
if (shouldUseWorker(request.useWorker)) {
|
||||
try {
|
||||
return await fetchAllCursorPagesInWorker<TItem>(request);
|
||||
} catch {
|
||||
return fetchAllCursorPagesMainThread<TItem>(request);
|
||||
}
|
||||
}
|
||||
|
||||
return fetchAllCursorPagesMainThread<TItem>(request);
|
||||
}
|
||||
|
||||
export type { CursorFetchRequest, CursorPageResponse } from "./cursorFetchCore";
|
||||
export { fetchAllCursorPagesMainThread } from "./cursorFetchCore";
|
||||
43
common/core/pagination/cursorFetch.worker.ts
Normal file
43
common/core/pagination/cursorFetch.worker.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
fetchAllCursorPagesMainThread,
|
||||
type CursorFetchRequest,
|
||||
type CursorPageResponse,
|
||||
} from "./cursorFetchCore";
|
||||
|
||||
type CursorWorkerRequestMessage = {
|
||||
id: string;
|
||||
request: CursorFetchRequest;
|
||||
};
|
||||
|
||||
type CursorWorkerResponseMessage<TItem> =
|
||||
| {
|
||||
id: string;
|
||||
ok: true;
|
||||
response: CursorPageResponse<TItem>;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
ok: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
self.addEventListener("message", async (event: MessageEvent<CursorWorkerRequestMessage>) => {
|
||||
const { id, request } = event.data;
|
||||
|
||||
try {
|
||||
const response = await fetchAllCursorPagesMainThread(request);
|
||||
self.postMessage({
|
||||
id,
|
||||
ok: true,
|
||||
response,
|
||||
} satisfies CursorWorkerResponseMessage<unknown>);
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
id,
|
||||
ok: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
} satisfies CursorWorkerResponseMessage<unknown>);
|
||||
}
|
||||
});
|
||||
|
||||
export {};
|
||||
106
common/core/pagination/cursorFetchCore.ts
Normal file
106
common/core/pagination/cursorFetchCore.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
export type CursorPageResponse<TItem> = {
|
||||
items: TItem[];
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
total?: number;
|
||||
cursor?: string;
|
||||
nextCursor?: string;
|
||||
next_cursor?: string;
|
||||
};
|
||||
|
||||
export type CursorFetchParams = Record<
|
||||
string,
|
||||
string | number | boolean | null | undefined
|
||||
>;
|
||||
|
||||
export type CursorFetchRequest = {
|
||||
baseUrl: string;
|
||||
path: string;
|
||||
pageSize?: number;
|
||||
params?: CursorFetchParams;
|
||||
headers?: Record<string, string>;
|
||||
credentials?: RequestCredentials;
|
||||
maxPages?: number;
|
||||
};
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string) {
|
||||
const value = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
||||
return new URL(value, globalThis.location?.origin ?? "http://localhost");
|
||||
}
|
||||
|
||||
function buildCursorFetchUrl(
|
||||
request: Required<Pick<CursorFetchRequest, "baseUrl" | "path">> &
|
||||
Pick<CursorFetchRequest, "params">,
|
||||
pageSize: number,
|
||||
cursor: string | undefined,
|
||||
) {
|
||||
const path = request.path.replace(/^\/+/, "");
|
||||
const url = new URL(path, normalizeBaseUrl(request.baseUrl));
|
||||
|
||||
for (const [key, value] of Object.entries(request.params ?? {})) {
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
url.searchParams.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
url.searchParams.set("limit", String(pageSize));
|
||||
url.searchParams.set("offset", "0");
|
||||
if (cursor) {
|
||||
url.searchParams.set("cursor", cursor);
|
||||
} else {
|
||||
url.searchParams.delete("cursor");
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function readNextCursor<TItem>(page: CursorPageResponse<TItem>) {
|
||||
return page.nextCursor || page.next_cursor || undefined;
|
||||
}
|
||||
|
||||
export async function fetchAllCursorPagesMainThread<TItem>({
|
||||
pageSize = 100,
|
||||
credentials = "same-origin",
|
||||
maxPages = 1000,
|
||||
...request
|
||||
}: CursorFetchRequest): Promise<CursorPageResponse<TItem>> {
|
||||
const items: TItem[] = [];
|
||||
let cursor: string | undefined;
|
||||
|
||||
for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {
|
||||
const url = buildCursorFetchUrl(request, pageSize, cursor);
|
||||
const response = await fetch(url, {
|
||||
headers: request.headers,
|
||||
credentials,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Cursor page request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const page = (await response.json()) as CursorPageResponse<TItem>;
|
||||
items.push(...page.items);
|
||||
|
||||
const nextCursor = readNextCursor(page);
|
||||
if (!nextCursor) {
|
||||
return {
|
||||
...page,
|
||||
items,
|
||||
limit: pageSize,
|
||||
offset: 0,
|
||||
total: items.length,
|
||||
cursor,
|
||||
nextCursor: undefined,
|
||||
next_cursor: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (nextCursor === cursor) {
|
||||
throw new Error("Cursor page request returned the same next cursor");
|
||||
}
|
||||
|
||||
cursor = nextCursor;
|
||||
}
|
||||
|
||||
throw new Error(`Cursor page request exceeded ${maxPages} pages`);
|
||||
}
|
||||
6
common/core/pagination/index.ts
Normal file
6
common/core/pagination/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
fetchAllCursorPages,
|
||||
fetchAllCursorPagesMainThread,
|
||||
type CursorFetchRequest,
|
||||
type CursorPageResponse,
|
||||
} from "./cursorFetch";
|
||||
Reference in New Issue
Block a user