mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
fix(cloud PFP): Not pulling from cloud on page load
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolveCloudPfp } from "@/seqta/utils/cloudPfpCache";
|
import { resolveCloudPfp, defaultAccountsPfpUrl } from "@/seqta/utils/cloudPfpCache";
|
||||||
import type { CloudUser } from "@/seqta/utils/CloudAuth";
|
import type { CloudUser } from "@/seqta/utils/CloudAuth";
|
||||||
|
|
||||||
const { user, class: className = "" } = $props<{
|
const { user, class: className = "" } = $props<{
|
||||||
@@ -18,10 +18,12 @@
|
|||||||
}
|
}
|
||||||
avatarSrc = undefined;
|
avatarSrc = undefined;
|
||||||
|
|
||||||
if (!u?.pfpUrl || !u.id) return;
|
if (!u?.id) return;
|
||||||
|
|
||||||
|
const pfpUrl = u.pfpUrl ?? defaultAccountsPfpUrl(u.id);
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
void resolveCloudPfp(u.id, u.pfpUrl).then((resolved) => {
|
void resolveCloudPfp(u.id, pfpUrl).then((resolved) => {
|
||||||
if (cancelled || !resolved) return;
|
if (cancelled || !resolved) return;
|
||||||
if (resolved.fromCache) {
|
if (resolved.fromCache) {
|
||||||
revokeUrl = resolved.src;
|
revokeUrl = resolved.src;
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import browser from 'webextension-polyfill'
|
||||||
|
import CloudPfpAvatar from '@/interface/components/CloudPfpAvatar.svelte'
|
||||||
|
import { cloudAuth } from '@/seqta/utils/CloudAuth'
|
||||||
import {
|
import {
|
||||||
|
clearCloudProfilePicture,
|
||||||
isUseCloudPfpEnabled,
|
isUseCloudPfpEnabled,
|
||||||
notifyProfilePictureChanged,
|
notifyProfilePictureChanged,
|
||||||
|
pullCloudProfilePictureFromServer,
|
||||||
syncLocalProfilePictureToCloud,
|
syncLocalProfilePictureToCloud,
|
||||||
} from '@/seqta/utils/cloudPfpSync'
|
} from '@/seqta/utils/cloudPfpSync'
|
||||||
|
|
||||||
let value = $state<string | undefined>(undefined)
|
let value = $state<string | undefined>(undefined)
|
||||||
let fileInput = $state<HTMLInputElement | undefined>(undefined)
|
let fileInput = $state<HTMLInputElement | undefined>(undefined)
|
||||||
let dragging = $state(false)
|
let dragging = $state(false)
|
||||||
let blobUrl = $state<string | undefined>(undefined)
|
let blobUrl = $state<string | undefined>(undefined)
|
||||||
|
let useCloudPfp = $state(false)
|
||||||
|
let refreshingCloud = $state(false)
|
||||||
|
let cloudRefreshError = $state<string | undefined>(undefined)
|
||||||
|
|
||||||
// Setup localforage instance
|
|
||||||
const store = localforage.createInstance({
|
const store = localforage.createInstance({
|
||||||
name: 'profile-picture-store',
|
name: 'profile-picture-store',
|
||||||
storeName: 'profilePicture',
|
storeName: 'profilePicture',
|
||||||
})
|
})
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
|
useCloudPfp = await isUseCloudPfpEnabled()
|
||||||
const blob = await store.getItem<Blob>('profile-picture')
|
const blob = await store.getItem<Blob>('profile-picture')
|
||||||
if (blob && blob instanceof Blob) {
|
if (blob && blob instanceof Blob) {
|
||||||
// Revoke old blobUrl if any
|
|
||||||
if (blobUrl) URL.revokeObjectURL(blobUrl)
|
if (blobUrl) URL.revokeObjectURL(blobUrl)
|
||||||
blobUrl = URL.createObjectURL(blob)
|
blobUrl = URL.createObjectURL(blob)
|
||||||
value = blobUrl
|
value = blobUrl
|
||||||
@@ -28,7 +37,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load()
|
onMount(() => {
|
||||||
|
void load()
|
||||||
|
const onStorage = (
|
||||||
|
changes: Record<string, browser.Storage.StorageChange>,
|
||||||
|
areaName: string,
|
||||||
|
) => {
|
||||||
|
if (areaName === 'local' && changes['plugin.profile-picture.settings']) {
|
||||||
|
void load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
browser.storage.onChanged.addListener(onStorage)
|
||||||
|
return () => browser.storage.onChanged.removeListener(onStorage)
|
||||||
|
})
|
||||||
|
|
||||||
async function afterProfilePictureChange() {
|
async function afterProfilePictureChange() {
|
||||||
window.dispatchEvent(new Event('profile-picture-updated'))
|
window.dispatchEvent(new Event('profile-picture-updated'))
|
||||||
@@ -38,6 +59,16 @@
|
|||||||
await notifyProfilePictureChanged()
|
await notifyProfilePictureChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshCloudPfp() {
|
||||||
|
cloudRefreshError = undefined
|
||||||
|
refreshingCloud = true
|
||||||
|
const result = await pullCloudProfilePictureFromServer()
|
||||||
|
refreshingCloud = false
|
||||||
|
if (!result.success) {
|
||||||
|
cloudRefreshError = result.error ?? 'Could not refresh cloud photo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function triggerSelect() {
|
function triggerSelect() {
|
||||||
fileInput?.click()
|
fileInput?.click()
|
||||||
}
|
}
|
||||||
@@ -46,12 +77,10 @@
|
|||||||
const file = files?.[0]
|
const file = files?.[0]
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
|
||||||
// Revoke old blob URL if it exists
|
|
||||||
if (blobUrl) {
|
if (blobUrl) {
|
||||||
URL.revokeObjectURL(blobUrl)
|
URL.revokeObjectURL(blobUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the blob in localforage
|
|
||||||
await store.setItem('profile-picture', file)
|
await store.setItem('profile-picture', file)
|
||||||
const newBlobUrl = URL.createObjectURL(file)
|
const newBlobUrl = URL.createObjectURL(file)
|
||||||
value = newBlobUrl
|
value = newBlobUrl
|
||||||
@@ -76,42 +105,64 @@
|
|||||||
}
|
}
|
||||||
value = undefined
|
value = undefined
|
||||||
await store.removeItem('profile-picture')
|
await store.removeItem('profile-picture')
|
||||||
|
if (await isUseCloudPfpEnabled()) {
|
||||||
|
await clearCloudProfilePicture()
|
||||||
|
}
|
||||||
await afterProfilePictureChange()
|
await afterProfilePictureChange()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="flex flex-col gap-2 items-end">
|
||||||
class="flex relative justify-center items-center rounded-lg cursor-pointer select-none border-zinc-300 dark:border-zinc-600 bg-white/20 dark:bg-zinc-800/30"
|
{#if useCloudPfp}
|
||||||
onclick={() => value ? null : triggerSelect()}
|
<div class="flex gap-2 items-center">
|
||||||
ondragover={(e) => { e.stopPropagation(); dragging = true }}
|
<CloudPfpAvatar user={cloudAuth.state.user} class="object-cover rounded-full size-10" />
|
||||||
ondragleave={() => dragging = false}
|
<button
|
||||||
ondrop={onDrop}
|
type="button"
|
||||||
onkeydown={(e) => {
|
class="px-2 py-1 text-xs rounded-md bg-zinc-200 dark:bg-zinc-700 hover:bg-zinc-300 dark:hover:bg-zinc-600 disabled:opacity-50"
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
disabled={refreshingCloud}
|
||||||
e.preventDefault()
|
onclick={() => refreshCloudPfp()}
|
||||||
triggerSelect()
|
>
|
||||||
}
|
{refreshingCloud ? 'Refreshing…' : 'Refresh from cloud'}
|
||||||
}}
|
</button>
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
{#if value}
|
|
||||||
<img src={value} alt="Profile" class="object-cover rounded-full size-10" />
|
|
||||||
<button
|
|
||||||
class="flex justify-center items-center m-1 text-lg dark:text-white size-7"
|
|
||||||
onclick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
removeImage()
|
|
||||||
}}
|
|
||||||
>×</button>
|
|
||||||
{:else}
|
|
||||||
<div class="flex gap-2 items-center px-3 py-1 text-xs rounded-lg border border-dashed transition border-zinc-300 dark:border-zinc-600 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300">
|
|
||||||
<span class="text-lg font-IconFamily">{'\ued47'}</span>
|
|
||||||
<span>Upload</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
{#if cloudRefreshError}
|
||||||
|
<p class="text-xs text-red-500">{cloudRefreshError}</p>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<input type="file" accept="image/*" class="hidden" bind:this={fileInput} onchange={onFileChange} />
|
|
||||||
{#if dragging}
|
<div
|
||||||
<div class="absolute inset-0 rounded-full bg-zinc-200/40 dark:bg-zinc-700/40"></div>
|
class="flex relative justify-center items-center rounded-lg cursor-pointer select-none border-zinc-300 dark:border-zinc-600 bg-white/20 dark:bg-zinc-800/30"
|
||||||
{/if}
|
onclick={() => value ? null : triggerSelect()}
|
||||||
|
ondragover={(e) => { e.stopPropagation(); dragging = true }}
|
||||||
|
ondragleave={() => dragging = false}
|
||||||
|
ondrop={onDrop}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
triggerSelect()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
{#if value}
|
||||||
|
<img src={value} alt="Profile" class="object-cover rounded-full size-10" />
|
||||||
|
<button
|
||||||
|
class="flex justify-center items-center m-1 text-lg dark:text-white size-7"
|
||||||
|
onclick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
removeImage()
|
||||||
|
}}
|
||||||
|
>×</button>
|
||||||
|
{:else}
|
||||||
|
<div class="flex gap-2 items-center px-3 py-1 text-xs rounded-lg border border-dashed transition border-zinc-300 dark:border-zinc-600 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300">
|
||||||
|
<span class="text-lg font-IconFamily">{'\ued47'}</span>
|
||||||
|
<span>Upload</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<input type="file" accept="image/*" class="hidden" bind:this={fileInput} onchange={onFileChange} />
|
||||||
|
{#if dragging}
|
||||||
|
<div class="absolute inset-0 rounded-full bg-zinc-200/40 dark:bg-zinc-700/40"></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import ProfilePictureSetting from "./ProfilePictureSetting.svelte";
|
|||||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import { cloudAuth } from "@/seqta/utils/CloudAuth";
|
import { cloudAuth } from "@/seqta/utils/CloudAuth";
|
||||||
import { resolveCloudPfp } from "@/seqta/utils/cloudPfpCache";
|
import { resolveCloudPfp, defaultAccountsPfpUrl } from "@/seqta/utils/cloudPfpCache";
|
||||||
import styles from "./styles.css?inline";
|
import styles from "./styles.css?inline";
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
|
|
||||||
@@ -64,10 +64,13 @@ const profilePicturePlugin: Plugin<typeof settings> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useCloud = api.settings.useCloudPfp;
|
const useCloud = api.settings.useCloudPfp;
|
||||||
const pfpUrl = cloudAuth.state.user?.pfpUrl;
|
const userId = cloudAuth.state.user?.id;
|
||||||
|
const pfpUrl =
|
||||||
|
cloudAuth.state.user?.pfpUrl ??
|
||||||
|
(userId ? defaultAccountsPfpUrl(userId) : undefined);
|
||||||
|
|
||||||
if (useCloud && pfpUrl && cloudAuth.state.user?.id) {
|
if (useCloud && pfpUrl && userId) {
|
||||||
const resolved = await resolveCloudPfp(cloudAuth.state.user.id, pfpUrl);
|
const resolved = await resolveCloudPfp(userId, pfpUrl);
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
currentBlobUrl = resolved.src;
|
currentBlobUrl = resolved.src;
|
||||||
img = document.createElement("img");
|
img = document.createElement("img");
|
||||||
@@ -92,6 +95,13 @@ const profilePicturePlugin: Plugin<typeof settings> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (api.settings.useCloudPfp && cloudAuth.state.isLoggedIn) {
|
||||||
|
const { pullCloudProfilePictureFromServer } = await import(
|
||||||
|
"@/seqta/utils/cloudPfpSync"
|
||||||
|
);
|
||||||
|
await pullCloudProfilePictureFromServer();
|
||||||
|
}
|
||||||
|
|
||||||
await applyProfileImage();
|
await applyProfileImage();
|
||||||
|
|
||||||
const onLocalPictureUpdated = () => {
|
const onLocalPictureUpdated = () => {
|
||||||
@@ -114,11 +124,9 @@ const profilePicturePlugin: Plugin<typeof settings> = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const useCloudUnreg = api.settings.onChange("useCloudPfp", (enabled: boolean) => {
|
const useCloudUnreg = api.settings.onChange("useCloudPfp", (enabled: boolean) => {
|
||||||
if (enabled) {
|
void import("@/seqta/utils/cloudPfpSync").then(({ onUseCloudPfpToggled }) =>
|
||||||
void import("@/seqta/utils/cloudPfpSync").then(({ syncLocalProfilePictureToCloud }) =>
|
onUseCloudPfpToggled(enabled),
|
||||||
syncLocalProfilePictureToCloud(),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
void applyProfileImage();
|
void applyProfileImage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ export function pfpUrlWithHash(url: string, hash: string | null | undefined): st
|
|||||||
return `${base}?v=${hash}`;
|
return `${base}?v=${hash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function defaultAccountsPfpUrl(userId: string): string {
|
||||||
|
return `${ACCOUNTS_BASE}/api/user/pfp/${userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchServerHash(userId: string): Promise<string | null> {
|
async function fetchServerHash(userId: string): Promise<string | null> {
|
||||||
const res = await fetch(`${ACCOUNTS_BASE}/api/user/pfp/${userId}/meta`);
|
const res = await fetch(`${ACCOUNTS_BASE}/api/user/pfp/${userId}/meta`);
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
@@ -95,6 +99,10 @@ export async function resolveCloudPfp(
|
|||||||
if (localHash) {
|
if (localHash) {
|
||||||
headers["If-None-Match"] = `"${localHash}"`;
|
headers["If-None-Match"] = `"${localHash}"`;
|
||||||
}
|
}
|
||||||
|
const token = await cloudAuth.getStoredToken();
|
||||||
|
if (token && isAccountsHostedPfpUrl(pfpUrl)) {
|
||||||
|
headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch(imageUrl, { headers });
|
const res = await fetch(imageUrl, { headers });
|
||||||
if (res.status === 304 && localBlob instanceof Blob) {
|
if (res.status === 304 && localBlob instanceof Blob) {
|
||||||
|
|||||||
+131
-18
@@ -1,7 +1,10 @@
|
|||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
import { cloudAuth } from "@/seqta/utils/CloudAuth";
|
import { cloudAuth, type CloudUser } from "@/seqta/utils/CloudAuth";
|
||||||
import { clearCloudPfpCache, pfpUrlWithHash } from "@/seqta/utils/cloudPfpCache";
|
import {
|
||||||
|
clearCloudPfpCache,
|
||||||
|
pfpUrlWithHash,
|
||||||
|
} from "@/seqta/utils/cloudPfpCache";
|
||||||
|
|
||||||
const ACCOUNTS_BASE = "https://accounts.betterseqta.org";
|
const ACCOUNTS_BASE = "https://accounts.betterseqta.org";
|
||||||
const PLUGIN_SETTINGS_KEY = "plugin.profile-picture.settings";
|
const PLUGIN_SETTINGS_KEY = "plugin.profile-picture.settings";
|
||||||
@@ -54,6 +57,92 @@ async function parseJsonResponse(r: Response): Promise<Record<string, unknown>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeMeIntoUser(current: CloudUser, data: Record<string, unknown>): CloudUser {
|
||||||
|
const raw = (data.user as Record<string, unknown> | undefined) ?? data;
|
||||||
|
const pfpUrlRaw = raw.pfpUrl as string | null | undefined;
|
||||||
|
const pfpHash = (raw.pfpHash as string | null | undefined) ?? null;
|
||||||
|
const pfpUrl =
|
||||||
|
pfpUrlRaw == null || pfpUrlRaw === ""
|
||||||
|
? undefined
|
||||||
|
: pfpUrlWithHash(pfpUrlRaw, pfpHash);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
email: (raw.email as string | undefined) ?? current.email,
|
||||||
|
username: (raw.username as string | undefined) ?? current.username,
|
||||||
|
displayName: (raw.displayName as string | undefined) ?? current.displayName,
|
||||||
|
admin_level: (raw.admin_level as number | undefined) ?? current.admin_level,
|
||||||
|
pfpUrl,
|
||||||
|
pfpHash,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch `/api/auth/me` and update stored user (pfpUrl / pfpHash). */
|
||||||
|
export async function refreshCloudUserFromServer(): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}> {
|
||||||
|
if (!cloudAuth.state.isLoggedIn) {
|
||||||
|
return { success: false, error: "Not signed in to BetterSEQTA Cloud" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await cloudAuth.getStoredToken();
|
||||||
|
if (!token) return { success: false, error: "Not signed in to BetterSEQTA Cloud" };
|
||||||
|
|
||||||
|
const current = cloudAuth.state.user;
|
||||||
|
if (!current?.id) return { success: false, error: "No cloud user on this device" };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${ACCOUNTS_BASE}/api/auth/me`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
const data = await parseJsonResponse(res);
|
||||||
|
if (!res.ok) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: (data.error as string) ?? `Could not refresh account (${res.status})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await cloudAuth.setUser(mergeMeIntoUser(current, data));
|
||||||
|
return { success: true };
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: err instanceof Error ? err.message : "Could not refresh account",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pull cloud avatar metadata from the server and refresh the in-page profile image. */
|
||||||
|
export async function pullCloudProfilePictureFromServer(): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}> {
|
||||||
|
const refreshed = await refreshCloudUserFromServer();
|
||||||
|
if (!refreshed.success) return refreshed;
|
||||||
|
|
||||||
|
const userId = cloudAuth.state.user?.id;
|
||||||
|
if (userId) await clearCloudPfpCache(userId);
|
||||||
|
await notifyProfilePictureChanged();
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** When cloud PFP is enabled: upload local image if present, otherwise pull from server. */
|
||||||
|
export async function onUseCloudPfpToggled(enabled: boolean): Promise<void> {
|
||||||
|
if (!enabled) {
|
||||||
|
await notifyProfilePictureChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await profileStore.getItem<Blob>("profile-picture");
|
||||||
|
if (blob instanceof Blob) {
|
||||||
|
await syncLocalProfilePictureToCloud();
|
||||||
|
} else {
|
||||||
|
await pullCloudProfilePictureFromServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function syncLocalProfilePictureToCloud(): Promise<{
|
export async function syncLocalProfilePictureToCloud(): Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -72,22 +161,7 @@ export async function syncLocalProfilePictureToCloud(): Promise<{
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!blob || !(blob instanceof Blob)) {
|
if (!blob || !(blob instanceof Blob)) {
|
||||||
const res = await fetch(`${ACCOUNTS_BASE}/api/user/pfp/clear`, {
|
// No local upload — keep the server avatar; do not clear cloud.
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
const data = await parseJsonResponse(res);
|
|
||||||
if (!res.ok) {
|
|
||||||
return { success: false, error: (data.error as string) ?? `Clear failed (${res.status})` };
|
|
||||||
}
|
|
||||||
if (user) {
|
|
||||||
await cloudAuth.setUser({ ...user, pfpUrl: undefined, pfpHash: null });
|
|
||||||
}
|
|
||||||
if (userId) await clearCloudPfpCache(userId);
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +205,45 @@ export async function syncLocalProfilePictureToCloud(): Promise<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Upload local image to cloud, or clear cloud only when explicitly removing local image. */
|
||||||
|
export async function clearCloudProfilePicture(): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}> {
|
||||||
|
if (!cloudAuth.state.isLoggedIn) return { success: true };
|
||||||
|
|
||||||
|
const token = await cloudAuth.getStoredToken();
|
||||||
|
if (!token) return { success: false, error: "Not logged in" };
|
||||||
|
|
||||||
|
const user = cloudAuth.state.user;
|
||||||
|
const userId = user?.id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${ACCOUNTS_BASE}/api/user/pfp/clear`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
});
|
||||||
|
const data = await parseJsonResponse(res);
|
||||||
|
if (!res.ok) {
|
||||||
|
return { success: false, error: (data.error as string) ?? `Clear failed (${res.status})` };
|
||||||
|
}
|
||||||
|
if (user) {
|
||||||
|
await cloudAuth.setUser({ ...user, pfpUrl: undefined, pfpHash: null });
|
||||||
|
}
|
||||||
|
if (userId) await clearCloudPfpCache(userId);
|
||||||
|
return { success: true };
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: err instanceof Error ? err.message : "Cloud profile picture clear failed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Notify SEQTA content scripts to refresh the in-page profile image. */
|
/** Notify SEQTA content scripts to refresh the in-page profile image. */
|
||||||
export async function notifyProfilePictureChanged(): Promise<void> {
|
export async function notifyProfilePictureChanged(): Promise<void> {
|
||||||
const revision = Date.now();
|
const revision = Date.now();
|
||||||
|
|||||||
Reference in New Issue
Block a user