import type { AuthModalSource } from '../components/Nav/AuthModal/types' import type { Author, Result } from '../graphql/schema/core.gen' import type { Accessor, JSX, Resource } from 'solid-js' import { VerifyEmailInput, LoginInput, AuthToken, User } from '@authorizerdev/authorizer-js' import { createContext, createMemo, createResource, createSignal, onMount, useContext } from 'solid-js' import { apiClient } from '../graphql/client/core' import { showModal } from '../stores/ui' import { useAuthorizer } from './authorizer' import { useLocalize } from './localize' import { useSnackbar } from './snackbar' import { cookieStorage, createStorage } from '@solid-primitives/storage' export type SessionContextType = { session: Resource isSessionLoaded: Accessor subscriptions: Accessor user: Accessor author: Resource isAuthenticated: Accessor actions: { loadSession: () => AuthToken | Promise loadSubscriptions: () => Promise requireAuthentication: ( callback: (() => Promise) | (() => void), modalSource: AuthModalSource, ) => void signIn: (params: LoginInput) => Promise signOut: () => Promise confirmEmail: (input: VerifyEmailInput) => Promise } } const SessionContext = createContext() export function useSession() { return useContext(SessionContext) } const EMPTY_SUBSCRIPTIONS = { topics: [], authors: [], } export const SessionProvider = (props: { children: JSX.Element }) => { const [isSessionLoaded, setIsSessionLoaded] = createSignal(false) const [subscriptions, setSubscriptions] = createSignal(EMPTY_SUBSCRIPTIONS) const { t } = useLocalize() const { actions: { showSnackbar }, } = useSnackbar() const [, { authorizer }] = useAuthorizer() // const [getToken, setToken] = createSignal('') // https://start.solidjs.com/api/createCookieSessionStorage const [store, setStore, { remove, clear, toJSON }] = createStorage({ api: cookieStorage, prefix: 'discoursio', }) const getToken = () => store.token const setToken = (value) => setStore('token', value) const resetToken = () => remove('token') const loadSubscriptions = async (): Promise => { const result = await apiClient.getMySubscriptions() if (result) { setSubscriptions(result) } else { setSubscriptions(EMPTY_SUBSCRIPTIONS) } } const getSession = async (): Promise => { try { const token = getToken() // FIXME: token in localStorage? const authResult = await authorizer().getSession({ Authorization: token, // authToken() }) if (authResult) { console.log(authResult) setToken(authResult.access_token || authResult.id_token) loadSubscriptions() return authResult } else { return null } } catch (error) { console.error('getSession error:', error) resetToken() return null } finally { setTimeout(() => { setIsSessionLoaded(true) }, 0) } } const [session, { refetch: loadSession, mutate }] = createResource(getSession, { ssrLoadFrom: 'initial', initialValue: null, }) const user = createMemo(() => session()?.user) const [author, { refetch: loadAuthor }] = createResource( async () => { const u = session()?.user if (u) { return (await apiClient.getAuthor({ user: u.id })) ?? null } return null }, { ssrLoadFrom: 'initial', initialValue: null, }, ) const isAuthenticated = createMemo(() => Boolean(session()?.user)) const signIn = async (params: LoginInput) => { const authResult = await authorizer().login(params) if (authResult) { setToken(authResult.access_token || authResult.id_token) mutate(authResult) } loadSubscriptions() console.debug('signed in') } const [isAuthWithCallback, setIsAuthWithCallback] = createSignal(null) const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => { setIsAuthWithCallback(() => callback) await loadSession() if (!isAuthenticated()) { showModal('auth', modalSource) } } onMount(async () => { // Load the session and author data on mount await loadSession() loadAuthor() }) const signOut = async () => { await authorizer().logout() mutate(null) resetToken() setSubscriptions(EMPTY_SUBSCRIPTIONS) showSnackbar({ body: t("You've successfully logged out") }) } const confirmEmail = async (input: VerifyEmailInput) => { const at: void | AuthToken = await authorizer().verifyEmail(input) if (at) { setToken(at.access_token) mutate(at) } } const actions = { loadSession, requireAuthentication, signIn, signOut, confirmEmail, loadSubscriptions, } const value: SessionContextType = { session, subscriptions, isSessionLoaded, author, user, isAuthenticated, actions, } onMount(() => { loadSession() }) return {props.children} }