- {lang() == 'en'
+ {lang() === 'en'
? capitalize(topic.slug.replaceAll('-', ' '))
: topic.title}
diff --git a/src/components/Views/Edit.tsx b/src/components/Views/Edit.tsx
index 6377a695..ca86b809 100644
--- a/src/components/Views/Edit.tsx
+++ b/src/components/Views/Edit.tsx
@@ -1,6 +1,6 @@
import { clsx } from 'clsx'
import deepEqual from 'fast-deep-equal'
-import { Accessor, createMemo, createSignal, lazy, onCleanup, onMount, Show, Suspense } from 'solid-js'
+import { Accessor, createMemo, createSignal, lazy, onCleanup, onMount, Show } from 'solid-js'
import { createStore } from 'solid-js/store'
import { ShoutForm, useEditorContext } from '../../context/editor'
diff --git a/src/components/Views/Feed.tsx b/src/components/Views/Feed.tsx
index 231f6891..fa071dfb 100644
--- a/src/components/Views/Feed.tsx
+++ b/src/components/Views/Feed.tsx
@@ -228,7 +228,7 @@ export const FeedView = (props: Props) => {
{(topic) => (
- {lang() == 'en' ? topic.slug.replaceAll('-', ' ') : topic.title}
+ {lang() === 'en' ? topic.slug.replaceAll('-', ' ') : topic.title}
{' '}
)}
diff --git a/src/components/Views/Inbox.tsx b/src/components/Views/Inbox.tsx
index ae4e1a01..20047839 100644
--- a/src/components/Views/Inbox.tsx
+++ b/src/components/Views/Inbox.tsx
@@ -53,7 +53,7 @@ export const InboxView = () => {
const [isClear, setClear] = createSignal(false)
const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false)
const { author } = useSession()
- const currentUserId = createMemo(() => author().id)
+ const currentUserId = createMemo(() => author()?.id)
const { changeSearchParam, searchParams } = useRouter
()
const messagesContainerRef: { current: HTMLDivElement } = {
@@ -100,7 +100,7 @@ export const InboxView = () => {
const handleSubmit = async (message: string) => {
await sendMessage({
body: message,
- chat_id: currentDialog().id.toString(),
+ chat_id: currentDialog()?.id.toString(),
reply_to: messageToReply()?.id,
})
setClear(true)
diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx
index 048a658c..e2477aec 100644
--- a/src/components/Views/PublishSettings/PublishSettings.tsx
+++ b/src/components/Views/PublishSettings/PublishSettings.tsx
@@ -1,12 +1,11 @@
import { redirectPage } from '@nanostores/router'
import { clsx } from 'clsx'
-import { createSignal, lazy, onMount, Show } from 'solid-js'
+import { lazy, Show } from 'solid-js'
import { createStore } from 'solid-js/store'
import { ShoutForm, useEditorContext } from '../../../context/editor'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
-import { Topic } from '../../../graphql/schema/core.gen'
import { UploadedFile } from '../../../pages/types'
import { router } from '../../../stores/router'
import { hideModal, showModal } from '../../../stores/ui'
diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx
index 888929c6..93864056 100644
--- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx
+++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx
@@ -1,5 +1,5 @@
import { clsx } from 'clsx'
-import { createEffect, createSignal, For, Show, on, onMount, lazy, onCleanup } from 'solid-js'
+import { createEffect, createSignal, For, Show, on, onMount, onCleanup } from 'solid-js'
import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper'
import { throttle } from 'throttle-debounce'
diff --git a/src/context/connect.tsx b/src/context/connect.tsx
index 311032d4..c243cfd7 100644
--- a/src/context/connect.tsx
+++ b/src/context/connect.tsx
@@ -1,10 +1,12 @@
import type { Accessor, JSX } from 'solid-js'
-import { fetchEventSource } from '@microsoft/fetch-event-source'
+import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
import { useSession } from './session'
+const RECONNECT_TIMES = 2
+
export interface SSEMessage {
id: string
entity: string
@@ -36,42 +38,53 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
const addHandler = (handler: MessageHandler) => {
setHandlers((hhh) => [...hhh, handler])
}
+
const [retried, setRetried] = createSignal(0)
- const listen = () => {
- const token = getToken()
- console.log(`[context.connect] token: ${token}`)
- if (token && !connected() && retried() < 4) {
- fetchEventSource('https://connect.discours.io', {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: token,
- },
- onmessage(event) {
- const m: SSEMessage = JSON.parse(event.data)
- console.log('[context.connect] Received message:', m)
-
- // Iterate over all registered handlers and call them
- messageHandlers().forEach((handler) => handler(m))
- },
- onclose() {
- console.log('[context.connect] sse connection closed by server')
- setConnected(false)
- },
- onerror(err) {
- console.error('[context.connect] sse connection error', err)
- setRetried((r) => r + 1)
- setConnected(false)
- throw new Error(err) // NOTE: simple hack to close the connection
- },
- })
- }
- }
-
- createEffect(() => {
+ createEffect(async () => {
if (isAuthenticated() && !connected()) {
- listen()
- setConnected(true)
+ const token = getToken()
+ if (token) {
+ await fetchEventSource('https://connect.discours.io', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: token,
+ },
+ onmessage(event) {
+ const m: SSEMessage = JSON.parse(event.data)
+ console.log('[context.connect] Received message:', m)
+
+ // Iterate over all registered handlers and call them
+ messageHandlers().forEach((handler) => handler(m))
+ },
+ async onopen(response) {
+ console.log('[context.connect] SSE connection opened', response)
+ if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
+ setConnected(true)
+ return
+ } else if (response.status === 401) {
+ throw new Error('unauthorized')
+ } else {
+ setRetried((r) => r + 1)
+ throw new Error()
+ }
+ },
+ onclose() {
+ console.log('[context.connect] SSE connection closed by server')
+ setConnected(false)
+ },
+ onerror(err) {
+ if (err.message == 'unauthorized' || retried() > RECONNECT_TIMES) {
+ throw err // rethrow to stop the operation
+ } else {
+ // do nothing to automatically retry. You can also
+ // return a specific retry interval here.
+ }
+ },
+ })
+
+ return
+ }
}
})
diff --git a/src/context/inbox.tsx b/src/context/inbox.tsx
index 4968a9e0..e2a74520 100644
--- a/src/context/inbox.tsx
+++ b/src/context/inbox.tsx
@@ -46,18 +46,23 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
} = useSession()
const apiClient = createMemo(() => {
const token = getToken()
- if (!inboxClient.private) inboxClient.connect(token)
- return inboxClient
+ if (!inboxClient.private) {
+ inboxClient.connect(token)
+ return inboxClient
+ }
})
const { addHandler } = useConnect()
addHandler(handleMessage)
const loadChats = async () => {
try {
- const newChats = await apiClient().loadChats({ limit: 50, offset: 0 })
- setChats(newChats)
+ const client = apiClient()
+ if (client) {
+ const newChats = await client.loadChats({ limit: 50, offset: 0 })
+ setChats(newChats)
+ }
} catch (error) {
- console.log('[loadChats]', error)
+ console.log('[loadChats] error: ', error)
}
}
diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx
index cc1124a2..a41d77c1 100644
--- a/src/context/notifications.tsx
+++ b/src/context/notifications.tsx
@@ -1,6 +1,6 @@
import type { Accessor, JSX } from 'solid-js'
-import { createContext, createMemo, createSignal, onMount, useContext } from 'solid-js'
+import { createContext, createEffect, createMemo, createSignal, onMount, useContext } from 'solid-js'
import { createStore } from 'solid-js/store'
import { Portal } from 'solid-js/web'
@@ -43,23 +43,35 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
isAuthenticated,
actions: { getToken },
} = useSession()
+
const apiClient = createMemo(() => {
const token = getToken()
- if (!notifierClient.private && isAuthenticated()) notifierClient.connect(token)
- return notifierClient
+ if (!notifierClient.private) {
+ notifierClient.connect(token)
+ return notifierClient
+ }
})
- const { addHandler } = useConnect()
- const loadNotifications = async (options: { limit?: number; offset?: number }) => {
- const { notifications, unread, total } = await apiClient().getNotifications(options)
- const newNotificationEntities = notifications.reduce((acc, notification) => {
- acc[notification.id] = notification
- return acc
- }, {})
- setTotalNotificationsCount(total)
- setUnreadNotificationsCount(unread)
- setNotificationEntities(newNotificationEntities)
- return notifications
+ const { addHandler } = useConnect()
+
+ const loadNotifications = async (options: { limit?: number; offset?: number }) => {
+ const client = apiClient()
+ if (isAuthenticated() && client) {
+ console.debug(client)
+ const { notifications, unread, total } = await client.getNotifications(options)
+ const newNotificationEntities = notifications.reduce((acc, notification) => {
+ acc[notification.id] = notification
+ return acc
+ }, {})
+
+ setTotalNotificationsCount(total)
+ setUnreadNotificationsCount(unread)
+ setNotificationEntities(newNotificationEntities)
+ console.debug(`[context.notifications] updated`)
+ return notifications
+ } else {
+ return []
+ }
}
const sortedNotifications = createMemo(() => {
@@ -70,7 +82,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
onMount(() => {
addHandler((data: SSEMessage) => {
- if (data.entity === 'reaction') {
+ if (data.entity === 'reaction' && isAuthenticated()) {
loadNotifications({ limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
} else {
console.error(`[NotificationsProvider] unhandled message type: ${JSON.stringify(data)}`)
@@ -79,14 +91,20 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
})
const markNotificationAsRead = async (notification: Notification) => {
- await apiClient().markNotificationAsRead(notification.id)
+ const client = apiClient()
+ if (client) {
+ await client.markNotificationAsRead(notification.id)
+ }
const nnn = new Set([...notification.seen, notification.id])
setNotificationEntities(notification.id, 'seen', [...nnn])
setUnreadNotificationsCount((oldCount) => oldCount - 1)
}
const markAllNotificationsAsRead = async () => {
- await apiClient().markAllNotificationsAsRead()
- loadNotifications({ limit: loadedNotificationsCount() })
+ const client = apiClient()
+ if (isAuthenticated() && client) {
+ await client.markAllNotificationsAsRead()
+ await loadNotifications({ limit: loadedNotificationsCount() })
+ }
}
const showNotificationsPanel = () => {
diff --git a/src/context/session.tsx b/src/context/session.tsx
index bfa116eb..f2c4aa06 100644
--- a/src/context/session.tsx
+++ b/src/context/session.tsx
@@ -23,6 +23,7 @@ import {
import { apiClient } from '../graphql/client/core'
import { showModal } from '../stores/ui'
+
import { useLocalize } from './localize'
import { useSnackbar } from './snackbar'
@@ -74,14 +75,16 @@ export const SessionProvider = (props: {
onStateChangeCallback(state: any): unknown
children: JSX.Element
}) => {
- const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
- const [subscriptions, setSubscriptions] = createSignal(EMPTY_SUBSCRIPTIONS)
const { t } = useLocalize()
const {
actions: { showSnackbar },
} = useSnackbar()
+
+ const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
+ const [subscriptions, setSubscriptions] = createSignal(EMPTY_SUBSCRIPTIONS)
const [token, setToken] = createSignal()
const [user, setUser] = createSignal()
+
const loadSubscriptions = async (): Promise => {
const result = await apiClient.getMySubscriptions()
if (result) {
@@ -91,24 +94,30 @@ export const SessionProvider = (props: {
}
}
+ const setAuth = (auth: AuthToken | void) => {
+ if (auth) {
+ setToken(auth)
+ setUser(auth.user)
+ mutate(auth)
+ }
+ }
+
const getSession = async (): Promise => {
try {
- const t = getToken()
- console.debug(t)
+ const tkn = getToken()
+ console.debug('[context.session] token before: ', tkn)
const authResult = await authorizer().getSession({
- Authorization: t,
+ Authorization: tkn,
})
if (authResult?.access_token) {
- console.log(authResult)
- setToken(authResult)
- if (authResult.user) setUser(authResult.user)
- loadSubscriptions()
+ setAuth(authResult)
+ console.debug('[context.session] token after: ', authResult.access_token)
+ await loadSubscriptions()
return authResult
}
} catch (error) {
- console.error('getSession error:', error)
- setToken(null)
- setUser(null)
+ console.error('[context.session] getSession error:', error)
+ setAuth(null)
return null
} finally {
setTimeout(() => {
@@ -142,10 +151,9 @@ export const SessionProvider = (props: {
const authResult: AuthToken | void = await authorizer().login(params)
if (authResult && authResult.access_token) {
- setToken(authResult)
- mutate(authResult)
- loadSubscriptions()
- console.debug('signed in')
+ setAuth(authResult)
+ await loadSubscriptions()
+ console.debug('[context.session] signed in')
} else {
console.info((authResult as AuthToken).message)
}
@@ -179,10 +187,10 @@ export const SessionProvider = (props: {
setConfig({ ...config, ...metaRes, redirectURL: window.location.origin + '/?modal=auth' })
console.log('[context.session] refreshing session...')
const s = await getSession()
- console.debug(s)
- setToken(s)
+ console.debug('[context.session] session: ', s)
console.log('[context.session] loading author...')
- await loadAuthor()
+ const a = await loadAuthor()
+ console.debug('[context.session] author: ', a)
setIsSessionLoaded(true)
console.log('[context.session] loaded')
})
@@ -191,7 +199,8 @@ export const SessionProvider = (props: {
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
setIsAuthWithCallback(() => callback)
- await authorizer().getProfile()
+ const userdata = await authorizer().getProfile()
+ if (userdata) setUser(userdata)
if (!isAuthenticated()) {
showModal('auth', modalSource)
@@ -200,19 +209,14 @@ export const SessionProvider = (props: {
const signOut = async () => {
await authorizer().logout()
- mutate(null)
- setToken(null)
- setUser(null)
+ setAuth(null)
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)
- mutate(at)
- }
+ setAuth(at)
}
const getToken = createMemo(() => token()?.access_token)
diff --git a/src/graphql/client/notifier.ts b/src/graphql/client/notifier.ts
index eb5b3396..676ff0a9 100644
--- a/src/graphql/client/notifier.ts
+++ b/src/graphql/client/notifier.ts
@@ -10,7 +10,7 @@ export const notifierClient = {
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise => {
const resp = await notifierClient.private.query(loadNotifications, params).toPromise()
- return resp.data.load_notifications
+ return resp.data?.load_notifications
},
markNotificationAsRead: async (notification_id: number): Promise => {
await notifierClient.private