mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 16:16:55 +00:00
사소한 리팩토링
This commit is contained in:
@@ -19,13 +19,13 @@ export const generateHmacSecret = async () => {
|
||||
};
|
||||
|
||||
export const signMessageHmac = async (message: Blob, hmacSecret: CryptoKey) => {
|
||||
const worker = new HmacWorker();
|
||||
const stream = message.stream();
|
||||
const hmacSecretRaw = new Uint8Array(await crypto.subtle.exportKey("raw", hmacSecret));
|
||||
const worker = new HmacWorker();
|
||||
|
||||
return new Promise<Uint8Array>((resolve, reject) => {
|
||||
worker.onmessage = (event: MessageEvent<ResultMessage>) => {
|
||||
resolve(event.data.result);
|
||||
worker.onmessage = ({ data }: MessageEvent<ResultMessage>) => {
|
||||
resolve(data.result);
|
||||
worker.terminate();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from "axios";
|
||||
import { limitFunction } from "p-limit";
|
||||
import { CHUNK_SIZE, ENCRYPTION_OVERHEAD } from "$lib/constants";
|
||||
import { ENCRYPTED_CHUNK_SIZE } from "$lib/constants";
|
||||
import { decryptChunk, concatenateBuffers } from "$lib/modules/crypto";
|
||||
|
||||
export interface FileDownloadState {
|
||||
@@ -100,7 +100,7 @@ export const downloadFile = async (id: number, dataKey: CryptoKey, isLegacy: boo
|
||||
return await decryptFile(
|
||||
state,
|
||||
fileEncrypted,
|
||||
isLegacy ? fileEncrypted.byteLength : CHUNK_SIZE + ENCRYPTION_OVERHEAD,
|
||||
isLegacy ? fileEncrypted.byteLength : ENCRYPTED_CHUNK_SIZE,
|
||||
dataKey,
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { browser } from "$app/environment";
|
||||
import { decryptData } from "$lib/modules/crypto";
|
||||
import { decryptChunk } from "$lib/modules/crypto";
|
||||
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
|
||||
import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs";
|
||||
import { getThumbnailUrl } from "$lib/modules/thumbnail";
|
||||
@@ -20,12 +20,7 @@ const fetchFromServer = async (fileId: number, dataKey: CryptoKey) => {
|
||||
const res = await fetch(`/api/file/${fileId}/thumbnail/download`);
|
||||
if (!res.ok) return null;
|
||||
|
||||
const thumbnailEncrypted = await res.arrayBuffer();
|
||||
const thumbnailBuffer = await decryptData(
|
||||
thumbnailEncrypted.slice(12),
|
||||
thumbnailEncrypted.slice(0, 12),
|
||||
dataKey,
|
||||
);
|
||||
const thumbnailBuffer = await decryptChunk(await res.arrayBuffer(), dataKey);
|
||||
|
||||
void writeFile(`/thumbnail/file/${fileId}`, thumbnailBuffer);
|
||||
return getThumbnailUrl(thumbnailBuffer);
|
||||
|
||||
@@ -58,8 +58,7 @@ const requestDuplicateFileScan = limitFunction(
|
||||
) => {
|
||||
state.status = "encryption-pending";
|
||||
|
||||
const hmacResult = await signMessageHmac(file, hmacSecret.secret);
|
||||
const fileSigned = encodeToBase64(hmacResult);
|
||||
const fileSigned = encodeToBase64(await signMessageHmac(file, hmacSecret.secret));
|
||||
const files = await trpc().file.listByHash.query({
|
||||
hskVersion: hmacSecret.version,
|
||||
contentHmac: fileSigned,
|
||||
@@ -171,7 +170,7 @@ const requestFileUpload = limitFunction(
|
||||
await uploadBlob(uploadId, file, dataKey, {
|
||||
onProgress(s) {
|
||||
state.progress = s.progress;
|
||||
state.rate = s.rateBps;
|
||||
state.rate = s.rate;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,27 +3,32 @@ import pLimit from "p-limit";
|
||||
import { ENCRYPTION_OVERHEAD, CHUNK_SIZE } from "$lib/constants";
|
||||
import { encryptChunk, digestMessage, encodeToBase64 } from "$lib/modules/crypto";
|
||||
|
||||
type UploadStats = {
|
||||
progress: number; // 0..1 (암호화 후 기준)
|
||||
rateBps: number; // bytes/sec
|
||||
uploadedBytes: number;
|
||||
totalBytes: number;
|
||||
};
|
||||
interface UploadStats {
|
||||
progress: number;
|
||||
rate: number;
|
||||
}
|
||||
|
||||
const createSpeedMeter = (timeWindow = 1500) => {
|
||||
const samples: { t: number; b: number }[] = [];
|
||||
let lastSpeed = 0;
|
||||
|
||||
return (bytesNow?: number) => {
|
||||
if (!bytesNow) return lastSpeed;
|
||||
|
||||
function createSpeedMeter(windowMs = 1500) {
|
||||
const samples: Array<{ t: number; b: number }> = [];
|
||||
return (bytesNow: number) => {
|
||||
const now = performance.now();
|
||||
samples.push({ t: now, b: bytesNow });
|
||||
const cutoff = now - windowMs;
|
||||
|
||||
const cutoff = now - timeWindow;
|
||||
while (samples.length > 2 && samples[0]!.t < cutoff) samples.shift();
|
||||
|
||||
const first = samples[0]!;
|
||||
const dt = now - first.t;
|
||||
const db = bytesNow - first.b;
|
||||
return dt > 0 ? (db / dt) * 1000 : 0;
|
||||
|
||||
lastSpeed = dt > 0 ? (db / dt) * 1000 : 0;
|
||||
return lastSpeed;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const uploadChunk = async (
|
||||
uploadId: string,
|
||||
@@ -66,10 +71,10 @@ export const uploadBlob = async (
|
||||
if (!onProgress) return;
|
||||
|
||||
const uploadedBytes = uploadedByChunk.reduce((a, b) => a + b, 0);
|
||||
const rateBps = speedMeter(uploadedBytes);
|
||||
const rate = speedMeter(uploadedBytes);
|
||||
const progress = Math.min(1, uploadedBytes / totalBytes);
|
||||
|
||||
onProgress({ progress, rateBps, uploadedBytes, totalBytes });
|
||||
onProgress({ progress, rate });
|
||||
};
|
||||
|
||||
const onChunkProgress = (idx: number, loaded: number) => {
|
||||
@@ -84,7 +89,7 @@ export const uploadBlob = async (
|
||||
limit(() =>
|
||||
uploadChunk(
|
||||
uploadId,
|
||||
i + 1, // 1-based chunk index
|
||||
i + 1,
|
||||
blob.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE),
|
||||
dataKey,
|
||||
onChunkProgress,
|
||||
@@ -93,11 +98,5 @@ export const uploadBlob = async (
|
||||
),
|
||||
);
|
||||
|
||||
// 완료 보정
|
||||
onProgress?.({
|
||||
progress: 1,
|
||||
rateBps: 0,
|
||||
uploadedBytes: totalBytes,
|
||||
totalBytes,
|
||||
});
|
||||
onProgress?.({ progress: 1, rate: speedMeter() });
|
||||
};
|
||||
|
||||
@@ -497,21 +497,22 @@ export const migrateFileContent = async (
|
||||
userId: number,
|
||||
fileId: number,
|
||||
newPath: string,
|
||||
dekVersion: Date,
|
||||
encContentHash: string,
|
||||
) => {
|
||||
const file = await trx
|
||||
.selectFrom("file")
|
||||
.select(["path", "encrypted_content_iv"])
|
||||
.select(["path", "data_encryption_key_version", "encrypted_content_iv"])
|
||||
.where("id", "=", fileId)
|
||||
.where("user_id", "=", userId)
|
||||
.limit(1)
|
||||
.forUpdate()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!file) {
|
||||
throw new IntegrityError("File not found");
|
||||
}
|
||||
if (!file.encrypted_content_iv) {
|
||||
} else if (file.data_encryption_key_version.getTime() !== dekVersion.getTime()) {
|
||||
throw new IntegrityError("Invalid DEK version");
|
||||
} else if (!file.encrypted_content_iv) {
|
||||
throw new IntegrityError("File is not legacy");
|
||||
}
|
||||
|
||||
@@ -525,7 +526,6 @@ export const migrateFileContent = async (
|
||||
.where("id", "=", fileId)
|
||||
.where("user_id", "=", userId)
|
||||
.execute();
|
||||
|
||||
await trx
|
||||
.insertInto("file_log")
|
||||
.values({
|
||||
@@ -534,8 +534,7 @@ export const migrateFileContent = async (
|
||||
action: "migrate",
|
||||
})
|
||||
.execute();
|
||||
|
||||
return file.path;
|
||||
return { oldPath: file.path };
|
||||
};
|
||||
|
||||
export const addFileToCategory = async (fileId: number, categoryId: number) => {
|
||||
|
||||
@@ -52,11 +52,11 @@ export const up = async (db: Kysely<any>) => {
|
||||
"hmac_secret_key",
|
||||
["user_id", "version"],
|
||||
)
|
||||
.addCheckConstraint("upload_session_ck01", sql`uploaded_chunks <= total_chunks`)
|
||||
.addCheckConstraint(
|
||||
"upload_session_ck02",
|
||||
"upload_session_ck01",
|
||||
sql`length(bitmap) = ceil(total_chunks / 8.0)::integer`,
|
||||
)
|
||||
.addCheckConstraint("upload_session_ck02", sql`uploaded_chunks <= total_chunks`)
|
||||
.execute();
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ interface UploadSessionTable {
|
||||
uploaded_chunks: Generated<number>;
|
||||
expires_at: Date;
|
||||
|
||||
// For file uploads
|
||||
parent_id: number | null;
|
||||
master_encryption_key_version: number | null;
|
||||
encrypted_data_encryption_key: string | null; // Base64
|
||||
@@ -21,8 +20,6 @@ interface UploadSessionTable {
|
||||
encrypted_name: Ciphertext | null;
|
||||
encrypted_created_at: Ciphertext | null;
|
||||
encrypted_last_modified_at: Ciphertext | null;
|
||||
|
||||
// For thumbnail uploads
|
||||
file_id: number | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,17 +26,12 @@ interface FileUploadSession extends BaseUploadSession {
|
||||
encLastModifiedAt: Ciphertext;
|
||||
}
|
||||
|
||||
interface ThumbnailUploadSession extends BaseUploadSession {
|
||||
type: "thumbnail";
|
||||
interface ThumbnailOrMigrationUploadSession extends BaseUploadSession {
|
||||
type: "thumbnail" | "migration";
|
||||
fileId: number;
|
||||
dekVersion: Date;
|
||||
}
|
||||
|
||||
interface MigrationUploadSession extends BaseUploadSession {
|
||||
type: "migration";
|
||||
fileId: number;
|
||||
}
|
||||
|
||||
export const createFileUploadSession = async (
|
||||
params: Omit<FileUploadSession, "type" | "bitmap" | "uploadedChunks">,
|
||||
) => {
|
||||
@@ -91,8 +86,8 @@ export const createFileUploadSession = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const createThumbnailUploadSession = async (
|
||||
params: Omit<ThumbnailUploadSession, "type" | "bitmap" | "uploadedChunks">,
|
||||
export const createThumbnailOrMigrationUploadSession = async (
|
||||
params: Omit<ThumbnailOrMigrationUploadSession, "bitmap" | "uploadedChunks">,
|
||||
) => {
|
||||
await db.transaction().execute(async (trx) => {
|
||||
const file = await trx
|
||||
@@ -113,7 +108,7 @@ export const createThumbnailUploadSession = async (
|
||||
.insertInto("upload_session")
|
||||
.values({
|
||||
id: params.id,
|
||||
type: "thumbnail",
|
||||
type: params.type,
|
||||
user_id: params.userId,
|
||||
path: params.path,
|
||||
bitmap: Buffer.alloc(Math.ceil(params.totalChunks / 8)),
|
||||
@@ -126,40 +121,6 @@ export const createThumbnailUploadSession = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const createMigrationUploadSession = async (
|
||||
params: Omit<MigrationUploadSession, "type" | "bitmap" | "uploadedChunks">,
|
||||
) => {
|
||||
await db.transaction().execute(async (trx) => {
|
||||
const file = await trx
|
||||
.selectFrom("file")
|
||||
.select("encrypted_content_iv")
|
||||
.where("id", "=", params.fileId)
|
||||
.where("user_id", "=", params.userId)
|
||||
.limit(1)
|
||||
.forUpdate()
|
||||
.executeTakeFirst();
|
||||
if (!file) {
|
||||
throw new IntegrityError("File not found");
|
||||
} else if (!file.encrypted_content_iv) {
|
||||
throw new IntegrityError("File is not legacy");
|
||||
}
|
||||
|
||||
await trx
|
||||
.insertInto("upload_session")
|
||||
.values({
|
||||
id: params.id,
|
||||
type: "migration",
|
||||
user_id: params.userId,
|
||||
path: params.path,
|
||||
bitmap: Buffer.alloc(Math.ceil(params.totalChunks / 8)),
|
||||
total_chunks: params.totalChunks,
|
||||
expires_at: params.expiresAt,
|
||||
file_id: params.fileId,
|
||||
})
|
||||
.execute();
|
||||
});
|
||||
};
|
||||
|
||||
export const getUploadSession = async (sessionId: string, userId: number) => {
|
||||
const session = await db
|
||||
.selectFrom("upload_session")
|
||||
@@ -191,9 +152,9 @@ export const getUploadSession = async (sessionId: string, userId: number) => {
|
||||
encCreatedAt: session.encrypted_created_at,
|
||||
encLastModifiedAt: session.encrypted_last_modified_at!,
|
||||
} satisfies FileUploadSession;
|
||||
} else if (session.type === "thumbnail") {
|
||||
} else {
|
||||
return {
|
||||
type: "thumbnail",
|
||||
type: session.type,
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
path: session.path,
|
||||
@@ -203,19 +164,7 @@ export const getUploadSession = async (sessionId: string, userId: number) => {
|
||||
expiresAt: session.expires_at,
|
||||
fileId: session.file_id!,
|
||||
dekVersion: session.data_encryption_key_version!,
|
||||
} satisfies ThumbnailUploadSession;
|
||||
} else {
|
||||
return {
|
||||
type: "migration",
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
path: session.path,
|
||||
bitmap: session.bitmap,
|
||||
totalChunks: session.total_chunks,
|
||||
uploadedChunks: session.uploaded_chunks,
|
||||
expiresAt: session.expires_at,
|
||||
fileId: session.file_id!,
|
||||
} satisfies MigrationUploadSession;
|
||||
} satisfies ThumbnailOrMigrationUploadSession;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user