Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
hscells committed Jun 12, 2018
0 parents commit 9235435
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.log
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Big Brother

_bigbro_ is a website interaction logging service.

## Client

To use _bigbro_ on a website, include the following Javascript snippet (this repository hosts `bigbro.js` in the [js folder](js)):

```html
<script type="text/javascript" src="bigbro.js"></script>
<script type="text/javascript">
BigBro.init("username", "localhost:1984");
</script>
```

This will allow the page to capture most events that occur by interacting with the page.

Custom logging events can also be added, see the following example:

```html
<script type="text/javascript" src="bigbro.js"></script>
<script type="text/javascript">
let bb = BigBro.init("username", "localhost:1984");
window.addEventListener("click", function (e) {
bb.log(e, "custom_event");
})
</script>
```

The arguments to `BigBro.init` are as follows:

- `actor`: A unique identifier of the current user.
- `server`: The address _bigbro_ is running on (please omit protocol; this will be determined automatically).
- (optional) `events`: A list of events that will be listened on globally (at the window level); e.g. "click", "mousemove".

## Server

_bigbro_ is written in Go. To install locally, please use:

```bash
go get -u install github.com/hscells/bigbro
```

Alternatively, download a [prebuilt binary](https://github.com/hscells/bigbro/releases).
65 changes: 65 additions & 0 deletions cmd/bigbro/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"fmt"
"github.com/buger/goterm"
"github.com/gin-gonic/gin"
"github.com/hscells/bigbro"
"log"
"time"
)

type server struct {
l bigbro.Logger
}

func main() {
t := time.Now().Format(time.Stamp)
logger, err := bigbro.NewLogger(fmt.Sprintf("bigbrother_%s.log", t), bigbro.CSVFormatter{})
if err != nil {
log.Fatalln(err)
}

s := server{
l: logger,
}

g := gin.Default()

g.Use(func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Accept, Origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")

if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}

c.Next()
})

g.GET("/event", s.handleEvent)
if goterm.Width() > 91 {
fmt.Print(`
@@@@@@@ @@@ @@@@@@@ @@@@@@@ @@@@@@@ @@@@@@ @@@@@@@ @@@ @@@ @@@@@@@@ @@@@@@@
@@! @@@ @@! !@@ @@! @@@ @@! @@@ @@! @@@ @@! @@! @@@ @@! @@! @@@
@!@!@!@ !!@ !@! @!@!@ @!@!@!@ @!@!!@! @!@ !@! @!! @!@!@!@! @!!!:! @!@!!@!
!!: !!! !!: :!! !!: !!: !!! !!: :!! !!: !!! !!: !!: !!! !!: !!: :!!
:: : :: : :: :: : :: : :: : : : : :. : : : : : : :: ::: : : :
...is always watching
Harry Scells 2018
`)
} else {
fmt.Print(`Big Brother
...is always watching
Harry Scells 2018
`)
}
g.Run("0.0.0.0:1984")
}
57 changes: 57 additions & 0 deletions cmd/bigbro/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/hscells/bigbro"
"log"
"net/http"
"time"
)

// upgrader upgrades a web socket.
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}

// wsEvent handles read events from the web socket.
func wsEvent(ws *websocket.Conn, l bigbro.Logger) {

readWait := 1 * time.Millisecond
readTicker := time.NewTicker(readWait)

// defer closing of web socket
defer func() {
readTicker.Stop()
ws.Close()
}()

for {
select {
case <-readTicker.C:
var event bigbro.Event
err := ws.ReadJSON(&event)
if err != nil {
return
}
err = l.Log(event)
if err != nil {
return
}
}
}
}

// handleEvent handles an incoming request and attempts to upgrade it to a websocket.
func (s server) handleEvent(c *gin.Context) {
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Println(err)
return
}

go wsEvent(ws, s.l)
}
16 changes: 16 additions & 0 deletions formatting.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package bigbro

import "fmt"

