기업협업 프로젝트를 진행중 알림기능이 있어 알림기능을 구현중 DB를쓰는거보단 실시간 통신을위한 소켓을 쓰는게 나아보여 알아보다가
GraphQL Subscription
이란게 있어 적용시켜보려 했다
기존 GraphQL로 들어오는 Context구조가 (Req,Res)로 만들어둔 상태라서 Req로 들어온 JWT토큰 사용했고 만약 최초 로그인을하면 Res에 setHeader를 통해 리프레쉬 토큰을 넣어주는 형태였다
근데 Subscription
을 쓰는 레퍼런스 코드를 보니 죄다
context: ({ req, connection }) => {
const TOKEN_KEY = 'x-jwt';
// http
if (req) {
return { token: req.headers[TOKEN_KEY] }; // 헤더에 있는 토큰만 쓸거니 헤더 중 토큰만 솎아서 보내자
}
// ws. connection은 ws 연결시 딱 한 번만 반환됨
if (connection) {
return { token: connection.context[TOKEN_KEY] }; // connection.context 에 토큰이 들어 있다
}
},
이런 형태로 만들어진거다.. 그래서 우리 프로젝트에는 이런 형태로쓰면 setHeader가불가능한디.. 이러면서 찾고 찾다가
darren
님의 블로그 내용중
매 요청마다 들어오는 req와는 달리 connection은 ws 연결되는 시점 1번만 출력된다.
라는 내용이있어 혹시 이부분을 GraphQL 모듈단에서 처리못하나? 하고 구글링을해서 찾아낸걸 적용해보니 Context부분을 해결했다!
subscriptions: {
'subscriptions-transport-ws': {
onConnect: (headersRaw: Record<string, unknown>) => {
console.log(headersRaw);
const headers = Object.keys(headersRaw).reduce((dest, key) => {
dest[key.toLowerCase()] = headersRaw[key];
console.log(dest);
return dest;
}, {});
return {
req: {
headers: headers,
},
};
},
},
},
onConnect
를 통해 연결시에 들어온 Header를 읽어 유저정보를 구분할수있게 만들었다
아래의 API는 예시용으로 만든 API입니다.
import { Global, Module } from '@nestjs/common';
import { PubSub } from 'graphql-subscriptions';
@Global()
@Module({
providers: [{ provide: 'PUB_SUB', useValue: new PubSub() }],
exports: ['PUB_SUB'],
})
export class pubsubModule {}
Subscription
을 할수있는 GQL API를 만들었다Inject 부분
@Resolver()
export class CourseReviewResolver {
constructor(
private readonly courseReviewService: CourseReviewService,
@Inject('PUB_SUB')
private readonly pubSub: PubSub,
) {}
API 부분
@Mutation(() => CourseReview)
async createCourseReview(
@Args('createCourseReviewInput')
createCourseReviewInput: CreateCourseReviewInput,
) {
const createdResult = await this.courseReviewService.create(
createCourseReviewInput,
);
const pub: CourseReviewPubSub = {
reviewId: createdResult.id,
userId: createdResult.user.userId,
courseTitle: createdResult.course.title,
};
this.pubSub.publish('reviewAdded', { reviewAdded: pub });
return createdResult;
}
강좌에대한 리뷰를 만들게되면 reviewAdded
라는 이름으로 소켓통신을 시작하고 reviewAdded: pub
의 내용을 보내게된다
@UseGuards(GqlAuthAccessGuard)
@Subscription(() => CourseReviewPubSub, {
filter: (result, _, context) => {
return result.reviewAdded.userId === context.req.user.userId;
},
})
reviewAdded() {
return this.pubSub.asyncIterator('reviewAdded');
}
그러면 asyncIterator
를통해 reviewAdded
라는 이름의 소켓통신이 들어오게되면 pub이라는 객체가 들어오는데 위의
@UseGuards(GqlAuthAccessGuard)
@Subscription(() => CourseReviewPubSub, {
filter: (result, _, context) => {
return result.reviewAdded.userId === context.req.user.userId;
},
})
부분에서 현재 유저
와 리뷰글을 작성한 유저
가 동일할때만 알람이 오게 설정을 해서 메x플의 확성기마냥 모든 알람이 들어오지 않도록 필터링을 거치게한다.
위의 예시 API를 만든후
댓글알람,좋아요알람,맨션알람등 다른곳에 적용해 실사용할 예정이다