121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import type { IncomingMessage } from "http"
|
|
import type { LoggerInstance, Session } from ".."
|
|
|
|
export interface AuthClientConfig {
|
|
baseUrl: string
|
|
basePath: string
|
|
baseUrlServer: string
|
|
basePathServer: string
|
|
/** Stores last session response */
|
|
_session?: Session | null | undefined
|
|
/** Used for timestamp since last sycned (in seconds) */
|
|
_lastSync: number
|
|
/**
|
|
* Stores the `SessionProvider`'s session update method to be able to
|
|
* trigger session updates from places like `signIn` or `signOut`
|
|
*/
|
|
_getSession: (...args: any[]) => any
|
|
}
|
|
|
|
export interface CtxOrReq {
|
|
req?: Partial<IncomingMessage> & { body?: any }
|
|
ctx?: { req: Partial<IncomingMessage> & { body?: any } }
|
|
}
|
|
|
|
/**
|
|
* If passed 'appContext' via getInitialProps() in _app.js
|
|
* then get the req object from ctx and use that for the
|
|
* req value to allow `fetchData` to
|
|
* work seemlessly in getInitialProps() on server side
|
|
* pages *and* in _app.js.
|
|
*/
|
|
export async function fetchData<T = any>(
|
|
path: string,
|
|
__NEXTAUTH: AuthClientConfig,
|
|
logger: LoggerInstance,
|
|
{ ctx, req = ctx?.req }: CtxOrReq = {}
|
|
): Promise<T | null> {
|
|
const url = `${apiBaseUrl(__NEXTAUTH)}/${path}`
|
|
try {
|
|
const options: RequestInit = {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(req?.headers?.cookie ? { cookie: req.headers.cookie } : {}),
|
|
},
|
|
}
|
|
|
|
if (req?.body) {
|
|
options.body = JSON.stringify(req.body)
|
|
options.method = "POST"
|
|
}
|
|
|
|
const res = await fetch(url, options)
|
|
const data = await res.json()
|
|
if (!res.ok) throw data
|
|
return Object.keys(data).length > 0 ? data : null // Return null if data empty
|
|
} catch (error) {
|
|
logger.error("CLIENT_FETCH_ERROR", { error: error as Error, url })
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function apiBaseUrl(__NEXTAUTH: AuthClientConfig) {
|
|
if (typeof window === "undefined") {
|
|
// Return absolute path when called server side
|
|
return `${__NEXTAUTH.baseUrlServer}${__NEXTAUTH.basePathServer}`
|
|
}
|
|
// Return relative path when called client side
|
|
return __NEXTAUTH.basePath
|
|
}
|
|
|
|
/** Returns the number of seconds elapsed since January 1, 1970 00:00:00 UTC. */
|
|
export function now() {
|
|
return Math.floor(Date.now() / 1000)
|
|
}
|
|
|
|
export interface BroadcastMessage {
|
|
event?: "session"
|
|
data?: { trigger?: "signout" | "getSession" }
|
|
clientId: string
|
|
timestamp: number
|
|
}
|
|
|
|
/**
|
|
* Inspired by [Broadcast Channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API)
|
|
* Only not using it directly, because Safari does not support it.
|
|
*
|
|
* https://caniuse.com/?search=broadcastchannel
|
|
*/
|
|
export function BroadcastChannel(name = "nextauth.message") {
|
|
return {
|
|
/** Get notified by other tabs/windows. */
|
|
receive(onReceive: (message: BroadcastMessage) => void) {
|
|
const handler = (event: StorageEvent) => {
|
|
if (event.key !== name) return
|
|
const message: BroadcastMessage = JSON.parse(event.newValue ?? "{}")
|
|
if (message?.event !== "session" || !message?.data) return
|
|
|
|
onReceive(message)
|
|
}
|
|
window.addEventListener("storage", handler)
|
|
return () => window.removeEventListener("storage", handler)
|
|
},
|
|
/** Notify other tabs/windows. */
|
|
post(message: Record<string, unknown>) {
|
|
if (typeof window === "undefined") return
|
|
try {
|
|
localStorage.setItem(
|
|
name,
|
|
JSON.stringify({ ...message, timestamp: now() })
|
|
)
|
|
} catch {
|
|
/**
|
|
* The localStorage API isn't always available.
|
|
* It won't work in private mode prior to Safari 11 for example.
|
|
* Notifications are simply dropped if an error is encountered.
|
|
*/
|
|
}
|
|
},
|
|
}
|
|
}
|