// Formatter is a way of specifying how to format an event for logging.
type Formatter interface {
Format(e Event) string
}

// CSVFormatter is a formatter that formats in comma separated format.
type CSVFormatter struct{}

// Format in comma separated file.
func (l CSVFormatter) Format(e Event) string {
return fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%d,%d,%d,%d,%s\n", e.Time.String(), e.Actor.Identifier, e.Method, e.Target, e.Name, e.ID, e.Location, e.X, e.Y, e.ScreenWidth, e.ScreenHeight, e.Comment)
}
64 changes: 64 additions & 0 deletions js/bigbro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
let BigBro = {
// This should not be modified outside of the init method.
data: {
user: "",
server: "",
events: ["click", "dblclick", "mousedown", "mouseup",
"mouseenter", "mouseout", "wheel", "loadstart", "loadend", "load",
"unload", "reset", "submit", "scroll", "resize",
"cut", "copy", "paste", "select", "keydown", "keyup"
],
},
// init must be called with the user and the server, and optionally a list of
// events to listen to globally.
init: function (user, server, events) {
this.data.user = user;
this.data.server = server;
this.data.events = events || this.data.events;

let protocol = 'ws://';
if (window.location.protocol === 'https:') {
protocol = 'wss://';
}

this.ws = new WebSocket(protocol + this.data.server + "/event");

let self = this;
for (let i = 0; i < this.data.events.length; i++) {
window.addEventListener(this.data.events[i], function (e) {
self.log(e, self.data.events[i]);
})
}
},
// log logs an event with a specified method name (normally the actual event name).
log: function (e, method) {
let event = {
target: e.target.tagName,
name: e.target.name,
id: e.target.id,
method: method,
location: window.location.href,
time: new Date().toISOString(),
x: e.x,
y: e.y,
screenWidth: window.innerWidth,
screenHeight: window.innerHeight,
actor: {
identifier: this.data.user
}
};
if (method === "keydown" || method === "keyup") {
// Which key was actually pressed?
event.comment = e.code;
}
if (method === "paste" || method === "cut" || method === "copy") {
// Seems like we can only get data for paste events.
event.comment = e.clipboardData.getData("text/plain")
}
if (method === "wheel") {
// Strength of the wheel rotation.
event.comment = e.deltaY.toString();
}
this.ws.send(JSON.stringify(event));
}
};
68 changes: 68 additions & 0 deletions logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package bigbro

import (
"io"
"os"
"time"
)

// Event is something which has happened on a web page.
type Event struct {
// The element which has been triggered.
Target string `json:"target"`
// The name attribute of the element.
Name string `json:"name"`
// The id attribute of the element.
ID string `json:"id"`
// The method which triggered the event.
Method string `json:"method"`
// The web page location on the server.
Location string `json:"location"`
// Any additional information that can be useful.
Comment string `json:"comment"`
// X position of the event.
X int `json:"x"`
// Y position of the event.
Y int `json:"y"`
// Width of the actors screen.
ScreenWidth int `json:"screenWidth"`
// Height of the actors screen.
ScreenHeight int `json:"screenHeight"`
// The time the Event happened.
Time time.Time `json:"time"`
// The actor that caused the Event.
Actor Actor `json:"actor"`
}

// Actor is something that can interact with web pages and can trigger events.
type Actor struct {
// The unique identifier which this actor is known by.
Identifier string `json:"identifier"`
}

// Logger is the way in which logs are written to a file.
type Logger struct {
f io.Writer
formatter Formatter
}

// NewLogger creates a new logger.
func NewLogger(name string, formatter Formatter) (Logger, error) {
lf, err := os.OpenFile(name, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return Logger{}, err
}
lf.Truncate(0)

return Logger{
f: lf,
formatter: formatter,
}, nil
}

// Log writes an event to the log file using the specified formatter.
func (l Logger) Log(e Event) error {
line := l.formatter.Format(e)
_, err := l.f.Write([]byte(line))
return err
}

0 comments on commit 9235435

Please sign in to comment.