feat(nuxt): allow readonly option for useCookie (#24503)

This commit is contained in:
Daniel Roe 2023-11-28 14:35:43 +01:00 committed by GitHub
parent 0c47399f33
commit e3b8b84a24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 16 additions and 4 deletions

View File

@ -133,6 +133,10 @@ be returned as the cookie's value.
Specifies a function that returns the cookie's default value. The function can also return a `Ref`. Specifies a function that returns the cookie's default value. The function can also return a `Ref`.
### `readonly`
Allows _accessing_ a cookie value without the ability to set it.
### `watch` ### `watch`
Specifies the `boolean` or `string` value for [watch](https://vuejs.org/api/reactivity-core.html#watch) cookie ref data. Specifies the `boolean` or `string` value for [watch](https://vuejs.org/api/reactivity-core.html#watch) cookie ref data.

View File

@ -16,6 +16,7 @@ export interface CookieOptions<T = any> extends _CookieOptions {
encode?(value: T): string encode?(value: T): string
default?: () => T | Ref<T> default?: () => T | Ref<T>
watch?: boolean | 'shallow' watch?: boolean | 'shallow'
readonly?: boolean
} }
export interface CookieRef<T> extends Ref<T> {} export interface CookieRef<T> extends Ref<T> {}
@ -27,6 +28,8 @@ const CookieDefaults = {
encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val)) encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val))
} satisfies CookieOptions<any> } satisfies CookieOptions<any>
export function useCookie<T = string | null | undefined> (name: string, _opts?: CookieOptions<T> & { readonly?: false }): CookieRef<T>
export function useCookie<T = string | null | undefined> (name: string, _opts: CookieOptions<T> & { readonly: true }): Readonly<CookieRef<T>>
export function useCookie<T = string | null | undefined> (name: string, _opts?: CookieOptions<T>): CookieRef<T> { export function useCookie<T = string | null | undefined> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
const opts = { ...CookieDefaults, ..._opts } const opts = { ...CookieDefaults, ..._opts }
const cookies = readRawCookies(opts) || {} const cookies = readRawCookies(opts) || {}
@ -55,6 +58,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
if (import.meta.client) { if (import.meta.client) {
const channel = typeof BroadcastChannel === 'undefined' ? null : new BroadcastChannel(`nuxt:cookies:${name}`) const channel = typeof BroadcastChannel === 'undefined' ? null : new BroadcastChannel(`nuxt:cookies:${name}`)
const callback = () => { const callback = () => {
if (opts.readonly || isEqual(cookie.value, cookies[name])) { return }
writeClientCookie(name, cookie.value, opts as CookieSerializeOptions) writeClientCookie(name, cookie.value, opts as CookieSerializeOptions)
channel?.postMessage(opts.encode(cookie.value as T)) channel?.postMessage(opts.encode(cookie.value as T))
} }
@ -72,7 +76,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
if (channel) { if (channel) {
channel.onmessage = (event) => { channel.onmessage = (event) => {
watchPaused = true watchPaused = true
cookie.value = opts.decode(event.data) cookies[name] = cookie.value = opts.decode(event.data)
nextTick(() => { watchPaused = false }) nextTick(() => { watchPaused = false })
} }
} }
@ -89,10 +93,9 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
} else if (import.meta.server) { } else if (import.meta.server) {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
const writeFinalCookieValue = () => { const writeFinalCookieValue = () => {
if (!isEqual(cookie.value, cookies[name])) { if (opts.readonly || isEqual(cookie.value, cookies[name])) { return }
writeServerCookie(useRequestEvent(nuxtApp), name, cookie.value, opts as CookieOptions<any>) writeServerCookie(useRequestEvent(nuxtApp), name, cookie.value, opts as CookieOptions<any>)
} }
}
const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue) const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue)
nuxtApp.hooks.hookOnce('app:error', () => { nuxtApp.hooks.hookOnce('app:error', () => {
unhook() // don't write cookie subsequently when app:rendered is called unhook() // don't write cookie subsequently when app:rendered is called

View File

@ -346,6 +346,11 @@ describe('composables', () => {
expectTypeOf(useFetch('/test', { default: () => 500 }).data).toEqualTypeOf<Ref<unknown>>() expectTypeOf(useFetch('/test', { default: () => 500 }).data).toEqualTypeOf<Ref<unknown>>()
}) })
it('enforces readonly cookies', () => {
// @ts-expect-error readonly cookie
useCookie('test', { readonly: true }).value = 'thing'
})
it('correct types when using ResT type-assertion with default function', () => { it('correct types when using ResT type-assertion with default function', () => {
// @ts-expect-error default type should match generic type // @ts-expect-error default type should match generic type
useFetch<string>('/test', { default: () => 0 }) useFetch<string>('/test', { default: () => 0 })