Sunday, August 4, 2013

Online Steam Group Chat App

I've just finished off another project, so it's time for another blog post. This time my main goal was to get some experience with AngularJS, something I have been reading a lot about which seems like a pretty cool bit of tech. When I was trying to think of something to make, I remembered that in the early days of my Steam chat bot, I made a little website that would allow people to send a message to the chat room through the bot. It was just a bit of fun at the time but I thought I could expand on that and make a web interface into a group chat room, something that Steam has not yet provided for some reason, even though you can do user-to-user chat through their site. This would also give me an opportunity to use Socket.IO, something I'd used previously but not really well.

This took me a bit longer than I was expecting for a "simple AngularJS project" but at the end of the project I've learnt a lot and I've made a nice website that at least a few people should find useful. It looks and acts pretty much exactly like the Steam client which I'm pretty happy about, and thanks to Foundation it handles mobile/tablet resolutions quite nicely. I've put the code up over at GitHub, it should be fairly easy to set up for any Steam group so go and have a look if you're interested. Here is a blurry view of what it ended up looking like on the desktop (if the screen gets small enough, the chat member section gets hidden and you can click a button to switch between the chat view and the member view):

Instead of going into implementation details I'll just quickly go through the different technologies I used and explain them one by one.

The first thing I should mention is that I again made use of the excellent node-steam node library, which is what allowed me to mirror the chat messages and send messages to the chat room. I had considered using my chat bot, but in the end I really didn't need any of the features it provided and I decided to just use the vanilla library. There isn't much to go into for my usage of the library, I basically just used the provided functions to get and send messages, and to find out when user statuses changed so that I could update the list of users in the chat room.

For client-side code I used AngularJS, which as I mentioned was the main reason for starting work on this project. I was very impressed with Angular, whenever anything changed on the Steam side (a new message, a user joined or left, or a user's state changed), all I needed to do was update my model and the UI was magically updated. I also really liked the separation of controller/filter/directive/services. Although I didn't really make use of any services (except a pre-made Socket.IO one), it was great to be able to make a filter that built a URL for a Steam profile image given a set of hex numbers, or a directive that represented the display of a users information, and have that completely separated out from my main app code so I could just concentrate on the task at hand. I feel like I still have a lot to learn about Angular, and I'm pretty sure a lot of what I did wasn't the "correct" Angular way of doing things, but even just scratching the surface of what Angular has to offer has been very interesting.

For server-side code I used the express framework, which I've used for a few different projects before. Since I was working on a single-page app without too much back-end logic (besides things I'll mention separately), there isn't too much to go into with my usage of express. One thing I'll say about the server-side implementation in general is that I really should have used TypeScript. The reason I didn't was because I wanted to use plain JavaScript for the front-end, and I thought it might get a bit confusing to switch between the two. It was good to get a bit more experience with JavaScript, but I really feel like some sort of type checking is crucial to coding, and TypeScript would have been a good safety net to use.

The communication between server and client was handled mainly by Socket.IO. When someone visits the site, they have a socket set up for them which goes through some authentication. To be able to chat they need to be signed in through Steam, so the Socket.IO authentication has access to the user's Steam ID. I use this to make sure that the user should be able to see the chat room, and if they are I set up a bunch of socket listeners so that I can notify the client of any chat room changes, or other state changes such as notifications that the chat has been muted or that the proxy user has disconnected for some reason. One thing I noticed was that a lot of this code was just boilerplate, since I had a model of sorts on the server side and an Angular model on the client side, and most of the Socket.IO code was used to synchronize these two models. I was going to try to think of a way to build some sort of system that would automatically listen for any changes on the server side and send that down to the client side via sockets, but it was looking like it was going to take too much time. Now that I've finished the project I think I may revisit that, as it seems like something that would be very useful for anyone making an Angular app that needs to be notified of state changes from the server side.

For the website layout I decided to use Foundation. I was originally going to use Bootstrap, but I wasn't going to use any of their styles and I had heard that Foundation was better as a "clean slate" solution. Also admittedly I just read about Foundation and it looked like something new and interesting. In the end I'm pretty happy with the way it worked out, it is a bit simpler than Bootstrap and it had a few features that came in very handy, particularly Top Bar which handled my menu needs really well, and the Reveal modal dialog which was great to use as a state messaging system since I needed something that would block the normal use of the site and provide a message in a very obvious way. One thing I wasn't quite able to work out was a non-hacky way of providing a mobile and full-screen interface to the chat room. For normal website usage I wanted the chat room and member details to be shown side-by-side and for mobile use I wanted the user to be able to switch between the two by pressing a button. In the end I solved this by having two completely separate divs, one that is shown for desktop use and one that is shown for mobile use. This wasn't too bad since I made use of Angular scope and directives to separate out code that would otherwise have been duplicated, but I could tell that there must be a better way of doing it. Of course, this isn't a problem with Foundation, just my lack of experience with it.

One final thing I'll mention is the authentication solution I came up with. This is one thing that went through a few different iterations but I think the solution I ended up with works really well. The first part of authentication was simple enough, Steam provides an Open ID login library in their Web API, so all I needed to do was use an Open ID node library and I could get access to a user's Steam ID without any risks. The second part of authentication was trying to figure out if a particular Steam ID should be able to see the chat room. I had originally thought of having a whitelist, so a user would have to request access and I'd have to go in look at the user's profile page and approve if it looked OK, but I decided that was too much work. After this I thought I'd just make it a rule that a user would have to be in the chat room the first time they used the site, and that would automatically add them to the whitelist so they'd be able to log in without being in chat from then on, but that seemed a bit too confusing for users. After a bit more digging around in Steam's web API, I found out that they provided a way to see the groups a particular steam ID belongs to, so I decided to just use that. Any time a user logs in, I check to make sure they are part of the group, and allow them access if they are. This is completely transparent to the users and it keeps out randoms from the site. The only problem is that someone can be kicked/banned from the chat room and they'd still be able to use the site, but that rarely if ever happens in the steam group I'm using the site for so it's not a big concern.

So that's another project and rambling blog post done. Hope either of them come in handy for someone else at some point!


  1. Replies
    1. Yeah it's a bit of a stream of thought more than an actual instructional post. It's also hard to set up unless you already know about everything I mentioned. Hopefuly Valve will actually make a real solution soon!