2022-12-11 21:44:52 +00:00
|
|
|
import type { Ref } from 'vue'
|
2023-06-06 22:21:51 +00:00
|
|
|
import { getCurrentInstance, nextTick, onUnmounted, ref, watch } from 'vue'
|
2022-12-11 21:44:52 +00:00
|
|
|
import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
|
|
|
|
import { parse, serialize } from 'cookie-es'
|
2023-05-26 19:42:12 +00:00
|
|
|
import { deleteCookie, getCookie, setCookie } from 'h3'
|
2022-10-15 18:42:57 +00:00
|
|
|
import type { H3Event } from 'h3'
|
2021-11-22 20:43:00 +00:00
|
|
|
import destr from 'destr'
|
2022-08-04 10:58:15 +00:00
|
|
|
import { isEqual } from 'ohash'
|
2022-09-14 09:22:03 +00:00
|
|
|
import { useNuxtApp } from '../nuxt'
|
2022-04-07 11:28:04 +00:00
|
|
|
import { useRequestEvent } from './ssr'
|
2021-11-22 20:43:00 +00:00
|
|
|
|
|
|
|
type _CookieOptions = Omit<CookieSerializeOptions & CookieParseOptions, 'decode' | 'encode'>
|
2021-11-29 10:40:12 +00:00
|
|
|
|
2022-09-07 08:34:16 +00:00
|
|
|
export interface CookieOptions<T = any> extends _CookieOptions {
|
2021-11-22 20:43:00 +00:00
|
|
|
decode?(value: string): T
|
2022-09-07 08:34:16 +00:00
|
|
|
encode?(value: T): string
|
2022-04-13 17:41:41 +00:00
|
|
|
default?: () => T | Ref<T>
|
2022-12-19 11:50:46 +00:00
|
|
|
watch?: boolean | 'shallow'
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
|
|
|
|
2022-12-02 09:45:33 +00:00
|
|
|
export interface CookieRef<T> extends Ref<T> {}
|
2021-11-22 20:43:00 +00:00
|
|
|
|
|
|
|
const CookieDefaults: CookieOptions<any> = {
|
2022-04-07 11:35:46 +00:00
|
|
|
path: '/',
|
2022-12-19 11:50:46 +00:00
|
|
|
watch: true,
|
2021-11-22 20:43:00 +00:00
|
|
|
decode: val => destr(decodeURIComponent(val)),
|
|
|
|
encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val))
|
|
|
|
}
|
|
|
|
|
2023-04-28 10:18:03 +00:00
|
|
|
export function useCookie<T = string | null | undefined> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
|
2021-11-22 20:43:00 +00:00
|
|
|
const opts = { ...CookieDefaults, ..._opts }
|
2022-08-12 17:47:58 +00:00
|
|
|
const cookies = readRawCookies(opts) || {}
|
2021-11-22 20:43:00 +00:00
|
|
|
|
2022-08-12 17:47:58 +00:00
|
|
|
const cookie = ref<T | undefined>(cookies[name] as any ?? opts.default?.())
|
2021-11-22 20:43:00 +00:00
|
|
|
|
|
|
|
if (process.client) {
|
2023-06-06 22:21:51 +00:00
|
|
|
const channel = typeof BroadcastChannel === 'undefined' ? null : new BroadcastChannel(`nuxt:cookies:${name}`)
|
|
|
|
if (getCurrentInstance()) { onUnmounted(() => { channel?.close() }) }
|
|
|
|
|
|
|
|
const callback = () => {
|
|
|
|
writeClientCookie(name, cookie.value, opts as CookieSerializeOptions)
|
|
|
|
channel?.postMessage(cookie.value)
|
|
|
|
}
|
|
|
|
|
|
|
|
let watchPaused = false
|
|
|
|
|
|
|
|
if (channel) {
|
|
|
|
channel.onmessage = (event) => {
|
|
|
|
watchPaused = true
|
|
|
|
cookie.value = event.data
|
|
|
|
nextTick(() => { watchPaused = false })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-19 11:50:46 +00:00
|
|
|
if (opts.watch) {
|
2023-06-06 22:21:51 +00:00
|
|
|
watch(cookie, (newVal, oldVal) => {
|
|
|
|
if (watchPaused || isEqual(newVal, oldVal)) { return }
|
|
|
|
callback()
|
|
|
|
},
|
|
|
|
{ deep: opts.watch !== 'shallow' })
|
2022-12-19 11:50:46 +00:00
|
|
|
} else {
|
|
|
|
callback()
|
|
|
|
}
|
2021-11-22 20:43:00 +00:00
|
|
|
} else if (process.server) {
|
|
|
|
const nuxtApp = useNuxtApp()
|
2022-04-19 19:13:11 +00:00
|
|
|
const writeFinalCookieValue = () => {
|
2022-08-04 10:58:15 +00:00
|
|
|
if (!isEqual(cookie.value, cookies[name])) {
|
2022-04-07 11:28:04 +00:00
|
|
|
writeServerCookie(useRequestEvent(nuxtApp), name, cookie.value, opts)
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
2022-04-19 19:13:11 +00:00
|
|
|
}
|
2022-09-07 08:34:16 +00:00
|
|
|
const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue)
|
2023-05-01 22:55:24 +00:00
|
|
|
nuxtApp.hooks.hookOnce('app:error', () => {
|
2023-04-26 13:36:59 +00:00
|
|
|
unhook() // don't write cookie subsequently when app:rendered is called
|
2022-09-07 08:34:16 +00:00
|
|
|
return writeFinalCookieValue()
|
2023-05-01 22:55:24 +00:00
|
|
|
})
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
|
|
|
|
2021-11-29 10:40:12 +00:00
|
|
|
return cookie as CookieRef<T>
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
|
|
|
|
2022-08-12 17:47:58 +00:00
|
|
|
function readRawCookies (opts: CookieOptions = {}): Record<string, string> | undefined {
|
2021-11-22 20:43:00 +00:00
|
|
|
if (process.server) {
|
2023-04-24 20:25:36 +00:00
|
|
|
return parse(useRequestEvent()?.node.req.headers.cookie || '', opts)
|
2021-11-22 20:43:00 +00:00
|
|
|
} else if (process.client) {
|
2021-11-22 23:20:20 +00:00
|
|
|
return parse(document.cookie, opts)
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function serializeCookie (name: string, value: any, opts: CookieSerializeOptions = {}) {
|
|
|
|
if (value === null || value === undefined) {
|
2021-12-21 12:02:55 +00:00
|
|
|
return serialize(name, value, { ...opts, maxAge: -1 })
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
2021-11-22 23:20:20 +00:00
|
|
|
return serialize(name, value, opts)
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function writeClientCookie (name: string, value: any, opts: CookieSerializeOptions = {}) {
|
|
|
|
if (process.client) {
|
|
|
|
document.cookie = serializeCookie(name, value, opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-15 18:42:57 +00:00
|
|
|
function writeServerCookie (event: H3Event, name: string, value: any, opts: CookieSerializeOptions = {}) {
|
2022-04-07 11:28:04 +00:00
|
|
|
if (event) {
|
2023-05-26 19:42:12 +00:00
|
|
|
// update if value is set
|
|
|
|
if (value !== null && value !== undefined) {
|
|
|
|
return setCookie(event, name, value, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete if cookie exists in browser and value is null/undefined
|
|
|
|
if (getCookie(event, name) !== undefined) {
|
|
|
|
return deleteCookie(event, name, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// else ignore if cookie doesn't exist in browser and value is null/undefined
|
2021-11-22 20:43:00 +00:00
|
|
|
}
|
|
|
|
}
|