diff --git a/edge-middleware/feature-flag-launchdarkly/app/page.tsx b/edge-middleware/feature-flag-launchdarkly/app/page.tsx index 99180d1f6d..81527bcfa2 100644 --- a/edge-middleware/feature-flag-launchdarkly/app/page.tsx +++ b/edge-middleware/feature-flag-launchdarkly/app/page.tsx @@ -1,6 +1,7 @@ import { Text, Page, Link } from '@vercel/examples-ui' -import { init } from '@launchdarkly/vercel-server-sdk' +import { type LDClient, init } from '@launchdarkly/vercel-server-sdk' import { createClient } from '@vercel/edge-config' +import { cache } from 'react' export const metadata = { title: 'Vercel x LaunchDarkly example', @@ -9,12 +10,43 @@ export const metadata = { } export const runtime = 'edge' -const edgeClient = createClient(process.env.EDGE_CONFIG) -const ldClient = init(process.env.NEXT_PUBLIC_LD_CLIENT_SIDE_ID!, edgeClient) +const edgeConfigClient = createClient(process.env.EDGE_CONFIG) + +// In Edge Runtime it's not possible to share promises across requests. +// +// waitForInitialization attempts to use a pending promise if one exists, so +// we need to create a new client for every request to prevent reading a promise +// created by an earlier request. +// +// However, we still want to allow reusing the LaunchDarkly client for the +// duration of a specific request. This allows multiple components +// to call getLdClient() and to get the same instance. We use cache() to +// keep the LaunchDarkly client around for the duration of a request. +// +// cache resets for each server request, so a subsequent request will receive +// a new instance of the LaunchDarkly client. +// +// Notes +// - This setup is only necessary for Edge Functions, not for Serverless Functions +// - When using the LaunchDarkly client in Edge Middleware make sure to use +// a fresh instance for every request, as it has the same promise sharing +// problem as Edge Functions otherwise. +// - "cache" does not work in Edge Middleware, so you'd need to create a fresh +// instance of the LaunchDarkly client for every request. +const getLdClient = cache(async (): Promise => { + const ldClient = init( + process.env.NEXT_PUBLIC_LD_CLIENT_SIDE_ID!, + edgeConfigClient + ) + await ldClient.waitForInitialization() + return ldClient +}) export default async function Home() { const before = Date.now() - await ldClient.waitForInitialization() + + const ldClient = await getLdClient() + const ldContext = { kind: 'org', key: 'my-org-key', diff --git a/edge-middleware/feature-flag-launchdarkly/package.json b/edge-middleware/feature-flag-launchdarkly/package.json index 4973f2c29d..77f8203d50 100644 --- a/edge-middleware/feature-flag-launchdarkly/package.json +++ b/edge-middleware/feature-flag-launchdarkly/package.json @@ -10,8 +10,8 @@ "lint": "next lint" }, "dependencies": { - "@launchdarkly/vercel-server-sdk": "^1.1.1", - "@vercel/edge-config": "^0.2.1", + "@launchdarkly/vercel-server-sdk": "^1.3.3", + "@vercel/edge-config": "^1.1.0", "@vercel/examples-ui": "^2.0.3", "next": "^13.4.19", "react": "^18.2.0", diff --git a/edge-middleware/feature-flag-launchdarkly/pnpm-lock.yaml b/edge-middleware/feature-flag-launchdarkly/pnpm-lock.yaml index 0912c8b169..909122c407 100644 --- a/edge-middleware/feature-flag-launchdarkly/pnpm-lock.yaml +++ b/edge-middleware/feature-flag-launchdarkly/pnpm-lock.yaml @@ -1,12 +1,16 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + dependencies: '@launchdarkly/vercel-server-sdk': - specifier: ^1.1.1 - version: 1.1.1 + specifier: ^1.3.3 + version: 1.3.3(typescript@5.1.6) '@vercel/edge-config': - specifier: ^0.2.1 - version: 0.2.1 + specifier: ^1.1.0 + version: 1.1.0(typescript@5.1.6) '@vercel/examples-ui': specifier: ^2.0.3 version: 2.0.3(next@13.4.19)(react-dom@18.2.0)(react@18.2.0) @@ -155,30 +159,33 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@launchdarkly/js-sdk-common@1.1.0: - resolution: {integrity: sha512-abCOQGknrAIFL8c3oHiUu6pHeLXrUw4Zfh7ngAO2EoKPDn+tfbXXUJy2zOymaIAD8W8haaVz6mGEB7UwgcU4HQ==} + /@launchdarkly/js-sdk-common@2.3.1: + resolution: {integrity: sha512-gLCrL7eLtbip5fZ3vLO5pIwv3JcCT/zH2FSGcX2uYEfzLc8Mz/H2Ug71mgpzj4RpJ4Co4M0KvY3ntiU5jXj8cg==} dev: false - /@launchdarkly/js-server-sdk-common-edge@1.0.10: - resolution: {integrity: sha512-qOhZpqjkpCByQNtmOnPLbYa/4LI7ghuzJSUze/0VolrP3G8XI6MuFDxibXBmOmx7nTo+4DQHQt1yHWAzwDP+cQ==} + /@launchdarkly/js-server-sdk-common-edge@2.2.2: + resolution: {integrity: sha512-uUypRZmTl72TNyQuXgcxvxDKZcHNCLcQhXUWqyf4zkCchi7vK6T6umhxR1/ToHV6rPVaeYqx8Cr3+2cgSjGrFw==} dependencies: - '@launchdarkly/js-server-sdk-common': 1.2.0 + '@launchdarkly/js-server-sdk-common': 2.2.2 crypto-js: 4.1.1 dev: false - /@launchdarkly/js-server-sdk-common@1.2.0: - resolution: {integrity: sha512-aUGQLQ7eWOAd5eRBZKtsrLW9qBnd3EPD4HeMLen3awvtNsshtiX6/ij3NB8iAWPYIRRyYJZyS7bhmwZdeW+RtA==} + /@launchdarkly/js-server-sdk-common@2.2.2: + resolution: {integrity: sha512-gFAEqC6xUbbDQavbjE25xXDruX6r9AwiCPMq5QN3qubVMjTT9DCQwcj181Wkjt3t60aDzkvZm9ZcMb4Sxwd/+g==} dependencies: - '@launchdarkly/js-sdk-common': 1.1.0 + '@launchdarkly/js-sdk-common': 2.3.1 semver: 7.5.4 dev: false - /@launchdarkly/vercel-server-sdk@1.1.1: - resolution: {integrity: sha512-VRQxVtYT1npL+PP0qjs91zPAsqYgSuphyz3+sFov4g8OZMc26qBkZTELezYQ5NtUR9bsPSntjvrrZH0sUcEu6A==} + /@launchdarkly/vercel-server-sdk@1.3.3(typescript@5.1.6): + resolution: {integrity: sha512-i2ZA3X6RFjesy/WeexvoCp0vS4/OlWR3WCCcvbX4+4bd96pvzXnYU3RWVwwB/38Ao5M7BGfpRQ9wEcIEqZCDgw==} dependencies: - '@launchdarkly/js-server-sdk-common-edge': 1.0.10 - '@vercel/edge-config': 0.1.11 + '@launchdarkly/js-server-sdk-common-edge': 2.2.2 + '@vercel/edge-config': 1.1.0(typescript@5.1.6) crypto-js: 4.1.1 + transitivePeerDependencies: + - '@opentelemetry/api' + - typescript dev: false /@next/env@13.4.19: @@ -394,18 +401,19 @@ packages: resolution: {integrity: sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg==} dev: false - /@vercel/edge-config@0.1.11: - resolution: {integrity: sha512-3UQsy6VqJ6LAls6YJpWtkxXSdj0pgZHOwikWtp3Mbk2qUkZRtZRQCTyIZyp6aqFqkuJxaCH7I7h3dhb82/EEdQ==} - engines: {node: '>=14.6'} - dependencies: - '@vercel/edge-config-fs': 0.1.0 - dev: false - - /@vercel/edge-config@0.2.1: - resolution: {integrity: sha512-847kYqJEbga4PGgNrctQ9XsD+2Kw/S+UjzZnIFpebQ9VbdtB0MX4anq33WetcYZYPfhZd2L0uXVnY/BcjI5dOw==} + /@vercel/edge-config@1.1.0(typescript@5.1.6): + resolution: {integrity: sha512-es/4BzzKfyUilL5E1knR42MZHJqHMRfqitrnv18gVZZUha9ywrX3qNoCrPsNMJ1HS8xAAz/FJEyel7YFIDfKoQ==} engines: {node: '>=14.6'} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true dependencies: '@vercel/edge-config-fs': 0.1.0 + ts-essentials: 9.4.1(typescript@5.1.6) + transitivePeerDependencies: + - typescript dev: false /@vercel/examples-ui@2.0.3(next@13.4.19)(react-dom@18.2.0)(react@18.2.0): @@ -2457,6 +2465,17 @@ packages: typescript: 5.1.6 dev: true + /ts-essentials@9.4.1(typescript@5.1.6): + resolution: {integrity: sha512-oke0rI2EN9pzHsesdmrOrnqv1eQODmJpd/noJjwj2ZPC3Z4N2wbjrOEqnsEgmvlO2+4fBb0a794DCna2elEVIQ==} + peerDependencies: + typescript: '>=4.1.0' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.1.6 + dev: false + /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true @@ -2589,7 +2608,6 @@ packages: resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} engines: {node: '>=14.17'} hasBin: true - dev: true /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}