Repository: honojs/hono
File: src/utils/cookie.ts (TypeScript)

Code before:
import { decodeURIComponent_ } from './url'

export type Cookie = Record<string, string>
export type SignedCookie = Record<string, string | false>
export type CookieOptions = {
  domain?: string
  expires?: Date
  httpOnly?: boolean
  maxAge?: number
  path?: string
  secure?: boolean
  signingSecret?: string
  sameSite?: 'Strict' | 'Lax' | 'None'
  partitioned?: boolean
  securePrefix?: boolean
  hostPrefix?: boolean
}
export type CookiePrefixOptions = {
  secure?: boolean
  host?: boolean
}

const algorithm = { name: 'HMAC', hash: 'SHA-256' }

const getCryptoKey = async (secret: string | BufferSource): Promise<CryptoKey> => {
  const secretBuf = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret
  return await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign', 'verify'])
}

const makeSignature = async (value: string, secret: string | BufferSource): Promise<string> => {
  const key = await getCryptoKey(secret)
  const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value))
  // the returned base64 encoded signature will always be 44 characters long and end with one or two equal signs
  return btoa(String.fromCharCode(...new Uint8Array(signature)))
}

const verifySignature = async (
  base64Signature: string,
  value: string,
  secret: CryptoKey
): Promise<boolean> => {
  try {
    const signatureBinStr = atob(base64Signature)
    const signature = new Uint8Array(signatureBinStr.length)
    for (let i = 0, len = signatureBinStr.length; i < len; i++) {

Review comment (suggestion):
I don't think there are cases where you want to use two prefixes, so I think it's better to design it so that you choose one or the other. That way, it's easier to understand that only one prefix is being used.

```suggestion
  prefix?: 'host' | 'secure'
}
export type CookiePrefixOptions = {
  prefix: 'host' | 'secure'
```

Code after:
import { decodeURIComponent_ } from './url'

export type Cookie = Record<string, string>
export type SignedCookie = Record<string, string | false>
export type CookieOptions = {
  domain?: string
  expires?: Date
  httpOnly?: boolean
  maxAge?: number
  path?: string
  secure?: boolean
  signingSecret?: string
  sameSite?: 'Strict' | 'Lax' | 'None'
  partitioned?: boolean
  prefix?: CookiePrefixOptions
}
export type CookiePrefixOptions = 'host' | 'secure'

const algorithm = { name: 'HMAC', hash: 'SHA-256' }

const getCryptoKey = async (secret: string | BufferSource): Promise<CryptoKey> => {
  const secretBuf = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret
  return await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign', 'verify'])
}

const makeSignature = async (value: string, secret: string | BufferSource): Promise<string> => {
  const key = await getCryptoKey(secret)
  const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value))
  // the returned base64 encoded signature will always be 44 characters long and end with one or two equal signs
  return btoa(String.fromCharCode(...new Uint8Array(signature)))
}

const verifySignature = async (
  base64Signature: string,
  value: string,
  secret: CryptoKey
): Promise<boolean> => {
  try {
    const signatureBinStr = atob(base64Signature)
    const signature = new Uint8Array(signatureBinStr.length)
    for (let i = 0, len = signatureBinStr.length; i < len; i++) {
      signature[i] = signatureBinStr.charCodeAt(i)
    }
    return await crypto.subtle.verify(algorithm, secret, signature, new TextEncoder().encode(value))
  } catch (e) {