mirror of
https://github.com/raz-varren/sacrificial-socket.git
synced 2025-10-24 16:40:26 +08:00
let there be websockets
This commit is contained in:
41
examples/simple-examples/chat/main.go
Normal file
41
examples/simple-examples/chat/main.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
ss "github.com/raz-varren/sacrificial-socket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ss.NewServer()
|
||||
|
||||
s.On("join", join)
|
||||
s.On("message", message)
|
||||
|
||||
http.Handle("/socket", s.WebHandler())
|
||||
http.Handle("/", http.FileServer(http.Dir("webroot")))
|
||||
|
||||
log.Fatalln(http.ListenAndServe(":80", nil))
|
||||
}
|
||||
|
||||
func join(s *ss.Socket, data []byte) {
|
||||
//just one room at a time for the simple example
|
||||
currentRooms := s.GetRooms()
|
||||
for _, room := range currentRooms {
|
||||
s.Leave(room)
|
||||
}
|
||||
s.Join(string(data))
|
||||
s.Emit("joinedRoom", string(data))
|
||||
}
|
||||
|
||||
type msg struct {
|
||||
Room string
|
||||
Message string
|
||||
}
|
||||
|
||||
func message(s *ss.Socket, data []byte) {
|
||||
var m msg
|
||||
json.Unmarshal(data, &m)
|
||||
s.Roomcast(m.Room, "message", m.Message)
|
||||
}
|
||||
27
examples/simple-examples/chat/webroot/css/app.css
Normal file
27
examples/simple-examples/chat/webroot/css/app.css
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
#container{
|
||||
width: 400px;
|
||||
margin: 30px auto 0px auto;
|
||||
}
|
||||
|
||||
#messages{
|
||||
height: 200px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.control-item{
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#message-send-btn,
|
||||
#clear-messages-btn{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#message-list{
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.message-item{
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
52
examples/simple-examples/chat/webroot/index.html
Normal file
52
examples/simple-examples/chat/webroot/index.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Sacrificial-Socket Chat Client</title>
|
||||
<link type="text/css" rel="stylesheet" href="/css/app.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
|
||||
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
|
||||
crossorigin="anonymous"
|
||||
>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="messages" class="well well-sm">
|
||||
<ul id="message-list"></ul>
|
||||
</div>
|
||||
|
||||
<div id="controls">
|
||||
<div class="control-item">
|
||||
<div id="clear-messages-btn" class="btn btn-default">Clear Messages</div>
|
||||
</div>
|
||||
|
||||
<div class="control-item">
|
||||
<label for="name-input">Name:</label>
|
||||
<input id="name-input" class="form-control" type="text" value="stranger" />
|
||||
</div>
|
||||
|
||||
<div class="control-item">
|
||||
<label for="join-room-input">Join Chat Room:</label>
|
||||
<div class="input-group">
|
||||
<input id="join-room-input" class="form-control" type="text" value="global" />
|
||||
<div class="input-group-btn">
|
||||
<div id="join-room-btn" class="btn btn-info">Join</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-item">
|
||||
<textarea id="message-body-input" class="form-control" placeholder="Type message here"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="control-item">
|
||||
<div id="message-send-btn" class="btn btn-success">Send Message</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/js/sacrificial-socket.js"></script>
|
||||
<script type="text/javascript" src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
69
examples/simple-examples/chat/webroot/js/app.js
Normal file
69
examples/simple-examples/chat/webroot/js/app.js
Normal file
@@ -0,0 +1,69 @@
|
||||
(function(SS){ 'use strict';
|
||||
var msgCnt = document.querySelector('#messages'),
|
||||
msgList = document.querySelector('#message-list'),
|
||||
msgClear = document.querySelector('#clear-messages-btn'),
|
||||
nameInput = document.querySelector('#name-input'),
|
||||
joinInput = document.querySelector('#join-room-input'),
|
||||
joinBtn = document.querySelector('#join-room-btn'),
|
||||
msgBody = document.querySelector('#message-body-input'),
|
||||
send = document.querySelector('#message-send-btn'),
|
||||
currentRoom = null,
|
||||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
|
||||
getTime = function(dt) {
|
||||
var hours = dt.getHours(),
|
||||
minutes = dt.getMinutes(),
|
||||
ampm = hours >= 12 ? 'pm' : 'am';
|
||||
hours %= 12;
|
||||
hours = hours ? hours : 12; // the hour '0' should be '12'
|
||||
minutes = minutes < 10 ? '0'+minutes : minutes;
|
||||
var strTime = hours + ':' + minutes + ' ' + ampm;
|
||||
return strTime;
|
||||
},
|
||||
addMsg = function(msg){
|
||||
var li = document.createElement('li'),
|
||||
dt = new Date(),
|
||||
dtString = months[dt.getMonth()]+' '+dt.getDate()+', '+dt.getFullYear();
|
||||
li.innerText = dtString+' '+getTime(dt)+' - '+msg;
|
||||
msgList.appendChild(li);
|
||||
msgCnt.scrollTop = msgCnt.scrollHeight;
|
||||
};
|
||||
|
||||
|
||||
var ss = new SS('ws://'+window.location.host+'/socket');
|
||||
|
||||
ss.onConnect(function(){
|
||||
ss.emit('join', joinInput.value);
|
||||
});
|
||||
|
||||
ss.onDisconnect(function(){
|
||||
alert('chat disconnected');
|
||||
});
|
||||
|
||||
ss.on('joinedRoom', function(room){
|
||||
currentRoom = room;
|
||||
addMsg('joined room: '+room);
|
||||
});
|
||||
|
||||
ss.on('message', function(msg){
|
||||
addMsg(msg);
|
||||
});
|
||||
|
||||
send.addEventListener('click', function(){
|
||||
var msg = msgBody.value;
|
||||
if(msg.length === 0) return;
|
||||
|
||||
ss.emit('message', {Room: currentRoom, Message: nameInput.value+' says: '+msg});
|
||||
});
|
||||
|
||||
joinBtn.addEventListener('click', function(){
|
||||
var room = joinInput.value;
|
||||
if(room.length === 0) return;
|
||||
ss.emit('join', joinInput.value);
|
||||
});
|
||||
|
||||
msgClear.addEventListener('click', function(){
|
||||
msgList.innerHTML = '';
|
||||
});
|
||||
|
||||
})(window.SS);
|
||||
170
examples/simple-examples/chat/webroot/js/sacrificial-socket.js
Normal file
170
examples/simple-examples/chat/webroot/js/sacrificial-socket.js
Normal file
@@ -0,0 +1,170 @@
|
||||
(function(window){ 'use strict';
|
||||
/**
|
||||
* SS is the constructor for the sacrificial-socket client
|
||||
*
|
||||
* @class SS
|
||||
* @constructor
|
||||
* @param {String} url - The url to the sacrificial-socket server endpoint. The url must conform to the websocket URI Scheme ("ws" or "wss")
|
||||
*/
|
||||
var SS = function(url){
|
||||
var self = this,
|
||||
ws = new WebSocket(url, 'sac-sock'),
|
||||
events = {},
|
||||
headerStartCharCode = 1,
|
||||
headerStartChar = String.fromCharCode(headerStartCharCode),
|
||||
dataStartCharCode = 2,
|
||||
dataStartChar = String.fromCharCode(dataStartCharCode);
|
||||
|
||||
//sorry, only supporting arraybuffer at this time
|
||||
//maybe if there is demand for it, I'll add Blob support
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
//Parses all incoming messages and dispatches their payload to the appropriate eventName if one has been registered. Messages received for unregistered events will be ignored.
|
||||
ws.onmessage = function(e){
|
||||
var msg = e.data,
|
||||
headers = {},
|
||||
eventName = '',
|
||||
data = '',
|
||||
chr = null,
|
||||
i, msgLen;
|
||||
|
||||
if(typeof msg === 'string'){
|
||||
var dataStarted = false,
|
||||
headerStarted = false;
|
||||
|
||||
for(i = 0, msgLen = msg.length; i < msgLen; i++){
|
||||
chr = msg[i];
|
||||
if(!dataStarted && !headerStarted && chr !== dataStartChar && chr !== headerStartChar){
|
||||
eventName += chr;
|
||||
}else if(!headerStarted && chr === headerStartChar){
|
||||
headerStarted = true;
|
||||
}else if(headerStarted && !dataStarted && chr !== dataStartChar){
|
||||
headers[chr] = true;
|
||||
}else if(!dataStarted && chr === dataStartChar){
|
||||
dataStarted = true;
|
||||
}else{
|
||||
data += chr;
|
||||
}
|
||||
}
|
||||
}else if(msg && msg instanceof ArrayBuffer && msg.byteLength !== undefined){
|
||||
var dv = new DataView(msg),
|
||||
headersStarted = false;
|
||||
|
||||
for(i = 0, msgLen = dv.byteLength; i < msgLen; i++){
|
||||
chr = dv.getUint8(i);
|
||||
|
||||
if(chr !== dataStartCharCode && chr !== headerStartCharCode && !headersStarted){
|
||||
eventName += String.fromCharCode(chr);
|
||||
}else if(chr === headerStartCharCode && !headersStarted){
|
||||
headersStarted = true;
|
||||
}else if(headersStarted && chr !== dataStartCharCode){
|
||||
headers[String.fromCharCode(chr)] = true;
|
||||
}else if(chr === dataStartCharCode){
|
||||
data = dv.buffer.slice(i+1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(eventName.length === 0) return; //no event to dispatch
|
||||
if(typeof events[eventName] === 'undefined') return;
|
||||
events[eventName]((headers.J) ? JSON.parse(data) : data);
|
||||
};
|
||||
|
||||
/**
|
||||
* onConnect registers a callback to be run when the websocket connection is open.
|
||||
*
|
||||
* @method onConnect
|
||||
* @param {Function} callback(SS) - The callback that will be executed when the websocket connection opens.
|
||||
*
|
||||
*/
|
||||
self.onConnect = function(callback){
|
||||
ws.onopen = function(){ callback(self); };
|
||||
};
|
||||
|
||||
/**
|
||||
* onDisconnect registers a callback to be run when the websocket connection is closed.
|
||||
*
|
||||
* @method onDisconnect
|
||||
* @param {Function} callback(SS) - The callback that will be executed when the websocket connection is closed.
|
||||
*/
|
||||
self.onDisconnect = function(callback){
|
||||
ws.onclose = function(){ callback(self); };
|
||||
};
|
||||
|
||||
/**
|
||||
* on registers an event to be called when the client receives an emit from the server for
|
||||
* the given eventName.
|
||||
*
|
||||
* @method on
|
||||
* @param {String} eventName - The name of the event being registerd
|
||||
* @param {Function} callback(payload) - The callback that will be ran whenever the client receives an emit from the server for the given eventName. The payload passed into callback may be of type String, Object, or ArrayBuffer
|
||||
*
|
||||
*/
|
||||
self.on = function(eventName, callback){
|
||||
events[eventName] = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* off unregisters an emit event
|
||||
*
|
||||
* @method off
|
||||
* @param {String} eventName - The name of event being unregistered
|
||||
*/
|
||||
self.off = function(eventName){
|
||||
if(events[eventName]){
|
||||
delete events[eventName];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* emit dispatches an event to the server
|
||||
*
|
||||
* @method emit
|
||||
* @param {String} eventName - The event to dispatch
|
||||
* @param {String|Object|ArrayBuffer} data - The data to be sent to the server. If data is a string then it will be sent as a normal string to the server. If data is an object it will be converted to JSON before being sent to the server. If data is an ArrayBuffer then it will be sent to the server as a uint8 binary payload.
|
||||
*/
|
||||
self.emit = function(eventName, data){
|
||||
var rs = ws.readyState;
|
||||
if(rs === 0){
|
||||
console.warn("websocket is not open yet");
|
||||
return;
|
||||
}else if(rs === 2 || rs === 3){
|
||||
console.error("websocket is closed");
|
||||
return;
|
||||
}
|
||||
var msg = '';
|
||||
if(data instanceof ArrayBuffer){
|
||||
var ab = new ArrayBuffer(data.byteLength+eventName.length+1),
|
||||
newBuf = new DataView(ab),
|
||||
oldBuf = new DataView(data),
|
||||
i = 0;
|
||||
for(var evtLen = eventName.length; i < evtLen; i++){
|
||||
newBuf.setUint8(i, eventName.charCodeAt(i));
|
||||
}
|
||||
newBuf.setUint8(i, dataStartCharCode);
|
||||
i++;
|
||||
for(var x = 0, xLen = oldBuf.byteLength; x < xLen; x++, i++){
|
||||
newBuf.setUint8(i, oldBuf.getUint8(x));
|
||||
}
|
||||
msg = ab;
|
||||
}else if(typeof data === 'object'){
|
||||
msg = eventName+dataStartChar+JSON.stringify(data);
|
||||
}else{
|
||||
msg = eventName+dataStartChar+data;
|
||||
}
|
||||
ws.send(msg);
|
||||
};
|
||||
|
||||
/**
|
||||
* close will close the websocket connection, calling the "onDisconnect" event if one has been registered.
|
||||
*
|
||||
* @method close
|
||||
*/
|
||||
self.close = function(){
|
||||
return ws.close();
|
||||
};
|
||||
};
|
||||
|
||||
window.SS = SS;
|
||||
})(window);
|
||||
Reference in New Issue
Block a user