diff --git a/.changeset/visitor-country-hostnames-env.md b/.changeset/visitor-country-hostnames-env.md new file mode 100644 index 000000000..0d2a1a371 --- /dev/null +++ b/.changeset/visitor-country-hostnames-env.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Read the production hostnames that receive the visitor country from the `GITBOOK_VISITOR_COUNTRY_HOSTNAMES` build-time env variable. diff --git a/.github/composite/deploy-vercel/action.yaml b/.github/composite/deploy-vercel/action.yaml index 4fd08e0c5..aeb934fd9 100644 --- a/.github/composite/deploy-vercel/action.yaml +++ b/.github/composite/deploy-vercel/action.yaml @@ -78,6 +78,11 @@ runs: echo "GITBOOK_DISABLE_INSIGHTS=true" >> .vercel/.env.${{ inputs.environment }}.local echo "--- .vercel/.env.${{ inputs.environment }}.local after inject ---" cat .vercel/.env.${{ inputs.environment }}.local + - name: Inject visitor country hostnames + if: ${{ env.GITBOOK_VISITOR_COUNTRY_HOSTNAMES != '' }} + shell: bash + run: | + echo "GITBOOK_VISITOR_COUNTRY_HOSTNAMES=$GITBOOK_VISITOR_COUNTRY_HOSTNAMES" >> .vercel/.env.${{ inputs.environment }}.local - name: Inject staging build env vars if: ${{ inputs.environment == 'staging' }} shell: bash diff --git a/.github/workflows/deploy-production.yaml b/.github/workflows/deploy-production.yaml index dcf465a76..471ada6ba 100644 --- a/.github/workflows/deploy-production.yaml +++ b/.github/workflows/deploy-production.yaml @@ -10,6 +10,8 @@ jobs: environment: name: 2v-production url: ${{ steps.deploy.outputs.deployment-url }} + env: + GITBOOK_VISITOR_COUNTRY_HOSTNAMES: ${{ vars.GITBOOK_VISITOR_COUNTRY_HOSTNAMES }} outputs: deployment-url: ${{ steps.deploy.outputs.deployment-url }} steps: @@ -31,6 +33,8 @@ jobs: environment: name: 2c-production url: ${{ steps.deploy.outputs.deployment-url }} + env: + GITBOOK_VISITOR_COUNTRY_HOSTNAMES: ${{ vars.GITBOOK_VISITOR_COUNTRY_HOSTNAMES }} outputs: deployment-url: ${{ steps.deploy.outputs.deployment-url }} steps: diff --git a/packages/gitbook/next.config.mjs b/packages/gitbook/next.config.mjs index ce1c0113b..6523801da 100644 --- a/packages/gitbook/next.config.mjs +++ b/packages/gitbook/next.config.mjs @@ -95,6 +95,7 @@ const nextConfig = { GITBOOK_BLOCK_SEARCH_INDEXATION: process.env.GITBOOK_BLOCK_SEARCH_INDEXATION, GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: process.env.GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE, GITBOOK_DISABLE_INSIGHTS: process.env.GITBOOK_DISABLE_INSIGHTS, + GITBOOK_VISITOR_COUNTRY_HOSTNAMES: process.env.GITBOOK_VISITOR_COUNTRY_HOSTNAMES, VERCEL_TARGET_ENV: process.env.VERCEL_TARGET_ENV, // Next.js envs diff --git a/packages/gitbook/src/lib/visitors.test.ts b/packages/gitbook/src/lib/visitors.test.ts index 0a583d330..f5e2f3c0b 100644 --- a/packages/gitbook/src/lib/visitors.test.ts +++ b/packages/gitbook/src/lib/visitors.test.ts @@ -13,6 +13,7 @@ import { getVisitorUnsignedClaims, isRevalidationRequest, normalizeVisitorURL, + parseHostnameList, shouldSendVisitorCountry, } from './visitors'; @@ -674,3 +675,19 @@ describe('shouldSendVisitorCountry', () => { } ); }); + +describe('parseHostnameList', () => { + it.each([undefined, '', ' , '])('should return an empty set for %p', (value) => { + expect(parseHostnameList(value).size).toBe(0); + }); + + it('should parse a single entry', () => { + expect(parseHostnameList('docs.foo.com')).toEqual(new Set(['docs.foo.com'])); + }); + + it('should trim, lowercase and drop empty entries', () => { + expect(parseHostnameList(' Docs.Foo.com , ,bar.io')).toEqual( + new Set(['docs.foo.com', 'bar.io']) + ); + }); +}); diff --git a/packages/gitbook/src/lib/visitors.ts b/packages/gitbook/src/lib/visitors.ts index 3a0186be1..33a7ed937 100644 --- a/packages/gitbook/src/lib/visitors.ts +++ b/packages/gitbook/src/lib/visitors.ts @@ -107,15 +107,29 @@ export function getVisitorType(request: { } // Production hostnames for which the visitor country is sent while the feature is rolled out. -const VISITOR_COUNTRY_HOSTNAMES = new Set([]); +// Set at build time from a GitHub Actions variable to keep customer hostnames out of the code. +const VISITOR_COUNTRY_HOSTNAMES = parseHostnameList(process.env.GITBOOK_VISITOR_COUNTRY_HOSTNAMES); const VISITOR_COUNTRY_STAGES = new Set(['dev', 'preview', 'staging']); +/** + * Parse a comma-separated list of hostnames. + */ +export function parseHostnameList(value: string | undefined): Set { + return new Set( + (value ?? '') + .split(',') + .map((hostname) => hostname.trim().toLowerCase()) + .filter(Boolean) + ); +} + /** * Whether the visitor country should be sent when resolving the site URL. */ export function shouldSendVisitorCountry(hostname: string, stage: string | undefined): boolean { return ( - (!!stage && VISITOR_COUNTRY_STAGES.has(stage)) || VISITOR_COUNTRY_HOSTNAMES.has(hostname) + (!!stage && VISITOR_COUNTRY_STAGES.has(stage)) || + VISITOR_COUNTRY_HOSTNAMES.has(hostname.toLowerCase()) ); }