d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
157 lines
4.7 KiB
TypeScript
157 lines
4.7 KiB
TypeScript
'use client'
|
|
|
|
import type React from 'react'
|
|
import { createContext, useEffect, useMemo } from 'react'
|
|
import { createLogger } from '@sim/logger'
|
|
import { useQueryClient } from '@tanstack/react-query'
|
|
import { requestJson } from '@/lib/api/client/request'
|
|
import { listCreatorOrganizationsContract } from '@/lib/api/contracts/organizations'
|
|
import { client } from '@/lib/auth/auth-client'
|
|
import {
|
|
type AppSession,
|
|
extractSessionDataFromAuthClientResult,
|
|
} from '@/lib/auth/session-response'
|
|
import { sessionKeys, useSessionQuery } from '@/hooks/queries/session'
|
|
|
|
export type SessionHookResult = {
|
|
data: AppSession
|
|
isPending: boolean
|
|
error: Error | null
|
|
refetch: () => Promise<void>
|
|
}
|
|
|
|
export const SessionContext = createContext<SessionHookResult | null>(null)
|
|
|
|
const logger = createLogger('SessionProvider')
|
|
|
|
export function SessionProvider({ children }: { children: React.ReactNode }) {
|
|
const queryClient = useQueryClient()
|
|
const query = useSessionQuery()
|
|
const { data, isPending, error, refetch } = query
|
|
|
|
useEffect(() => {
|
|
let isCancelled = false
|
|
|
|
const params = new URLSearchParams(window.location.search)
|
|
const wasUpgraded = params.get('upgraded') === 'true'
|
|
|
|
if (!wasUpgraded) {
|
|
return
|
|
}
|
|
|
|
params.delete('upgraded')
|
|
const newUrl = params.toString()
|
|
? `${window.location.pathname}?${params.toString()}`
|
|
: window.location.pathname
|
|
window.history.replaceState({}, '', newUrl)
|
|
|
|
const refreshAfterUpgrade = async () => {
|
|
const res = await client.getSession({ query: { disableCookieCache: true } })
|
|
const fresh = extractSessionDataFromAuthClientResult(res) as AppSession
|
|
|
|
if (isCancelled) return null
|
|
|
|
await queryClient.cancelQueries({ queryKey: sessionKeys.detail() })
|
|
queryClient.setQueryData(sessionKeys.detail(), fresh)
|
|
return fresh
|
|
}
|
|
|
|
const initializeSession = async () => {
|
|
let session: AppSession = null
|
|
try {
|
|
session = await refreshAfterUpgrade()
|
|
} catch (e) {
|
|
logger.warn('Failed to refresh session after subscription upgrade', { error: e })
|
|
}
|
|
|
|
if (isCancelled) {
|
|
return
|
|
}
|
|
|
|
// Refresh the plan surfaces even if the cookie-bypass read above failed: they
|
|
// query server truth (not the session cookie cache), so they still reconcile.
|
|
queryClient.invalidateQueries({ queryKey: ['organizations'] })
|
|
queryClient.invalidateQueries({ queryKey: ['subscription'] })
|
|
|
|
const activeOrganizationId = session?.session?.activeOrganizationId ?? null
|
|
if (!session || activeOrganizationId) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const orgData = await requestJson(listCreatorOrganizationsContract, {}).catch(() => null)
|
|
if (!orgData) return
|
|
|
|
const organizationId = orgData.organizations?.[0]?.id
|
|
|
|
if (!organizationId || isCancelled) {
|
|
return
|
|
}
|
|
|
|
await client.organization.setActive({ organizationId })
|
|
|
|
if (!isCancelled) {
|
|
const res = await client.getSession({ query: { disableCookieCache: true } })
|
|
const fresh = extractSessionDataFromAuthClientResult(res) as AppSession
|
|
if (!isCancelled) {
|
|
await queryClient.cancelQueries({ queryKey: sessionKeys.detail() })
|
|
queryClient.setQueryData(sessionKeys.detail(), fresh)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.warn('Failed to activate organization after subscription upgrade', { error })
|
|
}
|
|
}
|
|
|
|
void initializeSession()
|
|
|
|
return () => {
|
|
isCancelled = true
|
|
}
|
|
}, [queryClient])
|
|
|
|
useEffect(() => {
|
|
if (isPending) return
|
|
|
|
import('posthog-js')
|
|
.then(({ default: posthog }) => {
|
|
try {
|
|
if (typeof posthog.identify !== 'function') return
|
|
|
|
if (data?.user) {
|
|
posthog.identify(data.user.id, {
|
|
email: data.user.email,
|
|
name: data.user.name,
|
|
email_verified: data.user.emailVerified,
|
|
created_at: data.user.createdAt,
|
|
})
|
|
if (
|
|
typeof posthog.startSessionRecording === 'function' &&
|
|
typeof posthog.sessionRecordingStarted === 'function' &&
|
|
!posthog.sessionRecordingStarted()
|
|
) {
|
|
posthog.startSessionRecording()
|
|
}
|
|
} else {
|
|
posthog.reset()
|
|
}
|
|
} catch {}
|
|
})
|
|
.catch(() => {})
|
|
}, [data, isPending])
|
|
|
|
const value = useMemo<SessionHookResult>(
|
|
() => ({
|
|
data: data ?? null,
|
|
isPending,
|
|
error,
|
|
refetch: async () => {
|
|
await refetch()
|
|
},
|
|
}),
|
|
[data, isPending, error, refetch]
|
|
)
|
|
|
|
return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>
|
|
}
|