I use postMessage to get the token from the mobile client. Js client sends request using requestRefresh
function with postMessage to receive JWT token. Mobile clients execute JS code using a method call, which is described inside the WebView. I save the token in a cookie when I receive it in webview. I successfully get a token the first time I render a component, but I have a problem with updating the token asynchronously in the Apollo client when an "Unauthenticated" or "Invalid jwt token" error occurs.
I don't understand how I can call the call and response function from the mobile client asynchronously inside the onError handler and save the token.
I've reviewed several resources on this, StackOverflow answers 1, 2, Github examples 3, 4, and this blog post 5, but didn't find similar case .
index.tsx
import React, { Suspense, useState, useEffect, useCallback, useMemo } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { ApolloClient, InMemoryCache, createHttpLink, ApolloProvider, from } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { App } from 'app';
import Cookies from 'universal-cookie';
const cookies = new Cookies();
const httpLink = createHttpLink({
uri: process.env.API_HOST,
});
const authLink = setContext((_, { headers }) => {
const token = cookies.get('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
const errorLink = onError(({ graphQLErrors, operation, forward }) => {
if (graphQLErrors)
for (const err of graphQLErrors) {
switch (err?.extensions?.code) {
case 'UNAUTHENTICATED':
// error code is set to UNAUTHENTICATED
//How to call and process a response from a mobile client here?
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: token ? `Bearer ${token}` : '',
},
});
// retry the request, returning the new observable
return forward(operation);
}
}
});
const client = new ApolloClient({
cache: new InMemoryCache({
typePolicies: {
User: {
keyFields: ['userId'],
},
},
}),
link: from([errorLink, authLink, httpLink]),
uri: process.env.API_HOST,
connectToDevTools: process.env.NODE_ENV === 'development',
});
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import Cookies from 'universal-cookie';
interface IMessage {
action: string;
data?: {
text: string;
old_jwt?: string;
};
}
enum Actions {
refreshJWT = 'refresh_jwt',
}
interface IMessageEventData {
action: Actions;
success: boolean;
payload: {
data: string;
};
}
export const Index: React.FC = () => {
const cookies = new Cookies();
const token = cookies.get('token');
const message = useMemo((): IMessage => {
return {
action: Actions.refreshJWT,
data: {
text: 'Hello from JS',
...(token && { old_jwt: token }),
},
};
}, [token]);
const requestRefresh = useCallback(() => {
if (typeof Android !== 'undefined') {
Android.postMessage(JSON.stringify(message));
} else {
window?.webkit?.messageHandlers.iosHandler.postMessage(message);
}
}, [message]);
// @ts-ignore
window.callWebView = useCallback(
({ success, payload, action }: IMessageEventData) => {
if (success) {
if (action === Actions.refreshJWT) {
cookies.set('token', payload.data, { path: '/' });
}
} else {
throw new Error(payload.data);
}
},
[requestRefresh]
);
useEffect(() => {
requestRefresh();
}, []);
return (
<BrowserRouter>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</BrowserRouter>
);
};
ReactDOM.render(<Index />, document.getElementById('root'));