Ably JWT를 활용한 코드 활용 예시

Ably JWT Auth 방식
서비스에 로그인 할 때, 백엔드에서 ABLY_KEY를 통해 Ably.io에 requestToken을 하게됩니다. (위의 그림에서 1, 2의 단계에 해당)
리턴된 ably token을 payload에 {"x-ably-token" : 리턴된 ably 토큰 값}의 형식으로 담아 JWT 토큰을 발급하여 유저에게 리턴합니다. 유저는 이때 리턴된 토큰 값을 바탕으로 ably platform과 통신합니다.(위의 그림에 순서대로 3, 4, 5 단계에 해당)

Code 예시

백엔드

  • auth.service.ts
import { ConflictException, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { hash } from 'bcryptjs';
import { User } from 'src/users/user.entity';
import { UsersService } from 'src/users/users.service';

@Injectable()
export class AuthService {
  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
  ) {}
...... 중략 ......
  async login(userId: number) {
    const user = await this.usersService.findById(userId);
    const Ably = require('ably');
    var ably = new Ably.Rest.Promise({ key: process.env.ABLY_SECRET_KEY });
    const token = await ably.auth.requestToken({
      clientId: user.email,
    });
    const payload = { sub: userId, 'x-ably-token': token.token };
    return { token: this.jwtService.sign(payload) };
  }

  async reAbly() {
    const Ably = require('ably');
    var ably = new Ably.Rest.Promise({ key: process.env.ABLY_SECRET_KEY });
    const token = await ably.auth.requestToken();
    return token;
  }
}
  • auth.controller.ts
@Public()
@Get('/re-create')
  async reCreate() {
    return this.service.reAbly();
  } /// 해당 코드를 Auth Controller안에 추가
  • messages.service.ts( 이 부분은 앞전의 포스트와 동일합니다.)
import { Injectable } from '@nestjs/common';
import { TypeOrmService } from '../common/typeorm.service';
import { Message } from './message.entity';

@Injectable()
export class MessagesService extends TypeOrmService(Message) {
 ..... 중략 ......
  async createMessage(userId: number, roomId: string, message: string) {
    this.publishMessage(userId, roomId, message);
    return this.repository.save({ roomId, userId, message });
  }

  publishMessage(userId: number, roomId: string, chat: string) {
    const Ably = require('ably');
    const ably = new Ably.Rest(process.env.ABLY_SECRET_KEY);
    const channel = ably.channels.get(roomId);
    channel.publish(userId.toString(), chat);
  }
}

프론트엔드

  • ChatPage.tsx 혹은 채팅창 컴포넌트 or 페이지
import Ably from 'ably/promises';
import { Types } from 'ably';

export const ChatPage = () => {
  const { id } = useParams<{ id: string }>();
  const [client, setClient] = useState<Ably.Realtime>();
  const [channel, setChannel] = useState<Types.RealtimeChannelPromise>();
  const [chat, setChat] = useState('');
  ...... 중략 ......
  useEffect(() => {
    if (client) return;
    const _client = new Ably.Realtime({
      token: localStorage.getItem('token') ?? '',
      authUrl: `${process.env.REACT_APP_API_URL}auth/re-create`,
      transports: ['web_socket'],
    });
    setClient(_client);
  }, [client]);

  useEffect(() => {
    if (!client || channel || !room) return;
    const _channel = client.channels.get(room.id);
    _channel.subscribe(() => refetchMessage());
    setChannel(_channel);
    return () => _channel.unsubscribe();
  }, [client, channel, room]);

  const createMessage = async (message: string) => {
    return await api.post(`/rooms/${room?.id}/message`, { message });
  };
 ...... 중략 ...... 
 };

Realtime()의 옵션에 대해서 간략하게 설명해보면
token: localStorage.getItem('token') ?? ' '부분은 발급받은 토큰을 불러오고 없을시 ' '로 처리하는 옵션
authUrl: ${process.env.REACT_APP_API_URL}auth/re-create은 ably 토큰이 만료될 시, 재발급 받는 옵션
transports: ['web_socket']ably-transport-preference을 web_socket으로 고정시키는 옵션이다.

위와 같이 코드를 넣으면 Ably token 방식으로 ably 채팅 서비스가 작동하는 것을 확인할 수 있다.

마치면서

Ably.io 라는 리얼타임 서비스를 이번에 PCUP 서비스를 만들면서 처음 써보았는데 한국어로 된 기술 블로그도 없고, 공식 문서가 생략해버린 부분이 꽤 있었지만, 이러한 새로운 서비스를 활용해보는 경험 또한 값진 것 같다. 추후 채팅을 제외한 iot, 혹은 GPS, Live-Update 등을 활용할 기회가 있다면 이번 시리즈에 추가할 예정이다.

좋은 웹페이지 즐겨찾기