【问题标题】:Wrap javascript fetch to add custom functionality包装 javascript fetch 以添加自定义功能
【发布时间】:2017-02-06 23:06:13
【问题描述】:

我想知道是否可以这样做,因为我不确定我是错的还是不可能的。基本上,我想做的是为原生 fetch javascript 函数创建一个包装函数。此包装函数将实现令牌验证过程,如果给定的accessToken 已过期,则请求新的accessToken,并再次请求所需的资源。这是我到目前为止所达到的:

customFetch.js

// 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
window.customFetch = (url, options, authOptions) => {

    const OPTIONS = {
        url: '',
        unauthorizedRedirect: '',
        storage: window.sessionStorage,
        tokenName: 'accessToken'
    }

    // Merge options passed by user with the default auth options
    let opts = Object.assign({}, OPTIONS, authOptions);

    // Try to update 'authorizarion's header in order to send always the proper one to the server
    options.headers = options.headers || {};
    options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;

    // Actual server request that user wants to do.
    const request = window.fetch(url, options)
        .then((d) => {
            if (d.status === 401) {
                // Unauthorized
                console.log('not authorized');
                return refreshAccesToken();
            }
            else {
                return d.json();
            }
        });

    // Auxiliar server call to get refresh the access token if it is expired. Here also check if the 
    // cookie has expired and if it has expired, then we should redirect to other page to login again in
    // the application.
    const refreshAccesToken = () => {
        window.fetch(opts.url, {
            method: 'get',
            credentials: 'include'
        }).then((d) => {
            // For this example, we can omit this, we can suppose we always receive the access token
            if (d.status === 401) {
                // Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
                window.location.href = opts.unauthorizedRedirect;
            }

            return d.json();
        }).then((json) => {
            const jwt = json.token;
            if (jwt) {
                // Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
                opts.storage.setItem(opts.tokenName, jwt);
                console.log('new acces token: ' + jwt);

                // Re-send the original request when we have received the refreshed access token.
                return window.customFetch(url, options, authOptions);
            }
            else {
                console.log('no token has been sent');
                return null;
            }
        });
    }

    return request;
}

consumer.js

const getResourcePrivate = () => {
        const url = MAIN_URL + '/resource';
        customFetch(url, {
            method: 'get'
        },{
            url: AUTH_SERVER_TOKEN,
            unauthorizedRedirect: AUTH_URI,
            tokenName: TOKEN_NAME
        }).then((json) => {
            const resource = json ? json.resource : null;
            if (resource) {
                console.log(resource);
            }
            else {
                console.log('No resource has been provided.');
            }
        });
}

我将尝试更好地解释上面的代码:我想让用户对令牌验证透明,以便让他们只需担心请求他们想要的资源。当accessToken 仍然有效时,这种方法可以正常工作,因为return request 指令正在向消费者提供fetch 请求的承诺。

当然,当accessToken 已过期并且我们向auth 服务器请求一个新的时,这是行不通的。刷新令牌并请求私有资源,但consumer.js 看不到它。

对于最后一个场景,是否可以修改程序的流程,以刷新accessToken并执行服务器调用以再次获取私有资源?消费者不应该意识到这个过程;在这两种情况下(accessToken 有效且accessToken 已过期并已刷新)consumer.js 应该在其then 函数中获取私有请求的资源。

