具有动态标头授权的 Angular Apollo WebSocketLink
Posted
技术标签:
【中文标题】具有动态标头授权的 Angular Apollo WebSocketLink【英文标题】:Angular Apollo WebSocketLink with dynamic header authorization 【发布时间】:2021-09-07 22:54:59 【问题描述】:我正在使用 graphql 订阅;最初,标头有一个空的授权令牌,登录后在本地存储中生成一个令牌变量。我想要做的是在登录后自动更新订阅标头中的令牌变量,我已经按照文档中的说明进行了尝试,但是令牌永远不会更新并且它总是将其发送为空。
我需要 WebSocketLink 中的标头在 connectionParams 中是动态的
这是我的 GraphQLModule 文件,希望有人能帮助我...
import NgModule from '@angular/core';
import ApolloClientOptions, InMemoryCache, split ,ApolloLink from '@apollo/client/core';
import APOLLO_OPTIONS from 'apollo-angular';
import HttpLink from 'apollo-angular/http';
import WebSocketLink from '@apollo/client/link/ws';
import getMainDefinition from '@apollo/client/utilities';
import environment from 'environments/environment';
import * as CryptoJS from 'crypto-js';
import setContext from '@apollo/client/link/context';
import onError from '@apollo/client/link/error';
import Swal from 'sweetalert2';
import CoreTranslationService from '@core/services/translation.service';
import locale as en from 'app/main/pages/i18n/en';
import locale as es from 'app/main/pages/i18n/es';
const uri = environment.apiUrl; // <-- endpoint1 gql
const urisub = environment.apiSubs;// <-- endpoint2 gql
function operationFilter(operationName:string):boolean
if(operationName!="checkToken") return true;
else return false; //and the others
export function createApollo(httpLink: HttpLink,_coreTranslationService: CoreTranslationService): ApolloClientOptions<any>
_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;
);
const ws = 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` : "",
,
);
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,
//http
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',
//errorPolicy: 'all',
,
query:
fetchPolicy: 'network-only',
,
mutate:
,
;
@NgModule(
providers: [
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink, CoreTranslationService],
,
],
)
export class GraphQLModule
【问题讨论】:
您找到解决方案了吗?我也面临同样的问题,我无法继续。 @SureN 是的,登录后我立即关闭订阅,服务会重新连接并发送新标头,我不知道这是否是最佳选择,但它对我有用。 谢谢,我没有用模块实现套接字,而是作为服务实现,我能够解决。 【参考方案1】:我分享了我的解决方案,我不知道它是否是最佳选择,但它在客户端对我有用:
graphql.module.ts
import NgModule from '@angular/core';
import ApolloClientOptions, InMemoryCache, split ,ApolloLink, Operation, makeVar from '@apollo/client/core';
import Apollo, APOLLO_OPTIONS from 'apollo-angular';
import HttpLink from 'apollo-angular/http';
import WebSocketLink from '@apollo/client/link/ws';
import getMainDefinition from '@apollo/client/utilities';
import environment from 'environments/environment';
import * as CryptoJS from 'crypto-js';
import setContext from '@apollo/client/link/context';
import onError from '@apollo/client/link/error';
import Swal from 'sweetalert2';
import CoreTranslationService from '@core/services/translation.service';
import locale as en from 'app/main/pages/i18n/en';
import locale as es from 'app/main/pages/i18n/es';
import SubscriptionClient from 'subscriptions-transport-ws';
import HttpClientModule from '@angular/common/http';
import HttpLinkModule from 'apollo-angular-link-http';
const uri = environment.apiUrl;
const urisub = environment.apiSubs;
function operationFilter(operationName:string):boolean
if(operationName!="checkToken") return true;
else return false;
@NgModule(
exports: [
HttpClientModule,
HttpLinkModule
]
)
export class GraphQLModule
public Clientws: any;
public subscriptionClient: SubscriptionClient = null;
constructor(apollo: Apollo, httpLink: HttpLink,_coreTranslationService: CoreTranslationService)
_coreTranslationService.translate(en, es);
const getIdToken = () => localStorage.getItem('token') || null;
const auth = setContext((operation, context) =>
return
headers:
Authorization: `Bearer $getIdToken()`
;
);
const http = httpLink.create(
uri(operation)
return operationFilter(operation.operationName)? uri : urisub;
);
const wsClient = new SubscriptionClient(`ws://localhost:3005/subscriptions`,
reconnect: true,
connectionParams: async () =>
return
Authorization:`Bearer $getIdToken()`,
,
)
this.Clientws = wsClient
const ws = new WebSocketLink(wsClient)
this.subscriptionClient = (<any>ws).subscriptionClient;
const error = onError((networkError, graphQLErrors) =>
if (graphQLErrors && getIdToken()!=null && getIdToken()!='')
graphQLErrors.map((
message,
locations,
path,
extensions
) =>
if (extensions)
if (extensions.exception.status==401 && getIdToken()!=null && getIdToken()!='')
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 basic = setContext((operation, context) => (
headers:
Accept: 'charset=utf-8'
));
const link =ApolloLink.from([cleanTypeName, basic, error, _split]);
apollo.create(
link:link,
cache: new InMemoryCache(
addTypename: false,
),
defaultOptions:
watchQuery:
fetchPolicy: 'network-only',
//errorPolicy: 'all',
,
query:
fetchPolicy: 'network-only',
,
mutate:
,
);
login.component.ts
constructor(
.....,
private gqlModule: GraphQLModule,
.....
)
.......
onSubmit(event) //submit login form
......
//if service login response true
this.gqlModule.Clientws.close(false,false); // this closes the websocket and automatically it reconnects with the new information in the header
//Note .. If you do not have the reconnection in true, you need to connect again manually
......
在服务器端:
配置订阅选项
onConnect: (connectionParams, webSocket, context) =>
if (connectionParams['Authorization'] != null && connectionParams['Authorization'] != '')
if (connectionParams['Authorization'].split(" ")[1]!= null && connectionParams['Authorization'].split(" ")[1]!= '' && connectionParams['Authorization'].split(" ")[1]!= 'null')
return authorization: connectionParams['Authorization'] ;
,
【讨论】:
以上是关于具有动态标头授权的 Angular Apollo WebSocketLink的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Express 和 Apollo-Server 获取 HTTP 授权标头
如何在 React 组件中向 Apollo 客户端添加新的授权标头?