1import * as Effect from "effect/Effect";
2import * as FileSystem from "effect/FileSystem";
3import * as Path from "effect/Path";
4import * as SqlClient from "effect/sql/SqlClient";
5
6import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient";
7
8export interface ImportedCookie {
9 readonly url: string;
10 readonly name: string;
11 readonly value: string;
12 /** Leave host-only cookies undefined; Electron widens any supplied domain and rejects __Host- cookies with one. */
13 readonly domain: string | undefined;
14 readonly path: string;
15 readonly secure: boolean;
16 readonly httpOnly: boolean;
17 readonly expirationDate: number | undefined;
18 readonly sameSite: "unspecified" | "no_restriction" | "lax" | "strict";
19}
20
21export interface CookieReadResult {
22 readonly cookies: ReadonlyArray<ImportedCookie>;
23 readonly undecryptable: number;
24 readonly undecryptableHosts: ReadonlyArray<string>;
25}
26
27export const cookieScope = (
28 host: string,
29 path: string,
30 secure: boolean,
31): { readonly url: string; readonly domain: string | undefined } => {
32 const isDomainCookie = host.startsWith(".");
33 const unwrappedHost = bareHost(host);
34 const authority =
35 unwrappedHost.includes(":") && !(unwrappedHost.startsWith("[") && unwrappedHost.endsWith("]"))
36 ? `[${unwrappedHost}]`
37 : unwrappedHost;
38 return {
39 url: `${secure ? "https" : "http"}://${authority}${path}`,
40 domain: isDomainCookie ? host : undefined,
41 };
42};
43
44export const bareHost = (host: string): string => (host.startsWith(".") ? host.slice(1) : host);
45
46/** Copy before reading to avoid live WAL writes and never open the source browser database for writing. */
47export const snapshotCookieDatabase = Effect.fn("CookieDatabase.snapshotCookieDatabase")(function* (
48 cookiePath: string,
49 tempPrefix = "t3code-cookie-import-",
50) {
51 const fileSystem = yield* FileSystem.FileSystem;
52 const path = yield* Path.Path;
53 const directory = yield* fileSystem.makeTempDirectoryScoped({ prefix: tempPrefix });
54 const target = path.join(directory, path.basename(cookiePath));
55 yield* Effect.gen(function* () {
56 const sql = yield* SqlClient.SqlClient;
57 yield* sql`VACUUM INTO ${target}`;
58 }).pipe(Effect.provide(NodeSqliteClient.layer({ filename: cookiePath, readonly: true })));
59 return target;
60});