Basically I was able to simplify the code and use almost unmodified code copied from what I implemented for site wide chat by associating a room with a completely new instance of WebSocket.Server. Normally I would have had an object in memory tracking rooms and managed associating different socket connections. Not that that's hard but it would have been modifying more code and more new code to test.
The way I implemented it it is a lot like Apple's iMessage. The room id actually comes from a salted hash of the user list. So if you make a new chat with the same users you will see your prior messages.
Things I still want to work on:
Making it so you can add people to an existing chat. Making it so you can vote people out of a chat. Showing who is in the chat even if they haven't written anything yet. Sending a notice to other users that you started a chat with a link. Possibly showing you a link to the private chat in the global chat if you are on the list.
Of course there is some conflicting logic in some of those. For example kicking someone from a chat, if the room id is based on the people present, they could in theory just reload a chat with the exact same people. But I suppose the way around that is to associate the messages with the new id after you kick them. The way it would work when you add or kick someone is the messages will get associated with a new id and the page will reload into the new id. That is not implemented yet but that is the plan.
Here is the forum: https://matrix.gvid.tv. Test it out.