Skip to content

Instantly share code, notes, and snippets.

@hraban
Last active August 29, 2015 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hraban/10933487 to your computer and use it in GitHub Desktop.
Save hraban/10933487 to your computer and use it in GitHub Desktop.
"use strict";
// appended odd string to avoid shadowing existing vars
function chatroomwidget_88ed71a($) {
if ($ === undefined) {
throw "chatroomwidget.js requires jQuery";
}
// TODO: get from query string
if (chatroomwidgetServerUrl === undefined) {
throw "chatroomwidgetServerUrl must be defined";
}
function isScrolledToBottom(el) {
return el.scrollHeight === (el.offsetHeight + el.scrollTop);
}
function scrollToBottom(el) {
el.scrollTop = el.scrollHeight;
}
var name;
var id = "chatroomwidget_88ed71a";
var ws;
var $input = $("<form style='flex-shrink: 0'>")
.append($("<input id="+id+"input style='width: 100%'>").click(function (e) {
// don't hide
return false;
})
).submit(function (e) {
e.preventDefault();
var node = document.getElementById(id+"input");
ws.send("<" + name + "> " + node.value);
node.value = "";
});
function toMini(node) {
$(node)
.addClass("mini")
.css({
width: "100px",
height: "100px",
"font-size": '6pt',
})
.find('> :not(#'+id+'messages)')
.hide();
$('#'+id+'messages').css('overflow', 'hidden');
}
function toMaxi(node) {
$(node)
.removeClass("mini")
.css({
width: "300px",
height: "500px",
"font-size": '12pt',
})
.find('> :not(#'+id+'messages)')
.show();
scrollToBottom($('#'+id+'messages').css({
"overflow-y": "auto",
"overflow-x": "hidden",
})[0]);
if (name === undefined) {
var $banner = $("<div><p>What nickname do you want?<p></div>")
.css({
"background": "white",
"text-align": "center",
"top": 0,
"height": "100%",
"position": "absolute",
"width": "100%",
}).click(function () {
return false;
}).append($('<form><input>').submit(function (e) {
e.preventDefault();
name = $(this).find('input')[0].value;
$banner.remove();
$('#'+id).find('input')[0].focus();
})).appendTo(node);
}
}
var $node = $('<div id=' + id + '>')
.append($("<div id="+id+"messages>").css({
'white-space': 'pre-wrap',
})
).append($input)
.css({
position: "absolute",
right: "50px",
top: "50px",
border: "solid thin black",
"background-color": "white",
"z-index": 1000,
display: "flex",
"flex-direction": "column",
"justify-content": "flex-end",
}).click(function(e) {
e.preventDefault();
if ($(this).hasClass("mini")) {
toMaxi(this);
} else {
toMini(this);
}
}).appendTo('body');
toMini($node[0]);
var ws = new WebSocket(chatroomwidgetServerUrl);
ws.onopen = function onopen() {
};
ws.onmessage = function onmessage(evt) {
var el = document.getElementById(id+'messages');
var atbottom = isScrolledToBottom(el);
$('<div>').text(evt.data).appendTo(el);
if (atbottom || $('#'+id).hasClass("mini")) {
scrollToBottom(el);
}
};
}
if (typeof define === "function" && define.amd) {
define(["jquery"], chatroomwidget_88ed71a);
} else {
chatroomwidget_88ed71a(jQuery);
}
// Copyright © 2013 Hraban Luyat <hraban@0brg.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
package main
import (
"flag"
"fmt"
"log"
"net/http"
"sync"
"sync/atomic"
"github.com/gorilla/websocket"
"github.com/hraban/lrucache"
)
// in num msgs not bytes
var backlog_size int
// https://soundcloud.com/testa-jp/mask-on-mask-re-edit-free-dl
var backlog *lrucache.Cache
var clients struct {
c map[uint32]*websocket.Conn
l sync.RWMutex
}
var lowest uint32
var numclients uint32
var connectedclients int32
var nummessages uint32
var verbose bool
func inc(i *uint32) uint32 {
return atomic.AddUint32(i, 1)
}
func extendBacklog(msg []byte) {
msgid := inc(&nummessages)
if verbose {
}
backlog.Set(fmt.Sprint(msgid), msg)
}
func sendToClient(id uint32, c *websocket.Conn, msg []byte) error {
err := c.WriteMessage(websocket.TextMessage, msg)
if err != nil {
clients.l.Lock()
delete(clients.c, id)
clients.l.Unlock()
if verbose {
atomic.AddInt32(&connectedclients, -1)
log.Printf("Client count -1: %d\n", connectedclients)
}
return err
}
return nil
}
func ld(i *uint32) uint32 {
return atomic.LoadUint32(i)
}
func handler(w http.ResponseWriter, r *http.Request) {
ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
if _, ok := err.(websocket.HandshakeError); ok {
http.Error(w, "Not a websocket handshake", 400)
return
} else if err != nil {
log.Println(err)
return
}
if verbose {
atomic.AddInt32(&connectedclients, 1)
log.Printf("Client count +1: %d\n", connectedclients)
}
var i uint32
if ld(&nummessages) < uint32(backlog_size) {
i = 0
} else {
i = ld(&nummessages) - uint32(backlog_size)
}
for i <= ld(&nummessages) {
msgi, err := backlog.Get(fmt.Sprint(i))
if err != lrucache.ErrNotFound {
msg := msgi.([]byte)
if ws.WriteMessage(websocket.TextMessage, msg) != nil {
return
}
}
inc(&i)
}
id := inc(&numclients)
clients.l.Lock()
clients.c[id] = ws
clients.l.Unlock()
for {
typ, msg, err := ws.ReadMessage()
if err != nil {
ws.Close()
return
}
if typ != websocket.TextMessage {
ws.Close()
return
}
go extendBacklog(msg)
clients.l.RLock()
for id, c := range clients.c {
go sendToClient(id, c, msg)
}
clients.l.RUnlock()
}
}
func main() {
clients.c = map[uint32]*websocket.Conn{}
http.HandleFunc("/", handler)
addr := flag.String("l", "localhost:8081", "listen address")
flag.BoolVar(&verbose, "v", false, "verbose")
flag.IntVar(&backlog_size, "backlog", 30, "num messages in backlog")
flag.Parse()
backlog = lrucache.New(int64(backlog_size))
if verbose {
log.Print("Starting websocket groupchat server on ", *addr)
}
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment