Skip to content

Commit beb673b

Browse files
committed
refactor: trunstile flow
1 parent ca1fb80 commit beb673b

File tree

9 files changed

+152
-50
lines changed

9 files changed

+152
-50
lines changed

packages/webapp/Helpers/documentServerSideProps.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { upsertWorkspace, getChannelsByWorkspaceAndUserids } from '@api'
2-
import MobileDetect from 'mobile-detect'
32
import { fetchDocument } from '@utils/fetchDocument'
4-
import * as cookie from 'cookie'
53
import { createClient } from '@utils/supabase/server-props'
6-
import Config from '@config'
74
import { type GetServerSidePropsContext } from 'next'
5+
import { getDeviceInfo, validateTurnstileAccess } from '@helpers'
86

97
export async function handleUserSessionForServerProp(docMetadata: any, session: any) {
108
if (!session?.user) return null
@@ -39,21 +37,18 @@ export async function handleUserSessionForServerProp(docMetadata: any, session:
3937
}
4038

4139
export const documentServerSideProps = async (context: GetServerSidePropsContext) => {
42-
const { req, res, query } = context
43-
const cookies = cookie.parse(req.headers.cookie || '') as { turnstileVerified?: string }
44-
const userAgent = context.req.headers['user-agent'] || ''
45-
const device = new MobileDetect(userAgent)
40+
const { query } = context
4641
const documentSlug = query.slugs?.at(0)
4742

48-
const isTurnstileVerified = Config.app.turnstile.isEnabled
49-
? cookies.turnstileVerified === 'true'
50-
: true
43+
const { isMobile } = getDeviceInfo(context)
44+
45+
const isTurnstileVerified = validateTurnstileAccess(context)
5146

5247
if (!isTurnstileVerified) {
5348
return {
5449
props: {
5550
showTurnstile: true,
56-
isMobile: device.mobile()
51+
isMobile
5752
}
5853
}
5954
}
@@ -74,7 +69,7 @@ export const documentServerSideProps = async (context: GetServerSidePropsContext
7469
channels: channels || null,
7570
docMetadata,
7671
showTurnstile: false,
77-
isMobile: device.mobile()
72+
isMobile
7873
}
7974
}
8075
} catch (error: any) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import MobileDetect from 'mobile-detect'
2+
import { type GetServerSidePropsContext } from 'next'
3+
4+
export const getDeviceInfo = (context: GetServerSidePropsContext): { isMobile: boolean } => {
5+
const userAgent = context.req.headers['user-agent'] || ''
6+
const device = new MobileDetect(userAgent)
7+
return {
8+
isMobile: Boolean(device.mobile())
9+
}
10+
}

packages/webapp/Helpers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from './documentServerSideProps'
2+
export * from './getDeviceInfo'
3+
export * from './validateTurnstileAccess'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as cookie from 'cookie'
2+
import Config from '@config'
3+
import { type GetServerSidePropsContext } from 'next'
4+
5+
export const validateTurnstileAccess = (context: GetServerSidePropsContext): boolean => {
6+
const cookies = cookie.parse(context.req.headers.cookie || '')
7+
8+
const isTurnstileVerified = Config.app.turnstile.isEnabled
9+
? cookies.turnstileVerified === 'true'
10+
: true
11+
12+
return isTurnstileVerified
13+
}

