graphql订阅中未定义NestJS上下文



有人能帮我吗?为什么我的订阅中没有定义上下文?

@Subscription(returns => CommentsDto, {
filter: (payload, variables, context) => {
console.log({ payload, variables, context })  // <------------ context context undefined
const isSameCode = variables.code === payload.newComment.code
const isAuthorized = context.req.headers.clientauthorization === payload.clientauthorization
return isSameCode && isAuthorized
},
})
newComment(
@Context() context,  
@Args(({ name: 'code', type: () => String })) code: string,
) {
console.log(context) // <------------ undefined 
return this.publisherService.asyncIterator('newComment')
}

它正在为Queries和Mutatinos工作。。。

Graphql的定义是:

const GraphQLDefinition = GraphQLModule.forRoot({
context: ({ req, connection }) => {
// subscriptions
if (connection) { 
return { req: connection.context }
}
// queries and mutations
return { req }
},
installSubscriptionHandlers: true,
path: '/graphql',
playground: true,
})

感谢您的帮助

因为在订阅的情况下Req和Res是未定义的,所以当您尝试记录上下文时,它是未定义。

为了使上下文可用,您需要更改用于返回上下文的保护,该上下文可以在连接变量中找到。基本总结:

  • =>req,在http/query&突变
  • =>webSockets/订阅中使用的连接

现在要正确获取上下文,您必须准确执行以下步骤:

  1. 修改应用程序模块文件以使用GraphqlModuleImport
  2. 修改提取用户防护和身份验证防护(或您正在使用的任何防护(以返回查询/突变和订阅情况的数据
  3. 使用订阅中的上下文接收数据
  4. 在Auth服务中添加jwtTokenPayload提取器函数
  5. Opitonal:Typescript的Helper函数和DTO

1-详细信息:

GraphQLModule.forRootAsync({
//import AuthModule for JWT headers at graphql subscriptions
imports: [AuthModule],
//inject Auth Service
inject: [AuthService],
useFactory: async (authService: AuthService) => ({
debug: true,
playground: true,
installSubscriptionHandlers: true,
// pass the original req and res object into the graphql context,
// get context with decorator `@Context() { req, res, payload, connection }: GqlContext`
// req, res used in http/query&mutations, connection used in webSockets/subscriptions
context: ({ req, res, payload, connection }: GqlContext) => ({
req,
res,
payload,
connection,
}),
// subscriptions/webSockets authentication
typePaths: ["./**/*.graphql"],
resolvers: { ...resolvers },
subscriptions: {
// get headers
onConnect: (connectionParams: ConnectionParams) => {
// convert header keys to lowercase
const connectionParamsLowerKeys: Object = mapKeysToLowerCase(
connectionParams,
);
// get authToken from authorization header
let authToken: string | false = false;
const val = connectionParamsLowerKeys["authorization"];
if (val != null && typeof val === "string") {
authToken = val.split(" ")[1];
}
if (authToken) {
// verify authToken/getJwtPayLoad
const jwtPayload: JwtPayload = authService.getJwtPayLoad(
authToken,
);
// the user/jwtPayload object found will be available as context.currentUser/jwtPayload in your GraphQL resolvers
return {
currentUser: jwtPayload.username,
jwtPayload,
headers: connectionParamsLowerKeys,
};
}
throw new AuthenticationError("authToken must be provided");
},
},
definitions: {
path: join(process.cwd(), "src/graphql.classes.ts"),
outputAs: "class",
},
}),
}),

2-细节:我的getRequest函数示例来自ExtractUserGuard类,它扩展了AuthGuard(jwt(类。

更改自:

getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().req;
return request;}

到此:


getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
// req used in http queries and mutations, connection is used in websocket subscription connections, check AppModule
const { req, connection } = ctx.getContext();
// if subscriptions/webSockets, let it pass headers from connection.context to passport-jwt
const requestData =
connection && connection.context && connection.context.headers
? connection.context
: req;
return requestData;
}

3-现在您可以在解析器中获取这些数据。

@Subscription("testSubscription")
@UseGuards(ExtractUserGuard)
async testSubscription(
@Context("connection") connection: any,
): Promise<JSONObject> {
const subTopic = `${Subscriptions_Test_Event}.${connection.context.jwtPayload.email}`;
console.log("Listening to the event:", subTopic);
return this.pubSub.asyncIterator(subTopic);
}

4-为了使用令牌获取jwtPayload,请将以下函数添加到您的AuthService中。

getJwtPayLoad(token: string): JwtPayload {
const jwtPayload = this.jwtService.decode(token);
return jwtPayload as JwtPayload;
}

5-帮助函数和DTO示例(我在项目中使用过(

DTO:

export interface JwtPayload {
username?: string;
expiration?: Date;
}
export interface GqlContext {
req: Request;
res: Response;
payload?: JwtPayload;
// required for subscription
connection: any;
}
export interface ConnectionParams {
authorization: string;
}

助手功能:

export function mapKeysToLowerCase(
inputObject: Record<string, any>,
): Record<string, any> {
let key;
const keys = Object.keys(inputObject);
let n = keys.length;
const newobj: Record<string, any> = {};
while (n--) {
key = keys[n];
newobj[key.toLowerCase()] = inputObject[key];
}
return newobj;
}

最新更新