aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-03-03 08:57:37 -0800
committerFuwn <[email protected]>2026-03-03 08:57:43 -0800
commitbb8a71326a85a3d70bad36a35d300c614d8e8b7f (patch)
tree7bb0803f3bf2bcc1378f0cfd03d53e4dc81aeefa
parentfix(match): prevent cached airing injection mutation regressions (diff)
downloaddue.moe-bb8a71326a85a3d70bad36a35d300c614d8e8b7f.tar.xz
due.moe-bb8a71326a85a3d70bad36a35d300c614d8e8b7f.zip
chore(effect): add v4 cookie decode foundation and tests
-rw-r--r--package.json1
-rw-r--r--pnpm-lock.yaml162
-rw-r--r--src/lib/Effect/authCookie.test.ts47
-rw-r--r--src/lib/Effect/authCookie.ts45
4 files changed, 248 insertions, 7 deletions
diff --git a/package.json b/package.json
index 9c54a0c3..fd485c3b 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"botid": "^1.5.10",
"caniuse-lite": "^1.0.30001655",
"dexie": "^4.0.1-alpha.25",
+ "effect": "4.0.0-beta.25",
"fast-levenshtein": "^3.0.0",
"jsdom": "^23.0.1",
"jszip": "^3.10.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9510800b..1cc92e91 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
dexie:
specifier: ^4.0.1-alpha.25
version: 4.2.1
+ effect:
+ specifier: 4.0.0-beta.25
+ version: 4.0.0-beta.25
fast-levenshtein:
specifier: ^3.0.0
version: 3.0.0
@@ -137,7 +140,9 @@ importers:
version: 5.4.21(@types/[email protected])([email protected])
vitest:
specifier: ^4.0.18
+
+ packages/shared: {}
packages:
@@ -1185,6 +1190,36 @@ packages:
'@microsoft/[email protected]':
resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==}
+ '@msgpackr-extract/[email protected]':
+ resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@msgpackr-extract/[email protected]':
+ resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@msgpackr-extract/[email protected]':
+ resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@msgpackr-extract/[email protected]':
+ resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@msgpackr-extract/[email protected]':
+ resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@msgpackr-extract/[email protected]':
+ resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
+ cpu: [x64]
+ os: [win32]
+
'@nodelib/[email protected]':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -2253,6 +2288,9 @@ packages:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+ resolution: {integrity: sha512-5dFGfJuJOITAlKnK9tmBNAtTxJt0/G65QO0ouet0QqFsDNDptZf8egYaWAJ/8RnYigzLhieE/A8qvnyKwmAPjw==}
+
resolution: {integrity: sha512-wKXFZw4erWmmOz5N/grBoJ2XrNJGDFMu2+W5ACHza5rHtvsqrK4gb6rnLC7XxKB9WlJ+RmyQatuEXmtm86xbnw==}
@@ -2405,6 +2443,10 @@ packages:
resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==}
+ resolution: {integrity: sha512-IE9csY7lnhxBnA8g/WI5eg/hygA6MGWJMSNfFRrBlXUciADEhS1EDB0SIsMSvzubzIlOBbVITSsypCsW717poA==}
+ engines: {node: '>=12.17.0'}
+
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -2454,6 +2496,9 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
+ resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==}
+
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@@ -2708,6 +2753,10 @@ packages:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+
resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
@@ -2835,6 +2884,9 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
+ resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==}
+
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
@@ -3003,6 +3055,16 @@ packages:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
+ hasBin: true
+
+ resolution: {integrity: sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==}
+
+ resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==}
+
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -3044,6 +3106,10 @@ packages:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
+ hasBin: true
+
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
@@ -3232,6 +3298,9 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==}
+
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
@@ -3589,6 +3658,9 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
+
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@@ -3978,6 +4050,11 @@ packages:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -5008,6 +5085,24 @@ snapshots:
'@microsoft/[email protected]': {}
+ '@msgpackr-extract/[email protected]':
+ optional: true
+
+ '@msgpackr-extract/[email protected]':
+ optional: true
+
+ '@msgpackr-extract/[email protected]':
+ optional: true
+
+ '@msgpackr-extract/[email protected]':
+ optional: true
+
+ '@msgpackr-extract/[email protected]':
+ optional: true
+
+ '@msgpackr-extract/[email protected]':
+ optional: true
+
'@nodelib/[email protected]':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -5618,13 +5713,13 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.0.3
dependencies:
'@vitest/spy': 4.0.18
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 7.3.1(@types/[email protected])([email protected])
'@vitest/[email protected]':
dependencies:
@@ -6076,6 +6171,19 @@ snapshots:
dependencies:
safe-buffer: 5.1.2
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ fast-check: 4.5.3
+ find-my-way-ts: 0.1.6
+ ini: 6.0.0
+ kubernetes-types: 1.30.0
+ msgpackr: 1.11.8
+ multipasta: 0.2.7
+ toml: 3.0.0
+ uuid: 13.0.0
+ yaml: 2.8.2
+
@@ -6348,6 +6456,10 @@ snapshots:
+ dependencies:
+ pure-rand: 7.0.1
+
@@ -6405,6 +6517,8 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
+
dependencies:
locate-path: 5.0.0
@@ -6808,6 +6922,8 @@ snapshots:
+
dependencies:
'@formatjs/ecma402-abstract': 2.3.6
@@ -6953,6 +7069,8 @@ snapshots:
+
dependencies:
immediate: 3.0.6
@@ -7094,6 +7212,24 @@ snapshots:
+ dependencies:
+ node-gyp-build-optional-packages: 5.2.2
+ optionalDependencies:
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
+ optional: true
+
+ optionalDependencies:
+ msgpackr-extract: 3.0.3
+
+
@@ -7122,6 +7258,11 @@ snapshots:
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
+ dependencies:
+ detect-libc: 2.1.2
+ optional: true
+
@@ -7300,6 +7441,8 @@ snapshots:
+
@@ -7746,6 +7889,8 @@ snapshots:
dependencies:
is-number: 7.0.0
+
@@ -7857,7 +8002,7 @@ snapshots:
fsevents: 2.3.3
sass: 1.97.3
dependencies:
esbuild: 0.27.3
fdir: 6.5.0([email protected])
@@ -7869,6 +8014,7 @@ snapshots:
'@types/node': 25.3.3
fsevents: 2.3.3
sass: 1.97.3
+ yaml: 2.8.2
optionalDependencies:
@@ -7878,10 +8024,10 @@ snapshots:
optionalDependencies:
vite: 5.4.21(@types/[email protected])([email protected])
dependencies:
'@vitest/expect': 4.0.18
- '@vitest/mocker': 4.0.18([email protected](@types/[email protected])([email protected]))
'@vitest/pretty-format': 4.0.18
'@vitest/runner': 4.0.18
'@vitest/snapshot': 4.0.18
@@ -7898,7 +8044,7 @@ snapshots:
tinyexec: 1.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
- vite: 7.3.1(@types/[email protected])([email protected])
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.0
@@ -8003,6 +8149,8 @@ snapshots:
+
diff --git a/src/lib/Effect/authCookie.test.ts b/src/lib/Effect/authCookie.test.ts
new file mode 100644
index 00000000..2a27f0ce
--- /dev/null
+++ b/src/lib/Effect/authCookie.test.ts
@@ -0,0 +1,47 @@
+import { describe, expect, it } from "vitest";
+import { Result } from "effect";
+import {
+ decodeAuthCookieEither,
+ decodeAuthCookieOrThrow,
+} from "$lib/Effect/authCookie";
+
+describe("decodeAuthCookie", () => {
+ it("decodes a valid user cookie payload", () => {
+ const payload = JSON.stringify({
+ token_type: "Bearer",
+ expires_in: 3600,
+ access_token: "access-token",
+ refresh_token: "refresh-token",
+ });
+ const decoded = decodeAuthCookieEither(payload);
+
+ expect(Result.isSuccess(decoded)).toBe(true);
+
+ if (Result.isSuccess(decoded))
+ expect(decoded.success).toEqual({
+ tokenType: "Bearer",
+ expiresIn: 3600,
+ accessToken: "access-token",
+ refreshToken: "refresh-token",
+ });
+ });
+
+ it("returns a left when cookie JSON is invalid", () => {
+ const decoded = decodeAuthCookieEither("{oops");
+
+ expect(Result.isFailure(decoded)).toBe(true);
+ });
+
+ it("returns a left when required fields are missing", () => {
+ const payload = JSON.stringify({
+ token_type: "Bearer",
+ });
+ const decoded = decodeAuthCookieEither(payload);
+
+ expect(Result.isFailure(decoded)).toBe(true);
+ });
+
+ it("throws on invalid payload through decodeAuthCookieOrThrow", () => {
+ expect(() => decodeAuthCookieOrThrow("{oops")).toThrowError();
+ });
+});
diff --git a/src/lib/Effect/authCookie.ts b/src/lib/Effect/authCookie.ts
new file mode 100644
index 00000000..e716f5e9
--- /dev/null
+++ b/src/lib/Effect/authCookie.ts
@@ -0,0 +1,45 @@
+import type { AniListAuthorisation } from "$lib/Data/AniList/identity";
+import { Effect, Result, Schema } from "effect";
+
+const UserCookieSchema = Schema.Struct({
+ token_type: Schema.String,
+ expires_in: Schema.Number,
+ access_token: Schema.String,
+ refresh_token: Schema.String,
+});
+
+export const decodeAuthCookieEffect = (cookie: string) =>
+ Effect.gen(function* () {
+ const parsedCookie = yield* Effect.try({
+ try: () => JSON.parse(cookie) as unknown,
+ catch: (cause) => new Error("Invalid user cookie JSON", { cause }),
+ });
+ const decodedCookie = yield* Effect.try({
+ try: () => Schema.decodeUnknownSync(UserCookieSchema)(parsedCookie),
+ catch: (cause) => new Error("Invalid user cookie payload", { cause }),
+ });
+
+ return {
+ tokenType: decodedCookie.token_type,
+ expiresIn: decodedCookie.expires_in,
+ accessToken: decodedCookie.access_token,
+ refreshToken: decodedCookie.refresh_token,
+ } as AniListAuthorisation;
+ });
+
+export const decodeAuthCookieEither = (cookie: string) =>
+ Result.try({
+ try: () => Effect.runSync(decodeAuthCookieEffect(cookie)),
+ catch: (cause) =>
+ cause instanceof Error
+ ? cause
+ : new Error("Failed to decode user cookie", { cause }),
+ });
+
+export const decodeAuthCookieOrThrow = (
+ cookie: string,
+): AniListAuthorisation => {
+ const decoded = decodeAuthCookieEither(cookie);
+
+ return Result.getOrThrow(decoded);
+};