Skip to content

Commit

Permalink
fix: calling getVariableValue in server actions (#1014)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajwootto authored Dec 16, 2024
1 parent 5db8b9b commit b165bc2
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 174 deletions.
11 changes: 11 additions & 0 deletions e2e/nextjs/app-router/app/app/normal/ClientComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
useAllFeatures,
renderIfEnabled,
} from '@devcycle/nextjs-sdk'
import { useState } from 'react'
import { testAction } from './action'

const ConditionalComponent = renderIfEnabled(
'enabled-feature',
Expand All @@ -16,6 +18,7 @@ export const ClientComponent = () => {
const disabledVar = useVariableValue('disabled-feature', false)
const allVariables = useAllVariables()
const allFeatures = useAllFeatures()
const [actionResult, setActionResult] = useState<boolean | null>(null)

return (
<div>
Expand All @@ -24,6 +27,14 @@ export const ClientComponent = () => {
<p>Client Disabled Variable: {JSON.stringify(disabledVar)}</p>
<p>Client All Variables: {JSON.stringify(allVariables)}</p>
<p>Client All Features: {JSON.stringify(allFeatures)}</p>
<p>Server Function Result: {JSON.stringify(actionResult)}</p>
<button
onClick={() =>
testAction().then((result) => setActionResult(result))
}
>
Test Action
</button>
<ConditionalComponent />
</div>
)
Expand Down
6 changes: 6 additions & 0 deletions e2e/nextjs/app-router/app/app/normal/ServerComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { headers } from 'next/headers'
import { getAllFeatures, getAllVariables, getVariableValue } from './devcycle'
export const ServerComponent = async () => {
const enabledVar = await getVariableValue('enabled-feature', false)
const disabledVar = await getVariableValue('disabled-feature', false)
const allVariables = await getAllVariables()
const allFeatures = await getAllFeatures()
const reqHeaders = await headers()

return (
<div>
Expand All @@ -12,6 +14,10 @@ export const ServerComponent = async () => {
<p>Server Disabled Variable: {JSON.stringify(disabledVar)}</p>
<p>Server All Variables: {JSON.stringify(allVariables)}</p>
<p>Server All Features: {JSON.stringify(allFeatures)}</p>
<p>
Middleware Enabled Feature:{' '}
{reqHeaders.get('Middleware-Enabled-Feature')}
</p>
</div>
)
}
8 changes: 8 additions & 0 deletions e2e/nextjs/app-router/app/app/normal/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use server'

import { getVariableValue } from './devcycle'

export const testAction = async () => {
const result = await getVariableValue('enabled-feature', false)
return result
}
2 changes: 1 addition & 1 deletion e2e/nextjs/app-router/app/app/normal/devcycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const {
userGetter: async () => {
const reqHeaders = await headers()
return {
user_id: '123',
user_id: 'normal-user',
customData: {
// set a dummy field here so that the headers call stays in the build output
someKey: reqHeaders.get('some-key'),
Expand Down
19 changes: 19 additions & 0 deletions e2e/nextjs/app-router/app/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from 'next/server'
import { getVariableValue } from './app/normal/devcycle'

export const middleware = async () => {
const response = NextResponse.next()
const variableValue = await getVariableValue('enabled-feature', false)

// Add custom header
response.headers.set(
'Middleware-Enabled-Feature',
JSON.stringify(variableValue),
)

return response
}

export const config = {
matcher: '/normal',
}
49 changes: 25 additions & 24 deletions e2e/nextjs/app-router/app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,68 @@ __metadata:
cacheKey: 10

"@devcycle/bucketing@file:../../../../dist/lib/shared/bucketing::locator=app%40workspace%3A.":
version: 1.23.0
resolution: "@devcycle/bucketing@file:../../../../dist/lib/shared/bucketing#../../../../dist/lib/shared/bucketing::hash=3760ab&locator=app%40workspace%3A."
version: 1.25.0
resolution: "@devcycle/bucketing@file:../../../../dist/lib/shared/bucketing#../../../../dist/lib/shared/bucketing::hash=f620ff&locator=app%40workspace%3A."
dependencies:
"@devcycle/types": "npm:^1.18.0"
"@devcycle/types": "npm:^1.20.0"
lodash: "npm:^4.17.21"
murmurhash: "npm:^2.0.0"
ua-parser-js: "npm:^1.0.36"
checksum: 44395904d0770308ce33a41e7bf5aa82523b98ddf6a2e44d0a66e62a01ffbe76b0e931ad28def059afd260f296ca0f93c36e3960cc5f0a7f3d85b7a91b5fccd0
checksum: 26b227546ed21ed61ccbf917eb5f6d60e7c5a31fd0b61d015f92dd95b4c51b1f79182ef0cf9bf54ddbd423f4eda496a79af77d75460126ef59b543a8dbeab358
languageName: node
linkType: hard

"@devcycle/js-client-sdk@file:../../../../dist/sdk/js::locator=app%40workspace%3A.":
version: 1.31.0
resolution: "@devcycle/js-client-sdk@file:../../../../dist/sdk/js#../../../../dist/sdk/js::hash=40e7b0&locator=app%40workspace%3A."
version: 1.33.0
resolution: "@devcycle/js-client-sdk@file:../../../../dist/sdk/js#../../../../dist/sdk/js::hash=5d8b0d&locator=app%40workspace%3A."
dependencies:
"@devcycle/types": "npm:^1.18.0"
"@devcycle/types": "npm:^1.20.0"
fetch-retry: "npm:^5.0.6"
lodash: "npm:^4.17.21"
ua-parser-js: "npm:^1.0.36"
uuid: "npm:^8.3.2"
checksum: 3acdcbf8b351c08cf6b482e7a3e56aa9560a7e3589cfaf9fcd330624e101c3001479b68c457fa3a54384d1248b572f3ddb71364e6bf5ffbb385cd2f02dabb915
checksum: d27d6f6952f363ea71a3eb6865021d1af175d696a81284f36dd6e7dd134c00996f8506cf5c8c35cc7b9c5044dc30edbf9308d580c330ed6bad7623c98dc91e7a
languageName: node
linkType: hard

"@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs::locator=app%40workspace%3A.":
version: 2.6.0
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=f2c38c&locator=app%40workspace%3A."
version: 2.8.0
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=7ea780&locator=app%40workspace%3A."
dependencies:
"@devcycle/bucketing": "npm:^1.23.0"
"@devcycle/js-client-sdk": "npm:^1.31.0"
"@devcycle/react-client-sdk": "npm:^1.29.0"
"@devcycle/types": "npm:^1.18.0"
"@devcycle/bucketing": "npm:^1.25.0"
"@devcycle/js-client-sdk": "npm:^1.33.0"
"@devcycle/react-client-sdk": "npm:^1.31.0"
"@devcycle/types": "npm:^1.20.0"
class-transformer: "npm:^0.5.1"
hoist-non-react-statics: "npm:^3.3.2"
server-only: "npm:^0.0.1"
checksum: 9d905c89316e3e87fa1ec663ef29298abca0d40d7603bb156f4942568a322cf45e581d84481d7f0c2957f73f5fb72fbb5818307042ebd1a6d4ba796317f5ce2a
checksum: a97ec2cd27c27cdef68d9b87bd99e32a4babe7a8bdbdb33941463a7651402b95864202c1a028870a471925a3aa5644d7fdfacdd730072a76cf2a429df8e89329
languageName: node
linkType: hard

"@devcycle/react-client-sdk@file:../../../../dist/sdk/react::locator=app%40workspace%3A.":
version: 1.29.0
resolution: "@devcycle/react-client-sdk@file:../../../../dist/sdk/react#../../../../dist/sdk/react::hash=53291e&locator=app%40workspace%3A."
version: 1.31.0
resolution: "@devcycle/react-client-sdk@file:../../../../dist/sdk/react#../../../../dist/sdk/react::hash=3e2fc6&locator=app%40workspace%3A."
dependencies:
"@devcycle/js-client-sdk": "npm:^1.31.0"
"@devcycle/types": "npm:^1.18.0"
"@devcycle/js-client-sdk": "npm:^1.33.0"
"@devcycle/types": "npm:^1.20.0"
hoist-non-react-statics: "npm:^3.3.2"
peerDependencies:
react: ">=16.8.0"
checksum: bc8140acca4d07fd4c5adba4e3b750b52314eaf2f2b8794b073d5878dc99a6904594ef1c10702420a29e3712ea3d0238b72a511e3d731adc1fe7fc0c053cba67
checksum: f022ea564ad4ef722b33afc295ccdd61eb44c03dec21f47a368d418297da3ba7ad398b175016d3ad9bc32f051159fc8c5d6a1875c4bca7bca01176cf133d3a2f
languageName: node
linkType: hard

"@devcycle/types@file:../../../../dist/lib/shared/types::locator=app%40workspace%3A.":
version: 1.18.0
resolution: "@devcycle/types@file:../../../../dist/lib/shared/types#../../../../dist/lib/shared/types::hash=4eb545&locator=app%40workspace%3A."
version: 1.20.0
resolution: "@devcycle/types@file:../../../../dist/lib/shared/types#../../../../dist/lib/shared/types::hash=49328d&locator=app%40workspace%3A."
dependencies:
class-transformer: "npm:0.5.1"
class-validator: "npm:0.14.1"
iso-639-1: "npm:^2.1.13"
lodash: "npm:^4.17.21"
reflect-metadata: "npm:^0.1.13"
checksum: 9422caa1a8dd9cdd356ac213b968faca1e656381a195efdc12118bf65a672f84930d0866716c0717507ea9b54b031c779d3d1427fd608a4f511b5ee4a93ceb0c
checksum: 37a261b629edf04b9225ac3a29001c4c7694269d78221533b401ceb351ebfcf16602e18049797fe0af3baab17e84ffe1d7834227eeb4850f67d00925bc001437
languageName: node
linkType: hard

Expand Down Expand Up @@ -378,7 +379,7 @@ __metadata:
languageName: node
linkType: hard

"class-transformer@npm:0.5.1":
"class-transformer@npm:0.5.1, class-transformer@npm:^0.5.1":
version: 0.5.1
resolution: "class-transformer@npm:0.5.1"
checksum: 750327e3e9a5cf233c5234252f4caf6b06c437bf68a24acbdcfb06c8e0bfff7aa97c30428184813e38e08111b42871f20c5cf669ea4490f8ae837c09f08b31e7
Expand Down
9 changes: 9 additions & 0 deletions e2e/nextjs/app-router/tests/app-router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ test('has expected page elements', async ({ page }) => {
await expect(
page.getByText('Client Component Conditionally Bundled'),
).toBeVisible()

// test server action flagging
await page.getByText('Test Action').click()
await expect(page.getByText('Server Function Result: true')).toBeVisible()

// test middleware flagging
await expect(
page.getByText('Middleware Enabled Feature: true'),
).toBeVisible()
})

test('works after a client side navigation', async ({ page }) => {
Expand Down
14 changes: 0 additions & 14 deletions sdk/nextjs/src/server/allFeatures.ts

This file was deleted.

14 changes: 0 additions & 14 deletions sdk/nextjs/src/server/getAllVariables.ts

This file was deleted.

27 changes: 0 additions & 27 deletions sdk/nextjs/src/server/getVariableValue.ts

This file was deleted.

91 changes: 40 additions & 51 deletions sdk/nextjs/src/server/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { DevCycleUser, initializeDevCycle } from '@devcycle/js-client-sdk'
import { getClient, setClient } from './requestContext'
import {
DevCycleClient,
DevCycleUser,
initializeDevCycle,
} from '@devcycle/js-client-sdk'
import { getUserAgent } from './userAgent'
import { DevCycleNextOptions, DevCycleServerData } from '../common/types'
import { cache } from 'react'
Expand All @@ -23,61 +26,47 @@ const cachedUserGetter = cache(
},
)

export const initialize = async (
sdkKey: string,
clientSDKKey: string,
userGetter: () => DevCycleUser | Promise<DevCycleUser>,
options: DevCycleNextOptions = {},
): Promise<DevCycleServerData> => {
const [userAgent, user, configData] = await Promise.all([
getUserAgent(options),
cachedUserGetter(userGetter),
getConfigFromSource(sdkKey, clientSDKKey, options),
])

if (!user || typeof user.user_id !== 'string') {
throw new Error('DevCycle user getter must return a user')
}

const initializeAlreadyCalled = !!getClient()

if (!initializeAlreadyCalled) {
setClient(
initializeDevCycle(sdkKey, user, {
...options,
deferInitialization: true,
...jsClientOptions,
}),
)
}
export const initialize = cache(
async (
sdkKey: string,
clientSDKKey: string,
userGetter: () => DevCycleUser | Promise<DevCycleUser>,
options: DevCycleNextOptions = {},
): Promise<DevCycleServerData & { client: DevCycleClient }> => {
const [userAgent, user, configData] = await Promise.all([
getUserAgent(options),
cachedUserGetter(userGetter),
getConfigFromSource(sdkKey, clientSDKKey, options),
])

let config = null
try {
config = await getBucketedConfig(
configData.config,
configData.lastModified,
user,
options,
userAgent,
)
} catch (e) {
console.error('Error fetching DevCycle config', e)
}
if (!user || typeof user.user_id !== 'string') {
throw new Error('DevCycle user getter must return a user')
}

const client = getClient()
const client = initializeDevCycle(sdkKey, user, {
...options,
deferInitialization: true,
...jsClientOptions,
})

if (!client) {
throw new Error(
"React 'cache' function not working as expected. Please contact DevCycle support.",
)
}
let config = null
try {
config = await getBucketedConfig(
configData.config,
configData.lastModified,
user,
options,
userAgent,
)
} catch (e) {
console.error('Error fetching DevCycle config', e)
}

if (!initializeAlreadyCalled) {
client.synchronizeBootstrapData(config, user, userAgent)
}

return { config, user, userAgent }
}
return { config, user, userAgent, client }
},
)

export const validateSDKKey = (
sdkKey: string,
Expand Down
Loading

0 comments on commit b165bc2

Please sign in to comment.