如何使用Apollo刷新代币异步

发布于 2025-01-31 18:35:02 字数 4945 浏览 3 评论 0原文

我使用后邮政获取移动客户端的令牌。 JS Client使用requestRefresh函数发送请求,并带有邮政在接收JWT令牌。移动客户端使用方法调用执行JS代码,该方法在WebView中进行了描述。当我在WebView中收到cookie时,我将令牌保存在cookie中。第一次渲染组件时,我成功获得了一个令牌,但是当出现“未经身份验证”或“无效的JWT令牌”错误时,我在Apollo客户端中更新令牌异步有问题。

我不明白如何在Onerror处理程序内部异步从移动客户端拨打呼叫和响应函数并保存令牌。

我已经回顾了几个资源,stackoverflow答案 1 1 , 2 ,github示例 3 4 ,以及此博客文章a href =“ https://able.bio/anast/apollo-graphql-async-access-cess-token-refresh--470t1c8” rel =“ nofollow noreferrer”> 5 ,但没有找到类似的情况 。

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'));

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'));

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

-黛色若梦 2025-02-07 18:35:02

这个代码终于解决了我的问题

import { fromPromise } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';

interface IMessage {
    action: string;
    data?: {
        text?: string;
        old_jwt?: string;
    };
}

interface IMessageEventData {
    action: Actions;
    success: boolean;
    payload: {
        data: string;
    };
}

enum Actions {
    refreshJWT = 'refresh_jwt',
}

let authToken = '';
let time: NodeJS.Timeout;
let countOfRequests = 0;
let isRefreshing = false;

const INTERVAL_BETWEEN_REQUESTS = 30000;
const MAX_COUNT_REQUESTS = 2;

let pendingRequests = [];

const resolvePendingRequests = () => {
    pendingRequests.map((callback) => callback());
    pendingRequests = [];
};


const message = (): IMessage => {
    return {
        action: Actions.refreshJWT,
        data: {
            ...(authToken && { old_jwt: authToken }),
        },
    };
};

const asyncRequestRefresh = async () => {
    return new Promise((resolve) => {
        // @ts-ignore
        // eslint-disable-next-line no-undef
        if (typeof Android !== 'undefined') {
            // @ts-ignore
            // eslint-disable-next-line no-undef
            Android.postMessage(JSON.stringify(message()));
        } else {
            // @ts-ignore
            window?.webkit?.messageHandlers.inDriveriOSHandler.postMessage(message());
        }
        resolve('ok');
    });
};

const getNewToken = async () =>
    new Promise((resolve, reject) => {
        // @ts-ignore
        window.callWebView = ({ success, payload, action }: IMessageEventData) => {
            if (success) {
                if (action === Actions.refreshJWT) {
                    authToken = payload.data;
                    resolve(payload.data);
                }
            } else {
                reject(payload.data);
            }
        };
    });

export const getToken = async () => {
    countOfRequests++;

    time = setTimeout(() => {
        if (countOfRequests > MAX_COUNT_REQUESTS) {
            clearTimeout(time);
        } else {
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            getToken();
        }
    }, INTERVAL_BETWEEN_REQUESTS);

    return asyncRequestRefresh()
        .then(getNewToken)
        .then((token) => {
            clearTimeout(time);
            return token;
        })
        .catch((error) => {
            console.error(error);
        });
};

export const authLink = setContext((_, { headers }) => {
    return {
        headers: {
            ...headers,
            authorization: authToken ? `Bearer ${authToken}` : '',
        },
    };
});

export const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
    if (graphQLErrors) {
        for (const err of graphQLErrors) {
            switch (err?.extensions?.code) {
                case 'FORBIDDEN':
                    // eslint-disable-next-line no-case-declarations
                    let innerForward;

                    if (!isRefreshing) {
                        isRefreshing = true;
                        innerForward = fromPromise(
                            getToken()
                                // eslint-disable-next-line no-loop-func
                                .then((token) => {
                                    resolvePendingRequests();
                                    console.info(`token refreshed: ${token}`);
                                    authToken = token;
                                    operation.setContext(({ headers = {} }) => ({
                                        headers: {
                                            // Re-add old headers
                                            ...headers,
                                            // Switch out old access token for new one
                                            authorization: `Bearer ${token}` || '',
                                        },
                                    }));
                                })
                                // eslint-disable-next-line no-loop-func
                                .catch((error) => {
                                    console.error(error);
                                    pendingRequests = [];
                                })
                                // eslint-disable-next-line no-loop-func
                                .finally(() => {
                                    isRefreshing = false;
                                })
                        );
                    } else {
                        innerForward = fromPromise(
                            // eslint-disable-next-line no-loop-func
                            new Promise((resolve) => {
                                pendingRequests.push(() => resolve());
                            })
                        );
                    }

                    return innerForward.flatMap(() => forward(operation));
            }
        }
    }
    if (networkError) {
        console.log(`[Network error]: ${networkError}`);
    }
});

