Builds for
1 pipeline
passed
in
8 minutes 3 seconds
Merge branch 'develop' of http://khuhub.khu.ac.kr/2020105578/nodejs-game into develop
Showing
11 changed files
with
70 additions
and
41 deletions
... | @@ -14,7 +14,8 @@ | ... | @@ -14,7 +14,8 @@ |
14 | 14 | ||
15 | ## 로그인 | 15 | ## 로그인 |
16 | 16 | ||
17 | -서버에 `login`을 보냅니다. 아직 로그인 로직이 구현되지 않았으므로, 임의로 사용할 `username`만을 포함하여 보내면 됩니다. | 17 | +서버에 `login`을 보냅니다. 본인이 사용하고 싶은 닉네임을 `nickname`속성에 담아 보내면 됩니다. 닉네임은 공백이 허용되지 않고 최대 12글자입니다. 만약 이 조건을 만족하지 않는다면 요청에 실패하고 사유가 전달됩니다. 요청에 성공한다면 문자열이 반환되는데, 이는 본인의 고유한 `uuid`값입니다. 앞으로 사용되는 모든 `username` 속성에 이 값을 사용해주세요. **`username`은 유저의 이름이 아니라 고유한 `uuid`를 의미하고, `nickname`은 고유하지 않을 수 있습니다!** |
18 | +상대방의 닉네임은 방 접속시에 얻을 수 있습니다. 오직 UI에 보여주기 위한 용도로만 사용되며, 실제 처리는 모두 `username`을 통해 이뤄집니다. | ||
18 | 19 | ||
19 | ## 로비 | 20 | ## 로비 |
20 | 21 | ||
... | @@ -24,6 +25,8 @@ | ... | @@ -24,6 +25,8 @@ |
24 | 25 | ||
25 | 새로운 방을 만들고 싶다면 `createRoom`을 보내면 됩니다. 방의 이름을 `name` 속성에 담아 보내야 합니다. 방의 이름은 공백일 수 없고, 최대 30자까지 가능합니다. 방을 만드는데 성공했다면 방에 자동으로 접속되고 방장을 맡게 됩니다. 이때 반환값은 `joinRoom`과 동일한 `RoomInfo`를 받습니다. | 26 | 새로운 방을 만들고 싶다면 `createRoom`을 보내면 됩니다. 방의 이름을 `name` 속성에 담아 보내야 합니다. 방의 이름은 공백일 수 없고, 최대 30자까지 가능합니다. 방을 만드는데 성공했다면 방에 자동으로 접속되고 방장을 맡게 됩니다. 이때 반환값은 `joinRoom`과 동일한 `RoomInfo`를 받습니다. |
26 | 27 | ||
28 | +`RoomInfo`에는 각 유저의 닉네임을 `nickname` 속성으로 담고 있습니다. | ||
29 | + | ||
27 | ### 방 | 30 | ### 방 |
28 | 31 | ||
29 | 방에 접속중인 유저의 목록에 변화가 생기면, `updateRoomUser`가 수신됩니다. `state`는 다음 3가지 값 중 하나의 값을 가집니다. | 32 | 방에 접속중인 유저의 목록에 변화가 생기면, `updateRoomUser`가 수신됩니다. `state`는 다음 3가지 값 중 하나의 값을 가집니다. |
... | @@ -32,6 +35,8 @@ | ... | @@ -32,6 +35,8 @@ |
32 | - `updated`: 기존 유저의 정보가 업데이트되었습니다. 해당 유저의 방장, 준비 여부가 변경되면 수신됩니다. | 35 | - `updated`: 기존 유저의 정보가 업데이트되었습니다. 해당 유저의 방장, 준비 여부가 변경되면 수신됩니다. |
33 | - `removed`: 유저가 방에서 퇴장하였습니다. | 36 | - `removed`: 유저가 방에서 퇴장하였습니다. |
34 | 37 | ||
38 | +해당 메세지에는 변화된 유저를 특정하기 위한 `username`이 포함되어 있고, 그 유저의 닉네임인 `nickname`, 방장 여부인 `admin`, 준비 여부인 `ready`를 포함하고 있습니다. | ||
39 | + | ||
35 | 유저가 채팅을 입력했다면 `chat`을 보내면 됩니다. 다른 사람이 채팅을 입력한 경우에도 `chat`이 수신됩니다(`ServerInboundMessage<"chat">`과 `ServerOutboundMessage<"chat">`이 다르게 정의되었다는 점에 유의하세요). 자신이 보낸 채팅에 대해서는 수신되지 않으므로 이 경우 오프라인으로 메세지를 추가하여야 합니다. 채팅 문자열의 양 끝 공백을 제거했을 때 문자열이 빈 문자열이거나, 채팅 문자열의 길이가 300을 초과하는 경우 요청이 실패 처리됩니다. | 40 | 유저가 채팅을 입력했다면 `chat`을 보내면 됩니다. 다른 사람이 채팅을 입력한 경우에도 `chat`이 수신됩니다(`ServerInboundMessage<"chat">`과 `ServerOutboundMessage<"chat">`이 다르게 정의되었다는 점에 유의하세요). 자신이 보낸 채팅에 대해서는 수신되지 않으므로 이 경우 오프라인으로 메세지를 추가하여야 합니다. 채팅 문자열의 양 끝 공백을 제거했을 때 문자열이 빈 문자열이거나, 채팅 문자열의 길이가 300을 초과하는 경우 요청이 실패 처리됩니다. |
36 | 41 | ||
37 | 방을 나가고 싶으면 `leaveRoom`을 보내면 됩니다. | 42 | 방을 나가고 싶으면 `leaveRoom`을 보내면 됩니다. | ... | ... |
... | @@ -38,6 +38,7 @@ export const RoomInfoRecord = Record({ | ... | @@ -38,6 +38,7 @@ export const RoomInfoRecord = Record({ |
38 | users: Array( | 38 | users: Array( |
39 | Record({ | 39 | Record({ |
40 | username: String, | 40 | username: String, |
41 | + nickname: String, | ||
41 | admin: Boolean, | 42 | admin: Boolean, |
42 | ready: Boolean, | 43 | ready: Boolean, |
43 | }) | 44 | }) | ... | ... |
... | @@ -22,7 +22,7 @@ import { | ... | @@ -22,7 +22,7 @@ import { |
22 | // 'result' 속성은 서버 요청 결과에만 포함되는 특별한 속성입니다. | 22 | // 'result' 속성은 서버 요청 결과에만 포함되는 특별한 속성입니다. |
23 | export class ServerInboundMessageRecordMap { | 23 | export class ServerInboundMessageRecordMap { |
24 | // 로그인을 시도합니다. | 24 | // 로그인을 시도합니다. |
25 | - login = Record({ username: String }); | 25 | + login = Record({ nickname: String, result: String }); |
26 | 26 | ||
27 | // 방 목록을 요청합니다. | 27 | // 방 목록을 요청합니다. |
28 | roomList = Record({ | 28 | roomList = Record({ |
... | @@ -94,6 +94,7 @@ interface ServerOutboundMessageMap { | ... | @@ -94,6 +94,7 @@ interface ServerOutboundMessageMap { |
94 | state: "added" | "updated" | "removed"; | 94 | state: "added" | "updated" | "removed"; |
95 | user: { | 95 | user: { |
96 | username: string; | 96 | username: string; |
97 | + nickname: string; | ||
97 | admin: boolean; | 98 | admin: boolean; |
98 | ready: boolean; | 99 | ready: boolean; |
99 | }; | 100 | }; | ... | ... |
... | @@ -77,10 +77,17 @@ export class Connection { | ... | @@ -77,10 +77,17 @@ export class Connection { |
77 | private handleLogin( | 77 | private handleLogin( |
78 | message: ServerInboundMessage<"login"> | 78 | message: ServerInboundMessage<"login"> |
79 | ): ServerResponse<"login"> { | 79 | ): ServerResponse<"login"> { |
80 | - this.user = new User(message.username, this); | 80 | + if (message.nickname.length > 12) { |
81 | + return { ok: false, reason: "닉네임은 최대 12글자입니다." }; | ||
82 | + } | ||
83 | + if (message.nickname.trim().length === 0) { | ||
84 | + return { ok: false, reason: "닉네임을 공백으로 설정할 수 없습니다." }; | ||
85 | + } | ||
86 | + | ||
87 | + this.user = new User(message.nickname, this); | ||
81 | // console.log(`User ${message.username} has logged in!`); | 88 | // console.log(`User ${message.username} has logged in!`); |
82 | 89 | ||
83 | - return { ok: true }; | 90 | + return { ok: true, result: this.user.username }; |
84 | } | 91 | } |
85 | 92 | ||
86 | public handleDisconnect(): void { | 93 | public handleDisconnect(): void { | ... | ... |
... | @@ -106,6 +106,7 @@ export class Room { | ... | @@ -106,6 +106,7 @@ export class Room { |
106 | state: "added", | 106 | state: "added", |
107 | user: { | 107 | user: { |
108 | username: user.username, | 108 | username: user.username, |
109 | + nickname: user.nickname, | ||
109 | admin: user === this.admin, | 110 | admin: user === this.admin, |
110 | ready: this.usersReady.includes(user), | 111 | ready: this.usersReady.includes(user), |
111 | }, | 112 | }, |
... | @@ -128,6 +129,7 @@ export class Room { | ... | @@ -128,6 +129,7 @@ export class Room { |
128 | state: "removed", | 129 | state: "removed", |
129 | user: { | 130 | user: { |
130 | username: user.username, | 131 | username: user.username, |
132 | + nickname: user.nickname, | ||
131 | admin: user === this.admin, | 133 | admin: user === this.admin, |
132 | ready: this.usersReady.includes(user), | 134 | ready: this.usersReady.includes(user), |
133 | }, | 135 | }, |
... | @@ -221,6 +223,7 @@ export class Room { | ... | @@ -221,6 +223,7 @@ export class Room { |
221 | state: "updated", | 223 | state: "updated", |
222 | user: { | 224 | user: { |
223 | username: user.username, | 225 | username: user.username, |
226 | + nickname: user.nickname, | ||
224 | admin: this.isAdmin(user), | 227 | admin: this.isAdmin(user), |
225 | ready: this.isReady(user), | 228 | ready: this.isReady(user), |
226 | }, | 229 | }, |
... | @@ -244,6 +247,7 @@ export class Room { | ... | @@ -244,6 +247,7 @@ export class Room { |
244 | users: this.users.map((u) => { | 247 | users: this.users.map((u) => { |
245 | return { | 248 | return { |
246 | username: u.username, | 249 | username: u.username, |
250 | + nickname: u.nickname, | ||
247 | admin: this.isAdmin(u), | 251 | admin: this.isAdmin(u), |
248 | ready: this.usersReady.includes(u), | 252 | ready: this.usersReady.includes(u), |
249 | }; | 253 | }; | ... | ... |
... | @@ -24,16 +24,14 @@ describe("방장", () => { | ... | @@ -24,16 +24,14 @@ describe("방장", () => { |
24 | const response = socket2.test("joinRoom", { uuid: room.uuid }); | 24 | const response = socket2.test("joinRoom", { uuid: room.uuid }); |
25 | expect(response.ok).eq(true); | 25 | expect(response.ok).eq(true); |
26 | expect(room.admin).eq(user1); | 26 | expect(room.admin).eq(user1); |
27 | - expect(response.result?.users[0]).deep.eq({ | 27 | + |
28 | - username: user1.username, | 28 | + expect(response.result?.users[0].nickname).eq(user1.nickname); |
29 | - admin: true, | 29 | + expect(response.result?.users[0].admin).eq(true); |
30 | - ready: false, | 30 | + expect(response.result?.users[0].ready).eq(false); |
31 | - }); | 31 | + |
32 | - expect(response.result?.users[1]).deep.eq({ | 32 | + expect(response.result?.users[1].nickname).eq(user2.nickname); |
33 | - username: user2.username, | 33 | + expect(response.result?.users[1].admin).eq(false); |
34 | - admin: false, | 34 | + expect(response.result?.users[1].ready).eq(false); |
35 | - ready: false, | ||
36 | - }); | ||
37 | }); | 35 | }); |
38 | it("방장이 나가면 방장이 인계됩니다", () => { | 36 | it("방장이 나가면 방장이 인계됩니다", () => { |
39 | const { | 37 | const { | ... | ... |
... | @@ -14,6 +14,7 @@ describe("방 입장", () => { | ... | @@ -14,6 +14,7 @@ describe("방 입장", () => { |
14 | it("방에 입장합니다", () => { | 14 | it("방에 입장합니다", () => { |
15 | const { | 15 | const { |
16 | sockets: [socket], | 16 | sockets: [socket], |
17 | + users: [user], | ||
17 | rooms: [room], | 18 | rooms: [room], |
18 | } = prepareUsersEmptyRooms(1, 1); | 19 | } = prepareUsersEmptyRooms(1, 1); |
19 | 20 | ||
... | @@ -23,16 +24,17 @@ describe("방 입장", () => { | ... | @@ -23,16 +24,17 @@ describe("방 입장", () => { |
23 | expect(response.result?.name).eq(room.name); | 24 | expect(response.result?.name).eq(room.name); |
24 | expect(response.result?.maxUsers).eq(room.maxUsers); | 25 | expect(response.result?.maxUsers).eq(room.maxUsers); |
25 | expect(response.result?.users?.length).eq(1); | 26 | expect(response.result?.users?.length).eq(1); |
26 | - expect(response.result?.users[0]?.username).eq("guest1"); | 27 | + expect(response.result?.users[0]?.nickname).eq(user.nickname); |
27 | }); | 28 | }); |
28 | it("방에 입장하면 유저 목록이 업데이트 됩니다", () => { | 29 | it("방에 입장하면 유저 목록이 업데이트 됩니다", () => { |
29 | const { | 30 | const { |
30 | sockets: [socket1, socket2], | 31 | sockets: [socket1, socket2], |
32 | + users: [user1, user2], | ||
31 | } = prepareJoinedRoom(2); | 33 | } = prepareJoinedRoom(2); |
32 | 34 | ||
33 | const updated = socket1.socket.received("updateRoomUser"); | 35 | const updated = socket1.socket.received("updateRoomUser"); |
34 | expect(updated.state).eq("added"); | 36 | expect(updated.state).eq("added"); |
35 | - expect(updated.user.username).eq("guest2"); | 37 | + expect(updated.user.nickname).eq(user2.nickname); |
36 | }); | 38 | }); |
37 | it("방에 이미 입장한 상태에서 다른 방에 입장할 수 없습니다", () => { | 39 | it("방에 이미 입장한 상태에서 다른 방에 입장할 수 없습니다", () => { |
38 | const { | 40 | const { | ... | ... |
... | @@ -3,10 +3,23 @@ import { RoomManager } from "../room/RoomManager"; | ... | @@ -3,10 +3,23 @@ import { RoomManager } from "../room/RoomManager"; |
3 | import { SocketTester } from "./util/SocketTester"; | 3 | import { SocketTester } from "./util/SocketTester"; |
4 | 4 | ||
5 | describe("로그인", () => { | 5 | describe("로그인", () => { |
6 | - it("로그인합니다", () => { | 6 | + it("닉네임으로 로그인합니다", () => { |
7 | const roomManager = new RoomManager(); | 7 | const roomManager = new RoomManager(); |
8 | const socket = new SocketTester(roomManager); | 8 | const socket = new SocketTester(roomManager); |
9 | - socket.testOk("login", { username: "guest" }); | 9 | + const response = socket.test("login", { nickname: "guest" }); |
10 | - expect(socket.connection.user?.username).eq("guest"); | 10 | + expect(response.result !== undefined).eq(true); |
11 | + expect(socket.connection.user?.nickname).eq("guest"); | ||
12 | + }); | ||
13 | + it("빈 닉네임으로 로그인할 수 없습니다", () => { | ||
14 | + const roomManager = new RoomManager(); | ||
15 | + const socket = new SocketTester(roomManager); | ||
16 | + socket.testNotOk("login", { nickname: "" }); | ||
17 | + socket.testNotOk("login", { nickname: " " }); | ||
18 | + }); | ||
19 | + it("닉네임은 최대 12자입니다", () => { | ||
20 | + const roomManager = new RoomManager(); | ||
21 | + const socket = new SocketTester(roomManager); | ||
22 | + socket.testNotOk("login", { nickname: "a".repeat(13) }); | ||
23 | + socket.testNotOk("login", { nickname: "가".repeat(13) }); | ||
11 | }); | 24 | }); |
12 | }); | 25 | }); | ... | ... |
... | @@ -7,12 +7,13 @@ describe("준비", () => { | ... | @@ -7,12 +7,13 @@ describe("준비", () => { |
7 | it("방에 입장하면 준비되지 않은 상태입니다", () => { | 7 | it("방에 입장하면 준비되지 않은 상태입니다", () => { |
8 | const { | 8 | const { |
9 | sockets: [socket], | 9 | sockets: [socket], |
10 | + users: [user], | ||
10 | rooms: [room], | 11 | rooms: [room], |
11 | } = prepareUsersEmptyRooms(1, 1); | 12 | } = prepareUsersEmptyRooms(1, 1); |
12 | 13 | ||
13 | const response = socket.test("joinRoom", { uuid: room.uuid }); | 14 | const response = socket.test("joinRoom", { uuid: room.uuid }); |
14 | expect(response.ok).eq(true); | 15 | expect(response.ok).eq(true); |
15 | - expect(response.result?.users[0]?.username).eq("guest1"); | 16 | + expect(response.result?.users[0]?.nickname).eq(user.nickname); |
16 | expect(response.result?.users[0]?.ready).eq(false); | 17 | expect(response.result?.users[0]?.ready).eq(false); |
17 | }); | 18 | }); |
18 | it("준비 상태를 설정합니다", () => { | 19 | it("준비 상태를 설정합니다", () => { |
... | @@ -26,25 +27,19 @@ describe("준비", () => { | ... | @@ -26,25 +27,19 @@ describe("준비", () => { |
26 | 27 | ||
27 | socket.testOk("ready", { ready: true }); | 28 | socket.testOk("ready", { ready: true }); |
28 | expect(room.isReady(user)).eq(true); | 29 | expect(room.isReady(user)).eq(true); |
29 | - expect(socket.socket.received("updateRoomUser")).deep.eq({ | 30 | + |
30 | - state: "updated", | 31 | + let response = socket.socket.received("updateRoomUser"); |
31 | - user: { | 32 | + expect(response.state).eq("updated"); |
32 | - username: user.username, | 33 | + expect(response.user.nickname).eq(user.nickname); |
33 | - admin: false, | 34 | + expect(response.user.ready).eq(true); |
34 | - ready: true, | ||
35 | - }, | ||
36 | - }); | ||
37 | 35 | ||
38 | socket.testOk("ready", { ready: false }); | 36 | socket.testOk("ready", { ready: false }); |
39 | expect(room.isReady(user)).eq(false); | 37 | expect(room.isReady(user)).eq(false); |
40 | - expect(socket.socket.received("updateRoomUser")).deep.eq({ | 38 | + |
41 | - state: "updated", | 39 | + response = socket.socket.received("updateRoomUser"); |
42 | - user: { | 40 | + expect(response.state).eq("updated"); |
43 | - username: user.username, | 41 | + expect(response.user.nickname).eq(user.nickname); |
44 | - admin: false, | 42 | + expect(response.user.ready).eq(false); |
45 | - ready: false, | ||
46 | - }, | ||
47 | - }); | ||
48 | }); | 43 | }); |
49 | it("방장은 준비할 수 없습니다", () => { | 44 | it("방장은 준비할 수 없습니다", () => { |
50 | const { | 45 | const { | ... | ... |
... | @@ -49,8 +49,8 @@ export class SocketTester { | ... | @@ -49,8 +49,8 @@ export class SocketTester { |
49 | return this.connection.handleRaw(obj); | 49 | return this.connection.handleRaw(obj); |
50 | } | 50 | } |
51 | 51 | ||
52 | - public login(username: string): void { | 52 | + public login(nickname: string): void { |
53 | - this.testOk("login", { username }); | 53 | + this.testOk("login", { nickname }); |
54 | expect(this.connection.user !== undefined).eq(true); | 54 | expect(this.connection.user !== undefined).eq(true); |
55 | } | 55 | } |
56 | 56 | ... | ... |
... | @@ -3,9 +3,11 @@ import { Connection } from "../connection/Connection"; | ... | @@ -3,9 +3,11 @@ import { Connection } from "../connection/Connection"; |
3 | import { MessageHandler } from "../message/MessageHandler"; | 3 | import { MessageHandler } from "../message/MessageHandler"; |
4 | import { Room } from "../room/Room"; | 4 | import { Room } from "../room/Room"; |
5 | import { RoomManager } from "../room/RoomManager"; | 5 | import { RoomManager } from "../room/RoomManager"; |
6 | +import { v4 as uuidv4 } from "uuid"; | ||
6 | 7 | ||
7 | export class User { | 8 | export class User { |
8 | - public readonly username: string; | 9 | + public readonly username: string; // TODO: 실제 역할은 uuid임 |
10 | + public readonly nickname: string; | ||
9 | 11 | ||
10 | public readonly connection: Connection; | 12 | public readonly connection: Connection; |
11 | 13 | ||
... | @@ -13,8 +15,9 @@ export class User { | ... | @@ -13,8 +15,9 @@ export class User { |
13 | 15 | ||
14 | public handler: MessageHandler; | 16 | public handler: MessageHandler; |
15 | 17 | ||
16 | - constructor(username: string, connection: Connection) { | 18 | + constructor(nickname: string, connection: Connection) { |
17 | - this.username = username; | 19 | + this.username = uuidv4(); |
20 | + this.nickname = nickname; | ||
18 | this.connection = connection; | 21 | this.connection = connection; |
19 | this.handler = new MessageHandler({ | 22 | this.handler = new MessageHandler({ |
20 | roomList: (user, message) => { | 23 | roomList: (user, message) => { | ... | ... |
-
Please register or login to post a comment