-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9235435
Showing
7 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |