Skip to content

Instantly share code, notes, and snippets.

@Sephi-Chan
Last active December 15, 2015 19:29
Show Gist options
  • Save Sephi-Chan/5312077 to your computer and use it in GitHub Desktop.
Save Sephi-Chan/5312077 to your computer and use it in GitHub Desktop.
Chat server with Erlang.
-module(presence_server).
-behaviour(gen_server).
-export([ start_link/0 ]).
-export([ player_is_online/1, player_is_offline/1, online_players/0 ]).
-export([ init/1, handle_cast/2, handle_call/3, terminate/2, handle_info/2, code_change/3 ]).
start_link() ->
gen_server:start_link({ local, ?MODULE }, ?MODULE, [], []).
player_is_online(PlayerId) ->
gen_server:cast(?MODULE, { player_is_online, PlayerId }).
player_is_offline(PlayerId) ->
gen_server:cast(?MODULE, { player_is_offline, PlayerId }).
online_players() ->
gen_server:call(?MODULE, { online_players }).
init([]) ->
State = dict:new(),
{ ok, State }.
handle_cast({ player_is_online, PlayerId }, State) ->
NewState = dict:store(PlayerId, online, State),
{ noreply, NewState };
handle_cast({ player_is_offline, PlayerId }, State) ->
NewState = dict:erase(PlayerId, State),
{ noreply, NewState }.
handle_call({ online_players }, _From, State) ->
Players = dict:fetch_keys(State),
{ reply, Players, State }.
terminate(_Reason, _State) ->
ok.
handle_info(_Info, State) ->
{ noreply, State }.
code_change(_OldVersion, State, _Extra) ->
{ ok, State }.
-module(seelies_server_websocket_handler).
-behaviour(cowboy_websocket_handler).
-export([ init/3, websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3 ]).
-record(connection, { id, name = none }).
-include("../include/constants.hrl").
init({ tcp, http }, _Request, _Options) ->
{ upgrade, protocol, cowboy_websocket }.
websocket_init(_TransportName, Request, _Options) ->
State = #connection{ id = erlang:make_ref() },
{ ok, Request, State }.
websocket_handle({ text, Message }, Request, State) ->
{ _Type, _Reply, NewState } = parseAction(mochijson3:decode(Message), State),
{ ok, Request, NewState }.
websocket_info({ push_json, Json }, Request, State) ->
{ reply, { text, Json }, Request, State };
websocket_info(_Info, Request, State) ->
{ ok, Request, State }.
websocket_terminate(_Reason, _Request, State) ->
case Name = State#connection.name of
none ->
ok;
_ ->
presence_server:player_is_offline(Name),
push_json({ p, l, { room } }, [ ?PLAYER_LEAVE, Name ]),
ok
end.
parseAction([ ?IDENTIFY, Name ], State) ->
case gproc:lookup_local_name({ connection, Name }) of
undefined ->
gproc:add_local_name({ connection, Name }),
gproc:add_local_property({ room }, Name),
presence_server:player_is_online(Name),
push_json([ ?CONNECTED, Name, presence_server:online_players() ]),
push_json({ p, l, { room } }, [ ?PLAYER_JOIN, Name ]),
{ json, ok, State#connection{ name = Name } };
_ ->
push_json([ ?NAME_TAKEN ]),
{ json, ok, State }
end;
parseAction([ ?SEND_MESSAGE, Message ], State) ->
push_json({ p, l, { room } }, [ ?MESSAGE_RECEIVED, Message, State#connection.name ]),
{ json, ok, State };
parseAction(_Message, State) ->
{ error, none, State }.
push_json(Content) ->
self() ! { push_json, mochijson3:encode(Content) }.
push_json(GprocKey, Content) ->
gproc:send(GprocKey, { push_json, mochijson3:encode(Content) }).
-record(seelies_players, { id, name, password }).
% Actions.
-define(IDENTIFY, 0).
-define(SEND_MESSAGE, 1).
% Responses.
-define(NAME_TAKEN, 0).
-define(CONNECTED, 1).
-define(PLAYER_JOIN, 2).
-define(MESSAGE_RECEIVED, 3).
-define(PLAYER_LEAVE, 4).
var socket = new WebSocket("ws://app.dev:7788");
var actions = { IDENTIFY: 0, SEND_MESSAGE: 1 };
var responses = { NAME_TAKEN: 0, CONNECTED: 1, JOIN: 2, MESSAGE_RECEIVED: 3, LEAVE: 4 };
var callbacks = {};
var me = null;
var $body = $('body');
var $register = $body.find('#register');
var $nameForm = $register.find('form');
var $nameInput = $nameForm.find('input');
var $nameTaken = $nameForm.find('.name_taken');
var $nameBlank = $nameForm.find('.name_blank');
var $room = $body.find('#room');
var $players = $room.find('#players');
var $messages = $room.find('#messages');
var $messageForm = $room.find('form');
var $messageInput = $messageForm.find('input');
$nameForm.on('submit', function(event) {
event.preventDefault();
var name = $nameInput.val();
if (name.trim() == '') {
$nameBlank.removeClass('hidden');
}
else {
var message = [ actions.IDENTIFY, $nameInput.val() ];
socket.send(JSON.stringify(message));
$nameBlank.addClass('hidden');
}
});
$messageForm.on('submit', function(event) {
event.preventDefault();
var text = $messageInput.val().trim()
if (text != '') {
var message = [ actions.SEND_MESSAGE, text ];
socket.send(JSON.stringify(message));
$messageInput.val('');
}
});
callbacks[responses.NAME_TAKEN] = function(data) {
$nameTaken.removeClass("hidden");
};
callbacks[responses.CONNECTED] = function(data) {
me = data[1];
var otherPlayers = data[2];
$nameTaken.removeClass("hidden");
$nameTaken.addClass("hidden");
$register.addClass("hidden");
$room.removeClass("hidden");
$messageInput.focus();
_(otherPlayers).each(function(name) {
if (name == me) {
$('<li><strong>' + name + '</strong></li>').appendTo($players);
}
else {
$('<li>' + name + '</li>').appendTo($players);
}
});
};
callbacks[responses.JOIN] = function(data) {
var name = data[1];
if (name != me) { $('<li></li>', { text: name }).appendTo($players); }
$('<li>' + name + ' joined the room.</li>').prependTo($messages);
};
callbacks[responses.MESSAGE_RECEIVED] = function(data) {
var message = data[1];
var author = data[2];
$('<li><strong>' + author + '</strong> <span>' + message + '</span></li>').prependTo($messages);
};
callbacks[responses.LEAVE] = function(data) {
var name = data[1];
$players.find('li').each(function() {
$player = $(this);
if ($player.text() == name) { $player.remove(); }
});
$('<li>' + name + ' left the room.</li>').prependTo($messages);
};
socket.onmessage = function(event) {
var data = JSON.parse(event.data);
var responseType = data[0];
callbacks[responseType](data);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment