Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions flags-sdk/launchdarkly/.env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
# For @flags-sdk/launchdarkly
# The Flags Explorer uses this to securely read and override flags.
FLAGS_SECRET=""

# Server-side flags are read in one of two modes. Configure ONE of them.

# Mode 1 (recommended): Edge Config
# Flags are read from a Vercel Edge Config store that the LaunchDarkly
# Marketplace integration keeps in sync. Lowest latency.
# Set EITHER EDGE_CONFIG or EXPERIMENTATION_CONFIG (both are Edge Config
# connection strings). The Marketplace integration injects EXPERIMENTATION_CONFIG
# when you enable Edge Config syncing; EDGE_CONFIG takes precedence if both are set.
EDGE_CONFIG=""
LAUNCHDARKLY_PROJECT_SLUG=""
EXPERIMENTATION_CONFIG=""
LAUNCHDARKLY_CLIENT_SIDE_ID=""

# For launchdarkly-react-client-sdk
# Mode 2: LaunchDarkly server SDK
# Flags are read directly from LaunchDarkly using a server SDK key.
# No Edge Config required. Used automatically when neither EDGE_CONFIG nor
# EXPERIMENTATION_CONFIG is set.
LAUNCHDARKLY_SDK_KEY=""

# Required by the client-side React SDK (auto-injected by the integration).
NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID=""

# For Flags Explorer
# Optional: links each flag to its details page in the LaunchDarkly console.
LAUNCHDARKLY_PROJECT_SLUG=""

# Optional: lets the Flags Explorer fetch extra flag metadata from the LD API.
LAUNCHDARKLY_API_KEY=""
LAUNCHDARKLY_PROJECT_KEY=""
LAUNCHDARKLY_ENVIRONMENT=""
3 changes: 2 additions & 1 deletion flags-sdk/launchdarkly/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env*
!.env.example

# vercel
.vercel
Expand Down
74 changes: 65 additions & 9 deletions flags-sdk/launchdarkly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ If you deployed your own and configured the feature flags on LaunchDarkly, you c

The easiest way to get started with LaunchDarkly is through the integration in [Vercel Marketplace](https://vercel.com/marketplace/launchdarkly).

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fflags-sdk%2Flaunchdarkly&env=FLAGS_SECRET&envDescription=The+FLAGS_SECRET+will+be+used+by+the+Flags+Explorer+to+securely+overwrite+feature+flags.+Must+be+32+random+bytes%2C+base64-encoded.+Use+the+generated+value+or+set+your+own.&envLink=https%3A%2F%2Fvercel.com%2Fdocs%2Fworkflow-collaboration%2Ffeature-flags%2Fsupporting-feature-flags%23flags_secret-environment-variable&project-name=launchdarkly-flags-sdk&repository-name=launchdarkly-flags-sdk)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fflags-sdk%2Flaunchdarkly&env=FLAGS_SECRET&envDescription=The+FLAGS_SECRET+will+be+used+by+the+Flags+Explorer+to+securely+overwrite+feature+flags.+Must+be+32+random+bytes%2C+base64-encoded.+Use+the+generated+value+or+set+your+own.&envLink=https%3A%2F%2Fvercel.com%2Fdocs%2Fworkflow-collaboration%2Ffeature-flags%2Fsupporting-feature-flags%23flags_secret-environment-variable&project-name=launchdarkly-flags-sdk&repository-name=launchdarkly-flags-sdk&products=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22launchdarkly%22%2C%22productSlug%22%3A%22launchdarkly%22%2C%22protocol%22%3A%22experimentation%22%7D%5D)

Clicking the button above will:

1. Clone this repository to your GitHub account
2. Create a new Vercel project
3. Install the LaunchDarkly integration from the Vercel Marketplace, which connects your LaunchDarkly project and auto-injects `LAUNCHDARKLY_CLIENT_SIDE_ID`, `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID`,and `LAUNCHDARKLY_SDK_KEY`.
4. Prompt you to generate a `FLAGS_SECRET` for the Flags Explorer

