Skip to content

Commit b165bc2

Browse files
authored
fix: calling getVariableValue in server actions (#1014)
1 parent 5db8b9b commit b165bc2

13 files changed

+161
-174
lines changed

e2e/nextjs/app-router/app/app/normal/ClientComponent.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
useAllFeatures,
66
renderIfEnabled,
77
} from '@devcycle/nextjs-sdk'
8+
import { useState } from 'react'
9+
import { testAction } from './action'
810

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

2023
return (
2124
<div>
@@ -24,6 +27,14 @@ export const ClientComponent = () => {
2427
<p>Client Disabled Variable: {JSON.stringify(disabledVar)}</p>
2528
<p>Client All Variables: {JSON.stringify(allVariables)}</p>
2629
<p>Client All Features: {JSON.stringify(allFeatures)}</p>
30+
<p>Server Function Result: {JSON.stringify(actionResult)}</p>
31+
<button
32+
onClick={() =>
33+
testAction().then((result) => setActionResult(result))
34+
}
35+
>
36+
Test Action
37+
</button>
2738
<ConditionalComponent />
2839
</div>
2940
)

e2e/nextjs/app-router/app/app/normal/ServerComponent.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { headers } from 'next/headers'
12
import { getAllFeatures, getAllVariables, getVariableValue } from './devcycle'
23
export const ServerComponent = async () => {
34
const enabledVar = await getVariableValue('enabled-feature', false)
45
const disabledVar = await getVariableValue('disabled-feature', false)
56
const allVariables = await getAllVariables()
67
const allFeatures = await getAllFeatures()
8+
const reqHeaders = await headers()
79

810
return (
911
<div>
@@ -12,6 +14,10 @@ export const ServerComponent = async () => {
1214
<p>Server Disabled Variable: {JSON.stringify(disabledVar)}</p>
1315
<p>Server All Variables: {JSON.stringify(allVariables)}</p>
1416
<p>Server All Features: {JSON.stringify(allFeatures)}</p>
17+
<p>
18+
Middleware Enabled Feature:{' '}
19+
{reqHeaders.get('Middleware-Enabled-Feature')}
20+
</p>
1521
</div>
1622
)
1723
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use server'
2+
3+
import { getVariableValue } from './devcycle'
4+
5+
export const testAction = async () => {
6+
const result = await getVariableValue('enabled-feature', false)
7+
return result
8+
}

e2e/nextjs/app-router/app/app/normal/devcycle.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const {
1111
userGetter: async () => {
1212
const reqHeaders = await headers()
1313
return {
14-
user_id: '123',
14+
user_id: 'normal-user',
1515
customData: {
1616
// set a dummy field here so that the headers call stays in the build output
1717
someKey: reqHeaders.get('some-key'),
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { NextResponse } from 'next/server'
2+
import { getVariableValue } from './app/normal/devcycle'
3+
4+
export const middleware = async () => {
5+
const response = NextResponse.next()
6+
const variableValue = await getVariableValue('enabled-feature', false)
7+
8+
// Add custom header
9+
response.headers.set(
10+
'Middleware-Enabled-Feature',
11+
JSON.stringify(variableValue),
12+
)
13+
14+
return response
15+
}
16+
17+
export const config = {
18+
matcher: '/normal',
19+
}

e2e/nextjs/app-router/app/yarn.lock

+25-24
Original file line numberDiff line numberDiff line change
@@ -6,67 +6,68 @@ __metadata:
66
cacheKey: 10
77

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

2020
"@devcycle/js-client-sdk@file:../../../../dist/sdk/js::locator=app%40workspace%3A.":
21-
version: 1.31.0
22-
resolution: "@devcycle/js-client-sdk@file:../../../../dist/sdk/js#../../../../dist/sdk/js::hash=40e7b0&locator=app%40workspace%3A."
21+
version: 1.33.0
22+
resolution: "@devcycle/js-client-sdk@file:../../../../dist/sdk/js#../../../../dist/sdk/js::hash=5d8b0d&locator=app%40workspace%3A."
2323
dependencies:
24-
"@devcycle/types": "npm:^1.18.0"
24+
"@devcycle/types": "npm:^1.20.0"
2525
fetch-retry: "npm:^5.0.6"
2626
lodash: "npm:^4.17.21"
2727
ua-parser-js: "npm:^1.0.36"
2828
uuid: "npm:^8.3.2"
29-
checksum: 3acdcbf8b351c08cf6b482e7a3e56aa9560a7e3589cfaf9fcd330624e101c3001479b68c457fa3a54384d1248b572f3ddb71364e6bf5ffbb385cd2f02dabb915
29+
checksum: d27d6f6952f363ea71a3eb6865021d1af175d696a81284f36dd6e7dd134c00996f8506cf5c8c35cc7b9c5044dc30edbf9308d580c330ed6bad7623c98dc91e7a
3030
languageName: node
3131
linkType: hard
3232

3333
"@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs::locator=app%40workspace%3A.":
34-
version: 2.6.0
35-
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=f2c38c&locator=app%40workspace%3A."
34+
version: 2.8.0
35+
resolution: "@devcycle/nextjs-sdk@file:../../../../dist/sdk/nextjs#../../../../dist/sdk/nextjs::hash=7ea780&locator=app%40workspace%3A."
3636
dependencies:
37-
"@devcycle/bucketing": "npm:^1.23.0"
38-
"@devcycle/js-client-sdk": "npm:^1.31.0"
39-
"@devcycle/react-client-sdk": "npm:^1.29.0"
40-
"@devcycle/types": "npm:^1.18.0"
37+
"@devcycle/bucketing": "npm:^1.25.0"
38+
"@devcycle/js-client-sdk": "npm:^1.33.0"
39+
"@devcycle/react-client-sdk": "npm:^1.31.0"
40+
"@devcycle/types": "npm:^1.20.0"
41+
class-transformer: "npm:^0.5.1"
4142
hoist-non-react-statics: "npm:^3.3.2"
4243
server-only: "npm:^0.0.1"
43-
checksum: 9d905c89316e3e87fa1ec663ef29298abca0d40d7603bb156f4942568a322cf45e581d84481d7f0c2957f73f5fb72fbb5818307042ebd1a6d4ba796317f5ce2a
44+
checksum: a97ec2cd27c27cdef68d9b87bd99e32a4babe7a8bdbdb33941463a7651402b95864202c1a028870a471925a3aa5644d7fdfacdd730072a76cf2a429df8e89329
4445
languageName: node
4546
linkType: hard
4647

4748
"@devcycle/react-client-sdk@file:../../../../dist/sdk/react::locator=app%40workspace%3A.":
48-
version: 1.29.0
49-
resolution: "@devcycle/react-client-sdk@file:../../../../dist/sdk/react#../../../../dist/sdk/react::hash=53291e&locator=app%40workspace%3A."
49+
version: 1.31.0
50+
resolution: "@devcycle/react-client-sdk@file:../../../../dist/sdk/react#../../../../dist/sdk/react::hash=3e2fc6&locator=app%40workspace%3A."
5051
dependencies:
51-
"@devcycle/js-client-sdk": "npm:^1.31.0"
52-
"@devcycle/types": "npm:^1.18.0"
52+
"@devcycle/js-client-sdk": "npm:^1.33.0"
53+
"@devcycle/types": "npm:^1.20.0"
5354
hoist-non-react-statics: "npm:^3.3.2"
5455
peerDependencies:
5556
react: ">=16.8.0"
56-
checksum: bc8140acca4d07fd4c5adba4e3b750b52314eaf2f2b8794b073d5878dc99a6904594ef1c10702420a29e3712ea3d0238b72a511e3d731adc1fe7fc0c053cba67
57+
checksum: f022ea564ad4ef722b33afc295ccdd61eb44c03dec21f47a368d418297da3ba7ad398b175016d3ad9bc32f051159fc8c5d6a1875c4bca7bca01176cf133d3a2f
5758
languageName: node
5859
linkType: hard
5960

6061
"@devcycle/types@file:../../../../dist/lib/shared/types::locator=app%40workspace%3A.":
61-
version: 1.18.0
62-
resolution: "@devcycle/types@file:../../../../dist/lib/shared/types#../../../../dist/lib/shared/types::hash=4eb545&locator=app%40workspace%3A."
62+
version: 1.20.0
63+
resolution: "@devcycle/types@file:../../../../dist/lib/shared/types#../../../../dist/lib/shared/types::hash=49328d&locator=app%40workspace%3A."
6364
dependencies:
6465
class-transformer: "npm:0.5.1"
6566
class-validator: "npm:0.14.1"
6667
iso-639-1: "npm:^2.1.13"
6768
lodash: "npm:^4.17.21"
6869
reflect-metadata: "npm:^0.1.13"
69-
checksum: 9422caa1a8dd9cdd356ac213b968faca1e656381a195efdc12118bf65a672f84930d0866716c0717507ea9b54b031c779d3d1427fd608a4f511b5ee4a93ceb0c
70+
checksum: 37a261b629edf04b9225ac3a29001c4c7694269d78221533b401ceb351ebfcf16602e18049797fe0af3baab17e84ffe1d7834227eeb4850f67d00925bc001437
7071
languageName: node
7172
linkType: hard
7273

@@ -378,7 +379,7 @@ __metadata:
378379
languageName: node
379380
linkType: hard
380381

381-
"class-transformer@npm:0.5.1":
382+
"class-transformer@npm:0.5.1, class-transformer@npm:^0.5.1":
382383
version: 0.5.1
383384
resolution: "class-transformer@npm:0.5.1"
384385
checksum: 750327e3e9a5cf233c5234252f4caf6b06c437bf68a24acbdcfb06c8e0bfff7aa97c30428184813e38e08111b42871f20c5cf669ea4490f8ae837c09f08b31e7

e2e/nextjs/app-router/tests/app-router.spec.ts

+9
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ test('has expected page elements', async ({ page }) => {
9595
await expect(
9696
page.getByText('Client Component Conditionally Bundled'),
9797
).toBeVisible()
98+
99+
// test server action flagging
100+
await page.getByText('Test Action').click()
101+
await expect(page.getByText('Server Function Result: true')).toBeVisible()
102+
103+
// test middleware flagging
104+
await expect(
105+
page.getByText('Middleware Enabled Feature: true'),
106+
).toBeVisible()
98107
})
99108

100109
test('works after a client side navigation', async ({ page }) => {

sdk/nextjs/src/server/allFeatures.ts

-14
This file was deleted.

sdk/nextjs/src/server/getAllVariables.ts

-14
This file was deleted.

sdk/nextjs/src/server/getVariableValue.ts

-27
This file was deleted.

sdk/nextjs/src/server/initialize.ts

+40-51
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { DevCycleUser, initializeDevCycle } from '@devcycle/js-client-sdk'
2-
import { getClient, setClient } from './requestContext'
1+
import {
2+
DevCycleClient,
3+
DevCycleUser,
4+
initializeDevCycle,
5+
} from '@devcycle/js-client-sdk'
36
import { getUserAgent } from './userAgent'
47
import { DevCycleNextOptions, DevCycleServerData } from '../common/types'
58
import { cache } from 'react'
@@ -23,61 +26,47 @@ const cachedUserGetter = cache(
2326
},
2427
)
2528

26-
export const initialize = async (
27-
sdkKey: string,
28-
clientSDKKey: string,
29-
userGetter: () => DevCycleUser | Promise<DevCycleUser>,
30-
options: DevCycleNextOptions = {},
31-
): Promise<DevCycleServerData> => {
32-
const [userAgent, user, configData] = await Promise.all([
33-
getUserAgent(options),
34-
cachedUserGetter(userGetter),
35-
getConfigFromSource(sdkKey, clientSDKKey, options),
36-
])
37-
38-
if (!user || typeof user.user_id !== 'string') {
39-
throw new Error('DevCycle user getter must return a user')
40-
}
41-
42-
const initializeAlreadyCalled = !!getClient()
43-
44-
if (!initializeAlreadyCalled) {
45-
setClient(
46-
initializeDevCycle(sdkKey, user, {
47-
...options,
48-
deferInitialization: true,
49-
...jsClientOptions,
50-
}),
51-
)
52-
}
29+
export const initialize = cache(
30+
async (
31+
sdkKey: string,
32+
clientSDKKey: string,
33+
userGetter: () => DevCycleUser | Promise<DevCycleUser>,
34+
options: DevCycleNextOptions = {},
35+
): Promise<DevCycleServerData & { client: DevCycleClient }> => {
36+
const [userAgent, user, configData] = await Promise.all([
37+
getUserAgent(options),
38+
cachedUserGetter(userGetter),
39+
getConfigFromSource(sdkKey, clientSDKKey, options),
40+
])
5341

54-
let config = null
55-
try {
56-
config = await getBucketedConfig(
57-
configData.config,
58-
configData.lastModified,
59-
user,
60-
options,
61-
userAgent,
62-
)
63-
} catch (e) {
64-
console.error('Error fetching DevCycle config', e)
65-
}
42+
if (!user || typeof user.user_id !== 'string') {
43+
throw new Error('DevCycle user getter must return a user')
44+
}
6645

67-
const client = getClient()
46+
const client = initializeDevCycle(sdkKey, user, {
47+
...options,
48+
deferInitialization: true,
49+
...jsClientOptions,
50+
})
6851

69-
if (!client) {
70-
throw new Error(
71-
"React 'cache' function not working as expected. Please contact DevCycle support.",
72-
)
73-
}
52+
let config = null
53+
try {
54+
config = await getBucketedConfig(
55+
configData.config,
56+
configData.lastModified,
57+
user,
58+
options,
59+
userAgent,
60+
)
61+
} catch (e) {
62+
console.error('Error fetching DevCycle config', e)
63+
}
7464

75-
if (!initializeAlreadyCalled) {
7665
client.synchronizeBootstrapData(config, user, userAgent)
77-
}
7866

79-
return { config, user, userAgent }
80-
}
67+
return { config, user, userAgent, client }
68+
},
69+
)
8170

8271
export const validateSDKKey = (
8372
sdkKey: string,

0 commit comments

Comments
 (0)