session-connect-debug
This commit is contained in:
parent
070cd7ff2f
commit
9e6ba5a523
|
@ -75,7 +75,7 @@ export const FullArticle = (props: Props) => {
|
||||||
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
||||||
const mt = props.article.topics.find((tpc: Topic) => tpc.slug === main_topic_slug)
|
const mt = props.article.topics.find((tpc: Topic) => tpc.slug === main_topic_slug)
|
||||||
if (mt) {
|
if (mt) {
|
||||||
mt.title = lang() == 'en' ? capitalize(mt.slug.replace('-', ' ')) : mt.title
|
mt.title = lang() === 'en' ? capitalize(mt.slug.replace('-', ' ')) : mt.title
|
||||||
return mt
|
return mt
|
||||||
} else {
|
} else {
|
||||||
return props.article.topics[0]
|
return props.article.topics[0]
|
||||||
|
@ -329,7 +329,7 @@ export const FullArticle = (props: Props) => {
|
||||||
>
|
>
|
||||||
<figure class="img-align-column">
|
<figure class="img-align-column">
|
||||||
<Image width={800} alt={props.article.cover_caption} src={props.article.cover} />
|
<Image width={800} alt={props.article.cover_caption} src={props.article.cover} />
|
||||||
<figcaption>{props.article.cover_caption}</figcaption>
|
<figcaption innerHTML={props.article.cover_caption} />
|
||||||
</figure>
|
</figure>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
@ -513,7 +513,7 @@ export const FullArticle = (props: Props) => {
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<div class={styles.shoutTopic}>
|
<div class={styles.shoutTopic}>
|
||||||
<a href={getPagePath(router, 'topic', { slug: topic.slug })}>
|
<a href={getPagePath(router, 'topic', { slug: topic.slug })}>
|
||||||
{lang() == 'en' ? capitalize(topic.slug) : topic.title}
|
{lang() === 'en' ? capitalize(topic.slug) : topic.title}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { clsx } from 'clsx'
|
||||||
import { createSignal, JSX, Show } from 'solid-js'
|
import { createSignal, JSX, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import { ApiError } from '../../../graphql/error'
|
import { ApiError } from '../../../graphql/error'
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { validateEmail } from '../../../utils/validateEmail'
|
import { validateEmail } from '../../../utils/validateEmail'
|
||||||
|
@ -11,7 +12,6 @@ import { validateEmail } from '../../../utils/validateEmail'
|
||||||
import { email, setEmail } from './sharedLogic'
|
import { email, setEmail } from './sharedLogic'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
import { useSession } from '../../../context/session'
|
|
||||||
|
|
||||||
type FormFields = {
|
type FormFields = {
|
||||||
email: string
|
email: string
|
||||||
|
@ -71,7 +71,7 @@ export const ForgotPasswordForm = () => {
|
||||||
redirect_uri: window.location.href + '&success=1', // FIXME: redirect to success page accepting confirmation code
|
redirect_uri: window.location.href + '&success=1', // FIXME: redirect to success page accepting confirmation code
|
||||||
})
|
})
|
||||||
if (response) {
|
if (response) {
|
||||||
console.debug(response)
|
console.debug('[ForgotPasswordForm]', response)
|
||||||
if (response.message) setMessage(response.message)
|
if (response.message) setMessage(response.message)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { clsx } from 'clsx'
|
||||||
import { Show, createSignal } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import { ApiError } from '../../../graphql/error'
|
import { ApiError } from '../../../graphql/error'
|
||||||
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
|
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
|
@ -17,7 +18,6 @@ import { email, setEmail } from './sharedLogic'
|
||||||
import { SocialProviders } from './SocialProviders'
|
import { SocialProviders } from './SocialProviders'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
import { useSession } from '../../../context/session'
|
|
||||||
|
|
||||||
type FormFields = {
|
type FormFields = {
|
||||||
fullName: string
|
fullName: string
|
||||||
|
|
|
@ -134,28 +134,23 @@ export const HeaderAuth = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={isNotificationsVisible()}>
|
<Show when={isNotificationsVisible() || !author()}>
|
||||||
<div class={styles.userControlItem} onClick={handleBellIconClick}>
|
<div class={styles.userControlItem} onClick={handleBellIconClick}>
|
||||||
<div class={styles.button}>
|
<div class={styles.button}>
|
||||||
<Icon name="bell-white" counter={unreadNotificationsCount()} class={styles.icon} />
|
<Icon
|
||||||
|
name="bell-white"
|
||||||
|
counter={isAuthenticated() ? unreadNotificationsCount() : 1}
|
||||||
|
class={styles.icon}
|
||||||
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
name="bell-white-hover"
|
name="bell-white-hover"
|
||||||
counter={unreadNotificationsCount()}
|
counter={isAuthenticated() ? unreadNotificationsCount() : 1}
|
||||||
class={clsx(styles.icon, styles.iconHover)}
|
class={clsx(styles.icon, styles.iconHover)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!author()}>
|
|
||||||
<div class={styles.userControlItem} onClick={handleBellIconClick}>
|
|
||||||
<div class={styles.button}>
|
|
||||||
<Icon name="bell-white" counter={1} class={styles.icon} />
|
|
||||||
<Icon name="bell-white-hover" counter={1} class={clsx(styles.icon, styles.iconHover)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={isSaveButtonVisible()}>
|
<Show when={isSaveButtonVisible()}>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||||
{renderIconedButton({
|
{renderIconedButton({
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const NotificationView = (props: Props) => {
|
||||||
actions: { markNotificationAsRead, hideNotificationsPanel },
|
actions: { markNotificationAsRead, hideNotificationsPanel },
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
|
|
||||||
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
const { changeSearchParam } = useRouter<ArticlePageSearchParams>() // TODO: use search params
|
||||||
|
|
||||||
const { t, formatDate, formatTime } = useLocalize()
|
const { t, formatDate, formatTime } = useLocalize()
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ import deepEqual from 'fast-deep-equal'
|
||||||
import { createEffect, createSignal, For, lazy, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
|
import { createEffect, createSignal, For, lazy, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
import { useSession } from '../../context/session'
|
|
||||||
import { useConfirm } from '../../context/confirm'
|
import { useConfirm } from '../../context/confirm'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useProfileForm } from '../../context/profile'
|
import { useProfileForm } from '../../context/profile'
|
||||||
|
import { useSession } from '../../context/session'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
import { useSnackbar } from '../../context/snackbar'
|
||||||
import { clone } from '../../utils/clone'
|
import { clone } from '../../utils/clone'
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
|
|
|
@ -84,7 +84,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = createMemo(() =>
|
const title = createMemo(() =>
|
||||||
capitalize(lang() == 'en' ? props.topic.slug.replaceAll('-', ' ') : props.topic.title || ''),
|
capitalize(lang() === 'en' ? props.topic.slug.replaceAll('-', ' ') : props.topic.title || ''),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -54,7 +54,7 @@ export const TopicBadge = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
||||||
<span class={styles.title}>
|
<span class={styles.title}>
|
||||||
{lang() == 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title}
|
{lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title}
|
||||||
</span>
|
</span>
|
||||||
<Show
|
<Show
|
||||||
when={props.topic.body}
|
when={props.topic.body}
|
||||||
|
|
|
@ -54,7 +54,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => {
|
const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => {
|
||||||
return sortedTopics().reduce(
|
return sortedTopics().reduce(
|
||||||
(acc, topic) => {
|
(acc, topic) => {
|
||||||
let letter = lang() == 'en' ? topic.slug[0].toUpperCase() : topic.title[0].toUpperCase()
|
let letter = lang() === 'en' ? topic.slug[0].toUpperCase() : topic.title[0].toUpperCase()
|
||||||
if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '#'
|
if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '#'
|
||||||
if (/[^A-z]/.test(letter) && lang() === 'en') letter = '#'
|
if (/[^A-z]/.test(letter) && lang() === 'en') letter = '#'
|
||||||
if (!acc[letter]) acc[letter] = []
|
if (!acc[letter]) acc[letter] = []
|
||||||
|
@ -147,7 +147,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<div class={clsx(styles.topic, 'topic col-sm-12 col-md-8')}>
|
<div class={clsx(styles.topic, 'topic col-sm-12 col-md-8')}>
|
||||||
<a href={`/topic/${topic.slug}`}>
|
<a href={`/topic/${topic.slug}`}>
|
||||||
{lang() == 'en'
|
{lang() === 'en'
|
||||||
? capitalize(topic.slug.replaceAll('-', ' '))
|
? capitalize(topic.slug.replaceAll('-', ' '))
|
||||||
: topic.title}
|
: topic.title}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import deepEqual from 'fast-deep-equal'
|
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 { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
import { ShoutForm, useEditorContext } from '../../context/editor'
|
import { ShoutForm, useEditorContext } from '../../context/editor'
|
||||||
|
|
|
@ -228,7 +228,7 @@ export const FeedView = (props: Props) => {
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<span class={clsx(stylesTopic.shoutTopic, styles.topic)}>
|
<span class={clsx(stylesTopic.shoutTopic, styles.topic)}>
|
||||||
<a href={`/topic/${topic.slug}`}>
|
<a href={`/topic/${topic.slug}`}>
|
||||||
{lang() == 'en' ? topic.slug.replaceAll('-', ' ') : topic.title}
|
{lang() === 'en' ? topic.slug.replaceAll('-', ' ') : topic.title}
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export const InboxView = () => {
|
||||||
const [isClear, setClear] = createSignal(false)
|
const [isClear, setClear] = createSignal(false)
|
||||||
const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false)
|
const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false)
|
||||||
const { author } = useSession()
|
const { author } = useSession()
|
||||||
const currentUserId = createMemo(() => author().id)
|
const currentUserId = createMemo(() => author()?.id)
|
||||||
const { changeSearchParam, searchParams } = useRouter<InboxSearchParams>()
|
const { changeSearchParam, searchParams } = useRouter<InboxSearchParams>()
|
||||||
|
|
||||||
const messagesContainerRef: { current: HTMLDivElement } = {
|
const messagesContainerRef: { current: HTMLDivElement } = {
|
||||||
|
@ -100,7 +100,7 @@ export const InboxView = () => {
|
||||||
const handleSubmit = async (message: string) => {
|
const handleSubmit = async (message: string) => {
|
||||||
await sendMessage({
|
await sendMessage({
|
||||||
body: message,
|
body: message,
|
||||||
chat_id: currentDialog().id.toString(),
|
chat_id: currentDialog()?.id.toString(),
|
||||||
reply_to: messageToReply()?.id,
|
reply_to: messageToReply()?.id,
|
||||||
})
|
})
|
||||||
setClear(true)
|
setClear(true)
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { redirectPage } from '@nanostores/router'
|
import { redirectPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
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 { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
import { ShoutForm, useEditorContext } from '../../../context/editor'
|
import { ShoutForm, useEditorContext } from '../../../context/editor'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { Topic } from '../../../graphql/schema/core.gen'
|
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
import { router } from '../../../stores/router'
|
import { router } from '../../../stores/router'
|
||||||
import { hideModal, showModal } from '../../../stores/ui'
|
import { hideModal, showModal } from '../../../stores/ui'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
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 SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper'
|
||||||
import { throttle } from 'throttle-debounce'
|
import { throttle } from 'throttle-debounce'
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
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 { createContext, useContext, createSignal, createEffect } from 'solid-js'
|
||||||
|
|
||||||
import { useSession } from './session'
|
import { useSession } from './session'
|
||||||
|
|
||||||
|
const RECONNECT_TIMES = 2
|
||||||
|
|
||||||
export interface SSEMessage {
|
export interface SSEMessage {
|
||||||
id: string
|
id: string
|
||||||
entity: string
|
entity: string
|
||||||
|
@ -36,42 +38,53 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
|
||||||
const addHandler = (handler: MessageHandler) => {
|
const addHandler = (handler: MessageHandler) => {
|
||||||
setHandlers((hhh) => [...hhh, handler])
|
setHandlers((hhh) => [...hhh, handler])
|
||||||
}
|
}
|
||||||
|
|
||||||
const [retried, setRetried] = createSignal<number>(0)
|
const [retried, setRetried] = createSignal<number>(0)
|
||||||
const listen = () => {
|
createEffect(async () => {
|
||||||
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(() => {
|
|
||||||
if (isAuthenticated() && !connected()) {
|
if (isAuthenticated() && !connected()) {
|
||||||
listen()
|
const token = getToken()
|
||||||
setConnected(true)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -46,18 +46,23 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
|
||||||
} = useSession()
|
} = useSession()
|
||||||
const apiClient = createMemo(() => {
|
const apiClient = createMemo(() => {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
if (!inboxClient.private) inboxClient.connect(token)
|
if (!inboxClient.private) {
|
||||||
return inboxClient
|
inboxClient.connect(token)
|
||||||
|
return inboxClient
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const { addHandler } = useConnect()
|
const { addHandler } = useConnect()
|
||||||
addHandler(handleMessage)
|
addHandler(handleMessage)
|
||||||
|
|
||||||
const loadChats = async () => {
|
const loadChats = async () => {
|
||||||
try {
|
try {
|
||||||
const newChats = await apiClient().loadChats({ limit: 50, offset: 0 })
|
const client = apiClient()
|
||||||
setChats(newChats)
|
if (client) {
|
||||||
|
const newChats = await client.loadChats({ limit: 50, offset: 0 })
|
||||||
|
setChats(newChats)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('[loadChats]', error)
|
console.log('[loadChats] error: ', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
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 { createStore } from 'solid-js/store'
|
||||||
import { Portal } from 'solid-js/web'
|
import { Portal } from 'solid-js/web'
|
||||||
|
|
||||||
|
@ -43,23 +43,35 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
actions: { getToken },
|
actions: { getToken },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const apiClient = createMemo(() => {
|
const apiClient = createMemo(() => {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
if (!notifierClient.private && isAuthenticated()) notifierClient.connect(token)
|
if (!notifierClient.private) {
|
||||||
return notifierClient
|
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)
|
const { addHandler } = useConnect()
|
||||||
setUnreadNotificationsCount(unread)
|
|
||||||
setNotificationEntities(newNotificationEntities)
|
const loadNotifications = async (options: { limit?: number; offset?: number }) => {
|
||||||
return notifications
|
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(() => {
|
const sortedNotifications = createMemo(() => {
|
||||||
|
@ -70,7 +82,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
addHandler((data: SSEMessage) => {
|
addHandler((data: SSEMessage) => {
|
||||||
if (data.entity === 'reaction') {
|
if (data.entity === 'reaction' && isAuthenticated()) {
|
||||||
loadNotifications({ limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
|
loadNotifications({ limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
|
||||||
} else {
|
} else {
|
||||||
console.error(`[NotificationsProvider] unhandled message type: ${JSON.stringify(data)}`)
|
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) => {
|
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])
|
const nnn = new Set([...notification.seen, notification.id])
|
||||||
setNotificationEntities(notification.id, 'seen', [...nnn])
|
setNotificationEntities(notification.id, 'seen', [...nnn])
|
||||||
setUnreadNotificationsCount((oldCount) => oldCount - 1)
|
setUnreadNotificationsCount((oldCount) => oldCount - 1)
|
||||||
}
|
}
|
||||||
const markAllNotificationsAsRead = async () => {
|
const markAllNotificationsAsRead = async () => {
|
||||||
await apiClient().markAllNotificationsAsRead()
|
const client = apiClient()
|
||||||
loadNotifications({ limit: loadedNotificationsCount() })
|
if (isAuthenticated() && client) {
|
||||||
|
await client.markAllNotificationsAsRead()
|
||||||
|
await loadNotifications({ limit: loadedNotificationsCount() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const showNotificationsPanel = () => {
|
const showNotificationsPanel = () => {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
|
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
import { showModal } from '../stores/ui'
|
import { showModal } from '../stores/ui'
|
||||||
|
|
||||||
import { useLocalize } from './localize'
|
import { useLocalize } from './localize'
|
||||||
import { useSnackbar } from './snackbar'
|
import { useSnackbar } from './snackbar'
|
||||||
|
|
||||||
|
@ -74,14 +75,16 @@ export const SessionProvider = (props: {
|
||||||
onStateChangeCallback(state: any): unknown
|
onStateChangeCallback(state: any): unknown
|
||||||
children: JSX.Element
|
children: JSX.Element
|
||||||
}) => {
|
}) => {
|
||||||
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
|
|
||||||
const [subscriptions, setSubscriptions] = createSignal<Result>(EMPTY_SUBSCRIPTIONS)
|
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar },
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
|
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
|
||||||
|
const [subscriptions, setSubscriptions] = createSignal<Result>(EMPTY_SUBSCRIPTIONS)
|
||||||
const [token, setToken] = createSignal<AuthToken>()
|
const [token, setToken] = createSignal<AuthToken>()
|
||||||
const [user, setUser] = createSignal<User>()
|
const [user, setUser] = createSignal<User>()
|
||||||
|
|
||||||
const loadSubscriptions = async (): Promise<void> => {
|
const loadSubscriptions = async (): Promise<void> => {
|
||||||
const result = await apiClient.getMySubscriptions()
|
const result = await apiClient.getMySubscriptions()
|
||||||
if (result) {
|
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<AuthToken> => {
|
const getSession = async (): Promise<AuthToken> => {
|
||||||
try {
|
try {
|
||||||
const t = getToken()
|
const tkn = getToken()
|
||||||
console.debug(t)
|
console.debug('[context.session] token before: ', tkn)
|
||||||
const authResult = await authorizer().getSession({
|
const authResult = await authorizer().getSession({
|
||||||
Authorization: t,
|
Authorization: tkn,
|
||||||
})
|
})
|
||||||
if (authResult?.access_token) {
|
if (authResult?.access_token) {
|
||||||
console.log(authResult)
|
setAuth(authResult)
|
||||||
setToken(authResult)
|
console.debug('[context.session] token after: ', authResult.access_token)
|
||||||
if (authResult.user) setUser(authResult.user)
|
await loadSubscriptions()
|
||||||
loadSubscriptions()
|
|
||||||
return authResult
|
return authResult
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('getSession error:', error)
|
console.error('[context.session] getSession error:', error)
|
||||||
setToken(null)
|
setAuth(null)
|
||||||
setUser(null)
|
|
||||||
return null
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -142,10 +151,9 @@ export const SessionProvider = (props: {
|
||||||
const authResult: AuthToken | void = await authorizer().login(params)
|
const authResult: AuthToken | void = await authorizer().login(params)
|
||||||
|
|
||||||
if (authResult && authResult.access_token) {
|
if (authResult && authResult.access_token) {
|
||||||
setToken(authResult)
|
setAuth(authResult)
|
||||||
mutate(authResult)
|
await loadSubscriptions()
|
||||||
loadSubscriptions()
|
console.debug('[context.session] signed in')
|
||||||
console.debug('signed in')
|
|
||||||
} else {
|
} else {
|
||||||
console.info((authResult as AuthToken).message)
|
console.info((authResult as AuthToken).message)
|
||||||
}
|
}
|
||||||
|
@ -179,10 +187,10 @@ export const SessionProvider = (props: {
|
||||||
setConfig({ ...config, ...metaRes, redirectURL: window.location.origin + '/?modal=auth' })
|
setConfig({ ...config, ...metaRes, redirectURL: window.location.origin + '/?modal=auth' })
|
||||||
console.log('[context.session] refreshing session...')
|
console.log('[context.session] refreshing session...')
|
||||||
const s = await getSession()
|
const s = await getSession()
|
||||||
console.debug(s)
|
console.debug('[context.session] session: ', s)
|
||||||
setToken(s)
|
|
||||||
console.log('[context.session] loading author...')
|
console.log('[context.session] loading author...')
|
||||||
await loadAuthor()
|
const a = await loadAuthor()
|
||||||
|
console.debug('[context.session] author: ', a)
|
||||||
setIsSessionLoaded(true)
|
setIsSessionLoaded(true)
|
||||||
console.log('[context.session] loaded')
|
console.log('[context.session] loaded')
|
||||||
})
|
})
|
||||||
|
@ -191,7 +199,8 @@ export const SessionProvider = (props: {
|
||||||
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
|
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
|
||||||
setIsAuthWithCallback(() => callback)
|
setIsAuthWithCallback(() => callback)
|
||||||
|
|
||||||
await authorizer().getProfile()
|
const userdata = await authorizer().getProfile()
|
||||||
|
if (userdata) setUser(userdata)
|
||||||
|
|
||||||
if (!isAuthenticated()) {
|
if (!isAuthenticated()) {
|
||||||
showModal('auth', modalSource)
|
showModal('auth', modalSource)
|
||||||
|
@ -200,19 +209,14 @@ export const SessionProvider = (props: {
|
||||||
|
|
||||||
const signOut = async () => {
|
const signOut = async () => {
|
||||||
await authorizer().logout()
|
await authorizer().logout()
|
||||||
mutate(null)
|
setAuth(null)
|
||||||
setToken(null)
|
|
||||||
setUser(null)
|
|
||||||
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
||||||
showSnackbar({ body: t("You've successfully logged out") })
|
showSnackbar({ body: t("You've successfully logged out") })
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmEmail = async (input: VerifyEmailInput) => {
|
const confirmEmail = async (input: VerifyEmailInput) => {
|
||||||
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
||||||
if (at) {
|
setAuth(at)
|
||||||
setToken(at)
|
|
||||||
mutate(at)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getToken = createMemo(() => token()?.access_token)
|
const getToken = createMemo(() => token()?.access_token)
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const notifierClient = {
|
||||||
|
|
||||||
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
|
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
|
||||||
const resp = await notifierClient.private.query(loadNotifications, params).toPromise()
|
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<void> => {
|
markNotificationAsRead: async (notification_id: number): Promise<void> => {
|
||||||
await notifierClient.private
|
await notifierClient.private
|
||||||
|
|
Loading…
Reference in New Issue
Block a user