diff --git a/web/@types/global.d.ts b/web/@types/global.d.ts new file mode 100644 index 000000000..896fa97c9 --- /dev/null +++ b/web/@types/global.d.ts @@ -0,0 +1,7 @@ +import { Redis, Cluster as RedisCluster } from "ioredis"; + +type Messages = typeof en; + +declare global { + var RedisClient: Redis | RedisCluster | undefined; +} diff --git a/web/api/helpers/app-rating/index.ts b/web/api/helpers/app-rating/index.ts index ca5a99cf5..6e61bae72 100644 --- a/web/api/helpers/app-rating/index.ts +++ b/web/api/helpers/app-rating/index.ts @@ -1,5 +1,4 @@ "use server"; -import { createRedisClient } from "@/lib/redis"; import { getAPIServiceGraphqlClient } from "../graphql"; import { getSdk as getAppRatingSdk } from "./graphql/get-app-rating.generated"; @@ -8,11 +7,11 @@ export async function getAppRating(appId: string): Promise { const redisKey = `app:${appId}:rating`; const lockKey = `lock:${appId}:rating`; - const redis = createRedisClient({ - url: process.env.REDIS_URL!, - password: process.env.REDIS_PASSWORD!, - username: process.env.REDIS_USERNAME!, - }); + const redis = global.RedisClient; + + if (!redis) { + throw new Error("Redis client not found"); + } try { // Try to get from cache first diff --git a/web/instrumentation.ts b/web/instrumentation.ts new file mode 100644 index 000000000..86115a058 --- /dev/null +++ b/web/instrumentation.ts @@ -0,0 +1,34 @@ +export async function register() { + console.log("🛠️ Starting Instrumentation registration..."); + + try { + if ( + typeof window === "undefined" && + process.env.NEXT_RUNTIME === "nodejs" + ) { + const { createRedisClient } = await import("./lib/redis"); + + if ( + !process.env.REDIS_URL || + !process.env.REDIS_USERNAME || + !process.env.REDIS_PASSWORD + ) { + return console.error( + "🔴 Missing Redis configuration in instrumentation.ts", + ); + } + + const redis = createRedisClient({ + url: process.env.REDIS_URL, + password: process.env.REDIS_PASSWORD, + username: process.env.REDIS_USERNAME, + }); + + global.RedisClient = redis; + } + } catch (error) { + return console.error("🔴 Instrumentation registration error: ", error); + } + + console.log("✅ Instrumentation registration complete."); +} diff --git a/web/jest.setup.ts b/web/jest.setup.ts index 13f044db4..84cd2357c 100644 --- a/web/jest.setup.ts +++ b/web/jest.setup.ts @@ -1,3 +1,4 @@ +import IORedis from "ioredis-mock"; import { setConfig } from "next/config"; import "whatwg-fetch"; @@ -9,6 +10,12 @@ setConfig({ ), }); +// Create the mock Redis client +const redisMock = new IORedis(); + +// Set the global mock +global.RedisClient = redisMock; + export const MOCKED_GENERAL_SECRET_KEY = "0xsuperSecretKey99994ab56046d4d97695b9999999"; diff --git a/web/next.config.mjs b/web/next.config.mjs index e81bbc885..0e61fddaa 100644 --- a/web/next.config.mjs +++ b/web/next.config.mjs @@ -11,6 +11,11 @@ const publicAppURL = /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: false, + + experimental: { + instrumentationHook: true, + }, + output: "standalone", images: { remotePatterns: [ diff --git a/web/pages/api/v1/oidc/authorize.ts b/web/pages/api/v1/oidc/authorize.ts index 2e7718aeb..112f694d6 100644 --- a/web/pages/api/v1/oidc/authorize.ts +++ b/web/pages/api/v1/oidc/authorize.ts @@ -19,7 +19,6 @@ import { validateRequestSchema } from "@/legacy/backend/utils"; import { verifyProof } from "@/legacy/backend/verify"; import { logger } from "@/legacy/lib/logger"; import { OIDCFlowType, OIDCResponseType } from "@/legacy/lib/types"; -import { createRedisClient } from "@/lib/redis"; import { captureEvent } from "@/services/posthogClient"; import { gql } from "@apollo/client"; import { VerificationLevel } from "@worldcoin/idkit-core"; @@ -98,32 +97,20 @@ export default async function handleOIDCAuthorize( return errorNotAllowed(req.method, res, req); } - let redis; + const redis = global.RedisClient; - try { - // Initial validations... - if (!process.env.REDIS_URL || !process.env.REDIS_USERNAME) { - return errorResponse( - res, - 500, - "missing_redis_config", - "Missing ENV variables.", - "INVALID_CONFIG", - req, - ); - } - - if (process.env.NODE_ENV !== "development" && !process.env.REDIS_USERNAME) { - return errorResponse( - res, - 500, - "missing_redis_config", - "Missing ENV variables.", - "INVALID_CONFIG", - req, - ); - } + if (!redis) { + return errorResponse( + res, + 500, + "internal_server_error", + "Redis client not found", + "server", + req, + ); + } + try { const { isValid, parsedParams, handleError } = await validateRequestSchema({ schema, value: req.body, @@ -225,12 +212,6 @@ export default async function handleOIDCAuthorize( ); } - redis = createRedisClient({ - url: process.env.REDIS_URL, - password: process.env.REDIS_PASSWORD, - username: process.env.REDIS_USERNAME, - }); - // Anchor: Check the proof hasn't been replayed const hashedProof = createHash("sha256").update(proof).digest("hex"); const proofKey = `oidc:proof:${hashedProof}`; diff --git a/web/tests/api/oidc/authorize.test.ts b/web/tests/api/oidc/authorize.test.ts index ade03c6d9..9beef50bf 100644 --- a/web/tests/api/oidc/authorize.test.ts +++ b/web/tests/api/oidc/authorize.test.ts @@ -5,7 +5,6 @@ import { insertAuthCodeQuery, } from "@/legacy/backend/oidc"; import { OIDCResponseType } from "@/legacy/lib/types"; -import { createRedisClient } from "@/lib/redis"; import handleOIDCAuthorize from "@/pages/api/v1/oidc/authorize"; import { createPublicKey } from "crypto"; import dayjs from "dayjs"; @@ -26,15 +25,6 @@ jest.mock("legacy/backend/jwks", () => require("tests/api/__mocks__/jwks.mock.ts"), ); -jest.mock("ioredis", () => { - const ioredisMock = jest.requireActual("ioredis-mock"); - return { - __esModule: true, - Redis: ioredisMock, - Cluster: ioredisMock.Cluster, - }; -}); - const fetchAppQueryResponse = () => ({ data: { app: [ @@ -74,6 +64,8 @@ jest.mock( ); beforeEach(async () => { + await global.RedisClient?.flushall(); + when(requestReturnFn) .calledWith( expect.objectContaining({ @@ -108,14 +100,6 @@ beforeEach(async () => { .mockImplementation((args) => ({ data: { insert_auth_code_one: { auth_code: args.variables.auth_code } }, })); - - const redis = createRedisClient({ - url: process.env.REDIS_URL!, - password: process.env.REDIS_PASSWORD, - username: process.env.REDIS_USERNAME!, - }); - - await redis.flushall(); }); beforeAll(() => { diff --git a/web/tests/integration/oidc/authorize.test.ts b/web/tests/integration/oidc/authorize.test.ts index 88affc8e0..318a037fe 100644 --- a/web/tests/integration/oidc/authorize.test.ts +++ b/web/tests/integration/oidc/authorize.test.ts @@ -1,5 +1,4 @@ import { OIDCErrorCodes } from "@/legacy/backend/oidc"; -import { createRedisClient } from "@/lib/redis"; import handleOIDCAuthorize from "@/pages/api/v1/oidc/authorize"; import { createHash } from "crypto"; import fetchMock from "jest-fetch-mock"; @@ -10,24 +9,9 @@ import { validSemaphoreProofMock } from "tests/api/__mocks__/sequencer.mock"; import { integrationDBClean, integrationDBExecuteQuery } from "../setup"; import { testGetDefaultApp } from "../test-utils"; -jest.mock("ioredis", () => { - const ioredisMock = jest.requireActual("ioredis-mock"); - return { - __esModule: true, - Redis: ioredisMock, - Cluster: ioredisMock.Cluster, - }; -}); - beforeEach(async () => { await integrationDBClean(); - const redis = createRedisClient({ - url: process.env.REDIS_URL!, - password: process.env.REDIS_PASSWORD, - username: process.env.REDIS_USERNAME!, - }); - - await redis.flushall(); + await global.RedisClient?.flushall(); }); beforeAll(() => {