packages/webapp/components/skeleton/SlugPageLoaderWithTurnstile.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ const TurnstileModal = ({ showTurnstile }: Props) => {
5555
if (data.success) {
5656
setState('success')
5757
setWorkspaceSetting('isTurnstileVerified', true)
58-
// No page reload needed!
58+
59+
// TODO: I do not have any solution to reload the page without reloading the whole app
60+
window.location.reload()
5961
} else {
6062
throw new Error(data.message || 'Verification failed')
6163
}
@@ -186,10 +188,6 @@ const TurnstileModal = ({ showTurnstile }: Props) => {
186188
}
187189

188190
export const SlugPageLoaderWithTurnstile = ({ showTurnstile }: Props) => {
189-
if (!showTurnstile) {
190-
return <SlugPageLoader />
191-
}
192-
193191
return (
194192
<div className="h-full">
195193
<TurnstileModal showTurnstile={showTurnstile} />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useEffect } from 'react'
2+
import { useStore } from '@stores'
3+
4+
export const useHandleTurnstileVerficationState = (showTurnstile: boolean) => {
5+
const setWorkspaceSetting = useStore((state) => state.setWorkspaceSetting)
6+
7+
useEffect(() => {
8+
if (!showTurnstile) {
9+
setWorkspaceSetting('isTurnstileVerified', true)
10+
}
11+
}, [showTurnstile, setWorkspaceSetting])
12+
}

packages/webapp/pages/[...slugs].tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { type GetServerSidePropsContext } from 'next'
22
import React from 'react'
33
import HeadSeo from '@components/HeadSeo'
4-
import { useStore } from '@stores'
54
import useAddDeviceTypeHtmlClass from '@components/pages/document/hooks/useAddDeviceTypeHtmlClass'
5+
import { useHandleTurnstileVerficationState } from '@hooks/useHandleTurnstileVerficationState'
66
import dynamic from 'next/dynamic'
77
import { documentServerSideProps } from '@helpers'
88
import { SlugPageLoader } from '@components/skeleton/SlugPageLoader'
@@ -13,11 +13,10 @@ const DocumentPage = dynamic(() => import('@components/pages/document/DocumentPa
1313
})
1414

1515
const Document = ({ docMetadata, isMobile, channels, showTurnstile }: any) => {
16-
const { isTurnstileVerified } = useStore((state) => state.settings)
17-
1816
useAddDeviceTypeHtmlClass(isMobile)
17+
useHandleTurnstileVerficationState(showTurnstile)
1918

20-
if (!isTurnstileVerified) {
19+
if (showTurnstile) {
2120
return (
2221
<>
2322
<HeadSeo />
@@ -38,5 +37,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
3837
props: {}
3938
}
4039
})
40+
4141
return result
4242
}

packages/webapp/utils/fetchDocument.js

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
interface DocumentData {
2+
documentId: string
3+
title: string
4+
description?: string
5+
slug: string
6+
isPrivate: boolean
7+
[key: string]: any
8+
}
9+
10+
interface DocumentResponse {
11+
data: DocumentData
12+
}
13+
14+
interface Session {
15+
user: {
16+
id: string
17+
}
18+
access_token?: string
19+
}
20+
21+
interface DocumentWithClientId extends DocumentData {
22+
docClientId: string
23+
}
24+
25+
class DocumentFetchError extends Error {
26+
constructor(
27+
message: string,
28+
public status?: number,
29+
public originalError?: Error
30+
) {
31+
super(message)
32+
this.name = 'DocumentFetchError'
33+
}
34+
}
35+
36+
export async function fetchDocument(
37+
slug: string | undefined,
38+
session?: Session | null
39+
): Promise<DocumentWithClientId | null> {
40+
if (!slug?.trim()) return null
41+
42+
if (!process.env.NEXT_PUBLIC_RESTAPI_URL) throw new DocumentFetchError('API URL not configured')
43+
44+
try {
45+
const baseAPIUrl = `${process.env.NEXT_PUBLIC_RESTAPI_URL}/documents/${encodeURIComponent(slug)}`
46+
const url = session?.user?.id
47+
? `${baseAPIUrl}?userId=${encodeURIComponent(session.user.id)}`
48+
: baseAPIUrl
49+
50+
const headers: Record<string, string> = {
51+
'Content-Type': 'application/json'
52+
}
53+
54+
if (session?.access_token) {
55+
headers.token = session.access_token
56+
}
57+
58+
const response = await fetch(url, {
59+
headers,
60+
method: 'GET'
61+
})
62+
63+
if (!response.ok) {
64+
throw new DocumentFetchError(
65+
`Failed to fetch document: ${response.statusText}`,
66+
response.status
67+
)
68+
}
69+
70+
const responseData: DocumentResponse = await response.json()
71+
72+
if (!responseData?.data) {
73+
return null
74+
}
75+
76+
const { data } = responseData
77+
78+
// Validate required fields
79+
if (!data.documentId || typeof data.isPrivate !== 'boolean') {
80+
throw new DocumentFetchError('Invalid document data received')
81+
}
82+
83+
const visibility = data.isPrivate ? 'private' : 'public'
84+
const docClientId = `${visibility}.${data.documentId}`
85+
86+
return {
87+
...data,
88+
docClientId
89+
}
90+
} catch (error) {
91+
if (error instanceof DocumentFetchError) {
92+
throw error
93+
}
94+
95+
throw new DocumentFetchError(
96+
'Network error while fetching document',
97+
undefined,
98+
error instanceof Error ? error : new Error(String(error))
99+
)
100+
}
101+
}

0 commit comments

Comments
 (0)