next.js/packages/next/src/shared/lib/router/utils/cache-busting-search-param.ts
cache-busting-search-param.ts97 lines2.6 KB
import { hexHash } from '../../hash'

const CACHE_BUSTING_SEARCH_PARAM_DIGEST_BYTES = 12
const textEncoder = new TextEncoder()

function encodeCacheBustingSearchParam(bytes: Uint8Array): string {
  let binary = ''
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i])
  }

  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

function normalizeCacheBustingInput(
  value: string | string[] | undefined
): string {
  if (value === undefined) {
    return '0'
  }
  return Array.isArray(value) ? value.join(',') : value
}

function createCacheBustingSearchParamInput(
  prefetchHeader: '1' | '2' | '0' | undefined,
  segmentPrefetchHeader: string | string[] | undefined,
  stateTreeHeader: string | string[] | undefined,
  nextUrlHeader: string | string[] | undefined
): string | null {
  if (
    (prefetchHeader === undefined || prefetchHeader === '0') &&
    segmentPrefetchHeader === undefined &&
    stateTreeHeader === undefined &&
    nextUrlHeader === undefined
  ) {
    return null
  }

  return [
    prefetchHeader ?? '0',
    normalizeCacheBustingInput(segmentPrefetchHeader),
    normalizeCacheBustingInput(stateTreeHeader),
    normalizeCacheBustingInput(nextUrlHeader),
  ].join(',')
}

async function computeCacheBustingSearchParamFromInput(
  input: string
): Promise<string> {
  // Truncate SHA-256 to 96 bits to keep `_rsc` compact
  const digest = await globalThis.crypto.subtle.digest(
    'SHA-256',
    textEncoder.encode(input)
  )
  return encodeCacheBustingSearchParam(
    new Uint8Array(digest).subarray(0, CACHE_BUSTING_SEARCH_PARAM_DIGEST_BYTES)
  )
}

export async function computeCacheBustingSearchParam(
  prefetchHeader: '1' | '2' | '0' | undefined,
  segmentPrefetchHeader: string | string[] | undefined,
  stateTreeHeader: string | string[] | undefined,
  nextUrlHeader: string | string[] | undefined
): Promise<string> {
  const input = createCacheBustingSearchParamInput(
    prefetchHeader,
    segmentPrefetchHeader,
    stateTreeHeader,
    nextUrlHeader
  )
  if (input === null) {
    return ''
  }

  return computeCacheBustingSearchParamFromInput(input)
}

export function computeLegacyCacheBustingSearchParam(
  prefetchHeader: '1' | '2' | '0' | undefined,
  segmentPrefetchHeader: string | string[] | undefined,
  stateTreeHeader: string | string[] | undefined,
  nextUrlHeader: string | string[] | undefined
): string {
  const input = createCacheBustingSearchParamInput(
    prefetchHeader,
    segmentPrefetchHeader,
    stateTreeHeader,
    nextUrlHeader
  )
  if (input === null) {
    return ''
  }

  return hexHash(input)
}
Quest for Codev2.0.0
/
SIGN IN