feat: implement browser extension for media publishing and add backend support for media management

This commit is contained in:
2026-04-03 00:39:15 +08:00
parent 8958cb44c0
commit 32d6a462cd
60 changed files with 8268 additions and 26 deletions
+99
View File
@@ -0,0 +1,99 @@
import { browser } from "wxt/browser";
import { createDefaultPlatformStates, supportedPlatforms, type StoredPlatformState } from "./platforms";
export interface ExtensionStorageState {
installation_key: string;
plugin_installation_id: number | null;
installation_token: string | null;
api_base_url: string | null;
platforms: StoredPlatformState[];
last_updated_at: string | null;
}
const STORAGE_KEY = "geo.publisher.state";
function nextInstallationKey(): string {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
return crypto.randomUUID();
}
return `geo-extension-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
function mergePlatformStates(platforms?: StoredPlatformState[]): StoredPlatformState[] {
const defaults = createDefaultPlatformStates();
if (!platforms?.length) {
return defaults;
}
const incoming = new Map(platforms.map((platform) => [platform.platform_id, platform]));
return defaults.map((platform) => {
const stored = incoming.get(platform.platform_id);
return stored ? { ...platform, ...stored } : platform;
});
}
async function writeState(nextState: ExtensionStorageState): Promise<void> {
await browser.storage.local.set({
[STORAGE_KEY]: nextState,
});
}
export async function getExtensionState(): Promise<ExtensionStorageState> {
const storageValue = await browser.storage.local.get(STORAGE_KEY);
const stored = storageValue[STORAGE_KEY] as Partial<ExtensionStorageState> | undefined;
const nextState: ExtensionStorageState = {
installation_key: stored?.installation_key || nextInstallationKey(),
plugin_installation_id: stored?.plugin_installation_id ?? null,
installation_token: stored?.installation_token ?? null,
api_base_url: stored?.api_base_url ?? null,
platforms: mergePlatformStates(stored?.platforms),
last_updated_at: stored?.last_updated_at ?? null,
};
const shouldPersist =
!stored?.installation_key ||
!stored?.platforms ||
stored.platforms.length !== supportedPlatforms.length;
if (shouldPersist) {
await writeState(nextState);
}
return nextState;
}
export async function patchExtensionState(
patch: Partial<Omit<ExtensionStorageState, "platforms">> & { platforms?: StoredPlatformState[] },
): Promise<ExtensionStorageState> {
const current = await getExtensionState();
const nextState: ExtensionStorageState = {
...current,
...patch,
platforms: patch.platforms ? mergePlatformStates(patch.platforms) : current.platforms,
last_updated_at: new Date().toISOString(),
};
await writeState(nextState);
return nextState;
}
export async function updatePlatformState(
platformId: string,
updater: Partial<StoredPlatformState>,
): Promise<ExtensionStorageState> {
const current = await getExtensionState();
const platforms = current.platforms.map((platform) =>
platform.platform_id === platformId
? {
...platform,
...updater,
}
: platform,
);
return patchExtensionState({ platforms });
}
export async function resetPlatformStates(): Promise<ExtensionStorageState> {
return patchExtensionState({ platforms: createDefaultPlatformStates() });
}