Angular Apollo WebSocketLink订阅动态授权头登录后不更新问题求解
问题原因
WebSocket连接建立后,连接参数不会自动更新,你配置的connectionParams函数只会在首次连接建立、断线重连的时候被执行。登录前如果已经触发过订阅(哪怕是lazy模式),当时localStorage中token为空,连接已经用空token建立完成,后续登录后token更新,旧连接不会自动刷新参数。
同时注意:浏览器环境下WebSocket协议不支持自定义HTTP请求头,所以所有认证参数必须通过connectionParams传递,需要确认你的服务端是从WebSocket的connection_init消息的payload里读取Authorization字段,而不是读取HTTP请求头。
解决步骤
步骤1:调整GraphQLModule配置,暴露WebSocketLink实例
修改你的GraphQLModule代码,将WebSocketLink实例通过Angular注入令牌暴露,方便登录成功后调用:
import { NgModule, InjectionToken, Inject } from '@angular/core'; // 剩下的原有导入保持不变 // 新增WebSocketLink注入令牌 export const WEBSOCKET_LINK = new InjectionToken<WebSocketLink>('WEBSOCKET_LINK'); // 抽离WebSocketLink创建逻辑为单独factory export function createWebSocketLink(): WebSocketLink { return new WebSocketLink({ uri:`ws://localhost:3005/subscriptions`, options:{ lazy: true, reconnect: true, connectionParams: async () => { const token = localStorage.getItem('token'); let token_decrypt= null if (token) { token_decrypt= CryptoJS.AES.decrypt(token,environment.key_decrypt).toString(CryptoJS.enc.Utf8) } return { Authorization: token ? `Bearer ${token_decrypt}` : "", } }, } }); } // 调整APOLLO_OPTIONS的factory,接收注入的WebSocketLink实例 export function createApollo(httpLink: HttpLink,_coreTranslationService: CoreTranslationService, wsLink: WebSocketLink): ApolloClientOptions<any> { // 原有代码保持不变,把原来创建ws实例的代码删掉,直接用传入的wsLink即可 _coreTranslationService.translate(en, es); const basic = setContext((operation, context) => ({ headers: { Accept: 'charset=utf-8' } })); const auth = setContext((operation, context) => { const token = localStorage.getItem('token'); if (token === null) { return {}; } else { let token_decrypt= CryptoJS.AES.decrypt(token,environment.key_decrypt).toString(CryptoJS.enc.Utf8) return { headers: { Authorization: `Bearer ${token_decrypt}` } }; } }); const http = httpLink.create({ uri(operation){ return operationFilter(operation.operationName)? uri : urisub; } }); // 这里直接用传入的wsLink,不需要再创建 const ws = wsLink; // 剩下的原有逻辑保持不变 const error = onError(({networkError, graphQLErrors}) => { if (graphQLErrors) { graphQLErrors.map(({ message, locations, path, extensions }) =>{ console.log('error graph', localStorage.getItem('token')); if (extensions && localStorage.getItem('token')!=null) { if (extensions.exception.status==401) { Swal.fire({ icon: 'error', title: _coreTranslationService.instant('ErrorSub.Title'), text: _coreTranslationService.instant('ErrorSub.Message'), timer: 6000, timerProgressBar: true, showCancelButton: false, showConfirmButton: false, allowOutsideClick: false, allowEscapeKey: false }); setTimeout(() => { localStorage.clear(); window.location.href = "/pages/authentication/login-v2"; }, 7000); } } } ); } if (networkError) { console.log(`[Network error]: ${networkError}`); } }) const _split = split( ({query}) => { const data = getMainDefinition(query); return ( data.kind === 'OperationDefinition' && data.operation === 'subscription' ); }, ws, auth.concat(http) ) const cleanTypeName = new ApolloLink((operation, forward) => { if (operation.variables) { const omitTypename = (key, value) => (key === '__typename' ? undefined : value); operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename); } return forward(operation).map((data) => { return data; }); }); const link =ApolloLink.from([cleanTypeName, basic, error, _split]); return { link: link, cache: new InMemoryCache({ addTypename: false, }), defaultOptions: { watchQuery: { fetchPolicy: 'network-only', }, query: { fetchPolicy: 'network-only', }, mutate: { } }, }; } @NgModule({ providers: [ { provide: WEBSOCKET_LINK, useFactory: createWebSocketLink }, { provide: APOLLO_OPTIONS, useFactory: createApollo, deps: [HttpLink, CoreTranslationService, WEBSOCKET_LINK], }, ], }) export class GraphQLModule { }
步骤2:登录成功后主动触发WebSocket重连
在你处理登录逻辑的组件中,注入WEBSOCKET_LINK,登录成功存储完token后,关闭现有WebSocket连接,因为你已经开启了reconnect: true,连接会自动重建,重建时会执行connectionParams拿到最新的token:
// 登录组件相关代码 import { WEBSOCKET_LINK } from '你存放GraphQLModule的路径'; import { WebSocketLink } from '@apollo/client/link/ws'; constructor(@Inject(WEBSOCKET_LINK) private wsLink: WebSocketLink) {} // 登录成功后的回调方法 handleLoginSuccess(encryptedToken: string) { // 先把新token存到localStorage localStorage.setItem('token', encryptedToken); // 关闭现有WebSocket连接,触发自动重连,重连时会携带新token this.wsLink.subscriptionClient.close(); }
如果登录前没有触发过任何订阅,lazy模式下WebSocket连接还未建立,不需要执行上述关闭逻辑,首次触发订阅时会自动读取最新的token。
内容的提问来源于stack exchange,提问作者erick martinez
相关产品推荐
相关产品推荐

