feat(fe2): brave/safari cookie max-age workaround (#2211)
* feat(fe2): brave/safari cookie max-age workaround * minor adjustments * removed brave note
This commit is contained in:
committed by
GitHub
parent
0b6684e485
commit
4e9c9eba48
@@ -1,16 +1,34 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import type { CookieOptions } from 'nuxt/dist/app/composables/cookie'
|
||||
import dayjs from 'dayjs'
|
||||
import { useScopedState } from '~~/lib/common/composables/scopedState'
|
||||
import { isUndefined } from 'lodash-es'
|
||||
import { get, isUndefined } from 'lodash-es'
|
||||
import { isBraveOrSafari, type Nullable } from '@speckle/shared'
|
||||
|
||||
class AbortControllerManager {
|
||||
private abortController: Nullable<AbortController> = null
|
||||
|
||||
getAndAbortOld() {
|
||||
if (process.server) return null
|
||||
|
||||
// Abort old
|
||||
if (this.abortController) this.abortController.abort()
|
||||
this.abortController = null
|
||||
|
||||
// Create new
|
||||
this.abortController = new AbortController()
|
||||
return this.abortController
|
||||
}
|
||||
}
|
||||
|
||||
const abortControllerManager = new AbortControllerManager()
|
||||
|
||||
/**
|
||||
* Makes useCookie() synchronized across the app so that a change to it from one place
|
||||
* will also update other references elsewhere.
|
||||
*
|
||||
* Defaults to an expiration date of 1 year
|
||||
*
|
||||
* IMPORTANT NOTE: Both Safari & Brave limit client-side cookie max-age to 7 days. If your cookie is important, evaluate how to
|
||||
* ensure that the cookie is written to from the server-side (either SSR render or API route)
|
||||
*/
|
||||
export const useSynchronizedCookie = <CookieValue = string>(
|
||||
name: string,
|
||||
@@ -25,9 +43,34 @@ export const useSynchronizedCookie = <CookieValue = string>(
|
||||
}
|
||||
|
||||
// something's off with nuxt's types here, have to use any
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
||||
const cookie = useCookie<CookieValue>(name, finalOpts as any)
|
||||
|
||||
// Hack to resolve Safari & Brave limiting client-side cookies to 7 days - set temporary cookie to be read from server-side where it'll be fixed
|
||||
if (process.client && isBraveOrSafari()) {
|
||||
const tmpCookie = useCookie(`tmp-${name}`, finalOpts as any)
|
||||
|
||||
watch(cookie, (newVal) => {
|
||||
if (newVal) {
|
||||
tmpCookie.value = JSON.stringify({
|
||||
expires: finalOpts.expires?.toISOString(),
|
||||
maxAge: finalOpts.maxAge
|
||||
})
|
||||
} else {
|
||||
tmpCookie.value = undefined
|
||||
}
|
||||
|
||||
// Fetch w/ abort of previous call, if any
|
||||
const controller = abortControllerManager.getAndAbortOld()
|
||||
void fetch('/web-api/cookie-fix', {
|
||||
signal: controller?.signal
|
||||
}).catch((e) => {
|
||||
if (get(e, 'name') !== 'AbortError') {
|
||||
throw e
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// there's a bug in nuxt where a default value doesn't get set if useCookie is only invoked in CSR
|
||||
// TODO: https://github.com/nuxt/nuxt/issues/26701
|
||||
if (isUndefined(cookie.value) && opts?.default) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useCreateErrorLoggingTransport } from '~/lib/core/composables/error'
|
||||
import type { Plugin } from 'nuxt/dist/app/nuxt'
|
||||
import { isH3Error } from '~/lib/common/helpers/error'
|
||||
import { useRequestId, useServerRequestId } from '~/lib/core/composables/server'
|
||||
import { isBrave, isSafari } from '@speckle/shared'
|
||||
|
||||
type PluginNuxtApp = Parameters<Plugin>[0]
|
||||
|
||||
@@ -84,6 +85,14 @@ async function initRumClient(app: PluginNuxtApp) {
|
||||
datadog.onReady(async () => {
|
||||
if ('setGlobalContextProperty' in datadog && reqId?.length) {
|
||||
datadog.setGlobalContextProperty('requestId', reqId)
|
||||
|
||||
if (isSafari()) {
|
||||
datadog.setGlobalContextProperty('isSafari', 'true')
|
||||
}
|
||||
|
||||
if (isBrave()) {
|
||||
datadog.setGlobalContextProperty('isBrave', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
await onAuthStateChange(
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { Optional } from '@speckle/shared'
|
||||
import { has, isObjectLike } from 'lodash-es'
|
||||
|
||||
type TempCookieValue = { expires?: Optional<string>; maxAge?: Optional<number> }
|
||||
|
||||
const isValidTempCookieValue = (cookie: unknown): cookie is TempCookieValue => {
|
||||
if (!isObjectLike(cookie)) return false
|
||||
return has(cookie, 'expires') || has(cookie, 'maxAge')
|
||||
}
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const cookies = parseCookies(event)
|
||||
const {
|
||||
public: { baseUrl }
|
||||
} = useRuntimeConfig()
|
||||
const domain = new URL(baseUrl).hostname
|
||||
|
||||
for (const [key, val] of Object.entries(cookies)) {
|
||||
if (key.startsWith('tmp-')) {
|
||||
// Try reading in cookie settings
|
||||
let tempCookieVal: Optional<TempCookieValue> = undefined
|
||||
try {
|
||||
const parsedVal = JSON.parse(val) as unknown
|
||||
if (isValidTempCookieValue(parsedVal)) {
|
||||
tempCookieVal = parsedVal
|
||||
}
|
||||
} catch (e) {
|
||||
deleteCookie(event, key)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!tempCookieVal) {
|
||||
deleteCookie(event, key)
|
||||
continue
|
||||
}
|
||||
|
||||
// Try finding cookie that we need to fix
|
||||
const cookieName = key.replace('tmp-', '')
|
||||
const cookieValue = cookies[cookieName]
|
||||
if (!cookieValue) {
|
||||
deleteCookie(event, key)
|
||||
continue
|
||||
}
|
||||
|
||||
// Create new cookie with the correct settings
|
||||
setCookie(event, cookieName, cookieValue, {
|
||||
maxAge: tempCookieVal.maxAge,
|
||||
expires: tempCookieVal.expires ? new Date(tempCookieVal.expires) : undefined,
|
||||
domain
|
||||
})
|
||||
deleteCookie(event, key)
|
||||
}
|
||||
}
|
||||
|
||||
return { status: 'ok' }
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import { get } from 'lodash'
|
||||
import type { Nullable } from './utilityTypes'
|
||||
|
||||
export enum OperatingSystem {
|
||||
@@ -85,3 +86,19 @@ export function isSafari() {
|
||||
const userAgent = globalThis.navigator.userAgent
|
||||
return /^((?!chrome|android).)*safari/i.test(userAgent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is in Brave browser
|
||||
*/
|
||||
export function isBrave() {
|
||||
if (!globalThis || !globalThis.navigator || !('brave' in globalThis.navigator)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const braveObj = get(globalThis.navigator, 'brave')
|
||||
if (!braveObj) return false
|
||||
|
||||
return !!get(braveObj, 'isBrave', false)
|
||||
}
|
||||
|
||||
export const isBraveOrSafari = () => isBrave() || isSafari()
|
||||
|
||||
Reference in New Issue
Block a user