create-fuzionx 0.1.34 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/templates/spa/meta.json
CHANGED
|
@@ -1,7 +1,138 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { WsHandler } from '@fuzionx/framework';
|
|
2
|
+
|
|
3
|
+
/** 채팅 WebSocket 핸들러 */
|
|
4
|
+
export default class ChatHandler extends WsHandler {
|
|
5
|
+
static namespace = '/chat';
|
|
6
|
+
static middleware = ['auth'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* disconnect 시 metadata가 이미 삭제되므로,
|
|
10
|
+
* setUser 시점에 이름을 캐시하여 disconnect에서 사용.
|
|
11
|
+
* 연결/해제는 항상 같은 워커에서 발생하므로 per-worker 캐시로 충분.
|
|
12
|
+
*/
|
|
13
|
+
static _nameCache = new Map();
|
|
14
|
+
|
|
15
|
+
static events(e) {
|
|
16
|
+
e.on('message', ChatHandler.prototype.handleMessage);
|
|
17
|
+
e.on('broadcast', ChatHandler.prototype.handleBroadcast);
|
|
18
|
+
e.on('typing', ChatHandler.prototype.handleTyping);
|
|
19
|
+
e.on('userlist', ChatHandler.prototype.handleUserList);
|
|
20
|
+
e.on('setUser', ChatHandler.prototype.handleSetUser);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ── helpers ── */
|
|
24
|
+
|
|
25
|
+
/** 사용자 이름 조회 (캐시 → metadata → UUID 폴백) */
|
|
26
|
+
_getName(socket, sid) {
|
|
27
|
+
return ChatHandler._nameCache.get(sid)
|
|
28
|
+
|| socket.getMetadataFor(sid, 'name')
|
|
29
|
+
|| sid;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 단일 사용자 정보 빌드 */
|
|
33
|
+
_buildUserInfo(socket, sid) {
|
|
34
|
+
return {
|
|
35
|
+
sid,
|
|
36
|
+
name: this._getName(socket, sid),
|
|
37
|
+
email: socket.getMetadataFor(sid, 'email') || '',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 전체 접속자 목록 빌드 */
|
|
42
|
+
_buildUserList(socket) {
|
|
43
|
+
const sessions = socket.sessionIds;
|
|
44
|
+
return {
|
|
45
|
+
count: socket.onlineCount,
|
|
46
|
+
users: sessions.map(sid => this._buildUserInfo(socket, sid)),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ── lifecycle ── */
|
|
51
|
+
|
|
52
|
+
async onConnect(socket) {
|
|
53
|
+
const sid = socket.sessionId;
|
|
54
|
+
console.log(`[Chat] 연결: ${sid}`);
|
|
55
|
+
|
|
56
|
+
socket.send(JSON.stringify({
|
|
57
|
+
type: 'chat_ready',
|
|
58
|
+
data: { sessionId: sid, timestamp: Date.now() },
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async onDisconnect(socket, code, reason) {
|
|
63
|
+
const sid = socket.sessionId;
|
|
64
|
+
const name = ChatHandler._nameCache.get(sid);
|
|
65
|
+
ChatHandler._nameCache.delete(sid);
|
|
66
|
+
|
|
67
|
+
// setUser 미완료 세션은 user_joined도 안 보냈으므로 user_left 불필요
|
|
68
|
+
if (!name) return;
|
|
69
|
+
|
|
70
|
+
console.log(`[Chat] 해제: ${name} code=${code ?? 'N/A'}`);
|
|
71
|
+
socket.broadcastExcluding(JSON.stringify({
|
|
72
|
+
type: 'user_left',
|
|
73
|
+
data: { sid, name, timestamp: Date.now() },
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ── event handlers ── */
|
|
78
|
+
|
|
79
|
+
/** setUser — 사용자 정보 등록 + userlist 응답 */
|
|
80
|
+
async handleSetUser(socket, data) {
|
|
81
|
+
const sid = socket.sessionId;
|
|
82
|
+
const name = (data.name || '').trim() || sid;
|
|
83
|
+
|
|
84
|
+
// Bridge metadata + 로컬 캐시 동시 저장
|
|
85
|
+
socket.setMetadata('name', name);
|
|
86
|
+
if (data.email) socket.setMetadata('email', data.email);
|
|
87
|
+
if (data.id) socket.setMetadata('userId', String(data.id));
|
|
88
|
+
ChatHandler._nameCache.set(sid, name);
|
|
89
|
+
console.log(`[Chat] 사용자 등록: ${sid} → ${name}`);
|
|
90
|
+
|
|
91
|
+
// 다른 사용자에게 참여 알림
|
|
92
|
+
socket.broadcastExcluding(JSON.stringify({
|
|
93
|
+
type: 'user_joined',
|
|
94
|
+
data: this._buildUserInfo(socket, sid),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
// 본인에게 전체 목록 응답
|
|
98
|
+
return {
|
|
99
|
+
type: 'userlist',
|
|
100
|
+
data: this._buildUserList(socket),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** chat_msg — 본인 제외 전체 전송 */
|
|
105
|
+
async handleMessage(socket, data) {
|
|
106
|
+
const name = this._getName(socket, socket.sessionId);
|
|
107
|
+
socket.broadcastExcluding(JSON.stringify({
|
|
108
|
+
type: 'chat_msg',
|
|
109
|
+
data: { user: name, text: data.text || data, timestamp: Date.now() },
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** broadcast — 공지 */
|
|
114
|
+
async handleBroadcast(socket, data) {
|
|
115
|
+
const name = this._getName(socket, socket.sessionId);
|
|
116
|
+
socket.broadcastExcluding(JSON.stringify({
|
|
117
|
+
type: 'broadcast',
|
|
118
|
+
data: { user: name, text: data.text || data, timestamp: Date.now() },
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** typing — 타이핑 표시 */
|
|
123
|
+
async handleTyping(socket) {
|
|
124
|
+
const name = this._getName(socket, socket.sessionId);
|
|
125
|
+
socket.broadcastExcluding(JSON.stringify({
|
|
126
|
+
type: 'typing',
|
|
127
|
+
data: { user: name },
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** userlist — 목록 요청 */
|
|
132
|
+
async handleUserList(socket) {
|
|
133
|
+
return {
|
|
134
|
+
type: 'userlist',
|
|
135
|
+
data: this._buildUserList(socket),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|