mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-11-04 08:10:28 +09:00
서버, 클라이언트, 서비스 워커 공용 코드를 lib/shared로 분리하고 브라우저 전용 모듈을 lib/client로 재구성
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a6aa1765b
commit
aee87fd9b6
82 files changed
+326
-221
No files matched your search
@@ -1 +0,0 @@
|
||||
export * from "../lib/constants";
|
||||
@@ -1,14 +1,25 @@
|
||||
import { DECRYPTED_FILE_URL_PREFIX, CHUNK_SIZE, ENCRYPTED_CHUNK_SIZE } from "../constants";
|
||||
import { decryptChunk, getEncryptedRange, getDecryptedSize } from "../modules/crypto";
|
||||
import { parseRangeHeader, getContentRangeHeader } from "../modules/http";
|
||||
import { getFile } from "#lib/client/storage/opfs.js";
|
||||
import { decryptChunk } from "#lib/shared/crypto/index.js";
|
||||
import {
|
||||
CHUNK_SIZE,
|
||||
ENCRYPTED_CHUNK_SIZE,
|
||||
getDecryptedSize,
|
||||
getEncryptedRange,
|
||||
} from "#lib/shared/format/index.js";
|
||||
import {
|
||||
getContentRangeHeader,
|
||||
parseRangeHeader,
|
||||
resolveRange,
|
||||
type ByteRange,
|
||||
type RangeRequest,
|
||||
} from "#lib/shared/http.js";
|
||||
import { DECRYPTED_FILE_URL_PREFIX, type FileMetadata } from "#lib/shared/serviceWorker.js";
|
||||
import { getFileMetadata } from "../modules/metadata";
|
||||
import { getFile } from "../modules/opfs";
|
||||
import type { FileMetadata } from "../types";
|
||||
|
||||
const createResponse = (
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
isRangeRequest: boolean,
|
||||
range: { start: number; end: number; total: number },
|
||||
range: ByteRange,
|
||||
contentType?: string,
|
||||
downloadFilename?: string,
|
||||
) => {
|
||||
@@ -33,22 +44,28 @@ const createResponse = (
|
||||
});
|
||||
};
|
||||
|
||||
const createRangeNotSatisfiableResponse = (total: number) => {
|
||||
return new Response("Invalid range", {
|
||||
status: 416,
|
||||
headers: { "Content-Range": `bytes */${total}` },
|
||||
});
|
||||
};
|
||||
|
||||
const streamFromOpfs = async (
|
||||
file: File,
|
||||
metadata?: FileMetadata,
|
||||
range?: { start?: number; end?: number },
|
||||
rangeRequest?: RangeRequest,
|
||||
downloadFilename?: string,
|
||||
) => {
|
||||
const start = range?.start ?? 0;
|
||||
const end = range?.end ?? file.size - 1;
|
||||
if (start > end || start < 0 || end >= file.size) {
|
||||
return new Response("Invalid range", { status: 416 });
|
||||
const range = resolveRange(rangeRequest, file.size);
|
||||
if (!range) {
|
||||
return createRangeNotSatisfiableResponse(file.size);
|
||||
}
|
||||
|
||||
return createResponse(
|
||||
file.slice(start, end + 1).stream(),
|
||||
!!range,
|
||||
{ start, end, total: file.size },
|
||||
file.slice(range.start, range.end + 1).stream(),
|
||||
!!rangeRequest,
|
||||
range,
|
||||
metadata?.contentType,
|
||||
downloadFilename,
|
||||
);
|
||||
@@ -57,16 +74,16 @@ const streamFromOpfs = async (
|
||||
const streamFromServer = async (
|
||||
id: number,
|
||||
metadata: FileMetadata,
|
||||
range?: { start?: number; end?: number },
|
||||
rangeRequest?: RangeRequest,
|
||||
downloadFilename?: string,
|
||||
) => {
|
||||
const totalSize = getDecryptedSize(metadata.encContentSize, metadata.isLegacy);
|
||||
const start = range?.start ?? 0;
|
||||
const end = range?.end ?? totalSize - 1;
|
||||
if (start > end || start < 0 || end >= totalSize) {
|
||||
return new Response("Invalid range", { status: 416 });
|
||||
const range = resolveRange(rangeRequest, totalSize);
|
||||
if (!range) {
|
||||
return createRangeNotSatisfiableResponse(totalSize);
|
||||
}
|
||||
|
||||
const { start, end } = range;
|
||||
const encryptedRange = getEncryptedRange(start, end, metadata.encContentSize, metadata.isLegacy);
|
||||
const apiResponse = await fetch(`/api/file/${id}/download`, {
|
||||
headers: { Range: `bytes=${encryptedRange.start}-${encryptedRange.end}` },
|
||||
@@ -85,9 +102,10 @@ const streamFromServer = async (
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
!!range,
|
||||
{ start, end, total: totalSize },
|
||||
!!rangeRequest,
|
||||
range,
|
||||
metadata.contentType,
|
||||
downloadFilename,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,8 +142,8 @@ const streamFromServer = async (
|
||||
|
||||
return createResponse(
|
||||
apiResponse.body.pipeThrough(decryptingStream),
|
||||
!!range,
|
||||
{ start, end, total: totalSize },
|
||||
!!rangeRequest,
|
||||
range,
|
||||
metadata.contentType,
|
||||
downloadFilename,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { self } from "$app/service-worker";
|
||||
import { DECRYPTED_FILE_URL_PREFIX } from "./constants";
|
||||
import {
|
||||
DECRYPTED_FILE_URL_PREFIX,
|
||||
type ServiceWorkerMessage,
|
||||
type ServiceWorkerResponse,
|
||||
} from "#lib/shared/serviceWorker.js";
|
||||
import { decryptFile } from "./handlers";
|
||||
import { fileMetadataStore } from "./stores";
|
||||
import type { ServiceWorkerMessage, ServiceWorkerResponse } from "./types";
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
const message: ServiceWorkerMessage = event.data;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { ENCRYPTION_OVERHEAD, CHUNK_SIZE, ENCRYPTED_CHUNK_SIZE } from "../constants";
|
||||
|
||||
export * from "../../lib/modules/crypto";
|
||||
|
||||
export const getEncryptedRange = (
|
||||
start: number,
|
||||
end: number,
|
||||
totalEncryptedSize: number,
|
||||
isLegacy: boolean,
|
||||
) => {
|
||||
if (isLegacy) {
|
||||
return {
|
||||
firstChunkIndex: 0,
|
||||
lastChunkIndex: 0,
|
||||
start: 0,
|
||||
end: totalEncryptedSize - 1,
|
||||
};
|
||||
}
|
||||
|
||||
const firstChunkIndex = Math.floor(start / CHUNK_SIZE);
|
||||
const lastChunkIndex = Math.floor(end / CHUNK_SIZE);
|
||||
return {
|
||||
firstChunkIndex,
|
||||
lastChunkIndex,
|
||||
start: firstChunkIndex * ENCRYPTED_CHUNK_SIZE,
|
||||
end: Math.min((lastChunkIndex + 1) * ENCRYPTED_CHUNK_SIZE - 1, totalEncryptedSize - 1),
|
||||
};
|
||||
};
|
||||
|
||||
export const getDecryptedSize = (encryptedSize: number, isLegacy: boolean) => {
|
||||
if (isLegacy) {
|
||||
return encryptedSize - ENCRYPTION_OVERHEAD;
|
||||
}
|
||||
|
||||
const fullChunks = Math.floor(encryptedSize / ENCRYPTED_CHUNK_SIZE);
|
||||
const lastChunkEncSize = encryptedSize % ENCRYPTED_CHUNK_SIZE;
|
||||
return (
|
||||
fullChunks * CHUNK_SIZE + (lastChunkEncSize > 0 ? lastChunkEncSize - ENCRYPTION_OVERHEAD : 0)
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "../../lib/modules/http";
|
||||
@@ -1,6 +1,6 @@
|
||||
import { self } from "$app/service-worker";
|
||||
import type { FileMetadata, ServiceWorkerResponse } from "#lib/shared/serviceWorker.js";
|
||||
import { fileMetadataStore } from "../stores";
|
||||
import type { FileMetadata, ServiceWorkerResponse } from "../types";
|
||||
|
||||
const METADATA_REQUEST_TIMEOUT_MS = 3000;
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "../../lib/modules/opfs";
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FileMetadata } from "./types";
|
||||
import type { FileMetadata } from "#lib/shared/serviceWorker.js";
|
||||
|
||||
const MAX_FILE_METADATA_ENTRIES = 32;
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "../lib/serviceWorker/types";
|
||||
Reference in new issue
Block a user