156 lines
4.4 KiB
TypeScript
156 lines
4.4 KiB
TypeScript
import {
|
|
renderDevPanel,
|
|
renderLoggedIn,
|
|
renderLoggedOut,
|
|
setProtectedApiResult
|
|
} from "./view";
|
|
import { readAuthConfig, type AuthConfig } from "../shared/auth-config";
|
|
import {
|
|
isAuthResponseMessage,
|
|
type AuthResponseMessage
|
|
} from "../shared/auth-messages";
|
|
import { createProtectedApiClient } from "../shared/protected-api-client";
|
|
|
|
interface BootPopupOptions {
|
|
config?: Partial<AuthConfig>;
|
|
document?: Document;
|
|
fetchProtectedApi?: () => Promise<unknown>;
|
|
sendMessage?: (message: unknown) => Promise<unknown>;
|
|
}
|
|
|
|
export async function bootPopup(options: BootPopupOptions = {}): Promise<void> {
|
|
const currentDocument = options.document ?? document;
|
|
const popupConfig = readAuthConfig(options.config);
|
|
const root = currentDocument.querySelector("#app");
|
|
const HTMLElementCtor = currentDocument.defaultView?.HTMLElement;
|
|
|
|
if (!root || (HTMLElementCtor && !(root instanceof HTMLElementCtor))) {
|
|
throw new Error("popup root #app is required");
|
|
}
|
|
|
|
const sendMessage =
|
|
options.sendMessage ??
|
|
((message: unknown) =>
|
|
Promise.resolve(
|
|
(
|
|
globalThis as typeof globalThis & {
|
|
chrome?: {
|
|
runtime?: {
|
|
sendMessage?: (payload: unknown) => Promise<unknown>;
|
|
};
|
|
};
|
|
}
|
|
).chrome?.runtime?.sendMessage?.(message)
|
|
));
|
|
const fetchProtectedApi =
|
|
options.fetchProtectedApi ??
|
|
createProtectedApiClient({
|
|
baseUrl: "http://127.0.0.1:4319",
|
|
sendMessage
|
|
}).loadProtectedMockData;
|
|
|
|
await renderCurrentAuthState(root, popupConfig, sendMessage, fetchProtectedApi);
|
|
}
|
|
|
|
async function renderCurrentAuthState(
|
|
root: HTMLElement,
|
|
popupConfig: AuthConfig,
|
|
sendMessage: (message: unknown) => Promise<unknown>,
|
|
fetchProtectedApi: () => Promise<unknown>
|
|
): Promise<void> {
|
|
const response = await sendMessage({ type: "auth:get-state" });
|
|
if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") {
|
|
renderLoggedOut(root, "认证状态读取失败");
|
|
return;
|
|
}
|
|
|
|
if (!response.value.isAuthenticated) {
|
|
renderLoggedOut(root, response.value.lastError);
|
|
root
|
|
.querySelector('[data-popup-sign-in="button"]')
|
|
?.addEventListener("click", () => {
|
|
void runAuthAction(root, popupConfig, sendMessage, {
|
|
actionMessage: { type: "auth:sign-in" },
|
|
fetchProtectedApi
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
renderLoggedIn(root, response.value);
|
|
root
|
|
.querySelector('[data-popup-sign-out="button"]')
|
|
?.addEventListener("click", () => {
|
|
void runAuthAction(root, popupConfig, sendMessage, {
|
|
actionMessage: { type: "auth:sign-out" },
|
|
fetchProtectedApi
|
|
});
|
|
});
|
|
if (popupConfig.enableDevAuthPanel) {
|
|
renderDevPanel(root, response.value);
|
|
root
|
|
.querySelector('[data-popup-test-protected-api="button"]')
|
|
?.addEventListener("click", () => {
|
|
void runProtectedApiProbe(root, fetchProtectedApi);
|
|
});
|
|
}
|
|
}
|
|
|
|
async function runAuthAction(
|
|
root: HTMLElement,
|
|
popupConfig: AuthConfig,
|
|
sendMessage: (message: unknown) => Promise<unknown>,
|
|
options: {
|
|
actionMessage: { type: "auth:sign-in" } | { type: "auth:sign-out" };
|
|
fetchProtectedApi: () => Promise<unknown>;
|
|
}
|
|
): Promise<void> {
|
|
const response = await sendMessage(options.actionMessage);
|
|
|
|
if (isActionError(response)) {
|
|
renderLoggedOut(root, response.error);
|
|
root
|
|
.querySelector('[data-popup-sign-in="button"]')
|
|
?.addEventListener("click", () => {
|
|
void runAuthAction(root, popupConfig, sendMessage, options);
|
|
});
|
|
return;
|
|
}
|
|
|
|
await renderCurrentAuthState(
|
|
root,
|
|
popupConfig,
|
|
sendMessage,
|
|
options.fetchProtectedApi
|
|
);
|
|
}
|
|
|
|
function isActionError(response: unknown): response is Extract<AuthResponseMessage, { ok: false }> {
|
|
return (
|
|
isAuthResponseMessage(response) &&
|
|
!response.ok &&
|
|
response.type === "auth:error"
|
|
);
|
|
}
|
|
|
|
async function runProtectedApiProbe(
|
|
root: HTMLElement,
|
|
fetchProtectedApi: () => Promise<unknown>
|
|
): Promise<void> {
|
|
setProtectedApiResult(root, "请求中...");
|
|
|
|
try {
|
|
const result = await fetchProtectedApi();
|
|
setProtectedApiResult(root, JSON.stringify(result, null, 2));
|
|
} catch (error) {
|
|
setProtectedApiResult(
|
|
root,
|
|
error instanceof Error ? error.message : String(error)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (typeof document !== "undefined") {
|
|
void bootPopup();
|
|
}
|