Files
lms-adm-fe/src/lib/profileAvatar.ts
T

87 lines
3.1 KiB
TypeScript

const PROFILE_API_BASE_URL = (import.meta.env.VITE_LAUNCHER_API_BASE_URL ?? 'http://localhost:4200/api').replace(/\/+$/, '');
const AVATAR_MAX_BYTES = 80 * 1024;
export async function fetchProfileAvatar(accessToken: string, version: number): Promise<Blob | null> {
const response = await fetch(`${PROFILE_API_BASE_URL}/auth/profile/avatar?v=${version}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
credentials: 'include',
});
if (response.status === 404) return null;
if (!response.ok) throw new Error('avatar_load_failed');
return response.blob();
}
export async function uploadProfileAvatar(accessToken: string, imageBlob: Blob): Promise<void> {
const response = await fetch(`${PROFILE_API_BASE_URL}/auth/profile/avatar`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': imageBlob.type,
},
credentials: 'include',
body: imageBlob,
});
if (!response.ok) throw new Error('avatar_upload_failed');
}
export async function deleteProfileAvatar(accessToken: string): Promise<void> {
const response = await fetch(`${PROFILE_API_BASE_URL}/auth/profile/avatar`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${accessToken}`,
},
credentials: 'include',
});
if (!response.ok) throw new Error('avatar_delete_failed');
}
export async function resizeImageToWebp(file: File, size = 128): Promise<Blob> {
if (!file.type.startsWith('image/')) throw new Error('이미지 파일만 등록할 수 있습니다.');
const dataUrl = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result));
reader.onerror = () => reject(new Error('이미지를 읽을 수 없습니다.'));
reader.readAsDataURL(file);
});
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('이미지를 불러올 수 없습니다.'));
img.src = dataUrl;
});
const sourceSize = Math.min(image.naturalWidth, image.naturalHeight);
const sourceX = Math.max(0, (image.naturalWidth - sourceSize) / 2);
const sourceY = Math.max(0, (image.naturalHeight - sourceSize) / 2);
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
if (!context) throw new Error('이미지를 처리할 수 없습니다.');
context.drawImage(image, sourceX, sourceY, sourceSize, sourceSize, 0, 0, size, size);
const blob = await new Promise<Blob | null>((resolve) => {
canvas.toBlob(resolve, 'image/webp', 0.82);
});
if (!blob) throw new Error('이미지를 압축할 수 없습니다.');
if (blob.size > AVATAR_MAX_BYTES) throw new Error('이미지 용량이 너무 큽니다.');
return blob;
}
export function notifyProfileAvatarUpdated(): void {
window.dispatchEvent(new Event('profile-avatar-updated'));
}
export function notifyProfileAvatarDeleted(): void {
window.dispatchEvent(new Event('profile-avatar-deleted'));
}