Ably.io의 코드 예시 활용

오현택·2021년 7월 13일
0
post-thumbnail

전제 조건

채팅 기능 부분의 TypeORM Entity가 Rooms, 그리고 Messages로 구분되어있어야한다.
Entity 코드는 다음과 같다.

  • Messages Entity
import { Column, Entity, ManyToOne } from 'typeorm';
import { TypeOrmEntity } from '../common/typeorm.entity';
import { User } from '../users/user.entity';
import { Room } from '../rooms/room.entity';

@Entity()
export class Message extends TypeOrmEntity {
  @Column()
  message: string;

  @Column()
  userId: number;

  @Column()
  roomId: string;

  @ManyToOne(() => Room, (room) => room.messages)
  room: Room;

  @ManyToOne(() => User, (user) => user.messages)
  user: User;
}
  • Rooms Entity
import {
  Column,
  CreateDateColumn,
  Entity,
  ManyToOne,
  OneToMany,
  PrimaryGeneratedColumn,
  Unique,
  UpdateDateColumn,
} from 'typeorm';
import { Message } from '../messages/message.entity';
import { User } from '../users/user.entity';

@Entity()
@Unique(['ownerId', 'joinerId'])
export class Room {
  @PrimaryGeneratedColumn('uuid') // RoomId를 int형으로 사용할 경우, 보안적인 이슈가 발생할 수 있으므로 uuid를 활용합니다.
  id: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @Column()
  ownerId: number;

  @Column()
  joinerId: number;

  @OneToMany(() => Message, (message) => message.room)
  messages: Message[];

  @ManyToOne(() => User, (user) => user.rooms)
  owner: User;

  @ManyToOne(() => User, (joiner) => joiner)
  joiner: User;
}

Ably.io Publish 부분
백엔드에 아래의 코드를 넣습니다.

  • messages.services.ts
  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);
  }
  • rooms.controller.ts
@Controller('/rooms')
export class RoomsController {
  constructor(
      private readonly service: RoomsService,
      private readonly messageService: MessagesService,
    ) {}
  ..... 중략 ......
  @Post(':id/message')
    createMessage(
      @CurrentUser() user,
      @Param('id') roomId: string,
      @Body() { message },
    ) {
      return this.messageService.createMessage(user.id, roomId, message);
    }
 ...... 중략 ......
  }

Ably.io Subscribe 부분
프론트엔드의 ChatPage.tsx혹은 채팅 부분이 담긴 컴포넌트에 아래의 코드를 넣습니다.

const client = new Ably.Realtime(process.env.REACT_APP_ABLY_KEY ?? ''); // 요 부분

export const ChatPage = () => {
 ...... 중략 ......
  const { data: room } = useQuery<any>(`/rooms/${id}`, fetcher);
  const channel = client.channels.get(room?.id); // 요 부분
  const { data: messages } = useQuery<any>(`/rooms/${id}/messages`, fetcher);
  const { data: messages, refetch: refetchMessage } = useQuery<any>(
    `/rooms/${id}/messages`,
    fetcher,
  );
  const createMessage = async (message: string) => {
    return await api.post(`/rooms/${room?.id}/message`, { message });
  };
  // 여기서부터
  useEffect(() => {
    async function subscribe() {
      await channel.subscribe(() => {
        refetchMessage();
      });
    }
    subscribe();
    
    return function cleanup() {
      channel.unsubscribe(); // unsubscribe()안 넣을 시 ably와 연동이 끊기지않아 비용 발생이 증가합니다.
    };
  });
  // 여기 까지
...... 중략 ......
}

위의 코드를 활용하면 Ably.io에 나와있는 코드 예시를 활용한 채팅 기능의 '구현'은 끝이 난다.

'구현'이 끝났다고 했지, 끝났다고 한적 없다.

Ably 코드 예시 방법은 해당 사이트 docs에서 보면 1.1 Basic Authentication에 해당한다.
Basic Authentication의 방법을 사용할 시, 프론트엔드에서 Ably.io에서 발급받은 ABLY_KEY를 도난당할 위험성이 있으므로 JWTABLY_KEY 기반으로 발급받은 토큰을 포함하는 2.3(a) Ably-compatible token is embedded in a External JWT from your server and passed to clients 방식을 사용할 것이다.
해당 방식에 대한 내용은 다음 포스트에 이어서 하겠다.

profile
Hi, DevLog on 8:20

0개의 댓글