Skip to content

Commit

Permalink
short-lived cookie
Browse files Browse the repository at this point in the history
  • Loading branch information
nl0 committed Apr 24, 2024
1 parent c08dfff commit 4b9da87
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 29 deletions.
15 changes: 10 additions & 5 deletions catalog/app/components/Preview/loaders/Html/Html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export const detect = utils.extIn(['.htm', '.html'])

export const FILE_TYPE = FileType.Html

const SESSION_TTL = 60 * 3
const REFRESH_INTERVAL = SESSION_TTL * 0.2 * 1000
const SESSION_TTL = 60 * 3 // 3 minutes
const REFRESH_INTERVAL = 60 * 1000 // 1 minute

type SessionId = Model.GQLTypes.BrowsingSession['id']
type CreateData = GQL.DataForDoc<typeof CREATE_BROWSING_SESSION>['browsingSessionCreate']
Expand Down Expand Up @@ -172,18 +172,23 @@ function useSession(handle: FileHandle) {
async function initSession() {
setResult(AsyncResult.Pending())
try {
await ensureCookie()
const session = await createSession(scope, SESSION_TTL)
await ensureCookie(SESSION_TTL, REFRESH_INTERVAL)
if (ignore) return
sessionId = session.id
setResult(AsyncResult.Ok(sessionId))
} catch (e) {
handleError(e)
}

timer = setInterval(() => {
timer = setInterval(async () => {
if (!sessionId) return
refreshSession(sessionId, SESSION_TTL).catch(handleError)
try {
await refreshSession(sessionId, SESSION_TTL)
await ensureCookie(SESSION_TTL, REFRESH_INTERVAL)
} catch (e) {
handleError(e)
}
}, REFRESH_INTERVAL)
}

Expand Down
88 changes: 64 additions & 24 deletions catalog/app/utils/PFSCookieManager.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as dateFns from 'date-fns'
import invariant from 'invariant'
import * as React from 'react'
import * as redux from 'react-redux'
import { createSelector } from 'reselect'
import * as Sentry from '@sentry/react'

import cfg from 'constants/config'
import { tokens as tokensSelector } from 'containers/Auth/selectors'
import { useApi } from 'utils/APIConnector'
import usePrevious from 'utils/usePrevious'

type Ensure = () => Promise<void>
type Ensure = (ttl: number, histeresis: number) => Promise<void>

const Ctx = React.createContext<Ensure | null>(null)

Expand All @@ -18,37 +18,77 @@ const selectToken = createSelector(
(tokens) => tokens?.token as string | undefined,
)

interface State {
token: string | undefined
expires: Date
promise: Promise<void>
}

function needsRefresh(
state: State | undefined,
params: {
token: string | undefined
expires: Date
histeresis: number
},
): boolean {
if (!state) return true
if (state.token !== params.token) return true
if (state.expires.getTime() + params.histeresis < params.expires.getTime()) return true
return false
}

export function PFSCookieManager({ children }: React.PropsWithChildren<{}>) {
const promiseRef = React.useRef<Promise<void>>()
const stateRef = React.useRef<State>()

const req = useApi()

const setPFSCookie = React.useCallback(
(token: string | undefined, ttl: number): Promise<void> =>
token
? req({
auth: { tokens: { token }, handleInvalidToken: false },
url: `${cfg.s3Proxy}/browse/set_browse_cookie`,
method: 'POST',
credentials: 'include',
data: { ttl },
}).catch((e: any) => {
throw new Error(`Could not set PFS cookie: ${e.message}`)
})
: Promise.resolve(),

[req],
)

const token = redux.useSelector(selectToken)

const setBrowseCookie = React.useCallback(async () => {
if (!token) throw new Error('Unable to set PFS cookie: not authenticated')

try {
await req({
auth: { tokens: { token }, handleInvalidToken: false },
url: `${cfg.s3Proxy}/browse/set_browse_cookie`,
method: 'POST',
credentials: 'include',
})
} catch (e: any) {
Sentry.captureException(e)
throw new Error(`Could not set PFS cookie: ${e.message}`)
}
}, [req, token])

const ensure = React.useCallback(() => {
if (!promiseRef.current) promiseRef.current = setBrowseCookie()
return promiseRef.current
}, [promiseRef, setBrowseCookie])
const ensure = React.useCallback<Ensure>(
async (ttl: number, histeresis: number) => {
const expires = dateFns.addSeconds(new Date(), ttl)
if (needsRefresh(stateRef.current, { token, expires, histeresis })) {
const promise = setPFSCookie(token, ttl)
stateRef.current = { token, expires, promise }
}
while (true) {
// if a new request has been issued while waiting for the response,
// wait for it to complete
let promise = stateRef.current?.promise
if (promise) await promise
if (promise === stateRef.current?.promise) return
}
},
[token, stateRef, setPFSCookie],
)

// refresh on token change
usePrevious(token, (prev) => {
if (token !== prev && promiseRef.current) promiseRef.current = setBrowseCookie()
if (token === prev) return
if (!stateRef.current) return
const now = new Date()
const ttlLeft = Math.ceil((stateRef.current.expires.getTime() - now.getTime()) / 1000)
if (ttlLeft <= 0) return
stateRef.current.token = token
stateRef.current.promise = setPFSCookie(token, ttlLeft)
})

return <Ctx.Provider value={ensure}>{children}</Ctx.Provider>
Expand Down

0 comments on commit 4b9da87

Please sign in to comment.