이전 포스트에서 올린 서버에서 WebSocket 요청을 처리하는 부분에 이어서 이번에는 "패러디 웨이브" 서비스를 제공하는 부분이다. 정리가 제대로 안되서 코드가 보기에 좀 너접하지만, 이것 역시 참고 자료 삼아서 올린다.

ParodyWave.js
var ParodyWave = {
    totalcount: 0,
    clients: [],
    status: function () {
        var response = {type: 'status',
            connections: ParodyWave.clients.length};

        return JSON.stringify(response);
    },
    list: function () {
        var response = {type: 'list'}
        response.threads = ParodyWave.threads.list();

        return JSON.stringify(response);
    },
    newThread: function (title, conn) {
        var response = {type: 'join'};
        if (conn.activity.thread) {
            response = {type: 'error', text: 'already joined.'}
        } else {
            response.thread = conn.activity.thread = ParodyWave.threads.add(title);
        }
        return JSON.stringify(response);
    },
    joinThread: function (id, conn) {
        var response = {type: 'join'};
        if (conn.activity.thread) {
            response = {type: 'error', text: 'already joined.'}
        } else if (ParodyWave.threads.join(id)) {
            response.thread = conn.activity.thread = id;
            response.title = ParodyWave.threads._list[id].title;
            response.data = ParodyWave.threads._list[id]._data;
        } else {
            response = {type: 'warning', text: 'cannot joined'};
        }
        return JSON.stringify(response);
    },
    exit: function (msg, conn) {
        ParodyWave.threads.exit(msg.id);
        conn.activity.thread = null;
    },
    newLine: function (conn) {
        var l = ParodyWave.threads._list[conn.activity.thread].append(conn.activity.id, conn.activity.user);
        conn.activity.line = l;
        return JSON.stringify({type: "update", id: conn.activity.id, line: l, user: conn.activity.user});
    },
    update: function (v, conn) {
        ParodyWave.threads._list[conn.activity.thread].update(conn.activity.line, v);
        return JSON.stringify({type: "update", id: conn.activity.id, line: conn.activity.line, value: v});
    },
    submit: function (conn) {
        conn.activity.line = null;
    },
    notify: function (msg, conn, area) {
        for (var i = 0 ; i < ParodyWave.clients.length ; i ++) {
            var cond;
            switch (area) {
                case -1: // all, exclude same thread
                    cond = (conn.activity.thread !== ParodyWave.clients[i].activity.thread);
                    break;
                case 1: // same thread
                    cond = (conn.activity.thread === ParodyWave.clients[i].activity.thread);
                    break;
                case -2: // all, not in a thread
                    cond = (ParodyWave.clients[i].activity.thread === null);
                    break;
                case 2: // all in a thread
                    cond = (ParodyWave.clients[i].activity.thread !== null);
                    break;
                default:    // all, exclude me
                    cond = (conn !== ParodyWave.clients[i]);
                    break;
            }
            if (cond) ParodyWave.clients[i].write(msg);
        }
    }
};
ParodyWave.threads = {
    _list:[],
    count: 0,
    add: function (title) {
        var th = {}
        th.title = title;
        th.count = 1;
        th._data = [];
        th.append = function (id, user) {
            return th._data.push([id, user, ""]) - 1;
        }
        th.update = function (line, value) {
            th._data[line][2] = value;
        }
        th.get = function (line) {
            return th._data[line][2];
        }
        ParodyWave.threads.count ++;
        return ParodyWave.threads._list.push(th) - 1;
    },
    join: function (id) {
        if (ParodyWave.threads._list[id]) {
            ParodyWave.threads._list[id].count ++;
            return true;
        } else {
            return false;
        }
    },
    exit: function (id) {
        if (ParodyWave.threads._list[id] && -- ParodyWave.threads._list[id].count <= 0) {
            ParodyWave.threads._list[id] = null;
            ParodyWave.threads.count --;
        }
        ParodyWave.notify(ParodyWave.list());
    },
    list: function () {
        var l = [];
        for (var i = 0 ; i < ParodyWave.threads._list.length ; i++) {
            if (ParodyWave.threads._list[i] == null) continue;
            l.push([i, ParodyWave.threads._list[i].title, ParodyWave.threads._list[i].count]);
        }
        return l;
    }
};

exports.service = function(socket) {
    socket.on("open", onOpen);
    socket.on("message", onMessage);
    socket.on("close", onClose);
    socket.on("error", onError);

    function onOpen() {
        socket.activity = {id: null, user: null, thread: null, line: null};
        ParodyWave.clients.push(socket);
        socket.activity.id = ++ParodyWave.totalcount;
        ParodyWave.notify(ParodyWave.status(), socket);
    }

    function onMessage(data) {
        var msg;
        try {
            msg = JSON.parse(data);
        } catch (e) {
            if (data != 'HB') { // HeartBeat
                console.log(e);
                console.log("DATA: " + data);
            }
            return;
        }
        console.log(msg);   // Debug
        switch (msg.cmd) {
            case "user":
                break;
            case "status":
                socket.write(ParodyWave.status(msg));
                socket.write(ParodyWave.list());
                break;
            case "open":
                socket.activity.user = msg.user;
                socket.write(ParodyWave.newThread(msg.title, socket));
                ParodyWave.notify(ParodyWave.list(), socket, -2);
                break;
            case "join":
                socket.activity.user = msg.user;
                socket.write(ParodyWave.joinThread(msg.id, socket));
                ParodyWave.notify(ParodyWave.list(), socket, -2);
                break;
            case "exit":
                ParodyWave.exit(msg, socket);
                break;
            case "new":
                ParodyWave.notify(ParodyWave.newLine(socket), socket, 1);
                if (msg.value) ParodyWave.notify(ParodyWave.update(msg.value, socket), socket, 1);
                break;
            case "update":
                ParodyWave.notify(ParodyWave.update(msg.value, socket), socket, 1);
                break;
            case "submit":
                if (msg.value) ParodyWave.notify(ParodyWave.update(msg.value, socket), socket, 1);
                ParodyWave.submit(socket);
                break;
        }
    }

    function onClose() {
        for (var i = 0 ; i < ParodyWave.clients.length ; i ++) {
            if (socket === ParodyWave.clients[i]) {
                ParodyWave.clients.splice(i, 1);
            }
        }
        var idx = socket.activity.thread;
        ParodyWave.threads.exit(idx);
        ParodyWave.notify(ParodyWave.status(), socket);
    }

    function onError(exception) {
        console.log("ERROR: " + exception);
        for (var i = 0 ; i < ParodyWave.clients.length ; i ++) {
            if (socket === ParodyWave.clients[i]) {
                ParodyWave.clients.splice(i, 1);
            }
        }
    }
};

124~127줄의 부분을 보면 open, message, close, error라는 네가지의 이벤트에 대한 핸들러를 지정하는 부분이 있다. 원래 Node.js에서 WebSocket 접속이 이루어진다고 해도 HTML5 표준에 있는 WebSocket API에 정의된 이런 이벤트가 발생하지는 않지만, 서비스 구현시의 편리를 위해서 클라이언트와 비슷하게 임의로 저 이벤트를 발생하도록 WebSocket 요청 처리부분에서 구현했다. 따라서, 이전 포스트의 프로그램을 사용한다면, 서비스 부분에서는 HTML5에서 사용하는 것처럼 저 네가지 이벤트와 write(), end() 등의 메소드를 사용하면 된다.

+ Recent posts