From fce7ffb97237b2e54a3705e3fe32a312acfa72c4 Mon Sep 17 00:00:00 2001 From: Untone Date: Sun, 3 Dec 2023 13:22:42 +0300 Subject: [PATCH] forget-fix-graphql-client-fix --- public/locales/ru/translation.json | 1 + .../Author/AuthorCard/AuthorCard.tsx | 16 ++---- src/components/Author/Userpic/Userpic.tsx | 2 +- src/components/Nav/AuthModal/EmailConfirm.tsx | 2 +- .../Nav/AuthModal/ForgotPasswordForm.tsx | 14 +++-- src/components/Nav/AuthModal/RegisterForm.tsx | 1 + src/context/authorizer.tsx | 18 ++++-- src/context/connect.tsx | 15 +++-- src/context/editor.tsx | 15 +++-- src/context/inbox.tsx | 17 +++--- src/context/notifications.tsx | 12 ++-- src/context/profile.tsx | 11 +++- src/context/reactions.tsx | 17 ++++-- src/context/session.tsx | 20 +++---- src/graphql/client/chat.ts | 27 ++++----- src/graphql/client/core.ts | 33 +++++------ src/graphql/client/notifier.ts | 13 +++-- src/graphql/createGraphQLClient.ts | 23 ++++++++ src/graphql/privateGraphQLClient.ts | 55 ------------------- src/graphql/publicGraphQLClient.ts | 24 -------- src/stores/token.ts | 10 ++++ 21 files changed, 167 insertions(+), 179 deletions(-) create mode 100644 src/graphql/createGraphQLClient.ts delete mode 100644 src/graphql/privateGraphQLClient.ts delete mode 100644 src/graphql/publicGraphQLClient.ts create mode 100644 src/stores/token.ts diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 61695ca5..726a04ff 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -259,6 +259,7 @@ "Pin": "Закрепить", "Platform Guide": "Гид по дискурсу", "Please check your email address": "Пожалуйста, проверьте введенный адрес почты", + "Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте ваш адрес почты, мы отправили ссылку для сброса пароля", "Please confirm your email to finish": "Подтвердите почту и действие совершится", "Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте", "Please enter email": "Пожалуйста, введите почту", diff --git a/src/components/Author/AuthorCard/AuthorCard.tsx b/src/components/Author/AuthorCard/AuthorCard.tsx index edba6f67..3f8cd232 100644 --- a/src/components/Author/AuthorCard/AuthorCard.tsx +++ b/src/components/Author/AuthorCard/AuthorCard.tsx @@ -33,6 +33,7 @@ export const AuthorCard = (props: Props) => { const { t, lang } = useLocalize() const { session, + author, subscriptions, isSessionLoaded, actions: { loadSubscriptions, requireAuthentication }, @@ -57,7 +58,7 @@ export const AuthorCard = (props: Props) => { setIsSubscribing(false) } - const isProfileOwner = createMemo(() => session()?.user?.slug === props.author.slug) + const isProfileOwner = createMemo(() => author()?.slug === props.author.slug) const name = createMemo(() => { if (lang() !== 'ru') { @@ -123,7 +124,7 @@ export const AuthorCard = (props: Props) => { @@ -143,12 +144,7 @@ export const AuthorCard = (props: Props) => { {(f) => ( - + )}
@@ -166,7 +162,7 @@ export const AuthorCard = (props: Props) => { ) @@ -242,7 +238,7 @@ export const AuthorCard = (props: Props) => { } /> diff --git a/src/components/Author/Userpic/Userpic.tsx b/src/components/Author/Userpic/Userpic.tsx index 24a465db..bcebb25e 100644 --- a/src/components/Author/Userpic/Userpic.tsx +++ b/src/components/Author/Userpic/Userpic.tsx @@ -22,7 +22,7 @@ export const Userpic = (props: Props) => { const letters = () => { if (!props.name) return const names = props.name ? props.name.split(' ') : [] - return names[0][0] + '.' + (names.length > 1 ? names[1][0] : '') + '.' + return names[0][0 ?? names[0][0]] + '.' + (names.length > 1 ? names[1][0] + '.' : '') } const avatarSize = createMemo(() => { diff --git a/src/components/Nav/AuthModal/EmailConfirm.tsx b/src/components/Nav/AuthModal/EmailConfirm.tsx index 382b4e8f..c67bc74a 100644 --- a/src/components/Nav/AuthModal/EmailConfirm.tsx +++ b/src/components/Nav/AuthModal/EmailConfirm.tsx @@ -28,7 +28,7 @@ export const EmailConfirm = () => { onMount(async () => { const token = searchParams().token try { - await confirmEmail(token) + await confirmEmail({ token }) } catch (error) { if (error instanceof ApiError) { if (error.code === 'token_expired') { diff --git a/src/components/Nav/AuthModal/ForgotPasswordForm.tsx b/src/components/Nav/AuthModal/ForgotPasswordForm.tsx index dc13fa32..ff2b5176 100644 --- a/src/components/Nav/AuthModal/ForgotPasswordForm.tsx +++ b/src/components/Nav/AuthModal/ForgotPasswordForm.tsx @@ -34,6 +34,8 @@ export const ForgotPasswordForm = () => { const authFormRef: { current: HTMLFormElement } = { current: null } + const [message, setMessage] = createSignal('') + const handleSubmit = async (event: Event) => { event.preventDefault() @@ -61,11 +63,14 @@ export const ForgotPasswordForm = () => { } setIsSubmitting(true) - try { - const response = await authorizer().forgotPassword({ email: email() }) + const response = await authorizer().forgotPassword({ + email: email(), + redirect_uri: window.location.href + '&success=1', // FIXME: redirect to success page accepting confirmation code + }) if (response) { console.debug(response) + if (response.message) setMessage(response.message) } } catch (error) { if (error instanceof ApiError && error.code === 'user_not_found') { @@ -86,7 +91,9 @@ export const ForgotPasswordForm = () => { >

{t('Forgot password?')}

-
{t('Everything is ok, please give us your email address')}
+
+ {t(message()) || t('Everything is ok, please give us your email address')} +
{
- {/*TODO: text*/} {t("We can't find you, check email or")}{' '} { email: cleanEmail, password: password(), confirm_password: password(), + redirect_uri: window.location.origin, }) setIsSuccess(true) diff --git a/src/context/authorizer.tsx b/src/context/authorizer.tsx index 3bc631f1..13f724ca 100644 --- a/src/context/authorizer.tsx +++ b/src/context/authorizer.tsx @@ -1,7 +1,15 @@ import type { ParentComponent } from 'solid-js' import { Authorizer, User, AuthToken, ConfigType } from '@authorizerdev/authorizer-js' -import { createContext, createEffect, createMemo, onCleanup, onMount, useContext } from 'solid-js' +import { + createContext, + createEffect, + createMemo, + createSignal, + onCleanup, + onMount, + useContext, +} from 'solid-js' import { createStore } from 'solid-js/store' export type AuthorizerState = { @@ -21,7 +29,7 @@ type AuthorizerContextActions = { } const config: ConfigType = { authorizerURL: 'https://auth.discours.io', - redirectURL: 'https://auth.discours.io', + redirectURL: 'https://discoursio-webapp.vercel.app', clientID: '9c113377-5eea-4c89-98e1-69302462fc08', // FIXME: use env? } @@ -56,15 +64,16 @@ export const AuthorizerProvider: ParentComponent = (pro loading: true, config: { authorizerURL: props.authorizerURL, + redirectURL: props.redirectURL, clientID: props.clientID, } as ConfigType, }) - + const [redirect, setRedirect] = createSignal() const authorizer = createMemo( () => new Authorizer({ authorizerURL: props.authorizerURL, - redirectURL: props.redirectURL, + redirectURL: redirect(), clientID: props.clientID, }), ) @@ -148,6 +157,7 @@ export const AuthorizerProvider: ParentComponent = (pro } onMount(() => { + setRedirect(window.location.origin) !state.token && getToken() }) diff --git a/src/context/connect.tsx b/src/context/connect.tsx index a894f94b..f63fecec 100644 --- a/src/context/connect.tsx +++ b/src/context/connect.tsx @@ -3,9 +3,8 @@ import type { Accessor, JSX } from 'solid-js' import { fetchEventSource } from '@microsoft/fetch-event-source' import { createContext, useContext, createSignal, createEffect } from 'solid-js' -import { getToken } from '../graphql/privateGraphQLClient' - import { useSession } from './session' +import { useAuthorizer } from './authorizer' export interface SSEMessage { id: string @@ -30,20 +29,22 @@ export const ConnectProvider = (props: { children: JSX.Element }) => { // const [messages, setMessages] = createSignal>([]); const [connected, setConnected] = createSignal(false) - const { isAuthenticated } = useSession() + const { + isAuthenticated, + actions: { getToken }, + } = useSession() const addHandler = (handler: MessageHandler) => { setHandlers((hhh) => [...hhh, handler]) } const listen = () => { - const token = getToken() - if (token) { + if (isAuthenticated()) { fetchEventSource('https://connect.discours.io', { method: 'GET', headers: { 'Content-Type': 'application/json', - Authorization: token, + Authorization: getToken(), }, onmessage(event) { const m: SSEMessage = JSON.parse(event.data) @@ -54,9 +55,11 @@ export const ConnectProvider = (props: { children: JSX.Element }) => { }, onclose() { console.log('[context.connect] sse connection closed by server') + setConnected(false) }, onerror(err) { console.error('[context.connect] sse connection closed by error', err) + setConnected(false) throw new Error(err) // NOTE: simple hack to close the connection }, }) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 8d932b49..76f22ab8 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -2,10 +2,10 @@ import type { JSX } from 'solid-js' import { openPage } from '@nanostores/router' import { Editor } from '@tiptap/core' -import { Accessor, createContext, createSignal, useContext } from 'solid-js' +import { Accessor, createContext, createMemo, createSignal, useContext } from 'solid-js' import { createStore, SetStoreFunction } from 'solid-js/store' -import { apiClient } from '../graphql/client/core' +import { apiClient as coreClient } from '../graphql/client/core' import { ShoutVisibility, Topic, TopicInput } from '../graphql/schema/core.gen' import { router, useRouter } from '../stores/router' import { slugify } from '../utils/slugify' @@ -84,7 +84,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const { t } = useLocalize() const { page } = useRouter() - + const apiClient = createMemo(() => { + if (!coreClient.private) coreClient.connect() + return coreClient + }) const { actions: { showSnackbar }, } = useSnackbar() @@ -126,7 +129,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => { - return apiClient.updateArticle({ + return apiClient().updateArticle({ shoutId: formToUpdate.shoutId, shoutInput: { body: formToUpdate.body, @@ -211,7 +214,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const publishShoutById = async (shoutId: number) => { try { - await apiClient.updateArticle({ + await apiClient().updateArticle({ shoutId, publish: true, }) @@ -225,7 +228,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const deleteShout = async (shoutId: number) => { try { - await apiClient.deleteShout({ + await apiClient().deleteShout({ shoutId, }) return true diff --git a/src/context/inbox.tsx b/src/context/inbox.tsx index 89570990..434be75a 100644 --- a/src/context/inbox.tsx +++ b/src/context/inbox.tsx @@ -1,7 +1,7 @@ -import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/schema/chat.gen' +import type { Chat, Message, MutationCreate_MessageArgs } from '../graphql/schema/chat.gen' import type { Accessor, JSX } from 'solid-js' -import { createContext, createSignal, useContext } from 'solid-js' +import { createContext, createMemo, createSignal, useContext } from 'solid-js' import { inboxClient } from '../graphql/client/chat' import { loadMessages } from '../stores/inbox' @@ -15,7 +15,7 @@ type InboxContextType = { createChat: (members: number[], title: string) => Promise<{ chat: Chat }> loadChats: () => Promise getMessages?: (chatId: string) => Promise - sendMessage?: (args: MutationCreateMessageArgs) => void + sendMessage?: (args: MutationCreate_MessageArgs) => void } } @@ -39,13 +39,16 @@ export const InboxProvider = (props: { children: JSX.Element }) => { setChats((prev) => [...prev, relivedChat]) } } - + const apiClient = createMemo(() => { + if (!inboxClient.private) inboxClient.connect() + return inboxClient + }) const { addHandler } = useConnect() addHandler(handleMessage) const loadChats = async () => { try { - const newChats = await inboxClient.loadChats({ limit: 50, offset: 0 }) + const newChats = await apiClient().loadChats({ limit: 50, offset: 0 }) setChats(newChats) } catch (error) { console.log('[loadChats]', error) @@ -62,9 +65,9 @@ export const InboxProvider = (props: { children: JSX.Element }) => { } } - const sendMessage = async (args: MutationCreateMessageArgs) => { + const sendMessage = async (args: MutationCreate_MessageArgs) => { try { - const message = await inboxClient.createMessage(args) + const message = await apiClient().createMessage(args) setMessages((prev) => [...prev, message]) const currentChat = chats().find((chat) => chat.id === args.chat_id) setChats((prev) => [ diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx index a6fdd149..8afa027a 100644 --- a/src/context/notifications.tsx +++ b/src/context/notifications.tsx @@ -6,7 +6,7 @@ import { Portal } from 'solid-js/web' import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated' import { NotificationsPanel } from '../components/NotificationsPanel' -import { notifierClient as apiClient } from '../graphql/client/notifier' +import { notifierClient } from '../graphql/client/notifier' import { Notification } from '../graphql/schema/notifier.gen' import { SSEMessage, useConnect } from './connect' @@ -38,9 +38,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0) const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0) const [notificationEntities, setNotificationEntities] = createStore>({}) + const apiClient = createMemo(() => { + if (!notifierClient.private) notifierClient.connect() + return notifierClient + }) const { addHandler } = useConnect() const loadNotifications = async (options: { limit: number; offset?: number }) => { - const { notifications, unread, total } = await apiClient.getNotifications(options) + const { notifications, unread, total } = await apiClient().getNotifications(options) const newNotificationEntities = notifications.reduce((acc, notification) => { acc[notification.id] = notification return acc @@ -69,13 +73,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { }) const markNotificationAsRead = async (notification: Notification) => { - await apiClient.markNotificationAsRead(notification.id) + await apiClient().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() + await apiClient().markAllNotificationsAsRead() loadNotifications({ limit: loadedNotificationsCount() }) } diff --git a/src/context/profile.tsx b/src/context/profile.tsx index a2f8a2cf..807858b7 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -1,9 +1,9 @@ import type { ProfileInput } from '../graphql/schema/core.gen' -import { createEffect, createSignal } from 'solid-js' +import { createEffect, createMemo, createSignal } from 'solid-js' import { createStore } from 'solid-js/store' -import { apiClient } from '../graphql/client/core' +import { apiClient as coreClient } from '../graphql/client/core' import { loadAuthor } from '../stores/zine/authors' import { useSession } from './session' @@ -18,8 +18,13 @@ const useProfileForm = () => { const { author: currentAuthor } = useSession() const [slugError, setSlugError] = createSignal() + const apiClient = createMemo(() => { + if (!coreClient.private) coreClient.connect() + return coreClient + }) + const submit = async (profile: ProfileInput) => { - const response = await apiClient.updateProfile(profile) + const response = await apiClient().updateProfile(profile) if (response.error) { setSlugError(response.error) return response.error diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index 8588e413..93b57fa0 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -1,9 +1,9 @@ import type { JSX } from 'solid-js' -import { createContext, onCleanup, useContext } from 'solid-js' +import { createContext, createMemo, onCleanup, useContext } from 'solid-js' import { createStore, reconcile } from 'solid-js/store' -import { apiClient } from '../graphql/client/core' +import { apiClient as coreClient } from '../graphql/client/core' import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen' type ReactionsContextType = { @@ -33,6 +33,11 @@ export function useReactions() { export const ReactionsProvider = (props: { children: JSX.Element }) => { const [reactionEntities, setReactionEntities] = createStore>({}) + const apiClient = createMemo(() => { + if (!coreClient.private) coreClient.connect() + return coreClient + }) + const loadReactionsBy = async ({ by, limit, @@ -42,7 +47,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { limit?: number offset?: number }): Promise => { - const reactions = await apiClient.getReactionsBy({ by, limit, offset }) + const reactions = await coreClient.getReactionsBy({ by, limit, offset }) const newReactionEntities = reactions.reduce((acc, reaction) => { acc[reaction.id] = reaction return acc @@ -52,7 +57,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const createReaction = async (input: ReactionInput): Promise => { - const reaction = await apiClient.createReaction(input) + const reaction = await apiClient().createReaction(input) const changes = { [reaction.id]: reaction, @@ -79,14 +84,14 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const deleteReaction = async (id: number): Promise => { - const reaction = await apiClient.destroyReaction(id) + const reaction = await apiClient().destroyReaction(id) setReactionEntities({ [reaction.id]: undefined, }) } const updateReaction = async (id: number, input: ReactionInput): Promise => { - const reaction = await apiClient.updateReaction(id, input) + const reaction = await apiClient().updateReaction(id, input) setReactionEntities(reaction.id, reaction) } diff --git a/src/context/session.tsx b/src/context/session.tsx index da0616bf..7cd1adcd 100644 --- a/src/context/session.tsx +++ b/src/context/session.tsx @@ -12,6 +12,7 @@ import { showModal } from '../stores/ui' import { useAuthorizer } from './authorizer' import { useLocalize } from './localize' import { useSnackbar } from './snackbar' +import { getToken, resetToken, setToken } from '../stores/token' export type SessionContextType = { session: Resource @@ -21,6 +22,7 @@ export type SessionContextType = { author: Resource isAuthenticated: Accessor actions: { + getToken: () => string loadSession: () => AuthToken | Promise loadSubscriptions: () => Promise requireAuthentication: ( @@ -52,15 +54,6 @@ export const SessionProvider = (props: { children: JSX.Element }) => { 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() @@ -73,13 +66,13 @@ export const SessionProvider = (props: { children: JSX.Element }) => { const getSession = async (): Promise => { try { - const token = getToken() // FIXME: token in localStorage? + const token = getToken() const authResult = await authorizer().getSession({ - Authorization: token, // authToken() + Authorization: token, }) if (authResult) { console.log(authResult) - setToken(authResult.access_token || authResult.id_token) + setToken(authResult.access_token) loadSubscriptions() return authResult } else { @@ -122,7 +115,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => { const signIn = async (params: LoginInput) => { const authResult = await authorizer().login(params) if (authResult) { - setToken(authResult.access_token || authResult.id_token) + setToken(authResult.access_token) mutate(authResult) } loadSubscriptions() @@ -170,6 +163,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => { signOut, confirmEmail, loadSubscriptions, + getToken, } const value: SessionContextType = { session, diff --git a/src/graphql/client/chat.ts b/src/graphql/client/chat.ts index c9d13b94..c8ae77f7 100644 --- a/src/graphql/client/chat.ts +++ b/src/graphql/client/chat.ts @@ -1,4 +1,5 @@ // inbox +import { createGraphQLClient } from '../createGraphQLClient' import createChat from '../mutation/chat/chat-create' import deleteChat from '../mutation/chat/chat-delete' import markAsRead from '../mutation/chat/chat-mark-as-read' @@ -6,7 +7,6 @@ import createChatMessage from '../mutation/chat/chat-message-create' import deleteChatMessage from '../mutation/chat/chat-message-delete' import updateChatMessage from '../mutation/chat/chat-message-update' import updateChat from '../mutation/chat/chat-update' -import { getPrivateClient } from '../privateGraphQLClient' import chatMessagesLoadBy from '../query/chat/chat-messages-load-by' import loadRecipients from '../query/chat/chat-recipients' import myChats from '../query/chat/chats-load' @@ -24,55 +24,56 @@ import { QueryLoad_RecipientsArgs, } from '../schema/chat.gen' -const privateInboxGraphQLClient = getPrivateClient('chat') - export const inboxClient = { + private: null, + connect: () => (inboxClient.private = createGraphQLClient('chat')), + loadChats: async (options: QueryLoad_ChatsArgs): Promise => { - const resp = await privateInboxGraphQLClient.query(myChats, options).toPromise() + const resp = await inboxClient.private.query(myChats, options).toPromise() return resp.data.load_chats.chats }, createChat: async (options: MutationCreate_ChatArgs) => { - const resp = await privateInboxGraphQLClient.mutation(createChat, options).toPromise() + const resp = await inboxClient.private.mutation(createChat, options).toPromise() return resp.data.create_chat }, markAsRead: async (options: MutationMark_As_ReadArgs) => { - const resp = await privateInboxGraphQLClient.mutation(markAsRead, options).toPromise() + const resp = await inboxClient.private.mutation(markAsRead, options).toPromise() return resp.data.mark_as_read }, updateChat: async (options: MutationUpdate_ChatArgs) => { - const resp = await privateInboxGraphQLClient.mutation(updateChat, options).toPromise() + const resp = await inboxClient.private.mutation(updateChat, options).toPromise() return resp.data.update_chat }, deleteChat: async (options: MutationDelete_ChatArgs) => { - const resp = await privateInboxGraphQLClient.mutation(deleteChat, options).toPromise() + const resp = await inboxClient.private.mutation(deleteChat, options).toPromise() return resp.data.delete_chat }, createMessage: async (options: MutationCreate_MessageArgs) => { - const resp = await privateInboxGraphQLClient.mutation(createChatMessage, options).toPromise() + const resp = await inboxClient.private.mutation(createChatMessage, options).toPromise() return resp.data.create_message.message }, updateMessage: async (options: MutationUpdate_MessageArgs) => { - const resp = await privateInboxGraphQLClient.mutation(updateChatMessage, options).toPromise() + const resp = await inboxClient.private.mutation(updateChatMessage, options).toPromise() return resp.data.update_message.message }, deleteMessage: async (options: MutationDelete_MessageArgs) => { - const resp = await privateInboxGraphQLClient.mutation(deleteChatMessage, options).toPromise() + const resp = await inboxClient.private.mutation(deleteChatMessage, options).toPromise() return resp.data.delete_message }, loadChatMessages: async (options: QueryLoad_Messages_ByArgs) => { - const resp = await privateInboxGraphQLClient.query(chatMessagesLoadBy, options).toPromise() + const resp = await inboxClient.private.query(chatMessagesLoadBy, options).toPromise() return resp.data.load_messages_by.messages }, loadRecipients: async (options: QueryLoad_RecipientsArgs) => { - const resp = await privateInboxGraphQLClient.query(loadRecipients, options).toPromise() + const resp = await inboxClient.private.query(loadRecipients, options).toPromise() return resp.data.load_recipients.members }, } diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 8b8510ef..64c28d6f 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -13,6 +13,7 @@ import type { QueryLoad_Shouts_SearchArgs, } from '../schema/core.gen' +import { createGraphQLClient } from '../createGraphQLClient' import createArticle from '../mutation/core/article-create' import deleteShout from '../mutation/core/article-delete' import updateArticle from '../mutation/core/article-update' @@ -22,8 +23,6 @@ import reactionDestroy from '../mutation/core/reaction-destroy' import reactionUpdate from '../mutation/core/reaction-update' import unfollowMutation from '../mutation/core/unfollow' import updateProfile from '../mutation/core/update-profile' -import { getPrivateClient } from '../privateGraphQLClient' -import { getPublicClient } from '../publicGraphQLClient' import shoutLoad from '../query/core/article-load' import shoutsLoadBy from '../query/core/articles-load-by' import draftsLoad from '../query/core/articles-load-drafts' @@ -41,10 +40,12 @@ import topicsAll from '../query/core/topics-all' import userFollowedTopics from '../query/core/topics-by-author' import topicsRandomQuery from '../query/core/topics-random' -export const privateGraphQLClient = getPublicClient('core') -export const publicGraphQLClient = getPrivateClient('core') +const publicGraphQLClient = createGraphQLClient('core') export const apiClient = { + private: null, + connect: () => (apiClient.private = createGraphQLClient('core')), // NOTE: use it after token appears + getRandomTopics: async ({ amount }: { amount: number }) => { const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise() @@ -56,11 +57,11 @@ export const apiClient = { }, follow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => { - const response = await privateGraphQLClient.mutation(followMutation, { what, slug }).toPromise() + const response = await apiClient.private.mutation(followMutation, { what, slug }).toPromise() return response.data.follow }, unfollow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => { - const response = await privateGraphQLClient.mutation(unfollowMutation, { what, slug }).toPromise() + const response = await apiClient.private.mutation(unfollowMutation, { what, slug }).toPromise() return response.data.unfollow }, @@ -95,7 +96,7 @@ export const apiClient = { return response.data.userFollowedTopics }, updateProfile: async (input: ProfileInput) => { - const response = await privateGraphQLClient.mutation(updateProfile, { profile: input }).toPromise() + const response = await apiClient.private.mutation(updateProfile, { profile: input }).toPromise() return response.data.update_profile }, getTopic: async ({ slug }: { slug: string }): Promise => { @@ -103,7 +104,7 @@ export const apiClient = { return response.data.get_topic }, createArticle: async ({ article }: { article: ShoutInput }): Promise => { - const response = await privateGraphQLClient.mutation(createArticle, { shout: article }).toPromise() + const response = await apiClient.private.mutation(createArticle, { shout: article }).toPromise() return response.data.create_shout.shout }, updateArticle: async ({ @@ -115,33 +116,33 @@ export const apiClient = { shoutInput?: ShoutInput publish: boolean }): Promise => { - const response = await privateGraphQLClient + const response = await apiClient.private .mutation(updateArticle, { shoutId, shoutInput, publish }) .toPromise() console.debug('[graphql.client.core] updateArticle:', response.data) return response.data.update_shout.shout }, deleteShout: async ({ shoutId }: { shoutId: number }): Promise => { - const response = await privateGraphQLClient.mutation(deleteShout, { shout_id: shoutId }).toPromise() + const response = await apiClient.private.mutation(deleteShout, { shout_id: shoutId }).toPromise() console.debug('[graphql.client.core] deleteShout:', response) }, getDrafts: async (): Promise => { - const response = await privateGraphQLClient.query(draftsLoad, {}).toPromise() + const response = await apiClient.private.query(draftsLoad, {}).toPromise() console.debug('[graphql.client.core] getDrafts:', response) return response.data.load_shouts_drafts }, createReaction: async (input: ReactionInput) => { - const response = await privateGraphQLClient.mutation(reactionCreate, { reaction: input }).toPromise() + const response = await apiClient.private.mutation(reactionCreate, { reaction: input }).toPromise() console.debug('[graphql.client.core] createReaction:', response) return response.data.create_reaction.reaction }, destroyReaction: async (id: number) => { - const response = await privateGraphQLClient.mutation(reactionDestroy, { id: id }).toPromise() + const response = await apiClient.private.mutation(reactionDestroy, { id: id }).toPromise() console.debug('[graphql.client.core] destroyReaction:', response) return response.data.delete_reaction.reaction }, updateReaction: async (id: number, input: ReactionInput) => { - const response = await privateGraphQLClient + const response = await apiClient.private .mutation(reactionUpdate, { id: id, reaction: input }) .toPromise() console.debug('[graphql.client.core] updateReaction:', response) @@ -177,7 +178,7 @@ export const apiClient = { }, getMyFeed: async (options: LoadShoutsOptions) => { - const resp = await privateGraphQLClient.query(myFeed, { options }).toPromise() + const resp = await apiClient.private.query(myFeed, { options }).toPromise() if (resp.error) console.error(resp) return resp.data.load_shouts_feed @@ -190,7 +191,7 @@ export const apiClient = { return resp.data.load_reactions_by }, getMySubscriptions: async (): Promise => { - const resp = await privateGraphQLClient.query(mySubscriptions, {}).toPromise() + const resp = await apiClient.private.query(mySubscriptions, {}).toPromise() return resp.data.get_my_followed }, diff --git a/src/graphql/client/notifier.ts b/src/graphql/client/notifier.ts index 0e7b10da..2e6a748e 100644 --- a/src/graphql/client/notifier.ts +++ b/src/graphql/client/notifier.ts @@ -1,18 +1,19 @@ import markAllNotificationsAsRead from '../mutation/notifier/mark-all-notifications-as-read' import markNotificationAsRead from '../mutation/notifier/mark-notification-as-read' -import { getPrivateClient } from '../privateGraphQLClient' +import { createGraphQLClient } from '../createGraphQLClient' import loadNotifications from '../query/notifier/notifications-load' import { NotificationsResult, QueryLoad_NotificationsArgs } from '../schema/notifier.gen' -export const notifierPrivateGraphqlClient = getPrivateClient('notifier') - export const notifierClient = { + private: null, + connect: () => (notifierClient.private = createGraphQLClient('notifier')), + getNotifications: async (params: QueryLoad_NotificationsArgs): Promise => { - const resp = await notifierPrivateGraphqlClient.query(loadNotifications, params).toPromise() + const resp = await notifierClient.private.query(loadNotifications, params).toPromise() return resp.data.load_notifications }, markNotificationAsRead: async (notification_id: number): Promise => { - await notifierPrivateGraphqlClient + await notifierClient.private .mutation(markNotificationAsRead, { notification_id, }) @@ -20,6 +21,6 @@ export const notifierClient = { }, markAllNotificationsAsRead: async (): Promise => { - await notifierPrivateGraphqlClient.mutation(markAllNotificationsAsRead, {}).toPromise() + await notifierClient.private.mutation(markAllNotificationsAsRead, {}).toPromise() }, } diff --git a/src/graphql/createGraphQLClient.ts b/src/graphql/createGraphQLClient.ts new file mode 100644 index 00000000..b1a8b190 --- /dev/null +++ b/src/graphql/createGraphQLClient.ts @@ -0,0 +1,23 @@ +import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core' +import { devtoolsExchange } from '@urql/devtools' + +import { isDev } from '../utils/config' +import { getToken } from '../stores/token' + +const exchanges: Exchange[] = [dedupExchange, fetchExchange] + +if (isDev) { + exchanges.unshift(devtoolsExchange) +} + +export const createGraphQLClient = (serviceName: string) => { + const token = getToken() + const options: ClientOptions = { + url: `https://${serviceName}.discours.io`, + maskTypename: true, + requestPolicy: 'cache-and-network', + fetchOptions: () => (token ? { headers: { Authorization: token } } : {}), + exchanges, + } + return createClient(options) +} diff --git a/src/graphql/privateGraphQLClient.ts b/src/graphql/privateGraphQLClient.ts deleted file mode 100644 index 1a282abf..00000000 --- a/src/graphql/privateGraphQLClient.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core' -import { devtoolsExchange } from '@urql/devtools' - -import { isDev } from '../utils/config' - -const TOKEN_LOCAL_STORAGE_KEY = 'token' - -const exchanges: Exchange[] = [dedupExchange, fetchExchange] - -if (isDev) { - exchanges.unshift(devtoolsExchange) -} - -export const getToken = (): string => { - return localStorage.getItem(TOKEN_LOCAL_STORAGE_KEY) -} - -export const setToken = (token: string) => { - if (!token) { - console.error('[privateGraphQLClient] setToken: token is null!') - } - - localStorage.setItem(TOKEN_LOCAL_STORAGE_KEY, token) -} - -export const resetToken = () => { - localStorage.removeItem(TOKEN_LOCAL_STORAGE_KEY) -} - -const options: ClientOptions = { - url: '', - maskTypename: true, - requestPolicy: 'cache-and-network', - fetchOptions: () => { - try { - // localStorage is the source of truth for now - // to change token call setToken, for example after login - const token = localStorage.getItem(TOKEN_LOCAL_STORAGE_KEY) - if (!token) { - console.error('[privateGraphQLClient] fetchOptions: token is null!') - } - const headers = { Authorization: token } - return { headers } - } catch { - return {} - } - }, - exchanges, -} - -export const getPrivateClient = (name: string) => - createClient({ - ...options, - url: `https://${name}.discours.io`, - }) diff --git a/src/graphql/publicGraphQLClient.ts b/src/graphql/publicGraphQLClient.ts deleted file mode 100644 index e4a0b570..00000000 --- a/src/graphql/publicGraphQLClient.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core' -import { devtoolsExchange } from '@urql/devtools' - -import { isDev } from '../utils/config' -// import { cache } from './cache' - -const exchanges: Exchange[] = [dedupExchange, fetchExchange] //, cache] - -if (isDev) { - exchanges.unshift(devtoolsExchange) -} - -const options: ClientOptions = { - url: '', - maskTypename: true, - requestPolicy: 'cache-and-network', - exchanges, -} - -export const getPublicClient = (name: string) => - createClient({ - ...options, - url: `https://${name}.discours.io`, - }) diff --git a/src/stores/token.ts b/src/stores/token.ts new file mode 100644 index 00000000..0f1d13e9 --- /dev/null +++ b/src/stores/token.ts @@ -0,0 +1,10 @@ +import { cookieStorage, createStorage } from '@solid-primitives/storage' + +// https://start.solidjs.com/api/createCookieSessionStorage +export const [store, setStore, { remove }] = createStorage({ + api: cookieStorage, + prefix: 'discoursio', +}) +export const getToken = () => store.token +export const setToken = (value) => setStore('token', value) +export const resetToken = () => remove('token')