【问题讨论】:

    标签: javascript asynchronous promise fetch


    【解决方案1】:

    好吧,我终于找到了解决方案。我尝试使用Promise 解决它并且它有效。这是customFetch.js文件的方法:

    window.customFetch = (url, options, authOptions) => {
    
        const OPTIONS = {
            url: '',
            unauthorizedRedirect: '',
            storage: window.sessionStorage,
            tokenName: 'accessToken'
        }
    
        // Merge options passed by user with the default auth options
        let opts = Object.assign({}, OPTIONS, authOptions);
    
        const requestResource = (resolve) => {
            // Try to update 'authorizarion's header in order to send always the proper one to the server
            options.headers = options.headers || {};
            options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
    
            window.fetch(url, options)
                .then((d) => {
                    if (d.status === 401) {
                        // Unauthorized
                        console.log('not authorized');
                        return refreshAccesToken(resolve);
                    }
                    else {
                        resolve(d.json());
                    }
                });
        }
    
        // Auxiliar server call to get refresh the access token if it is expired. Here also check if the 
        // cookie has expired and if it has expired, then we should redirect to other page to login again in
        // the application.
        const refreshAccesToken = (resolve) => {
            window.fetch(opts.url, {
                method: 'get',
                credentials: 'include'
            }).then((d) => {
                if (d.status === 401) {
                    // Unauthorized
                    window.location.href = opts.unauthorizedRedirect;
                }
    
                return d.json();
            }).then((json) => {
                const jwt = json.token;
                if (jwt) {
                    // Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
                    opts.storage.setItem(opts.tokenName, jwt);
                    console.log('new acces token: ' + jwt);
    
                    // Re-send the original request when we have received the refreshed access token.
                    requestResource(resolve);
                }
                else {
                    console.log('no token has been sent');
                    return null;
                }
            });
        }
    
        let promise = new Promise((resolve, reject) => {
            requestResource(resolve);
        });
    
        return promise;
    }
    

    基本上,我创建了一个Promise,并在其中调用了调用服务器以获取资源的函数。我对request(现在称为requestResource)和refreshAccessToken 进行了一些修改,以使它们成为可参数化的函数。我已经将resolve 函数传递给他们,以便在我收到新令牌后“解析”任何函数。

    可能该解决方案可以改进和优化,但作为第一种方法,它按我的预期工作,所以我认为这是一个有效的解决方案。

    编辑:正如@Dennis 建议我的那样,我在最初的方法中犯了一个错误。我只需要在refreshAccessToken 函数中返回承诺,它就可以正常工作。这就是customFetch.js 文件的外观(这与我第一次发布的代码更相似。事实上,我刚刚在函数中添加了return 指令,尽管删除开始和结束括号也可以) :

    // 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
    window.customFetch = (url, options, authOptions) => {
    
        const OPTIONS = {
            url: '',
            unauthorizedRedirect: '',
            storage: window.sessionStorage,
            tokenName: 'accessToken'
        }
    
        // Merge options passed by user with the default auth options
        let opts = Object.assign({}, OPTIONS, authOptions);
    
        // Try to update 'authorizarion's header in order to send always the proper one to the server
        options.headers = options.headers || {};
        options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
    
        // Actual server request that user wants to do.
        const request = window.fetch(url, options)
            .then((d) => {
                if (d.status === 401) {
                    // Unauthorized
                    console.log('not authorized');
                    return refreshAccesToken();
                }
                else {
                    return d.json();
                }
            });
    
        // Auxiliar server call to get refresh the access token if it is expired. Here also check if the 
        // cookie has expired and if it has expired, then we should redirect to other page to login again in
        // the application.
        const refreshAccesToken = () => {
            return window.fetch(opts.url, {
                method: 'get',
                credentials: 'include'
            }).then((d) => {
                // For this example, we can omit this, we can suppose we always receive the access token
                if (d.status === 401) {
                    // Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
                    window.location.href = opts.unauthorizedRedirect;
                }
    
                return d.json();
            }).then((json) => {
                const jwt = json.token;
                if (jwt) {
                    // Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
                    opts.storage.setItem(opts.tokenName, jwt);
                    console.log('new acces token: ' + jwt);
    
                    // Re-send the original request when we have received the refreshed access token.
                    return window.customFetch(url, options, authOptions);
                }
                else {
                    console.log('no token has been sent');
                    return null;
                }
            });
        }
    
        return request;
    }
    

    【讨论】:

    • 哦,我想我来晚了 :D 我想建议在这里删除一组括号 const refreshAccesToken = () => window.fetch(...); 这样refreshAccesToken 应该返回一个承诺,而不是 undefined 并且一切都应该工作正常
    • @Dennis 你是对的!! Y 在那个函数中犯了一个错误,我忘记了返回承诺。我已经尝试过并且效果很好,因此我将编辑我的答案以添加您的方法,这更接近我的初始解决方案。
    猜你喜欢
    • 1970-01-01
    • 2020-05-24
    • 1970-01-01
    • 2018-03-03
    • 1970-01-01
    • 1970-01-01
    • 2014-04-15
    • 2022-01-22
    • 1970-01-01
    相关资源
    最近更新 更多