diff --git a/src/events/components/dotcom-cookies.ts b/src/events/components/dotcom-cookies.ts index d92f7288ef51..aebd8710ab25 100644 --- a/src/events/components/dotcom-cookies.ts +++ b/src/events/components/dotcom-cookies.ts @@ -1,8 +1,9 @@ +import { isHeadless } from './is-headless' + // We cannot use Cookies.get() on the frontend for httpOnly cookies // so we need to make a request to the server to get the cookies type DotcomCookies = { - dotcomUsername?: string isStaff?: boolean } @@ -19,6 +20,8 @@ const LOCAL_STORAGE_KEY = 'dotcomCookies' // If a user is staff and they didn't happen to be logged in when these cookies were saved, // we can instruct them as needed to update the cookies and correctly set the isStaff flag. async function fetchCookies(): Promise { + if (isHeadless()) return { isStaff: false } + // Return the cached object if we have it in memory. if (cachedCookies) { return cachedCookies @@ -63,7 +66,6 @@ async function fetchCookies(): Promise { console.error('Error fetching cookies:', err) // On failure, return default values. const defaultCookies: DotcomCookies = { - dotcomUsername: '', isStaff: false, } cachedCookies = defaultCookies @@ -81,8 +83,3 @@ export async function getIsStaff(): Promise { const cookies = await fetchCookies() return cookies.isStaff || false } - -export async function getDotcomUsername(): Promise { - const cookies = await fetchCookies() - return cookies.dotcomUsername || '' -} diff --git a/src/events/components/events.ts b/src/events/components/events.ts index b9764397976f..ab0861c71d94 100644 --- a/src/events/components/events.ts +++ b/src/events/components/events.ts @@ -5,6 +5,7 @@ import { Router } from 'next/router' import { isLoggedIn } from 'src/frame/components/hooks/useHasAccount' import { getExperimentVariationForContext } from './experiments/experiment' import { EventType, EventPropsByType } from '../types' +import { isHeadless } from './is-headless' const COOKIE_NAME = '_docs-events' @@ -73,6 +74,8 @@ export function sendEvent({ eventGroupKey?: string eventGroupId?: string } & EventPropsByType[T]) { + if (isHeadless()) return + const body = { type, diff --git a/src/events/components/is-headless.ts b/src/events/components/is-headless.ts new file mode 100644 index 000000000000..048647f67792 --- /dev/null +++ b/src/events/components/is-headless.ts @@ -0,0 +1,18 @@ +// Basic checks for if the browser is actually a headless browser robot + +declare global { + interface Window { + GHDOCSPLAYWRIGHT: any + } +} + +export function isHeadless(): boolean { + if (window.GHDOCSPLAYWRIGHT) return false + if (navigator.webdriver) return true + if (/headless/i.test(navigator.userAgent)) return true + if (/headless/i.test(navigator.appVersion)) return true + if (!window.outerHeight || !window.innerHeight) return true + if (navigator.languages?.length === 0) return true + if (navigator.mimeTypes?.length === 0) return true + return false +} diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index 4bb644435cf6..599be99e7d75 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -664,6 +664,10 @@ test.describe('survey', () => { // See https://github.com/microsoft/playwright/issues/12231 }) + await page.addInitScript(() => { + window.GHDOCSPLAYWRIGHT = 1 + }) + await page.goto('/get-started/foo/for-playwright') // The label is visually an SVG. Finding it by its `for` value feels easier. @@ -709,6 +713,10 @@ test.describe('survey', () => { // See https://github.com/microsoft/playwright/issues/12231 }) + await page.addInitScript(() => { + window.GHDOCSPLAYWRIGHT = 1 + }) + await page.goto('/get-started/foo/for-playwright') await page.locator('[for=survey-yes]').click() diff --git a/src/frame/middleware/api.ts b/src/frame/middleware/api.ts index 8f2efcd9ea5f..7992969d2d23 100644 --- a/src/frame/middleware/api.ts +++ b/src/frame/middleware/api.ts @@ -64,7 +64,6 @@ router.get('/cookies', (req, res) => { noCacheControl(res) const cookies = { isStaff: Boolean(req.cookies?.staffonly?.startsWith('yes')) || false, - dotcomUsername: req.cookies?.dotcom_user || '', } return res.json(cookies) }) diff --git a/src/ghes-releases/lib/enterprise-dates.json b/src/ghes-releases/lib/enterprise-dates.json index fbf03ee20516..08850f713903 100644 --- a/src/ghes-releases/lib/enterprise-dates.json +++ b/src/ghes-releases/lib/enterprise-dates.json @@ -173,10 +173,22 @@ }, "3.19": { "releaseDate": "2025-11-11", - "deprecationDate": "2026-12-02" + "deprecationDate": "2026-12-09" }, "3.20": { "releaseDate": "2026-02-17", - "deprecationDate": "2027-03-10" + "deprecationDate": "2027-03-17" + }, + "3.21": { + "releaseDate": "2026-05-12", + "deprecationDate": "2027-06-02" + }, + "3.22": { + "releaseDate": "2026-08-04", + "deprecationDate": "2027-08-25" + }, + "3.23": { + "releaseDate": "2026-11-10", + "deprecationDate": "2027-12-08" } } \ No newline at end of file