From b9f20bccef68f790d1773e77b2213b0182408921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Moreau?= Date: Wed, 12 Jul 2023 16:13:23 +0200 Subject: [PATCH] Fix/supabase 2.26.0 gotrue warning (#74) * upgrade supabase-js + supabase browser client as singleton * upgrade minor libs + fix tests * fix lint and type issues * fix gh workflow and vitest ui coverage --- .github/workflows/deploy.yml | 3 -- .../workflows/for-this-stack-repo-only.yml | 3 -- app/integrations/i18n/i18next.server.tsx | 3 +- app/integrations/supabase/client.ts | 15 ++----- app/modules/user/service.server.test.ts | 4 +- app/routes/notes/new.tsx | 4 +- app/routes/oauth.callback.tsx | 9 ++-- app/routes/reset-password.tsx | 9 ++-- app/utils/http.test.ts | 11 +++-- cypress/support/delete-user.ts | 4 +- cypress/tsconfig.json | 2 +- package.json | 45 +++++++++---------- tailwind.config.js | 1 - test/setup-test-env.ts | 7 +++ 14 files changed, 58 insertions(+), 62 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6051c71..8e0543f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,9 +30,6 @@ jobs: with: useLockFile: false - - name: ⚙️ Build CSS - run: npm run generate:css - - name: 🔬 Lint run: npm run lint diff --git a/.github/workflows/for-this-stack-repo-only.yml b/.github/workflows/for-this-stack-repo-only.yml index f665d9a..5ec5f82 100644 --- a/.github/workflows/for-this-stack-repo-only.yml +++ b/.github/workflows/for-this-stack-repo-only.yml @@ -30,9 +30,6 @@ jobs: with: useLockFile: false - - name: ⚙️ Build CSS - run: npm run generate:css - - name: 🔬 Lint run: npm run lint diff --git a/app/integrations/i18n/i18next.server.tsx b/app/integrations/i18n/i18next.server.tsx index 688f6fa..c0c46ce 100644 --- a/app/integrations/i18n/i18next.server.tsx +++ b/app/integrations/i18n/i18next.server.tsx @@ -24,12 +24,13 @@ export const i18nextServer = new RemixI18Next({ // The backend you want to use to load the translations // Tip: You could pass `resources` to the `i18next` configuration and avoid // a backend here + // @ts-expect-error - `i18next-fs-backend` is not typed backend: Backend, }); export async function createI18nextServerInstance( request: Request, - remixContext: EntryContext + remixContext: EntryContext, ) { // Create a new instance of i18next so every request will have a // completely unique instance and not share any state diff --git a/app/integrations/supabase/client.ts b/app/integrations/supabase/client.ts index 87a8c85..8b16e69 100644 --- a/app/integrations/supabase/client.ts +++ b/app/integrations/supabase/client.ts @@ -29,17 +29,6 @@ function getSupabaseClient(supabaseKey: string, accessToken?: string) { }); } -/** - * Provides a Supabase Client for the logged in user or get back a public and safe client without admin privileges - * - * It's a per request scoped client to prevent access token leaking over multiple concurrent requests and from different users. - * - * Reason : https://github.com/rphlmr/supa-fly-stack/pull/43#issue-1336412790 - */ -function getSupabase(accessToken?: string) { - return getSupabaseClient(SUPABASE_ANON_PUBLIC, accessToken); -} - /** * Provides a Supabase Admin Client with full admin privileges * @@ -56,4 +45,6 @@ function getSupabaseAdmin() { return getSupabaseClient(SUPABASE_SERVICE_ROLE); } -export { getSupabaseAdmin, getSupabase }; +const supabaseClient = getSupabaseClient(SUPABASE_ANON_PUBLIC); + +export { getSupabaseAdmin, supabaseClient }; diff --git a/app/modules/user/service.server.test.ts b/app/modules/user/service.server.test.ts index 3c849b5..9d8ae12 100644 --- a/app/modules/user/service.server.test.ts +++ b/app/modules/user/service.server.test.ts @@ -12,6 +12,9 @@ import { db } from "~/database"; import { createUserAccount } from "./service.server"; +// @vitest-environment node +// 👋 see https://vitest.dev/guide/environment.html#environments-for-specific-files + // mock db vitest.mock("~/database", () => ({ db: { @@ -113,7 +116,6 @@ describe(createUserAccount.name, () => { expect(signInRequest.body).toEqual({ email: USER_EMAIL, password: USER_PASSWORD, - data: {}, gotrue_meta_security: {}, }); expect(fetchAuthAdminUserAPI.size).toEqual(1); diff --git a/app/routes/notes/new.tsx b/app/routes/notes/new.tsx index 5fbc6a1..c09e96a 100644 --- a/app/routes/notes/new.tsx +++ b/app/routes/notes/new.tsx @@ -11,7 +11,7 @@ import { createNote } from "~/modules/note"; import { assertIsPost, isFormProcessing } from "~/utils"; export const NewNoteFormSchema = z.object({ - title: z.string().min(2, "require-title"), + title: z.string().min(1, "require-title"), body: z.string().min(1, "require-body"), }); @@ -31,7 +31,7 @@ export async function action({ request }: LoaderArgs) { headers: { "Set-Cookie": await commitAuthSession(request, { authSession }), }, - } + }, ); } diff --git a/app/routes/oauth.callback.tsx b/app/routes/oauth.callback.tsx index 8096ef5..0d984af 100644 --- a/app/routes/oauth.callback.tsx +++ b/app/routes/oauth.callback.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useEffect } from "react"; import { json, redirect } from "@remix-run/node"; import type { LoaderArgs, ActionArgs } from "@remix-run/node"; @@ -6,7 +6,7 @@ import { useActionData, useFetcher, useSearchParams } from "@remix-run/react"; import { parseFormAny } from "react-zorm"; import { z } from "zod"; -import { getSupabase } from "~/integrations/supabase"; +import { supabaseClient } from "~/integrations/supabase"; import { refreshAccessToken, commitAuthSession, @@ -98,12 +98,11 @@ export default function LoginCallback() { const fetcher = useFetcher(); const [searchParams] = useSearchParams(); const redirectTo = searchParams.get("redirectTo") ?? "/notes"; - const supabase = useMemo(() => getSupabase(), []); useEffect(() => { const { data: { subscription }, - } = supabase.auth.onAuthStateChange((event, supabaseSession) => { + } = supabaseClient.auth.onAuthStateChange((event, supabaseSession) => { if (event === "SIGNED_IN") { // supabase sdk has ability to read url fragment that contains your token after third party provider redirects you here // this fragment url looks like https://.....#access_token=evxxxxxxxx&refresh_token=xxxxxx, and it's not readable server-side (Oauth security) @@ -129,7 +128,7 @@ export default function LoginCallback() { // prevent memory leak. Listener stays alive 👨‍🎤 subscription.unsubscribe(); }; - }, [fetcher, redirectTo, supabase.auth]); + }, [fetcher, redirectTo]); return error ?
{error.message}
: null; } diff --git a/app/routes/reset-password.tsx b/app/routes/reset-password.tsx index 6f1fff4..d0d675e 100644 --- a/app/routes/reset-password.tsx +++ b/app/routes/reset-password.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import type { ActionArgs, LoaderArgs } from "@remix-run/node"; import { json, redirect } from "@remix-run/node"; @@ -8,7 +8,7 @@ import { parseFormAny, useZorm } from "react-zorm"; import { z } from "zod"; import { i18nextServer } from "~/integrations/i18n"; -import { getSupabase } from "~/integrations/supabase"; +import { supabaseClient } from "~/integrations/supabase"; import { commitAuthSession, getAuthSession, @@ -104,12 +104,11 @@ export default function ResetPassword() { const actionData = useActionData(); const transition = useTransition(); const disabled = isFormProcessing(transition.state); - const supabase = useMemo(() => getSupabase(), []); useEffect(() => { const { data: { subscription }, - } = supabase.auth.onAuthStateChange((event, supabaseSession) => { + } = supabaseClient.auth.onAuthStateChange((event, supabaseSession) => { // In local development, we doesn't see "PASSWORD_RECOVERY" event because: // Effect run twice and break listener chain if (event === "PASSWORD_RECOVERY" || event === "SIGNED_IN") { @@ -125,7 +124,7 @@ export default function ResetPassword() { // prevent memory leak. Listener stays alive 👨‍🎤 subscription.unsubscribe(); }; - }, [supabase.auth]); + }, []); return (
diff --git a/app/utils/http.test.ts b/app/utils/http.test.ts index f979afa..0c5130c 100644 --- a/app/utils/http.test.ts +++ b/app/utils/http.test.ts @@ -8,6 +8,9 @@ import { safeRedirect, } from "./http.server"; +// @vitest-environment node +// 👋 see https://vitest.dev/guide/environment.html#environments-for-specific-files + const BASE_URL = "https://my-app.com"; describe(getCurrentPath.name, () => { @@ -19,7 +22,7 @@ describe(getCurrentPath.name, () => { describe(makeRedirectToFromHere.name, () => { it("should return search params with redirectTo set with current request url path", () => { expect(makeRedirectToFromHere(new Request(`${BASE_URL}/profile`))).toEqual( - new URLSearchParams([["redirectTo", "/profile"]]) + new URLSearchParams([["redirectTo", "/profile"]]), ); }); }); @@ -31,13 +34,13 @@ describe(getRedirectTo.name, () => { it("should return url redirectTo param value", () => { expect(getRedirectTo(new Request(`${BASE_URL}?redirectTo=/profile`))).toBe( - "/profile" + "/profile", ); }); it("should return root redirectTo param value if invalid param value", () => { expect(getRedirectTo(new Request(`${BASE_URL}?redirectTo=//profile`))).toBe( - "/" + "/", ); }); }); @@ -75,7 +78,7 @@ describe(notFound.name, () => { it("should return message", async () => { expect(await notFound("not-found-message").text()).toBe( - "not-found-message" + "not-found-message", ); }); }); diff --git a/cypress/support/delete-user.ts b/cypress/support/delete-user.ts index c762fbe..7593d71 100644 --- a/cypress/support/delete-user.ts +++ b/cypress/support/delete-user.ts @@ -35,7 +35,9 @@ async function deleteUser(email: string) { } } - await deleteAuthAccount(user?.id!); + if (user?.id) { + await deleteAuthAccount(user.id); + } } deleteUser(process.argv[2]); diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 4b0b5d0..0d07fa8 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -12,7 +12,7 @@ "compilerOptions": { "baseUrl": ".", "noEmit": true, - "types": ["node", "cypress", "@testing-library/cypress"], + "types": ["node"], "esModuleInterop": true, "jsx": "react-jsx", "moduleResolution": "node", diff --git a/package.json b/package.json index b7d77a3..a6286e2 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@remix-run/node": "*", "@remix-run/react": "*", "@remix-run/serve": "*", - "@supabase/supabase-js": "^2.24.0", + "@supabase/supabase-js": "^2.26.0", "cookie": "^0.5.0", "i18next": "^21.9.1", "i18next-browser-languagedetector": "^6.1.5", @@ -42,46 +42,45 @@ "react-i18next": "^11.18.5", "react-zorm": "^0.6.1", "remix-i18next": "^4.1.1", - "tailwind-merge": "^1.10.0", - "zod": "^3.19.1" + "tailwind-merge": "^1.13.2", + "zod": "^3.21.4" }, "devDependencies": { - "@faker-js/faker": "^7.6.0", + "@faker-js/faker": "^8.0.2", "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", + "@remix-run/eslint-config": "1.18.1", "@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/forms": "^0.5.3", - "@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/typography": "^0.5.9", - "@testing-library/cypress": "^8.0.3", + "@testing-library/cypress": "^9.0.0", "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", + "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "@types/i18next-fs-backend": "^1.1.2", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^3.1.0", - "@vitest/coverage-c8": "^0.28.5", + "@vitest/coverage-v8": "^0.33.0", "cross-env": "^7.0.3", - "cypress": "^10.10.0", + "cypress": "^12.17.1", "dotenv-cli": "^7.0.0", - "eslint": "^8.34.0", + "eslint": "^8.44.0", "eslint-config-prettier": "^8.6.0", - "eslint-plugin-tailwindcss": "^3.9.0", - "happy-dom": "^7.6.0", - "msw": "^0.49.0", + "eslint-plugin-tailwindcss": "^3.13.0", + "happy-dom": "^10.1.1", + "msw": "^1.2.2", "npm-run-all": "^4.1.5", - "prettier": "2.8.4", - "prettier-plugin-tailwindcss": "^0.2.3", + "prettier": "3.0.0", + "prettier-plugin-tailwindcss": "^0.4.0", "prisma": "^4.10.1", - "start-server-and-test": "^1.14.0", - "tailwind-scrollbar": "^2.1.0", - "tailwindcss": "^3.2.7", + "start-server-and-test": "^2.0.0", + "tailwind-scrollbar": "^3.0.4", + "tailwindcss": "^3.3.2", "ts-node": "^10.9.1", - "tsconfig-paths": "^4.1.2", - "typescript": "^4.9.5", - "vite-tsconfig-paths": "^4.0.5", - "vitest": "^0.28.5" + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.6", + "vite-tsconfig-paths": "^4.2.0", + "vitest": "^0.33.0" }, "engines": { "node": ">=16" diff --git a/tailwind.config.js b/tailwind.config.js index ecc91c8..cc0b78c 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,7 +7,6 @@ module.exports = { require("@tailwindcss/typography"), require("@tailwindcss/forms"), require("@tailwindcss/aspect-ratio"), - require("@tailwindcss/line-clamp"), require("tailwind-scrollbar"), ], }; diff --git a/test/setup-test-env.ts b/test/setup-test-env.ts index fd958e1..262a737 100644 --- a/test/setup-test-env.ts +++ b/test/setup-test-env.ts @@ -8,6 +8,13 @@ process.env.SUPABASE_ANON_PUBLIC = "{ANON_PUBLIC}"; process.env.SUPABASE_URL = "https://supabase-project.supabase.co"; process.env.SERVER_URL = "http://localhost:3000"; + +if(typeof window !== 'undefined'){ + // @ts-expect-error missing vitest type + window.happyDOM.settings.enableFileSystemHttpRequests = true; +} + + installGlobals(); beforeAll(() => server.listen({ onUnhandledRequest: "error" }));