The integration does **not** set your `EDGE_CONFIG`. See [Step 5](#step-5-set-environment-variables) for other variables that you can set.

### Step 1: Link the project

Expand All @@ -47,7 +56,7 @@ vercel env pull

Head over to the [LaunchDarkly Console](https://app.launchdarkly.com) and create the feature flags and experiments required by this template.

Be sure to enable the `SDKs using Client-side ID` option for each Feature Flag.
Be sure to enable the `Available on client-side SDKs` option for each Feature Flag.

Feature Flags:

Expand Down Expand Up @@ -85,18 +94,65 @@ Create the `Proceed to Checkout` experiment:

After that, start the Experiment.

### Step 6: Set environment variables
### Step 5: Set environment variables

This template reads server-side flags in one of two modes, and picks whichever you configure (Edge Config is preferred when both are set). Configure **one**:

- **Edge Config mode (recommended):** set `EDGE_CONFIG` **or** `EXPERIMENTATION_CONFIG` (both are Vercel Edge Config connection strings). Flags are read from a Vercel Edge Config store that the Marketplace integration keeps in sync — the lowest-latency option. The Marketplace integration injects `EXPERIMENTATION_CONFIG` when you enable Edge Config syncing; `EDGE_CONFIG` takes precedence if both are set.
- **Server SDK mode (default):** Flags are read directly from LaunchDarkly, with no Edge Config required. Used automatically when neither `EDGE_CONFIG` nor `EXPERIMENTATION_CONFIG` is set.

See `.env.example` for a template.
You also need to set (see `.env.example` for a template):

- [`FLAGS_SECRET`](https://vercel.com/docs/feature-flags/flags-explorer/reference#flags_secret-environment-variable)
- `EDGE_CONFIG` (Vercel Edge Config connection string)
- `LAUNCHDARKLY_PROJECT_SLUG`
- `LAUNCHDARKLY_CLIENT_SIDE_ID`
- `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` (set to same value as `LAUNCHDARKLY_CLIENT_SIDE_ID`)
- [`FLAGS_SECRET`](https://vercel.com/docs/feature-flags/flags-explorer/reference#flags_secret-environment-variable) — 32 random bytes, base64-encoded. The deploy button prompts you to generate this.
- _(Optional)_ `LAUNCHDARKLY_PROJECT_SLUG` — used only to link each flag to its details page in the LaunchDarkly console.

_(Optional)_ If you provide the `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_PROJECT_KEY` and `LAUNCHDARKLY_ENVIRONMENT` environment variables, the Flags Explorer will fetch additional metadata from the LaunchDarkly API.

This will show the description (if set) and displays a link to the feature flag on the LaunchDarkly Console.

You can create an API key and find project and environment values in the [LaunchDarkly Console](https://app.launchdarkly.com/settings/projects).

## Manual Setup (without Marketplace integration)

If you don't use the marketplace integration, you can set up this template by hand:

1. Pick a server-side flag source: either install the [LaunchDarkly Vercel integration](https://launchdarkly.com/docs/integrations/vercel) so your flags are replicated into a Vercel Edge Config store, or grab a server-side SDK key from LaunchDarkly (server SDK mode). See [Step 5](#step-5-set-environment-variables).
2. Generate a `FLAGS_SECRET` yourself instead of relying on the deploy prompt:

```bash
node -e "console.log(crypto.randomBytes(32).toString('base64url'))"
```
Comment on lines +120 to +124

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too familiar, can you just do this?


3. Set the environment variables below (see `.env.example` for a template). Which ones you need depends on the server-side flag source you picked in step 1.

**Always required:**

| Variable | Description |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `FLAGS_SECRET` | Used by the Flags Explorer to securely read and override flags. 32 random bytes, base64url-encoded (see step 2). |
| `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` | Your LaunchDarkly client-side ID, used by the client-side React SDK. |

**Edge Config mode** (recommended — set these if you are not using the marketplace integration):

| Variable | Description |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EDGE_CONFIG` | Connection string for the Vercel Edge Config store the integration keeps in sync. Preferred when set. |
| `EXPERIMENTATION_CONFIG` | Alternative Edge Config connection string. Injected by the Marketplace integration when Edge Config syncing is enabled. Used when `EDGE_CONFIG` is not set. |
| `LAUNCHDARKLY_CLIENT_SIDE_ID` | Your LaunchDarkly client-side ID, used to read flags from Edge Config. |

**Server SDK mode** (set this instead if you're reading flags directly from LaunchDarkly):

| Variable | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `LAUNCHDARKLY_SDK_KEY` | A LaunchDarkly server-side SDK key. Used automatically when neither `EDGE_CONFIG` nor `EXPERIMENTATION_CONFIG` is set. |

**Optional** (enable richer Flags Explorer metadata):

| Variable | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `LAUNCHDARKLY_PROJECT_SLUG` | Links each flag to its details page in the LaunchDarkly console. |
| `LAUNCHDARKLY_API_KEY` | Lets the Flags Explorer fetch extra flag metadata (descriptions, console links) from the LaunchDarkly API. |
| `LAUNCHDARKLY_PROJECT_KEY` | Project the API key reads from. Required alongside `LAUNCHDARKLY_API_KEY`. |
| `LAUNCHDARKLY_ENVIRONMENT` | Environment the API key reads from. Required alongside `LAUNCHDARKLY_API_KEY`. |

You can create an API key and find your project and environment values in the [LaunchDarkly Console](https://app.launchdarkly.com/settings/projects).
16 changes: 8 additions & 8 deletions flags-sdk/launchdarkly/app/[code]/layout.tsx

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is just the linter

Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export async function generateStaticParams() {

// Instead of returning an empty array you could also call generatePermutations
// to generate the permutations upfront.
const codes = await generatePermutations(productFlags);
return codes.map((code) => ({ code }));
const codes = await generatePermutations(productFlags)
return codes.map((code) => ({ code }))
}

export default async function Layout(props: {
Expand All @@ -25,13 +25,13 @@ export default async function Layout(props: {
code: string
}>
}) {
const params = await props.params;
const values = await deserialize(productFlags, params.code);
const params = await props.params
const values = await deserialize(productFlags, params.code)

const showFreeDeliveryBanner = await showFreeDeliveryBannerFlag(
params.code,
productFlags,
);
productFlags
)

return (
<StaticLDProvider>
Expand All @@ -47,5 +47,5 @@ export default async function Layout(props: {
<DevTools />
</div>
</StaticLDProvider>
);
)
}
9 changes: 7 additions & 2 deletions flags-sdk/launchdarkly/app/api/bootstrap/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { getStableId } from '@/utils/get-stable-id'
import { ldAdapter, type LDContext } from '@flags-sdk/launchdarkly'
import { NextResponse } from 'next/server'
import { ldAdapter, type LDContext } from '@/lib/launchdarkly-adapter'
import { connection, NextResponse } from 'next/server'

export async function GET(request: Request): Promise<NextResponse> {
// This route returns a per-user flag payload (keyed by the stable id cookie),
// so it must run per request. Marking it dynamic also avoids initializing the
// LaunchDarkly client at build time.
await connection()

await ldAdapter.ldClient.waitForInitialization()
const stableId = await getStableId()

Expand Down
2 changes: 1 addition & 1 deletion flags-sdk/launchdarkly/flags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ldAdapter, type LDContext } from '@flags-sdk/launchdarkly'
import { flag } from 'flags/next'
import { ldAdapter, type LDContext } from './lib/launchdarkly-adapter'
import { identify } from './lib/identify'

export const showSummerBannerFlag = flag<boolean, LDContext>({
Expand Down
148 changes: 148 additions & 0 deletions flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import type { Adapter } from 'flags'
import {
createLaunchDarklyAdapter,
type LDContext,
} from '@flags-sdk/launchdarkly'

export type { LDContext }

/**
* This adapter supports two ways of reading LaunchDarkly flags, and picks one
* automatically based on which environment variables are present:
*
* 1. Edge Config mode (`EDGE_CONFIG` or `EXPERIMENTATION_CONFIG`, plus

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template originally had EDGE_CONFIG, probably the connected account integration expected that env var. let me know what's the preferred env var now.

* `LAUNCHDARKLY_CLIENT_SIDE_ID`): reads flags from a Vercel Edge Config
* store using `@flags-sdk/launchdarkly`. This is edge-native and the lowest
* latency, and is what the LaunchDarkly Vercel Marketplace integration is
* designed for. The Marketplace integration injects the connection string
* as `EXPERIMENTATION_CONFIG` when Edge Config syncing is enabled; a
* manually-provisioned store is typically `EDGE_CONFIG`. Either works.
*
* 2. Server SDK mode (`LAUNCHDARKLY_SDK_KEY`): talks to LaunchDarkly directly
* using `@launchdarkly/node-server-sdk`. This requires no Edge Config, so
* the template works with just the SDK key.
*
* Edge Config mode is preferred when both are configured.
*/

// The Marketplace integration provides the Edge Config connection string as
// `EXPERIMENTATION_CONFIG`; a manually-created store is usually `EDGE_CONFIG`.
// Both are valid Edge Config URLs, so accept either (EDGE_CONFIG wins if set).
const edgeConfigConnectionString =
process.env.EDGE_CONFIG ?? process.env.EXPERIMENTATION_CONFIG
const clientSideId = process.env.LAUNCHDARKLY_CLIENT_SIDE_ID
const projectSlug = process.env.LAUNCHDARKLY_PROJECT_SLUG
const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY

/** Links a flag to its details page in the LaunchDarkly console. */
function flagOrigin(key: string): string | undefined {
return projectSlug
? `https://app.launchdarkly.com/projects/${projectSlug}/flags/${key}/`
: undefined
}

/** The subset of the LaunchDarkly client this app relies on. */
interface LDClientLike {
waitForInitialization(): Promise<unknown>
allFlagsState(context: LDContext): Promise<{ toJSON(): unknown } | undefined>
}

interface LDAdapterLike {
variation<ValueType = unknown>(): Adapter<ValueType, LDContext>
ldClient: LDClientLike
}

function createEdgeConfigAdapter(): LDAdapterLike {
return createLaunchDarklyAdapter({
projectSlug: projectSlug ?? '',
clientSideId: clientSideId!,
edgeConfigConnectionString: edgeConfigConnectionString!,
}) as unknown as LDAdapterLike
}

function createServerSdkAdapter(): LDAdapterLike {
// Lazily initialize a single client so we only connect when this mode runs.
let clientPromise:
| Promise<import('@launchdarkly/node-server-sdk').LDClient>
| undefined

async function getClient() {
if (!clientPromise) {
clientPromise = import('@launchdarkly/node-server-sdk').then(
async ({ init }) => {
const client = init(sdkKey!)
await client.waitForInitialization({ timeout: 10 })
return client
}
)
}
return clientPromise
}

return {
variation<ValueType = unknown>(): Adapter<ValueType, LDContext> {
return {
origin: flagOrigin,
async decide({ key, entities, defaultValue }) {
const client = await getClient()
const context: LDContext = entities ?? {
kind: 'user',
key: 'anonymous',
anonymous: true,
}
return client.variation(
key,
context,
defaultValue
) as Promise<ValueType>
},
}
},
ldClient: {
waitForInitialization: () => getClient(),
async allFlagsState(context: LDContext) {
const client = await getClient()
return (await client.allFlagsState(context)) as unknown as {
toJSON(): unknown
}
},
},
}
}

let resolved: LDAdapterLike | undefined

// Resolve the mode lazily (on first use) so importing this module never throws
// at build time when env vars are not yet available.
function adapter(): LDAdapterLike {
if (resolved) return resolved
if (edgeConfigConnectionString && clientSideId) {
resolved = createEdgeConfigAdapter()
} else if (sdkKey) {
resolved = createServerSdkAdapter()
} else {
throw new Error(
'LaunchDarkly is not configured. Set EDGE_CONFIG (or EXPERIMENTATION_CONFIG) ' +
'and LAUNCHDARKLY_CLIENT_SIDE_ID for Edge Config mode, or LAUNCHDARKLY_SDK_KEY ' +
'for server SDK mode.'
)
}
return resolved
}

export const ldAdapter: LDAdapterLike = {
variation<ValueType = unknown>(): Adapter<ValueType, LDContext> {
let inner: Adapter<ValueType, LDContext> | undefined
return {
origin: flagOrigin,
decide(options) {
if (!inner) inner = adapter().variation<ValueType>()
return inner.decide(options)
},
}
},
ldClient: {
waitForInitialization: () => adapter().ldClient.waitForInitialization(),
allFlagsState: (context) => adapter().ldClient.allFlagsState(context),
},
}
1 change: 1 addition & 0 deletions flags-sdk/launchdarkly/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@flags-sdk/launchdarkly": "^0.3.2",
"@launchdarkly/node-server-sdk": "^9.11.3",
"@headlessui/react": "^2.2.0",
"@heroicons/react": "2.2.0",
"@tailwindcss/aspect-ratio": "0.4.2",
Expand Down
Loading