서버, 클라이언트, 서비스 워커 공용 코드를 lib/shared로 분리하고 브라우저 전용 모듈을 lib/client로 재구성

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
staticandClaude Opus 5.5 committed 2026-10-12 00:04:56 +09:00
1 parent 6a6aa1765b
commit aee87fd9b6
82 files changed
+326 -221

No files matched your search

-1
View File
@@ -1 +0,0 @@
export * from "../lib/constants";
+41 -23
View File
@@ -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,
);
+5 -2
View File
@@ -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;
-40
View File
@@ -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
View File
@@ -1 +0,0 @@
export * from "../../lib/modules/http";
+1 -1
View File
@@ -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
View File
@@ -1 +0,0 @@
export * from "../../lib/modules/opfs";
+1 -1
View File
@@ -1,4 +1,4 @@
import type { FileMetadata } from "./types";
import type { FileMetadata } from "#lib/shared/serviceWorker.js";
const MAX_FILE_METADATA_ENTRIES = 32;
-1
View File
@@ -1 +0,0 @@
export * from "../lib/serviceWorker/types";