diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f5331a..f157822 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ on: jobs: from-template: - uses: Unleash/.github/.github/workflows/npm-release.yml@v1.2.0 + uses: Unleash/.github/.github/workflows/npm-release.yml@v2.0.0 with: version: ${{ github.event.inputs.version }} tag: ${{ github.event.inputs.tag }} @@ -23,5 +23,6 @@ jobs: working-directory: lib secrets: - GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} NPM_ACCESS_TOKEN: ${{ secrets.NPM_TOKEN }} + UNLEASH_BOT_APP_ID: ${{ secrets.UNLEASH_BOT_APP_ID }} + UNLEASH_BOT_PRIVATE_KEY: ${{ secrets.UNLEASH_BOT_PRIVATE_KEY }} diff --git a/lib/package.json b/lib/package.json index 3769085..83e2b4a 100644 --- a/lib/package.json +++ b/lib/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/nextjs", - "version": "1.5.0", + "version": "1.6.0", "description": "Unleash SDK for Next.js", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -53,8 +53,8 @@ "commander": "12.1.0", "murmurhash3js": "3.0.1", "semver": "7.6.3", - "unleash-client": "6.1.1", - "unleash-proxy-client": "3.6.1" + "unleash-client": "^6.4.1", + "unleash-proxy-client": "^3.7.2" }, "peerDependencies": { "next": ">=12", diff --git a/lib/src/flagsClient.test.ts b/lib/src/flagsClient.test.ts index 4dc19f0..a1eb9ae 100644 --- a/lib/src/flagsClient.test.ts +++ b/lib/src/flagsClient.test.ts @@ -1,7 +1,12 @@ import { flagsClient } from "./flagsClient"; +const fetchMock = vi.fn(); +global.fetch = fetchMock; + describe("flagsClient", () => { - global.fetch = vi.fn(); + beforeEach(() => { + fetchMock.mockReset(); + }); it("should return methods", () => { const client = flagsClient(); @@ -67,4 +72,64 @@ describe("flagsClient", () => { feature_enabled: true, }); }); + + it("should not send metrics", async () => { + const client = flagsClient( + [ + { + name: "foo", + enabled: true, + variant: { name: "disabled", enabled: false }, + impressionData: false, + }, + ], + { + url: 'http://test.com/api', + appName: 'custom-app-name', + clientKey: 'a-very-nice-very-secure-custom-key' + } + ); + + await client.sendMetrics(); + + expect(fetchMock).toHaveBeenCalledTimes(0); + }); + + it("should send metrics", async () => { + const client = flagsClient( + [ + { + name: "foo", + enabled: true, + variant: { name: "disabled", enabled: false }, + impressionData: false, + }, + ], + { + url: 'http://test.com/api', + appName: 'custom-app-name', + clientKey: 'a-very-nice-very-secure-custom-key' + } + ); + + client.getVariant('foo'); + + await client.sendMetrics(); + + expect(fetchMock).toHaveBeenCalled(); + + expect(fetchMock).toHaveBeenCalledWith('http://test.com/api/client/metrics', expect.objectContaining({ + method: 'POST', + cache: 'no-cache', + headers: { + Authorization: 'a-very-nice-very-secure-custom-key', + Accept: 'application/json', + 'Content-Type': 'application/json', + 'x-unleash-appname': 'custom-app-name', + 'x-unleash-connection-id': expect.stringMatching(/[a-f0-9-]{36}/), + 'x-unleash-sdk': expect.stringMatching(/^unleash-js@\d+\.\d+\.\d+/), + }, + body: expect.stringContaining('custom-app-name'), + })); + }); }); diff --git a/lib/src/flagsClient.ts b/lib/src/flagsClient.ts index 9b50263..ea9e0c1 100644 --- a/lib/src/flagsClient.ts +++ b/lib/src/flagsClient.ts @@ -1,27 +1,37 @@ import { UnleashClient, type IToggle } from "unleash-proxy-client"; -import { getDefaultClientConfig, getServerBaseUrl } from "./utils"; +import { + getDefaultClientConfig, + getDefaultServerConfig, + getServerBaseUrl, +} from "./utils"; const getMetricsConfig = () => { - const serverUrl = getServerBaseUrl() + const serverUrl = getServerBaseUrl(); - if (serverUrl && process.env.UNLEASH_SERVER_API_TOKEN){ - return { - ...getDefaultClientConfig(), - url: serverUrl, - clientKey: process.env.UNLEASH_SERVER_API_TOKEN, - } + if (serverUrl && process.env.UNLEASH_SERVER_API_TOKEN) { + return getDefaultServerConfig(); } - return getDefaultClientConfig() -} + return getDefaultClientConfig(); +}; /** * Simplified client SDK to work offline with pre-evaluated flags */ -export const flagsClient = (toggles = [] as IToggle[]) => { +export const flagsClient = ( + toggles = [] as IToggle[], + config?: Partial> +) => { + const { appName, url, clientKey } = { + ...getMetricsConfig(), + ...(config || {}), + }; + const client = new UnleashClient({ + url, + appName, + clientKey, bootstrap: toggles, - ...getMetricsConfig(), createAbortController: () => null, refreshInterval: 0, metricsInterval: 0, @@ -33,9 +43,5 @@ export const flagsClient = (toggles = [] as IToggle[]) => { }, }); - return { - isEnabled: (name: string) => client.isEnabled(name), - getVariant: (name: string) => client.getVariant(name), - sendMetrics: () => client.sendMetrics(), - }; + return client; }; diff --git a/lib/src/utils.ts b/lib/src/utils.ts index 2529fce..530eac1 100644 --- a/lib/src/utils.ts +++ b/lib/src/utils.ts @@ -26,7 +26,7 @@ export const safeCompare = (a: string, b: string) => { export const randomSessionId = () => `${Math.floor(Math.random() * 1_000_000_000)}`; -const validateEnviromentVariables = () => { +const validateEnvironmentVariables = () => { if (process.env.NEXT_PUBLIC_UNLEASH_SERVER_API_TOKEN) { console.warn( "You are trying to set `NEXT_PUBLIC_UNLEASH_SERVER_API_TOKEN`. Server keys shouldn't be public. Use frontend keys or skip `NEXT_PUBLIC_ prefix." @@ -43,7 +43,7 @@ export const getServerBaseUrl = () => process.env.NEXT_PUBLIC_UNLEASH_SERVER_API_URL; export const getDefaultClientConfig = () => { - validateEnviromentVariables(); + validateEnvironmentVariables(); return { url: @@ -60,5 +60,20 @@ export const getDefaultClientConfig = () => { }; }; +export const getDefaultServerConfig = () => { + validateEnvironmentVariables(); + + return { + url: getServerBaseUrl() || "http://localhost:4242/api", + appName: + process.env.UNLEASH_APP_NAME || + process.env.NEXT_PUBLIC_UNLEASH_APP_NAME || + "nextjs", + clientKey: + process.env.UNLEASH_SERVER_API_TOKEN || + "default:development.unleash-insecure-server-api-token", + }; +}; + export const removeTrailingSlash = (url?: string) => url?.replace(/\/$/, ""); diff --git a/yarn.lock b/yarn.lock index c5e3213..2ee110c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1003,6 +1003,20 @@ resolved "https://registry.yarnpkg.com/@unleash/client-specification/-/client-specification-5.1.7.tgz#8f8363803cca96afca4e65938d97951ffa8facc4" integrity sha512-PIaVKrFuXS/HMaWFOmLVAKssNlPKVvQ1+EgUMZ+SL+4d1U2bMevR8QJB0Efk2F6FhjSvJTN1vPlpuacfc/F25A== +"@unleash/nextjs@*": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@unleash/nextjs/-/nextjs-1.5.0.tgz#d6df6347c090a4f385327df5320dbb26de94ee63" + integrity sha512-c0X+3/xVoPszOJknJwByw4L5tKJqxGPIufeI5dtL2m9KX/KoBRHuYc1YtxG1kE8Uf6Bv+faTnKWHB02w09pEHw== + dependencies: + "@commander-js/extra-typings" "12.1.0" + "@next/env" "14.2.13" + "@unleash/proxy-client-react" "4.3.1" + commander "12.1.0" + murmurhash3js "3.0.1" + semver "7.6.3" + unleash-client "6.1.1" + unleash-proxy-client "3.6.1" + "@unleash/proxy-client-react@4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@unleash/proxy-client-react/-/proxy-client-react-4.3.1.tgz#49d43224aa493fc8e589f043c8ff983c526099f8" @@ -2063,7 +2077,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.57.0, eslint@^8.57.1: +eslint@^8.57.1: version "8.57.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -2934,6 +2948,11 @@ language-tags@^1.0.9: dependencies: language-subtag-registry "^0.3.20" +launchdarkly-eventsource@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/launchdarkly-eventsource/-/launchdarkly-eventsource-2.0.3.tgz#8a7b8da5538153f438f7d452b1c87643d900f984" + integrity sha512-VhFjppK7jXlcEKaS7bxdoibB5j01NKyeDR7a8XfssdDGNWCTsbF0/5IExSmPi44eDncPhkoPNxlSZhEZvrbD5w== + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -3524,6 +3543,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -4307,6 +4331,20 @@ unleash-client@6.1.1: murmurhash3js "^3.0.1" semver "^7.6.2" +unleash-client@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/unleash-client/-/unleash-client-6.4.1.tgz#ae9a570e23488a250c74fb1576611bca5130eeb1" + integrity sha512-Tu2fNqu2Gy3u0KnJh0kPmyCAnEi1hr6319ct3rSjE1e4kEIlML6ZDwqUNZk2t6/yzijrGfgFfVJRxTivYEBVfQ== + dependencies: + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" + ip-address "^9.0.5" + launchdarkly-eventsource "2.0.3" + make-fetch-happen "^13.0.1" + murmurhash3js "^3.0.1" + proxy-from-env "^1.1.0" + semver "^7.6.2" + unleash-proxy-client@3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.6.1.tgz#ec709bd03010d190977f99a0900117f9e9ab6ea4" @@ -4315,6 +4353,14 @@ unleash-proxy-client@3.6.1: tiny-emitter "^2.1.0" uuid "^9.0.1" +unleash-proxy-client@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.7.2.tgz#c6166bbaf293f8dea12cf65d061d72234c5b76a8" + integrity sha512-1SvHsl3kQh1DT9EKMQsN9alOvXZEz9hpxa3mG6QWtTmXJqa6VZi25dQ2U8Y2KAULKg6ARLMUQkod74Fe/pKp0g== + dependencies: + tiny-emitter "^2.1.0" + uuid "^9.0.1" + update-browserslist-db@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"