You need to enable JavaScript to run this app. Angular Apollo WebSocketLink订阅动态授权头登录后不更新问题求解
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.15 01:27:04