Безопасный способ инициализации клиента Apollo при асинхронном токене

#reactjs #ionic-framework #apollo #apollo-client

#reactjs #ionic-framework #apollo #apollo-client

Вопрос:

Я пишу приложение, которое использует клиент Apollo для выполнения запросов graphql к базе данных MongoDB Realm.

Это его абстрактная структура:

 <MongoContext>
  <ApolloContext>
    <App/>
  </ApolloContext>
</MongoContext>
  

Компонент верхнего уровня обрабатывает аутентификацию пользователя и предоставляет контекст. Следующий компонент запускает клиент Apollo и логику кэширования и задает контекст для всего приложения.

Ожидаемый поток данных показан на схеме на этой странице. Поведение по умолчанию для useQuery заключается в том, что Apollo:

  1. Пытается извлечь из кэша;
  2. Пытается выполнить выборку с сервера; в случае успеха сохраняет в кеш.

Моя цель — добиться возможности работы в автономном режиме. Итак, еще раз обратившись к диаграмме, самый первый запрос должен быть разрешен кэшем, когда он содержит данные. Механизм кэширования Apollo по умолчанию находится в памяти, поэтому я использую apollo-cache-persist для кэширования его в localStorage.

Более конкретно, это требуемые условия:

  1. Приложение должно реагировать при запуске.
  2. При первом рендеринге приложение либо отключено, либо еще не прошло проверку подлинности
    1. Поэтому он должен считываться из кэша, если таковой имеется
    2. Если кэша нет, не делайте никаких запросов к серверу (все они завершатся неудачей).
  3. Если пользователь находится в Сети, приложение должно получить токен аутентификации для запросов
    1. Токен запрашивается асинхронно
    2. Пока токен неизвестен, считывайте только из кэша (как указано в пункте 1.2 выше).
    3. Когда токен известен, используйте поток данных, описанный выше

Мои основные проблемы конкретно связаны с 1.2 и 2.2 выше. Т. е. предотвращение отправки запросов Apollo на сервер, когда мы уже знаем, что это приведет к сбою.

Я также искал глобальное решение, поэтому изменение отдельных запросов с помощью skip или useLazyQuery не является опцией. (И я даже не уверен, что это сработает — мне все еще нужно, чтобы запросы выполнялись в кэше.)


Код:

ApolloContext компонент:

 import * as React from 'react';
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  createHttpLink,
  NormalizedCacheObject,
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { persistCache } from 'apollo-cache-persist';
import { PersistentStorage } from 'apollo-cache-persist/types';


const ApolloContext: React.FC = ({ children }) => {
  // this hook gets me the token asynchronously
  // token is '' initially but eventually resolves... or not
  const { token } = useToken();
  const cache = new InMemoryCache();
  const [client, setClient] = React.useState(createApolloClient(token, cache))

  // first initialize the client without the token, then again upon receiving it
  React.useEffect(() => {
    const initPersistCache = async () => {
      await persistCache({
        cache,
        storage: capacitorStorageMethods, 
        debug: true,
      });
    };
    const initApollo = async () => {
      await initPersistCache();
      setClient(createApolloClient(token, cache));
    };
    if (token) {
      initApollo();
    } else {
      initPersistCache();
    }
  }, [token]);
  console.log('id user', id, user);

  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

function createApolloClient(
  token: string,
  cache: InMemoryCache
) {

  const graphql_url = `https://realm.mongodb.com/api/client/v2.0/app/${realmAppId}/graphql`;

  const httpLink = createHttpLink({
    uri: graphql_url,
  });
  
  const authorizationHeaderLink = setContext(async (_, { headers }) => {
      return {
        headers: {
          ...headers,
          Authorization: `Bearer ${token}`,
        },
      };
  });
  return new ApolloClient({
    link: authorizationHeaderLink.concat(httpLink),
    cache,
  });
}

  

Что я пробовал:

После попытки многих разных действий. Я нашел кое-что, что работает, но выглядит ужасно. Хитрость заключается в том, чтобы предоставить Apollo пользовательский fetch , который отклоняет все запросы, когда пользователь не вошел в систему:

 
  const customFetch = (input: RequestInfo, init?: RequestInit | undefined) => {
    return user.isLoggedIn
      ? fetch(input, init)
      : Promise.reject(new Response());
  };

  const httpLink = createHttpLink({
    uri: graphql_url,
    fetch: customFetch,
  });
  
  

Другой способ предотвратить исходящие запросы — просто опустить link свойство:

   return new ApolloClient({
    link: user.isLoggedIn
      ? authorizationHeaderLink.concat(httpLink)
      : undefined,
    cache,
  });
}

  

Это выглядит намного чище, но теперь проблема в том, что запросы, которые не могут быть выполнены кэшем, зависают при загрузке навсегда.(связанная проблема)

Я ищу более чистый и безопасный способ сделать это.