(
req: Request,
options?: { disableIdleTimeout?: () => void },
)
| 124 | } |
| 125 | |
| 126 | export function handleFileBrowserFilesStream( |
| 127 | req: Request, |
| 128 | options?: { disableIdleTimeout?: () => void }, |
| 129 | ): Response { |
| 130 | const url = new URL(req.url); |
| 131 | const rawDirPaths = url.searchParams.getAll("dirPath"); |
| 132 | if (rawDirPaths.length === 0) { |
| 133 | return Response.json({ error: "Missing dirPath parameter" }, { status: 400 }); |
| 134 | } |
| 135 | |
| 136 | const dirPaths: string[] = []; |
| 137 | const clientDirPaths: string[] = []; |
| 138 | for (const rawDirPath of rawDirPaths) { |
| 139 | const dirPath = resolveUserPath(rawDirPath); |
| 140 | if (!isValidDirectory(dirPath)) { |
| 141 | return Response.json({ error: "Invalid directory path" }, { status: 400 }); |
| 142 | } |
| 143 | if (!dirPaths.includes(dirPath)) { |
| 144 | dirPaths.push(dirPath); |
| 145 | clientDirPaths.push(rawDirPath); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | options?.disableIdleTimeout?.(); |
| 150 | const entries = dirPaths.map((dirPath) => ensureWatcher(dirPath)); |
| 151 | |
| 152 | let controllerRef: ReadableStreamDefaultController | null = null; |
| 153 | let heartbeatTimer: ReturnType<typeof setInterval> | null = null; |
| 154 | const stream = new ReadableStream({ |
| 155 | start(controller) { |
| 156 | controllerRef = controller; |
| 157 | for (let i = 0; i < entries.length; i++) { |
| 158 | const entry = entries[i]!; |
| 159 | const clientDirPath = clientDirPaths[i] ?? entry.dirPath; |
| 160 | entry.subscribers.set(controller, clientDirPath); |
| 161 | controller.enqueue(serialize({ |
| 162 | type: "ready", |
| 163 | dirPath: clientDirPath, |
| 164 | reason: "initial", |
| 165 | timestamp: Date.now(), |
| 166 | })); |
| 167 | } |
| 168 | heartbeatTimer = setInterval(() => { |
| 169 | try { |
| 170 | controller.enqueue(encoder.encode(": heartbeat\n\n")); |
| 171 | } catch { |
| 172 | for (const entry of entries) releaseSubscriber(entry, controller); |
| 173 | if (heartbeatTimer) clearInterval(heartbeatTimer); |
| 174 | } |
| 175 | }, HEARTBEAT_MS); |
| 176 | }, |
| 177 | cancel() { |
| 178 | if (heartbeatTimer) clearInterval(heartbeatTimer); |
| 179 | if (controllerRef) { |
| 180 | for (const entry of entries) releaseSubscriber(entry, controllerRef); |
| 181 | } |
| 182 | }, |
| 183 | }); |
no test coverage detected