This code finally solved my problem

import { fromPromise } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';

interface IMessage {
    action: string;
    data?: {
        text?: string;
        old_jwt?: string;
    };
}

interface IMessageEventData {
    action: Actions;
    success: boolean;
    payload: {
        data: string;
    };
}

enum Actions {
    refreshJWT = 'refresh_jwt',
}

let authToken = '';
let time: NodeJS.Timeout;
let countOfRequests = 0;
let isRefreshing = false;

const INTERVAL_BETWEEN_REQUESTS = 30000;
const MAX_COUNT_REQUESTS = 2;

let pendingRequests = [];

const resolvePendingRequests = () => {
    pendingRequests.map((callback) => callback());
    pendingRequests = [];
};


const message = (): IMessage => {
    return {
        action: Actions.refreshJWT,
        data: {
            ...(authToken && { old_jwt: authToken }),
        },
    };
};

const asyncRequestRefresh = async () => {
    return new Promise((resolve) => {
        // @ts-ignore
        // eslint-disable-next-line no-undef
        if (typeof Android !== 'undefined') {
            // @ts-ignore
            // eslint-disable-next-line no-undef
            Android.postMessage(JSON.stringify(message()));
        } else {
            // @ts-ignore
            window?.webkit?.messageHandlers.inDriveriOSHandler.postMessage(message());
        }
        resolve('ok');
    });
};

const getNewToken = async () =>
    new Promise((resolve, reject) => {
        // @ts-ignore
        window.callWebView = ({ success, payload, action }: IMessageEventData) => {
            if (success) {
                if (action === Actions.refreshJWT) {
                    authToken = payload.data;
                    resolve(payload.data);
                }
            } else {
                reject(payload.data);
            }
        };
    });

export const getToken = async () => {
    countOfRequests++;

    time = setTimeout(() => {
        if (countOfRequests > MAX_COUNT_REQUESTS) {
            clearTimeout(time);
        } else {
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            getToken();
        }
    }, INTERVAL_BETWEEN_REQUESTS);

    return asyncRequestRefresh()
        .then(getNewToken)
        .then((token) => {
            clearTimeout(time);
            return token;
        })
        .catch((error) => {
            console.error(error);
        });
};

export const authLink = setContext((_, { headers }) => {
    return {
        headers: {
            ...headers,
            authorization: authToken ? `Bearer ${authToken}` : '',
        },
    };
});

export const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
    if (graphQLErrors) {
        for (const err of graphQLErrors) {
            switch (err?.extensions?.code) {
                case 'FORBIDDEN':
                    // eslint-disable-next-line no-case-declarations
                    let innerForward;

                    if (!isRefreshing) {
                        isRefreshing = true;
                        innerForward = fromPromise(
                            getToken()
                                // eslint-disable-next-line no-loop-func
                                .then((token) => {
                                    resolvePendingRequests();
                                    console.info(`token refreshed: ${token}`);
                                    authToken = token;
                                    operation.setContext(({ headers = {} }) => ({
                                        headers: {
                                            // Re-add old headers
                                            ...headers,
                                            // Switch out old access token for new one
                                            authorization: `Bearer ${token}` || '',
                                        },
                                    }));
                                })
                                // eslint-disable-next-line no-loop-func
                                .catch((error) => {
                                    console.error(error);
                                    pendingRequests = [];
                                })
                                // eslint-disable-next-line no-loop-func
                                .finally(() => {
                                    isRefreshing = false;
                                })
                        );
                    } else {
                        innerForward = fromPromise(
                            // eslint-disable-next-line no-loop-func
                            new Promise((resolve) => {
                                pendingRequests.push(() => resolve());
                            })
                        );
                    }

                    return innerForward.flatMap(() => forward(operation));
            }
        }
    }
    if (networkError) {
        console.log(`[Network error]: ${networkError}`);
    }
});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文