├── .babelrc ├── .eslintrc ├── .gitignore ├── Godeps ├── Godeps.json └── Readme ├── README.md ├── assets └── screenshot.png ├── docker-compose.yml ├── handlers.go ├── index.html ├── main.go ├── middleware.go ├── package.json ├── payload.go ├── router.go ├── server.go ├── src ├── actions.js ├── components │ ├── Button.jsx │ ├── CallersPanel.jsx │ ├── CallersPanelCaller.jsx │ ├── CloseButton.jsx │ ├── Comment.jsx │ ├── CommentInput.jsx │ ├── Comments.jsx │ ├── DropdownButton.jsx │ ├── DropdownList.jsx │ ├── EmptySeat.jsx │ ├── EventNotice.jsx │ ├── Icon.jsx │ ├── MenuItem.jsx │ ├── ModalButton.jsx │ ├── ProfileDropdown.jsx │ ├── Room.jsx │ ├── RoomTitle.jsx │ ├── Seat.jsx │ ├── Seats.jsx │ ├── TwitterLogin.jsx │ ├── User.jsx │ ├── UserIcon.jsx │ ├── UserModal.jsx │ ├── UserPreviewButton.jsx │ ├── Username.jsx │ ├── ViewerInfo.jsx │ └── ViewerList.jsx ├── constants.js ├── handlers │ ├── App.jsx │ ├── Home.jsx │ └── RoomLoader.jsx ├── index.jsx ├── middleware │ └── connection.js ├── normalize.scss ├── reducer.js ├── routes.jsx └── style.scss ├── state.go ├── vendor ├── github.com │ ├── garyburd │ │ ├── go-oauth │ │ │ └── oauth │ │ │ │ └── oauth.go │ │ └── redigo │ │ │ ├── LICENSE │ │ │ ├── internal │ │ │ └── commandinfo.go │ │ │ └── redis │ │ │ ├── conn.go │ │ │ ├── doc.go │ │ │ ├── log.go │ │ │ ├── pool.go │ │ │ ├── pubsub.go │ │ │ ├── redis.go │ │ │ ├── reply.go │ │ │ ├── scan.go │ │ │ └── script.go │ ├── gorilla │ │ ├── context │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── context.go │ │ │ └── doc.go │ │ ├── securecookie │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── fuzz.go │ │ │ └── securecookie.go │ │ ├── sessions │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── lex.go │ │ │ ├── sessions.go │ │ │ └── store.go │ │ └── websocket │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── client.go │ │ │ ├── compression.go │ │ │ ├── conn.go │ │ │ ├── conn_read.go │ │ │ ├── conn_read_legacy.go │ │ │ ├── doc.go │ │ │ ├── json.go │ │ │ ├── server.go │ │ │ └── util.go │ ├── pjebs │ │ └── tokbox │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── gae.go │ │ │ ├── standalone.go │ │ │ └── tokbox.go │ └── rs │ │ ├── cors │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── cors.go │ │ └── utils.go │ │ └── xhandler │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── chain.go │ │ ├── middleware.go │ │ └── xhandler.go ├── goji.io │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── dispatch.go │ ├── goji.go │ ├── handle.go │ ├── internal │ │ ├── context.go │ │ ├── http.go │ │ └── internal.go │ ├── middleware.go │ ├── middleware │ │ └── middleware.go │ ├── mux.go │ ├── pat │ │ ├── match.go │ │ ├── methods.go │ │ ├── pat.go │ │ └── url.go │ ├── pattern.go │ ├── pattern │ │ └── pattern.go │ ├── router.go │ ├── router_simple.go │ └── router_trie.go ├── golang.org │ └── x │ │ └── net │ │ ├── LICENSE │ │ ├── PATENTS │ │ └── context │ │ └── context.go └── gopkg.in │ └── boj │ └── redistore.v1 │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc.go │ └── redistore.go ├── webpack.config.js └── ws.go /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"], 3 | "plugins": [ 4 | [ 5 | "transform-decorators-legacy", 6 | "transform-regenerator", 7 | ["babel-root-import", { 8 | "rootPathSuffix": "src" 9 | }] 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "rules": { 5 | "semi": [2, "never"], 6 | "import/prefer-default-export": [0, "never"], 7 | "arrow-parens": 0, 8 | "react/no-unused-prop-types": 0, 9 | "jsx-a11y/no-static-element-interactions": 0, 10 | }, 11 | "settings": { 12 | "import/resolver": "webpack" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go ### 2 | # Compiled Object files, Static and Dynamic libs (Shared 3 | Objects) 4 | *.o 5 | *.a 6 | *.so 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | 28 | # Output of the go coverage tool, specifically when used with 29 | LiteIDE 30 | *.out 31 | 32 | /blarg-api 33 | /scripts/dev_env.sh 34 | /scripts/prod_env.sh 35 | 36 | # client 37 | 38 | # Dependency directory 39 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 40 | node_modules 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional REPL history 46 | .node_repl_history 47 | 48 | npm-debug.log* 49 | 50 | static 51 | bin/setup_dev.sh -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "bitbucket.com/jsdir/blarg", 3 | "GoVersion": "go1.7", 4 | "GodepVersion": "v74", 5 | "Packages": [ 6 | "./..." 7 | ], 8 | "Deps": [ 9 | { 10 | "ImportPath": "github.com/garyburd/go-oauth/oauth", 11 | "Rev": "719b069913e1151a73ea30d2ea4f90deda3ce234" 12 | }, 13 | { 14 | "ImportPath": "github.com/garyburd/redigo/internal", 15 | "Rev": "6ece6e0a09f28cc399b21550cbf37ab39ba63cce" 16 | }, 17 | { 18 | "ImportPath": "github.com/garyburd/redigo/redis", 19 | "Rev": "6ece6e0a09f28cc399b21550cbf37ab39ba63cce" 20 | }, 21 | { 22 | "ImportPath": "github.com/gorilla/context", 23 | "Comment": "v1.1-7-g08b5f42", 24 | "Rev": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" 25 | }, 26 | { 27 | "ImportPath": "github.com/gorilla/securecookie", 28 | "Comment": "v1.1-4-gc13558c", 29 | "Rev": "c13558c2b1c44da35e0eb043053609a5ba3a1f19" 30 | }, 31 | { 32 | "ImportPath": "github.com/gorilla/sessions", 33 | "Comment": "v1.1", 34 | "Rev": "ca9ada44574153444b00d3fd9c8559e4cc95f896" 35 | }, 36 | { 37 | "ImportPath": "github.com/gorilla/websocket", 38 | "Comment": "v1.0.0-26-g8003df8", 39 | "Rev": "8003df83eef3a5d5301beb970356755128810563" 40 | }, 41 | { 42 | "ImportPath": "github.com/pjebs/tokbox", 43 | "Rev": "11ae75f315cac232d2371621d1e46656fb74e34c" 44 | }, 45 | { 46 | "ImportPath": "github.com/rs/cors", 47 | "Rev": "ceb1fbf238d7711a11a86a2622d0b85305348aeb" 48 | }, 49 | { 50 | "ImportPath": "github.com/rs/xhandler", 51 | "Rev": "d9d9599b6aaf6a058cb7b1f48291ded2cbd13390" 52 | }, 53 | { 54 | "ImportPath": "goji.io", 55 | "Comment": "v1.0-7-ge355964", 56 | "Rev": "e355964ac565b94cf0fc7f218346626529125086" 57 | }, 58 | { 59 | "ImportPath": "goji.io/internal", 60 | "Comment": "v1.0-7-ge355964", 61 | "Rev": "e355964ac565b94cf0fc7f218346626529125086" 62 | }, 63 | { 64 | "ImportPath": "goji.io/middleware", 65 | "Comment": "v1.0-7-ge355964", 66 | "Rev": "e355964ac565b94cf0fc7f218346626529125086" 67 | }, 68 | { 69 | "ImportPath": "goji.io/pat", 70 | "Comment": "v1.0-7-ge355964", 71 | "Rev": "e355964ac565b94cf0fc7f218346626529125086" 72 | }, 73 | { 74 | "ImportPath": "goji.io/pattern", 75 | "Comment": "v1.0-7-ge355964", 76 | "Rev": "e355964ac565b94cf0fc7f218346626529125086" 77 | }, 78 | { 79 | "ImportPath": "golang.org/x/net/context", 80 | "Rev": "3b90a77d2885fb0429e8a21ab72fc73ca6f8b401" 81 | }, 82 | { 83 | "ImportPath": "gopkg.in/boj/redistore.v1", 84 | "Comment": "v1.2", 85 | "Rev": "fc113767cd6b051980f260d6dbe84b2740c46ab0" 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blarg 2 | 3 | a humble clone of blab.im 4 | 5 | https://blarg-im.herokuapp.com 6 | 7 | ![Screenshot](./assets/screenshot.png) 8 | -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsdir/blarg/7fe307eb777561b710184670463e55c4e100e84d/assets/screenshot.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | redis: 2 | image: redis:3.2.4-alpine 3 | ports: 4 | - "16380:6379" 5 | -------------------------------------------------------------------------------- /handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/garyburd/go-oauth/oauth" 12 | ) 13 | 14 | // Session keys. 15 | const ( 16 | tempCredKey = "tempCred" 17 | tokenCredKey = "tokenCred" 18 | sessionPrefix = "session" 19 | usernameKey = "username" 20 | initialRoomIdKey = "roomId" 21 | ) 22 | 23 | func handleInternalServerError(err error, w http.ResponseWriter) { 24 | log.Println(err.Error()) 25 | http.Error(w, "Internal Server Error", 500) 26 | } 27 | 28 | // decodeResponse decodes the JSON response from the Twitter API. 29 | func decodeResponse(resp *http.Response, data interface{}) error { 30 | if resp.StatusCode != 200 { 31 | p, _ := ioutil.ReadAll(resp.Body) 32 | return fmt.Errorf("get %s returned status %d, %s", resp.Request.URL, resp.StatusCode, p) 33 | } 34 | return json.NewDecoder(resp.Body).Decode(data) 35 | } 36 | 37 | func apiGet( 38 | oauthClient *oauth.Client, 39 | cred *oauth.Credentials, 40 | urlStr string, 41 | form url.Values, 42 | data interface{}, 43 | ) error { 44 | resp, err := oauthClient.Get(nil, cred, urlStr, form) 45 | if err != nil { 46 | return err 47 | } 48 | defer resp.Body.Close() 49 | return decodeResponse(resp, data) 50 | } 51 | 52 | func getUsername( 53 | oauthClient *oauth.Client, 54 | cred *oauth.Credentials, 55 | ) (string, error) { 56 | data := map[string]interface{}{} 57 | if err := apiGet( 58 | oauthClient, 59 | cred, 60 | "https://api.twitter.com/1.1/account/verify_credentials.json", 61 | url.Values{}, 62 | &data, 63 | ); err != nil { 64 | return "", err 65 | } 66 | 67 | return data["screen_name"].(string), nil 68 | } 69 | 70 | func (s *Server) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { 71 | session, err := s.RediStore.Get(r, sessionPrefix) 72 | if err != nil { 73 | handleInternalServerError(err, w) 74 | return 75 | } 76 | 77 | callback := "http://" + r.Host + "/v1/callback" 78 | tempCred, err := s.OAuthClient.RequestTemporaryCredentials( 79 | nil, callback, nil, 80 | ) 81 | if err != nil { 82 | handleInternalServerError(err, w) 83 | return 84 | } 85 | 86 | session.Values[tempCredKey] = tempCred 87 | session.Values[initialRoomIdKey] = r.URL.Query().Get("roomId") 88 | if err = session.Save(r, w); err != nil { 89 | handleInternalServerError(err, w) 90 | return 91 | } 92 | 93 | http.Redirect( 94 | w, r, s.OAuthClient.AuthorizationURL(tempCred, nil), 302, 95 | ) 96 | } 97 | 98 | func (s *Server) HandleCallback(w http.ResponseWriter, r *http.Request) { 99 | session, err := s.RediStore.Get(r, sessionPrefix) 100 | if err != nil { 101 | handleInternalServerError(err, w) 102 | return 103 | } 104 | 105 | tempCred, _ := session.Values[tempCredKey].(*oauth.Credentials) 106 | if tempCred == nil || tempCred.Token != r.FormValue("oauth_token") { 107 | handleInternalServerError(err, w) 108 | return 109 | } 110 | 111 | tokenCred, _, err := s.OAuthClient.RequestToken( 112 | nil, tempCred, r.FormValue("oauth_verifier"), 113 | ) 114 | 115 | if err != nil { 116 | handleInternalServerError(err, w) 117 | return 118 | } 119 | delete(session.Values, tempCredKey) 120 | session.Values[tokenCredKey] = tokenCred 121 | 122 | username, err := getUsername(&s.OAuthClient, tokenCred) 123 | if err != nil { 124 | handleInternalServerError(err, w) 125 | return 126 | } 127 | 128 | session.Values[usernameKey] = username 129 | 130 | if err := session.Save(r, w); err != nil { 131 | handleInternalServerError(err, w) 132 | return 133 | } 134 | 135 | roomId := session.Values[initialRoomIdKey].(string) 136 | if roomId == "" { 137 | roomId = username 138 | } 139 | 140 | http.Redirect(w, r, s.ClientBaseUrl+"/"+roomId, 302) 141 | } 142 | 143 | func (s *Server) HandleLogout(w http.ResponseWriter, r *http.Request) { 144 | session, err := s.RediStore.Get(r, sessionPrefix) 145 | if err != nil { 146 | handleInternalServerError(err, w) 147 | return 148 | } 149 | 150 | delete(session.Values, usernameKey) 151 | delete(session.Values, tokenCredKey) 152 | 153 | if err := session.Save(r, w); err != nil { 154 | handleInternalServerError(err, w) 155 | return 156 | } 157 | 158 | http.Redirect(w, r, s.ClientBaseUrl+"/", 302) 159 | } 160 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Blarg.im 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | server := NewServer() 11 | defer server.RediStore.Close() 12 | defer server.RedisPool.Close() 13 | 14 | port := os.Getenv("PORT") 15 | if port == "" { 16 | port = "8000" 17 | } 18 | 19 | addr := "0.0.0.0:" + port 20 | 21 | log.Println("started server at " + addr) 22 | log.Fatal(http.ListenAndServe(addr, NewRouter(&server))) 23 | } 24 | -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | func isHTTPS(r *http.Request) bool { 9 | if r.URL.Scheme == "https" { 10 | return true 11 | } 12 | if strings.HasPrefix(r.Proto, "HTTPS") { 13 | return true 14 | } 15 | if r.Header.Get("X-Forwarded-Proto") == "https" { 16 | return true 17 | } 18 | return false 19 | } 20 | 21 | func ForceHTTPS(h http.Handler) http.Handler { 22 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | url := r.URL 24 | // thx: https://github.com/unrolled/secure 25 | if !isHTTPS(r) { 26 | url.Scheme = "https" 27 | url.Host = r.Host 28 | http.Redirect(w, r, url.String(), http.StatusMovedPermanently) 29 | return 30 | } 31 | 32 | h.ServeHTTP(w, r) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blarg-client", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "start": "node_modules/.bin/webpack -w", 8 | "build": "BLARG_ENV=production node_modules/.bin/webpack", 9 | "deploy": "aws s3 cp ./dist s3://blarg.im --recursive", 10 | "postinstall": "npm run build" 11 | }, 12 | "author": "Jason Sommer ", 13 | "license": "MIT", 14 | "description": "", 15 | "devDependencies": { 16 | "babel-core": "^6.3.26", 17 | "babel-eslint": "^7.0.0", 18 | "babel-loader": "^6.2.0", 19 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 20 | "babel-plugin-transform-flow-strip-types": "^6.8.0", 21 | "babel-polyfill": "^6.3.14", 22 | "babel-preset-es2015": "^6.3.13", 23 | "babel-preset-react": "^6.3.13", 24 | "babel-preset-stage-0": "^6.3.13", 25 | "babel-root-import": "^4.1.3", 26 | "css-loader": "^0.23.1", 27 | "eslint": "^3.7.0", 28 | "eslint-config-airbnb": "^12.0.0", 29 | "eslint-import-resolver-webpack": "^0.5.1", 30 | "eslint-loader": "^1.5.0", 31 | "eslint-plugin-import": "^1.13.0", 32 | "eslint-plugin-jsx-a11y": "^2.1.0", 33 | "eslint-plugin-react": "^6.0.0", 34 | "node-sass": "^3.4.2", 35 | "sass-loader": "^3.1.2", 36 | "style-loader": "^0.13.0", 37 | "webpack": "^1.13.2" 38 | }, 39 | "dependencies": { 40 | "classnames": "^2.2.5", 41 | "history": "^4.3.0", 42 | "lodash": "^4.16.4", 43 | "react": "^15.3.2", 44 | "react-addons-css-transition-group": "^15.3.2", 45 | "react-addons-update": "^15.3.2", 46 | "react-dom": "^15.3.2", 47 | "react-redux": "^4.4.5", 48 | "react-router": "^2.8.1", 49 | "redux": "^3.6.0", 50 | "redux-actions": "^0.12.0", 51 | "redux-router": "^2.1.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /payload.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "text/template" 8 | 9 | "goji.io" 10 | "goji.io/middleware" 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | var indexTmpl *template.Template 15 | 16 | func init() { 17 | data, err := ioutil.ReadFile("./index.html") 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | tmpl, err := template.New("index").Parse(string(data)) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | indexTmpl = tmpl 28 | } 29 | 30 | type TemplateContext struct { 31 | Payload string 32 | } 33 | 34 | func (s *Server) NotFound(h goji.Handler) goji.Handler { 35 | return goji.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 36 | if middleware.Handler(ctx) == nil { 37 | session, err := s.RediStore.Get(r, sessionPrefix) 38 | if err != nil { 39 | handleInternalServerError(err, w) 40 | return 41 | } 42 | 43 | payload := map[string]interface{}{} 44 | userID, ok := session.Values[usernameKey].(string) 45 | if ok && userID != "" { 46 | payload["userId"] = userID 47 | } 48 | data, err := json.Marshal(payload) 49 | if err != nil { 50 | handleInternalServerError(err, w) 51 | return 52 | } 53 | 54 | ctx := TemplateContext{ 55 | Payload: string(data), 56 | } 57 | 58 | w.WriteHeader(http.StatusOK) 59 | indexTmpl.Execute(w, &ctx) 60 | } else { 61 | h.ServeHTTPC(ctx, w, r) 62 | } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | "goji.io" 8 | "goji.io/pat" 9 | ) 10 | 11 | func NewRouter(s *Server) *goji.Mux { 12 | mux := goji.NewMux() 13 | 14 | if s.ForceHTTPS { 15 | mux.Use(ForceHTTPS) 16 | } 17 | 18 | // CORS 19 | c := cors.New(cors.Options{ 20 | AllowedOrigins: []string{"*"}, 21 | AllowCredentials: true, 22 | }) 23 | 24 | mux.Use(c.Handler) 25 | mux.UseC(s.NotFound) 26 | 27 | // Handlers 28 | mux.HandleFunc(pat.Get("/v1/authenticate"), s.HandleAuthenticate) 29 | mux.HandleFunc(pat.Get("/v1/callback"), s.HandleCallback) 30 | mux.HandleFunc(pat.Get("/v1/ws"), s.HandleWS) 31 | mux.HandleFunc(pat.Get("/v1/logout"), s.HandleLogout) 32 | mux.HandleFunc( 33 | pat.Get("/static/*"), 34 | http.FileServer(http.Dir(".")).ServeHTTP, 35 | ) 36 | 37 | return mux 38 | } 39 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/gob" 5 | "os" 6 | "time" 7 | 8 | "github.com/garyburd/go-oauth/oauth" 9 | "github.com/garyburd/redigo/redis" 10 | "github.com/pjebs/tokbox" 11 | "gopkg.in/boj/redistore.v1" 12 | ) 13 | 14 | const ( 15 | Development = iota 16 | Production 17 | ) 18 | 19 | // Environment Variables: 20 | 21 | // BLARG_ENV 22 | // TWITTER_KEY 23 | // TWITTER_SECRET_KEY 24 | // REDIS_PORT_6379_TCP_ADDR 25 | // REDIS_PORT_6379_TCP_POST 26 | // REDIS_PASSWORD 27 | 28 | type Server struct { 29 | RediStore *redistore.RediStore 30 | RedisPool *redis.Pool 31 | OAuthClient oauth.Client 32 | State State 33 | ClientBaseUrl string 34 | ForceHTTPS bool 35 | TokBox *tokbox.Tokbox 36 | TokBoxKey string 37 | } 38 | 39 | func newPool(addr, password string) *redis.Pool { 40 | return &redis.Pool{ 41 | // Maximum number of idle connections in the pool. 42 | MaxIdle: 100, 43 | 44 | // Maximum number of connections allocated by the pool at a given time. 45 | // When zero, there is no limit on the number of connections in the pool. 46 | MaxActive: 100, 47 | 48 | // Close connections after remaining idle for this duration. If the value 49 | // is zero, then idle connections are not closed. Applications should set 50 | // the timeout to a value less than the server's timeout. 51 | IdleTimeout: 240 * time.Second, 52 | 53 | // Dial is an application supplied function for creating and configuring a 54 | // connection 55 | Dial: func() (redis.Conn, error) { 56 | c, err := redis.Dial("tcp", addr) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | if password != "" { 62 | if _, err := c.Do("AUTH", password); err != nil { 63 | c.Close() 64 | return nil, err 65 | } 66 | } 67 | 68 | return c, nil 69 | }, 70 | } 71 | } 72 | 73 | func NewServer() Server { 74 | // Get environment 75 | environment := Development 76 | if os.Getenv("BLARG_ENV") == "production" { 77 | environment = Production 78 | } 79 | 80 | // Redis 81 | redisAddr := os.Getenv("REDIS_PORT_6379_TCP_ADDR") + 82 | ":" + os.Getenv("REDIS_PORT_6379_TCP_PORT") 83 | redisPool := newPool(redisAddr, os.Getenv("REDIS_PASSWORD")) 84 | 85 | // Redis store 86 | rediStore, err := redistore.NewRediStoreWithPool(redisPool, []byte("secret string ")) 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | // TokBox 92 | tokBoxKey := os.Getenv("TOKBOX_KEY") 93 | tb := tokbox.New( 94 | tokBoxKey, 95 | os.Getenv("TOKBOX_SECRET_KEY"), 96 | ) 97 | 98 | // OAuth 99 | gob.Register(&oauth.Credentials{}) 100 | oauthClient := oauth.Client{ 101 | TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token", 102 | ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authenticate", 103 | TokenRequestURI: "https://api.twitter.com/oauth/access_token", 104 | } 105 | oauthClient.Credentials.Token = os.Getenv("TWITTER_KEY") 106 | oauthClient.Credentials.Secret = os.Getenv("TWITTER_SECRET_KEY") 107 | 108 | // State 109 | state := NewLocalState() 110 | 111 | // Config 112 | clientBaseUrl := "http://localhost:8000" 113 | forceHTTPS := false 114 | if environment == Production { 115 | clientBaseUrl = "https://blarg-im.herokuapp.com" 116 | forceHTTPS = true 117 | } 118 | 119 | return Server{ 120 | RedisPool: redisPool, 121 | RediStore: rediStore, 122 | OAuthClient: oauthClient, 123 | State: &state, 124 | ClientBaseUrl: clientBaseUrl, 125 | ForceHTTPS: forceHTTPS, 126 | TokBox: tb, 127 | TokBoxKey: tokBoxKey, 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { 4 | CONNECT_FEED, 5 | SHOW_CALLERS, 6 | HIDE_CALLERS, 7 | HANDLE_ERROR, 8 | 9 | JOIN_ACTION, 10 | LEAVE_ACTION, 11 | ADD_COMMENT_ACTION, 12 | CHANGE_TITLE_ACTION, 13 | CALL_ACTION, 14 | CANCEL_CALL_ACTION, 15 | ACCEPT_CALLER_ACTION, 16 | LEAVE_SEAT_ACTION, 17 | } from 'constants' 18 | 19 | // connectFeed() 20 | export const connectFeed = createAction(CONNECT_FEED) 21 | // handleError(error) 22 | export const handleError = createAction(HANDLE_ERROR) 23 | // showCallers() 24 | export const showCallers = createAction(SHOW_CALLERS) 25 | // hideCallers() 26 | export const hideCallers = createAction(HIDE_CALLERS) 27 | // changeTitle(title) 28 | export const changeTitle = createAction(CHANGE_TITLE_ACTION) 29 | // join(roomId) 30 | export const join = createAction(JOIN_ACTION) 31 | // leave() 32 | export const leave = createAction(LEAVE_ACTION) 33 | // addComment({ text, senderId }) 34 | export const addComment = createAction(ADD_COMMENT_ACTION) 35 | // acceptCaller(userId) 36 | export const acceptCaller = createAction(ACCEPT_CALLER_ACTION) 37 | // call(userId) 38 | export const call = createAction(CALL_ACTION) 39 | // cancelCall(userId) 40 | export const cancelCall = createAction(CANCEL_CALL_ACTION) 41 | // leaveSeat(userId) 42 | export const leaveSeat = createAction(LEAVE_SEAT_ACTION) 43 | -------------------------------------------------------------------------------- /src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import React, { PropTypes } from 'react' 3 | import classnames from 'classnames' 4 | 5 | const Button = props => { 6 | const Component = props.component 7 | 8 | return ( 9 | props.onClick && props.onClick()} 15 | /> 16 | ) 17 | } 18 | 19 | Button.propTypes = { 20 | className: PropTypes.string, 21 | children: PropTypes.node.isRequired, 22 | component: PropTypes.oneOfType([ 23 | PropTypes.string, 24 | PropTypes.func, 25 | ]), 26 | onClick: PropTypes.func, 27 | primary: PropTypes.bool, 28 | } 29 | 30 | Button.defaultProps = { 31 | component: 'button', 32 | } 33 | 34 | export default Button 35 | -------------------------------------------------------------------------------- /src/components/CallersPanel.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import { 5 | hideCallers, 6 | acceptCaller, 7 | } from 'actions' 8 | import CloseButton from 'components/CloseButton' 9 | import CallersPanelCaller from 'components/CallersPanelCaller' 10 | 11 | class CallersPanel extends Component { 12 | 13 | static propTypes = { 14 | callers: PropTypes.arrayOf( 15 | PropTypes.string.isRequired, 16 | ).isRequired, 17 | hideCallers: PropTypes.func.isRequired, 18 | acceptCaller: PropTypes.func.isRequired, 19 | } 20 | 21 | renderCaller = (userId) => ( 22 | 27 | ) 28 | 29 | // TODO: overlay (click to close) 30 | render() { 31 | return ( 32 |
33 |
34 | 35 | 36 | {this.props.callers.length} 37 | 38 | {' '} 39 | {this.props.callers.length === 1 ? 'person' : 'people'} 40 | {' '} 41 | calling in. 42 | 43 | 44 |
45 |
46 | {this.props.callers.map(this.renderCaller)} 47 |
48 |
49 | ) 50 | } 51 | } 52 | 53 | export default connect( 54 | ({ room }) => ({ callers: room.callers }), 55 | { 56 | hideCallers, 57 | acceptCaller, 58 | }, 59 | )(CallersPanel) 60 | -------------------------------------------------------------------------------- /src/components/CallersPanelCaller.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import User from 'components/User' 5 | import Button from 'components/Button' 6 | import { acceptCaller } from 'actions' 7 | 8 | class CallersPanelCaller extends Component { 9 | 10 | static propTypes = { 11 | acceptCaller: PropTypes.func.isRequired, 12 | userId: PropTypes.string.isRequired, 13 | } 14 | 15 | acceptCaller = () => { 16 | this.props.acceptCaller(this.props.userId) 17 | } 18 | 19 | render() { 20 | return ( 21 |
25 | 29 | 35 |
36 | ) 37 | } 38 | } 39 | 40 | export default connect(null, { 41 | acceptCaller, 42 | })(CallersPanelCaller) 43 | -------------------------------------------------------------------------------- /src/components/CloseButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import Icon from 'components/Icon' 4 | 5 | const CloseButton = props => ( 6 | 12 | ) 13 | 14 | CloseButton.propTypes = { 15 | onClick: PropTypes.func.isRequired, 16 | } 17 | 18 | export default CloseButton 19 | -------------------------------------------------------------------------------- /src/components/Comment.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | import User from 'components/User' 4 | import Username from 'components/Username' 5 | 6 | class Comment extends Component { 7 | 8 | static propTypes = { 9 | comment: PropTypes.shape({ 10 | text: PropTypes.string.isRequired, 11 | senderId: PropTypes.string.isRequired, 12 | }).isRequired, 13 | } 14 | 15 | shouldComponentUpdate() { 16 | return false 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 | 23 |
24 | 25 | 26 | {this.props.comment.text} 27 | 28 |
29 |
30 | ) 31 | } 32 | } 33 | 34 | export default Comment 35 | -------------------------------------------------------------------------------- /src/components/CommentInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class CommentInput extends Component { 4 | 5 | static propTypes = { 6 | onAddComment: PropTypes.func.isRequired, 7 | } 8 | 9 | state = { 10 | commentText: '', 11 | } 12 | 13 | handleSubmit = (event) => { 14 | event.preventDefault() 15 | if (!this.state.commentText) { 16 | return 17 | } 18 | this.props.onAddComment(this.state.commentText) 19 | this.setState({ commentText: '' }) 20 | } 21 | 22 | handleChangeCommentText = (event) => { 23 | this.setState({ commentText: event.target.value }) 24 | } 25 | 26 | render() { 27 | return ( 28 |
29 | 34 |
35 | ) 36 | } 37 | } 38 | 39 | export default CommentInput 40 | -------------------------------------------------------------------------------- /src/components/Comments.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | import Comment from 'components/Comment' 4 | import EventNotice from 'components/EventNotice' 5 | import CommentInput from 'components/CommentInput' 6 | import TwitterLogin from 'components/TwitterLogin' 7 | 8 | class Comments extends Component { 9 | 10 | static propTypes = { 11 | userId: PropTypes.string, 12 | roomId: PropTypes.string.isRequired, 13 | comments: PropTypes.arrayOf(PropTypes.shape({ 14 | text: PropTypes.string, 15 | user: PropTypes.string, 16 | })).isRequired, 17 | onAddComment: PropTypes.func.isRequired, 18 | } 19 | 20 | state = { 21 | unreadMessages: 0, 22 | } 23 | 24 | componentDidMount() { 25 | // Show the latest comments by default. 26 | this.scrollToBottom() 27 | this.node.addEventListener('scroll', this.handleScroll) 28 | } 29 | 30 | componentWillUpdate(nextProps) { 31 | const newCommentCount = nextProps.comments.length 32 | const oldCommentCount = this.props.comments.length 33 | if (newCommentCount > oldCommentCount) { 34 | this.shouldScroll = this.isScrolledToBottom() 35 | if (!this.shouldScroll) { 36 | this.setState({ 37 | unreadMessages: this.state.unreadMessages + ( 38 | newCommentCount - oldCommentCount 39 | ), 40 | }) 41 | } 42 | } 43 | } 44 | 45 | componentDidUpdate() { 46 | if (this.shouldScroll) { 47 | this.scrollToBottom() 48 | } 49 | this.shouldScroll = false 50 | } 51 | 52 | componentWillUnmount() { 53 | this.node.removeEventListener('scroll', this.handleScroll) 54 | } 55 | 56 | setNode = (node) => { 57 | this.node = node 58 | } 59 | 60 | isScrolledToBottom = () => this.node.scrollTop === ( 61 | this.node.scrollHeight - this.node.offsetHeight 62 | ) 63 | 64 | scrollToBottom = () => { 65 | this.node.scrollTop = this.node.scrollHeight 66 | } 67 | 68 | showUnreadMessages = () => { 69 | this.scrollToBottom() 70 | this.setState({ unreadMessages: 0 }) 71 | } 72 | 73 | handleAddComment = (commentText) => { 74 | this.props.onAddComment(commentText) 75 | } 76 | 77 | handleScroll = () => { 78 | if (this.isScrolledToBottom() && this.state.unreadMessages > 0) { 79 | this.setState({ unreadMessages: 0 }) 80 | } 81 | } 82 | 83 | renderComment = (comment, index) => { 84 | const CommentComponent = comment.event 85 | ? EventNotice 86 | : Comment 87 | 88 | return ( 89 | 90 | ) 91 | } 92 | 93 | render() { 94 | return ( 95 |
96 |
97 | {this.props.comments.map(this.renderComment)} 98 |
99 | {this.state.unreadMessages > 0 && ( 100 | 105 | Show {this.state.unreadMessages} unread messages 106 | 107 | )} 108 | { 109 | this.props.userId ? ( 110 | 111 | ) : ( 112 | Login to Chat 115 | ) 116 | } 117 |
118 | ) 119 | } 120 | } 121 | 122 | export default Comments 123 | -------------------------------------------------------------------------------- /src/components/DropdownButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import classnames from 'classnames' 3 | 4 | import Icon from 'components/Icon' 5 | import DropdownList from 'components/DropdownList' 6 | 7 | class DropdownButton extends Component { 8 | 9 | static propTypes = { 10 | title: PropTypes.node.isRequired, 11 | children: PropTypes.node.isRequired, 12 | className: PropTypes.string, 13 | noArrow: PropTypes.bool, 14 | } 15 | 16 | state = { active: false }; 17 | 18 | toggle = event => { 19 | if (event) { 20 | event.stopPropagation() 21 | } 22 | 23 | this.setState({ active: !this.state.active }) 24 | } 25 | 26 | renderArrow = () => ( 27 | 30 | ) 31 | 32 | render() { 33 | const className = classnames( 34 | 'DropdownButton', this.props.className 35 | ) 36 | 37 | return ( 38 | 52 | ) 53 | } 54 | } 55 | 56 | export default DropdownButton 57 | -------------------------------------------------------------------------------- /src/components/DropdownList.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import React, { PropTypes, Component } from 'react' 4 | import { findDOMNode } from 'react-dom' 5 | 6 | export default 7 | class DropdownList extends Component { 8 | 9 | static propTypes = { 10 | onOutsideClick: PropTypes.func.isRequired, 11 | children: PropTypes.node, 12 | } 13 | 14 | componentDidMount() { 15 | window.addEventListener('click', this.handleClick) 16 | } 17 | 18 | componentWillUnmount() { 19 | window.removeEventListener('click', this.handleClick) 20 | } 21 | 22 | handleClick = event => { 23 | if (!this.props.children) { 24 | return 25 | } 26 | 27 | // Hide the dropdown if there is a click outside 28 | // the component. 29 | const node = findDOMNode(this) 30 | let parentNode = event.target 31 | 32 | while (parentNode) { 33 | if (parentNode === node) { 34 | return 35 | } 36 | parentNode = parentNode.parentNode 37 | } 38 | 39 | this.props.onOutsideClick() 40 | } 41 | 42 | render = () => this.props.children && ( 43 |
    44 | {this.props.children} 45 |
46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /src/components/EmptySeat.jsx: -------------------------------------------------------------------------------- 1 | /* global BASE_URL */ 2 | 3 | import React, { Component, PropTypes } from 'react' 4 | import { connect } from 'react-redux' 5 | 6 | import Button from 'components/Button' 7 | import { 8 | showCallers, 9 | call, 10 | cancelCall, 11 | } from 'actions' 12 | import User from 'components/User' 13 | 14 | const CALLER_PREVIEW_LIMIT = 5 15 | 16 | const renderCaller = (userId) => ( 17 | 21 | ) 22 | 23 | class EmptySeat extends Component { 24 | 25 | static propTypes = { 26 | userId: PropTypes.string.isRequired, 27 | callers: PropTypes.arrayOf( 28 | PropTypes.string.isRequired 29 | ).isRequired, 30 | isHost: PropTypes.bool.isRequired, 31 | showCallers: PropTypes.func.isRequired, 32 | call: PropTypes.func.isRequired, 33 | cancelCall: PropTypes.func.isRequired, 34 | size: PropTypes.number.isRequired, 35 | seatCount: PropTypes.number.isRequired, 36 | } 37 | 38 | call = () => { 39 | this.props.call(this.props.userId) 40 | } 41 | 42 | cancelCall = () => { 43 | this.props.cancelCall(this.props.userId) 44 | } 45 | 46 | renderJoin() { 47 | const { callers, userId } = this.props 48 | const isCalling = callers.indexOf(userId) > -1 49 | return isCalling ? [ 50 |
51 | 52 | Calling in... 53 |
, 54 | , 57 | ] : ( 58 | 61 | ) 62 | } 63 | 64 | renderCallers() { 65 | return this.props.callers.length ? [ 66 |
67 | {this.props.callers.slice(0, CALLER_PREVIEW_LIMIT).map(renderCaller)} 68 |
, 69 | , 76 | ] : [ 77 |
78 | Waiting for callers... 79 |
, 80 | this.props.seatCount === 0 ? ( 81 |
82 | Share: 83 | {' '} 84 | 85 | {BASE_URL}/{this.props.userId} 86 | 87 |
88 | ) : null, 89 | ] 90 | } 91 | 92 | render() { 93 | const { size } = this.props 94 | return ( 95 |
96 | {this.props.isHost ? this.renderCallers() : this.renderJoin()} 97 |
98 | ) 99 | } 100 | } 101 | 102 | export default connect(({ user }) => ({ userId: user.id }), { 103 | showCallers, 104 | call, 105 | cancelCall, 106 | })(EmptySeat) 107 | -------------------------------------------------------------------------------- /src/components/EventNotice.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import Username from 'components/Username' 4 | 5 | const EventNotice = props => ( 6 |
7 | 8 | {' '} 9 | {props.comment.joined ? 'joined' : 'left'} 10 |
11 | ) 12 | 13 | EventNotice.propTypes = { 14 | comment: PropTypes.shape({ 15 | senderId: PropTypes.string.isRequired, 16 | joined: PropTypes.bool.isRequired, 17 | }).isRequired, 18 | } 19 | 20 | export default EventNotice 21 | -------------------------------------------------------------------------------- /src/components/Icon.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const Icon = props => ( 4 | 5 | ) 6 | 7 | Icon.propTypes = { 8 | type: PropTypes.string.isRequired, 9 | } 10 | 11 | export default Icon 12 | -------------------------------------------------------------------------------- /src/components/MenuItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const MenuItem = (props) => ( 4 |
  • 5 | 6 | {props.children} 7 | 8 |
  • 9 | ) 10 | 11 | MenuItem.propTypes = { 12 | children: PropTypes.node.isRequired, 13 | } 14 | 15 | export default MenuItem 16 | -------------------------------------------------------------------------------- /src/components/ModalButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class ModalButton extends Component { 4 | 5 | static propTypes = { 6 | modal: PropTypes.func.isRequired, 7 | modalProps: PropTypes.shape({}), 8 | children: PropTypes.node.isRequired, 9 | className: PropTypes.string, 10 | } 11 | 12 | state = { 13 | showModal: false, 14 | } 15 | 16 | handleOnClick = () => ( 17 | this.setState({ showModal: !this.state.showModal }) 18 | ) 19 | 20 | handleClose = () => ( 21 | this.setState({ showModal: false }) 22 | ) 23 | 24 | handleContainerClick = (event) => { 25 | event.stopPropagation() 26 | } 27 | 28 | renderChildren = () => React.cloneElement( 29 | React.Children.only(this.props.children), 30 | { onClick: this.handleOnClick } 31 | ) 32 | 33 | renderModalContainer() { 34 | const ModalComponent = this.props.modal 35 | return ( 36 |
    37 |
    38 | 42 |
    43 |
    44 | ) 45 | } 46 | 47 | render() { 48 | return ( 49 | 50 | {this.renderChildren()} 51 | {this.state.showModal && this.renderModalContainer()} 52 | 53 | ) 54 | } 55 | } 56 | 57 | export default ModalButton 58 | -------------------------------------------------------------------------------- /src/components/ProfileDropdown.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | // import User from 'components/User' 4 | import DropdownButton from 'components/DropdownButton' 5 | import MenuItem from 'components/MenuItem' 6 | 7 | const ProfileDropdown = (props) => ( 8 | 12 | My Room 13 | Logout 14 | 15 | ) 16 | 17 | ProfileDropdown.propTypes = { 18 | userId: PropTypes.string.isRequired, 19 | } 20 | 21 | export default ProfileDropdown 22 | -------------------------------------------------------------------------------- /src/components/Room.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import ViewerInfo from 'components/ViewerInfo' 5 | import ViewerList from 'components/ViewerList' 6 | import Comments from 'components/Comments' 7 | import { 8 | addComment, 9 | changeTitle, 10 | } from 'actions' 11 | import Username from 'components/Username' 12 | import Seats from 'components/Seats' 13 | import CallersPanel from 'components/CallersPanel' 14 | import RoomTitle from 'components/RoomTitle' 15 | import ProfileDropdown from 'components/ProfileDropdown' 16 | 17 | class Room extends Component { 18 | 19 | static propTypes = { 20 | roomId: PropTypes.string.isRequired, 21 | userId: PropTypes.string, 22 | room: PropTypes.shape({ 23 | title: PropTypes.string.isRequired, 24 | viewers: PropTypes.arrayOf(PropTypes.string).isRequired, 25 | totalViewers: PropTypes.number.isRequired, 26 | activeViewers: PropTypes.number.isRequired, 27 | comments: PropTypes.array.isRequired, 28 | }).isRequired, 29 | addComment: PropTypes.func.isRequired, 30 | changeTitle: PropTypes.func.isRequired, 31 | } 32 | 33 | addComment = (text) => { 34 | this.props.addComment({ 35 | text, 36 | senderId: this.props.userId, 37 | }) 38 | } 39 | 40 | render() { 41 | const { room, userId, roomId } = this.props 42 | const isHost = userId ? (userId === roomId) : false 43 | 44 | return ( 45 |
    46 |
    47 | 52 | 56 | 57 | {userId && ()} 58 |
    59 |
    60 | {room.showCallers && ()} 61 |
    62 | { 63 | (this.props.room.viewers.indexOf(roomId) > -1) ? ( 64 | 70 | ) : ( 71 |
    72 |
    73 | :/ 74 |
    75 |
    76 | 77 | {' isn\'t here right now.'} 78 |
    79 |
    80 | ) 81 | } 82 |
    83 | 89 |
    90 |
    91 | ) 92 | } 93 | } 94 | 95 | export default connect( 96 | ({ user }) => ({ userId: user.id }), { 97 | addComment, 98 | changeTitle, 99 | } 100 | )(Room) 101 | -------------------------------------------------------------------------------- /src/components/RoomTitle.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | class RoomTitle extends Component { 4 | 5 | static propTypes = { 6 | onChange: PropTypes.func.isRequired, 7 | title: PropTypes.string, 8 | isHost: PropTypes.bool.isRequired, 9 | } 10 | 11 | constructor(...args) { 12 | super(...args) 13 | 14 | this.state = { title: this.props.title } 15 | } 16 | 17 | setInputNode = (node) => { 18 | this.inputNode = node 19 | } 20 | 21 | handleChangeTitle = (event) => { 22 | this.setState({ title: event.target.value }) 23 | } 24 | 25 | handleBlurTitle = (event) => { 26 | this.props.onChange(event.target.value) 27 | } 28 | 29 | handleSubmit = (event) => { 30 | event.preventDefault() 31 | this.props.onChange(this.state.title) 32 | this.inputNode.blur() 33 | } 34 | 35 | render() { 36 | const content = this.props.isHost ? ( 37 |
    38 | 45 |
    46 | ) : ( 47 | {this.props.title} 48 | ) 49 | 50 | return ( 51 | 52 | {content} 53 | 54 | ) 55 | } 56 | } 57 | 58 | export default RoomTitle 59 | -------------------------------------------------------------------------------- /src/components/Seat.jsx: -------------------------------------------------------------------------------- 1 | /* global OT */ 2 | 3 | import React, { PropTypes } from 'react' 4 | 5 | import User from 'components/User' 6 | import CloseButton from 'components/CloseButton' 7 | 8 | class Seat extends React.Component { 9 | 10 | static propTypes = { 11 | userId: PropTypes.string, 12 | seatUserId: PropTypes.string.isRequired, 13 | isHost: PropTypes.bool.isRequired, 14 | size: PropTypes.number.isRequired, 15 | session: PropTypes.shape({ 16 | on: PropTypes.func.isRequired, 17 | off: PropTypes.func.isRequired, 18 | publish: PropTypes.func.isRequired, 19 | subscribe: PropTypes.func.isRequired, 20 | }).isRequired, 21 | leaveSeat: PropTypes.func.isRequired, 22 | } 23 | 24 | componentDidMount() { 25 | const { session } = this.props 26 | if (this.isMySeat()) { 27 | // Publish the stream if the session is connected. 28 | if (session.connection) { 29 | this.publish() 30 | } else { 31 | session.once('sessionConnected', this.publish) 32 | } 33 | } else { 34 | this.props.session.on('streamCreated', this.handleStreamCreated) 35 | } 36 | } 37 | 38 | componentWillUnmount() { 39 | const { session } = this.props 40 | 41 | if (!this.isMySeat) { 42 | this.props.session.off('streamCreated', this.handleStreamCreated) 43 | } 44 | 45 | if (this.publisher) { 46 | session.unpublish(this.publisher) 47 | } 48 | 49 | if (this.subscriber) { 50 | session.unsubscribe(this.subscriber) 51 | } 52 | } 53 | 54 | setVideoNode = (videoNode) => { 55 | this.videoNode = videoNode 56 | } 57 | 58 | handleStreamCreated = (event) => { 59 | const userId = event.stream.connection.data.replace('userId=', '') 60 | if (userId !== this.props.seatUserId) { 61 | return 62 | } 63 | 64 | this.subscriber = this.props.session.subscribe(event.stream, null, { 65 | insertDefaultUI: false, 66 | }) 67 | 68 | this.subscriber.once('videoElementCreated', this.handleVideoElementCreated) 69 | } 70 | 71 | closeSeat = () => { 72 | this.props.leaveSeat(this.props.seatUserId) 73 | } 74 | 75 | isMySeat() { 76 | const { userId, seatUserId } = this.props 77 | return userId && (userId === seatUserId) 78 | } 79 | 80 | publish = () => { 81 | this.publisher = OT.initPublisher(null, { 82 | resolution: '640x480', 83 | insertDefaultUI: false, 84 | }, (error) => { 85 | if (error) throw error 86 | }) 87 | 88 | this.publisher.once('videoElementCreated', this.handleVideoElementCreated) 89 | this.props.session.publish(this.publisher) 90 | } 91 | 92 | handleVideoElementCreated = (event) => { 93 | const { videoNode } = this 94 | if (!videoNode) { 95 | return 96 | } 97 | 98 | while (videoNode.firstChild) { 99 | videoNode.removeChild(videoNode.firstChild) 100 | } 101 | videoNode.appendChild(event.element) 102 | } 103 | 104 | renderCloseButton() { 105 | const isMySeat = this.isMySeat() 106 | return (this.props.isHost ? !isMySeat : isMySeat) && ( 107 | 108 | ) 109 | } 110 | 111 | render() { 112 | const { size } = this.props 113 | return ( 114 |
    118 |
    122 | {this.renderCloseButton()} 123 |
    124 | 128 |
    129 |
    130 | ) 131 | } 132 | } 133 | 134 | export default Seat 135 | -------------------------------------------------------------------------------- /src/components/Seats.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | /* global OT */ 3 | 4 | import React, { PropTypes } from 'react' 5 | import { connect } from 'react-redux' 6 | 7 | import Seat from 'components/Seat' 8 | import EmptySeat from 'components/EmptySeat' 9 | import { leaveSeat } from 'actions' 10 | 11 | const MAX_GUESTS = 3 12 | const SEAT_MARGIN = 12 13 | const CONTAINER_SIZE_DIFF = 60 14 | const SEAT_SIZE_DIFF = SEAT_MARGIN * 2 15 | 16 | class Seats extends React.Component { 17 | 18 | static propTypes = { 19 | isHost: PropTypes.bool.isRequired, 20 | userId: PropTypes.string, 21 | roomId: PropTypes.string.isRequired, 22 | room: PropTypes.shape({ 23 | seats: PropTypes.arrayOf( 24 | PropTypes.string.isRequired, 25 | ).isRequired, 26 | token: PropTypes.string.isRequired, 27 | tokBoxKey: PropTypes.string.isRequired, 28 | sessionId: PropTypes.string.isRequired, 29 | }).isRequired, 30 | leaveSeat: PropTypes.func.isRequired, 31 | } 32 | 33 | state = { 34 | containerSize: 0, 35 | size: false, 36 | } 37 | 38 | componentWillMount() { 39 | const { tokBoxKey, sessionId, token } = this.props.room 40 | this.session = OT.initSession(tokBoxKey, sessionId) 41 | this.session.connect(token, (error) => { 42 | if (error) { 43 | throw error 44 | // return 45 | } 46 | }) 47 | } 48 | 49 | componentDidMount() { 50 | window.addEventListener('resize', this.handleResize) 51 | this.handleResize() 52 | } 53 | 54 | componentWillUnmount() { 55 | window.removeEventListener('resize', this.handleResize) 56 | } 57 | 58 | setSeatsNode = (node) => { 59 | this.node = node 60 | } 61 | 62 | handleResize = () => { 63 | if (!this.node) { 64 | return 65 | } 66 | 67 | const rect = this.node.getBoundingClientRect() 68 | const containerSize = Math.min(rect.height, rect.width) - CONTAINER_SIZE_DIFF 69 | const size = Math.floor(containerSize / 2) - SEAT_SIZE_DIFF 70 | this.setState({ size, containerSize }) 71 | } 72 | 73 | seats = {} 74 | streams = {} 75 | 76 | renderSeat = (seat) => ( 77 | 86 | ) 87 | 88 | render() { 89 | const { size, containerSize } = this.state 90 | 91 | const { roomId, userId, room, isHost } = this.props 92 | const seats = [{ 93 | // isHost: true, 94 | seatUserId: roomId, 95 | }].concat(room.seats.map(seatUserId => ({ 96 | seatUserId, 97 | // isHost: false, 98 | }))) 99 | 100 | const showEmptySeat = (room.seats.length < MAX_GUESTS) 101 | && userId 102 | && (isHost || room.seats.indexOf(userId) === -1) 103 | 104 | return ( 105 |
    106 |
    110 | {size && seats.map(this.renderSeat)} 111 | { 112 | size && showEmptySeat && ( 113 | 120 | ) 121 | } 122 |
    123 |
    124 | ) 125 | } 126 | } 127 | 128 | export default connect(null, { 129 | leaveSeat, 130 | })(Seats) 131 | -------------------------------------------------------------------------------- /src/components/TwitterLogin.jsx: -------------------------------------------------------------------------------- 1 | /* global API_BASE_URL */ 2 | 3 | import React, { PropTypes } from 'react' 4 | 5 | import Button from 'components/Button' 6 | import Icon from 'components/Icon' 7 | 8 | const TwitterLogin = (props) => { 9 | const params = props.roomId 10 | ? `?roomId=${props.roomId}` 11 | : '' 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | TwitterLogin.propTypes = { 25 | children: PropTypes.node, 26 | roomId: PropTypes.string, 27 | } 28 | 29 | export default TwitterLogin 30 | -------------------------------------------------------------------------------- /src/components/User.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import Username from 'components/Username' 4 | import UserPreviewButton from 'components/UserPreviewButton' 5 | import UserIcon from 'components/UserIcon' 6 | 7 | const renderUser = (username) => ( 8 | 9 | 10 | 11 | 12 | 13 | ) 14 | 15 | const User = (props) => (props.showUsername ? ( 16 | 17 | {renderUser(props.username)} 18 | 19 | 20 | ) : renderUser(props.username)) 21 | 22 | // TODO: add `shouldComponentUpdate` 23 | 24 | User.propTypes = { 25 | username: PropTypes.string.isRequired, 26 | showUsername: PropTypes.bool, 27 | } 28 | 29 | export default User 30 | 31 | // TODO: reorganize users 32 | -------------------------------------------------------------------------------- /src/components/UserIcon.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | const UserIcon = (props) => ( 4 | {props.userId} 9 | ) 10 | 11 | UserIcon.propTypes = { 12 | userId: PropTypes.string.isRequired, 13 | } 14 | 15 | export default UserIcon 16 | -------------------------------------------------------------------------------- /src/components/UserModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import UserIcon from 'components/UserIcon' 4 | import CloseButton from 'components/CloseButton' 5 | import Icon from 'components/Icon' 6 | import Button from 'components/Button' 7 | 8 | const UserModal = (props) => ( 9 |
    10 | 11 |
    12 | 13 | @{props.userId} 14 | 19 | 20 | 21 |
    22 |
    23 | 26 |
    27 |
    28 | ) 29 | 30 | UserModal.propTypes = { 31 | userId: PropTypes.string.isRequired, 32 | onClose: PropTypes.func.isRequired, 33 | } 34 | 35 | export default UserModal 36 | -------------------------------------------------------------------------------- /src/components/UserPreviewButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import ModalButton from 'components/ModalButton' 4 | import UserModal from 'components/UserModal' 5 | 6 | const UserPreviewButton = props => ( 7 | 12 | {props.children} 13 | 14 | ) 15 | 16 | UserPreviewButton.propTypes = { 17 | userId: PropTypes.string.isRequired, 18 | children: PropTypes.node.isRequired, 19 | className: PropTypes.string, 20 | } 21 | 22 | export default UserPreviewButton 23 | -------------------------------------------------------------------------------- /src/components/Username.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import UserPreviewButton from 'components/UserPreviewButton' 4 | 5 | const Username = (props) => ( 6 | 7 | 8 | @{props.username}{props.possessive && "'s"} 9 | 10 | 11 | ) 12 | 13 | Username.propTypes = { 14 | username: PropTypes.string.isRequired, 15 | possessive: PropTypes.bool, 16 | } 17 | 18 | export default Username 19 | -------------------------------------------------------------------------------- /src/components/ViewerInfo.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import Icon from 'components/Icon' 4 | 5 | const ViewerInfo = (props) => ( 6 |
    7 | 8 | {props.totalViewers} 9 | 10 | 11 | {props.activeViewers} 12 | 13 |
    14 | ) 15 | 16 | ViewerInfo.propTypes = { 17 | totalViewers: PropTypes.number.isRequired, 18 | activeViewers: PropTypes.number.isRequired, 19 | } 20 | 21 | export default ViewerInfo 22 | -------------------------------------------------------------------------------- /src/components/ViewerList.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | import User from 'components/User' 4 | 5 | const renderViewer = username => ( 6 | 7 | ) 8 | 9 | const ViewerList = props => ( 10 |
    11 | {props.viewers.map(renderViewer)} 12 |
    13 | ) 14 | 15 | ViewerList.propTypes = { 16 | viewers: PropTypes.arrayOf(PropTypes.string).isRequired, 17 | } 18 | 19 | export default ViewerList 20 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const CONNECT_FEED = 'CONNECT_FEED' 2 | export const ROOM_DATA = 'ROOM_DATA' 3 | export const SHOW_CALLERS = 'SHOW_CALLERS' 4 | export const HIDE_CALLERS = 'HIDE_CALLERS' 5 | export const HANDLE_ERROR = 'HANDLE_ERROR' 6 | export const RESET_ROOM = 'RESET_ROOM' 7 | 8 | export const JOIN_ACTION = 'JOIN_ACTION' 9 | export const JOIN = 'JOIN' 10 | 11 | export const LEAVE_ACTION = 'LEAVE_ACTION' 12 | export const LEAVE = 'LEAVE' 13 | 14 | export const ADD_COMMENT_ACTION = 'ADD_COMMENT_ACTION' 15 | export const ADD_COMMENT = 'ADD_COMMENT' 16 | 17 | export const CHANGE_TITLE_ACTION = 'CHANGE_TITLE_ACTION' 18 | export const CHANGE_TITLE = 'CHANGE_TITLE' 19 | 20 | export const ACCEPT_CALLER_ACTION = 'ACCEPT_CALLER_ACTION' 21 | export const ACCEPT_CALLER = 'ACCEPT_CALLER' 22 | 23 | export const LEAVE_SEAT_ACTION = 'LEAVE_SEAT_ACTION' 24 | export const LEAVE_SEAT = 'LEAVE_SEAT' 25 | 26 | export const CALL_ACTION = 'CALL_ACTION' 27 | export const CALL = 'CALL' 28 | 29 | export const CANCEL_CALL_ACTION = 'CANCEL_CALL_ACTION' 30 | export const CANCEL_CALL = 'CANCEL_CALL' 31 | -------------------------------------------------------------------------------- /src/handlers/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | 3 | export default 4 | class App extends Component { 5 | 6 | static propTypes = { 7 | children: PropTypes.node, 8 | } 9 | 10 | state = {} 11 | 12 | render() { 13 | return ( 14 |
    15 | {this.props.children} 16 |
    17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/handlers/Home.jsx: -------------------------------------------------------------------------------- 1 | /* global API_BASE_URL */ 2 | 3 | import React, { Component, PropTypes } from 'react' 4 | import { connect } from 'react-redux' 5 | 6 | import TwitterLogin from 'components/TwitterLogin' 7 | 8 | class Home extends Component { 9 | 10 | static propTypes = { 11 | userId: PropTypes.string, 12 | } 13 | 14 | static contextTypes = { 15 | router: PropTypes.object.isRequired, 16 | } 17 | 18 | componentWillMount() { 19 | // If the user is authenticated, immediately redirect to their room. 20 | const { userId } = this.props 21 | 22 | if (userId) { 23 | this.context.router.replace(userId) 24 | } 25 | } 26 | 27 | render() { 28 | return ( 29 |
    30 |

    Blarg

    31 |

    Ramble with friends.

    32 | 33 |
    34 | ) 35 | } 36 | } 37 | 38 | export default connect( 39 | ({ user }) => ({ userId: user.id }) 40 | )(Home) 41 | -------------------------------------------------------------------------------- /src/handlers/RoomLoader.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import Room from 'components/Room' 5 | import { 6 | join, 7 | leave, 8 | } from 'actions' 9 | 10 | class RoomLoader extends React.Component { 11 | 12 | static propTypes = { 13 | params: PropTypes.shape({ 14 | roomId: PropTypes.string.isRequired, 15 | }).isRequired, 16 | join: PropTypes.func.isRequired, 17 | leave: PropTypes.func.isRequired, 18 | room: PropTypes.shape({}).isRequired, 19 | } 20 | 21 | componentWillMount() { 22 | const { roomId } = this.props.params 23 | this.props.join(roomId) 24 | } 25 | 26 | componentWillUnmount() { 27 | this.props.leave() 28 | } 29 | 30 | render() { 31 | const { room } = this.props 32 | if (room.loading) { 33 | return (
    Loading room...
    ) 34 | } else if (room.error) { 35 | return (
    You fail it.
    ) 36 | } 37 | 38 | return () 39 | } 40 | } 41 | 42 | export default connect( 43 | ({ room }) => ({ room }), { 44 | join, 45 | leave, 46 | } 47 | )(RoomLoader) 48 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import React from 'react' 4 | import { render } from 'react-dom' 5 | import { createStore, applyMiddleware, compose } from 'redux' 6 | import { Provider } from 'react-redux' 7 | import { Router, browserHistory } from 'react-router' 8 | // import createHistory from 'history/createBrowserHistory' 9 | // import { Router } from 'react-router' 10 | 11 | import reducer from './reducer' 12 | import routes from './routes' 13 | import { connectFeed } from './actions' 14 | import connectionMiddleware from './middleware/connection' 15 | import './style.scss' 16 | 17 | const store = createStore( 18 | reducer, 19 | {}, 20 | compose( 21 | applyMiddleware( 22 | connectionMiddleware, 23 | ), 24 | // reduxReactRouter({ createHistory }), 25 | window.devToolsExtension 26 | ? window.devToolsExtension() 27 | : f => f, 28 | ), 29 | ) 30 | 31 | store.dispatch(connectFeed()) 32 | 33 | window.store = store 34 | 35 | render(( 36 | 37 | 38 | {routes} 39 | 40 | 41 | ), document.getElementById('root')) 42 | -------------------------------------------------------------------------------- /src/middleware/connection.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | /* global WS_URL */ 3 | 4 | import { handleError } from 'actions' 5 | import { 6 | CONNECT_FEED, 7 | JOIN_ACTION, 8 | LEAVE_ACTION, 9 | ADD_COMMENT_ACTION, 10 | CHANGE_TITLE_ACTION, 11 | CALL_ACTION, 12 | CANCEL_CALL_ACTION, 13 | ACCEPT_CALLER_ACTION, 14 | LEAVE_SEAT_ACTION, 15 | } from 'constants' 16 | 17 | let connection 18 | const messages = [] 19 | 20 | const send = data => { 21 | if (connection.readyState === 1) { 22 | connection.send(data) 23 | } else { 24 | messages.push(data) 25 | } 26 | } 27 | 28 | const handleConnectionEvent = (event, data, dispatch) => dispatch({ 29 | type: event, 30 | payload: data, 31 | }) 32 | 33 | const connectionMiddleware = ({ 34 | dispatch, 35 | }) => (next) => (action) => { 36 | switch (action.type) { 37 | case CONNECT_FEED: 38 | connection = new WebSocket(WS_URL) 39 | connection.onerror = (error) => { 40 | dispatch(handleError(error)) 41 | } 42 | connection.onmessage = (event) => { 43 | const data = JSON.parse(event.data) 44 | handleConnectionEvent( 45 | data.type, data.payload, dispatch 46 | ) 47 | } 48 | connection.onopen = () => { 49 | messages.forEach(message => connection.send(message)) 50 | } 51 | break 52 | case ADD_COMMENT_ACTION: 53 | send( 54 | JSON.stringify({ 55 | type: action.type, 56 | payload: action.payload.text, 57 | }) 58 | ) 59 | break 60 | case JOIN_ACTION: 61 | case LEAVE_ACTION: 62 | case CHANGE_TITLE_ACTION: 63 | case CALL_ACTION: 64 | case CANCEL_CALL_ACTION: 65 | case ACCEPT_CALLER_ACTION: 66 | case LEAVE_SEAT_ACTION: 67 | send(JSON.stringify(action)) 68 | break 69 | default: 70 | break 71 | } 72 | 73 | return next(action) 74 | } 75 | 76 | export default connectionMiddleware 77 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import { combineReducers } from 'redux' 4 | import { handleActions } from 'redux-actions' 5 | import update from 'react-addons-update' 6 | 7 | import { 8 | ROOM_DATA, 9 | SHOW_CALLERS, 10 | HIDE_CALLERS, 11 | RESET_ROOM, 12 | 13 | JOIN_ACTION, 14 | JOIN, 15 | LEAVE_ACTION, 16 | LEAVE, 17 | ADD_COMMENT_ACTION, 18 | ADD_COMMENT, 19 | CHANGE_TITLE_ACTION, 20 | CHANGE_TITLE, 21 | ACCEPT_CALLER_ACTION, 22 | ACCEPT_CALLER, 23 | LEAVE_SEAT_ACTION, 24 | LEAVE_SEAT, 25 | CALL_ACTION, 26 | CALL, 27 | CANCEL_CALL_ACTION, 28 | CANCEL_CALL, 29 | } from 'constants' 30 | 31 | const addComment = (state, action) => update(state, { 32 | comments: { $push: [action.payload] }, 33 | }) 34 | 35 | const changeTitle = (state, action) => update(state, { 36 | title: { $set: action.payload }, 37 | }) 38 | 39 | const defaultRoom = { 40 | loading: true, 41 | error: null, 42 | showCallers: false, 43 | } 44 | 45 | const getDefaultRoom = () => defaultRoom 46 | 47 | const hideCallers = (state) => update(state, { 48 | showCallers: { $set: false }, 49 | }) 50 | 51 | const removeFromArray = value => values => ( 52 | values.filter(v => v !== value) 53 | ) 54 | 55 | const acceptCaller = (state, action) => update(state, { 56 | callers: { $apply: removeFromArray(action.payload) }, 57 | seats: { $push: [action.payload] }, 58 | showCallers: { $set: false }, 59 | }) 60 | 61 | const call = (state, action) => update(state, { 62 | callers: { $push: [action.payload] }, 63 | }) 64 | 65 | const leaveSeat = (state, action) => update(state, { 66 | seats: { $apply: removeFromArray(action.payload) }, 67 | }) 68 | 69 | const cancelCall = (state, action) => update(state, { 70 | callers: { $apply: removeFromArray(action.payload) }, 71 | }) 72 | 73 | const roomReducer = handleActions({ 74 | [ROOM_DATA]: (_, action) => action.payload, 75 | [SHOW_CALLERS]: (state) => update(state, { 76 | showCallers: { $set: true }, 77 | }), 78 | [HIDE_CALLERS]: hideCallers, 79 | [RESET_ROOM]: (state) => update(state, { 80 | callers: { $set: [] }, 81 | seats: { $set: [] }, 82 | }), 83 | [JOIN_ACTION]: getDefaultRoom, 84 | [JOIN]: (state, action) => update(state, { 85 | totalViewers: { $set: action.payload.totalViewers }, 86 | activeViewers: { $set: action.payload.activeViewers }, 87 | ...(action.payload.userId && { 88 | viewers: { $push: [action.payload.userId] }, 89 | comments: { $push: [{ 90 | senderId: action.payload.userId, 91 | event: true, 92 | joined: true, 93 | }] }, 94 | }), 95 | }), 96 | [LEAVE_ACTION]: getDefaultRoom, 97 | [LEAVE]: (state, action) => update(state, { 98 | totalViewers: { $set: action.payload.totalViewers }, 99 | activeViewers: { $set: action.payload.activeViewers }, 100 | ...(action.payload.userId && { 101 | viewers: { $apply: removeFromArray(action.payload.userId) }, 102 | comments: { $push: [{ 103 | senderId: action.payload.userId, 104 | event: true, 105 | joined: false, 106 | }] }, 107 | }), 108 | }), 109 | [ADD_COMMENT_ACTION]: addComment, 110 | [ADD_COMMENT]: addComment, 111 | [CHANGE_TITLE_ACTION]: changeTitle, 112 | [CHANGE_TITLE]: changeTitle, 113 | [ACCEPT_CALLER_ACTION]: acceptCaller, 114 | [ACCEPT_CALLER]: acceptCaller, 115 | [LEAVE_SEAT_ACTION]: leaveSeat, 116 | [LEAVE_SEAT]: leaveSeat, 117 | [CALL_ACTION]: call, 118 | [CALL]: call, 119 | [CANCEL_CALL_ACTION]: cancelCall, 120 | [CANCEL_CALL]: cancelCall, 121 | }, defaultRoom) 122 | 123 | const userReducer = (state = { 124 | id: window.BLARG_PAYLOAD.userId, 125 | }) => state 126 | 127 | export default combineReducers({ 128 | room: roomReducer, 129 | user: userReducer, 130 | }) 131 | -------------------------------------------------------------------------------- /src/routes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, IndexRoute } from 'react-router' 3 | 4 | import App from 'handlers/App' 5 | import Home from 'handlers/Home' 6 | import RoomLoader from 'handlers/RoomLoader' 7 | 8 | export default ( 9 | 10 | 11 | 12 | 13 | ) 14 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/internal/commandinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | const ( 22 | WatchState = 1 << iota 23 | MultiState 24 | SubscribeState 25 | MonitorState 26 | ) 27 | 28 | type CommandInfo struct { 29 | Set, Clear int 30 | } 31 | 32 | var commandInfos = map[string]CommandInfo{ 33 | "WATCH": {Set: WatchState}, 34 | "UNWATCH": {Clear: WatchState}, 35 | "MULTI": {Set: MultiState}, 36 | "EXEC": {Clear: WatchState | MultiState}, 37 | "DISCARD": {Clear: WatchState | MultiState}, 38 | "PSUBSCRIBE": {Set: SubscribeState}, 39 | "SUBSCRIBE": {Set: SubscribeState}, 40 | "MONITOR": {Set: MonitorState}, 41 | } 42 | 43 | func init() { 44 | for n, ci := range commandInfos { 45 | commandInfos[strings.ToLower(n)] = ci 46 | } 47 | } 48 | 49 | func LookupCommandInfo(commandName string) CommandInfo { 50 | if ci, ok := commandInfos[commandName]; ok { 51 | return ci 52 | } 53 | return commandInfos[strings.ToUpper(commandName)] 54 | } 55 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redis is a client for the Redis database. 16 | // 17 | // The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more 18 | // documentation about this package. 19 | // 20 | // Connections 21 | // 22 | // The Conn interface is the primary interface for working with Redis. 23 | // Applications create connections by calling the Dial, DialWithTimeout or 24 | // NewConn functions. In the future, functions will be added for creating 25 | // sharded and other types of connections. 26 | // 27 | // The application must call the connection Close method when the application 28 | // is done with the connection. 29 | // 30 | // Executing Commands 31 | // 32 | // The Conn interface has a generic method for executing Redis commands: 33 | // 34 | // Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | // 36 | // The Redis command reference (http://redis.io/commands) lists the available 37 | // commands. An example of using the Redis APPEND command is: 38 | // 39 | // n, err := conn.Do("APPEND", "key", "value") 40 | // 41 | // The Do method converts command arguments to binary strings for transmission 42 | // to the server as follows: 43 | // 44 | // Go Type Conversion 45 | // []byte Sent as is 46 | // string Sent as is 47 | // int, int64 strconv.FormatInt(v) 48 | // float64 strconv.FormatFloat(v, 'g', -1, 64) 49 | // bool true -> "1", false -> "0" 50 | // nil "" 51 | // all other types fmt.Print(v) 52 | // 53 | // Redis command reply types are represented using the following Go types: 54 | // 55 | // Redis type Go type 56 | // error redis.Error 57 | // integer int64 58 | // simple string string 59 | // bulk string []byte or nil if value not present. 60 | // array []interface{} or nil if value not present. 61 | // 62 | // Use type assertions or the reply helper functions to convert from 63 | // interface{} to the specific Go type for the command result. 64 | // 65 | // Pipelining 66 | // 67 | // Connections support pipelining using the Send, Flush and Receive methods. 68 | // 69 | // Send(commandName string, args ...interface{}) error 70 | // Flush() error 71 | // Receive() (reply interface{}, err error) 72 | // 73 | // Send writes the command to the connection's output buffer. Flush flushes the 74 | // connection's output buffer to the server. Receive reads a single reply from 75 | // the server. The following example shows a simple pipeline. 76 | // 77 | // c.Send("SET", "foo", "bar") 78 | // c.Send("GET", "foo") 79 | // c.Flush() 80 | // c.Receive() // reply from SET 81 | // v, err = c.Receive() // reply from GET 82 | // 83 | // The Do method combines the functionality of the Send, Flush and Receive 84 | // methods. The Do method starts by writing the command and flushing the output 85 | // buffer. Next, the Do method receives all pending replies including the reply 86 | // for the command just sent by Do. If any of the received replies is an error, 87 | // then Do returns the error. If there are no errors, then Do returns the last 88 | // reply. If the command argument to the Do method is "", then the Do method 89 | // will flush the output buffer and receive pending replies without sending a 90 | // command. 91 | // 92 | // Use the Send and Do methods to implement pipelined transactions. 93 | // 94 | // c.Send("MULTI") 95 | // c.Send("INCR", "foo") 96 | // c.Send("INCR", "bar") 97 | // r, err := c.Do("EXEC") 98 | // fmt.Println(r) // prints [1, 1] 99 | // 100 | // Concurrency 101 | // 102 | // Connections do not support concurrent calls to the write methods (Send, 103 | // Flush) or concurrent calls to the read method (Receive). Connections do 104 | // allow a concurrent reader and writer. 105 | // 106 | // Because the Do method combines the functionality of Send, Flush and Receive, 107 | // the Do method cannot be called concurrently with the other methods. 108 | // 109 | // For full concurrent access to Redis, use the thread-safe Pool to get and 110 | // release connections from within a goroutine. 111 | // 112 | // Publish and Subscribe 113 | // 114 | // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. 115 | // 116 | // c.Send("SUBSCRIBE", "example") 117 | // c.Flush() 118 | // for { 119 | // reply, err := c.Receive() 120 | // if err != nil { 121 | // return err 122 | // } 123 | // // process pushed message 124 | // } 125 | // 126 | // The PubSubConn type wraps a Conn with convenience methods for implementing 127 | // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods 128 | // send and flush a subscription management command. The receive method 129 | // converts a pushed message to convenient types for use in a type switch. 130 | // 131 | // psc := redis.PubSubConn{c} 132 | // psc.Subscribe("example") 133 | // for { 134 | // switch v := psc.Receive().(type) { 135 | // case redis.Message: 136 | // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) 137 | // case redis.Subscription: 138 | // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 139 | // case error: 140 | // return v 141 | // } 142 | // } 143 | // 144 | // Reply Helpers 145 | // 146 | // The Bool, Int, Bytes, String, Strings and Values functions convert a reply 147 | // to a value of a specific type. To allow convenient wrapping of calls to the 148 | // connection Do and Receive methods, the functions take a second argument of 149 | // type error. If the error is non-nil, then the helper function returns the 150 | // error. If the error is nil, the function converts the reply to the specified 151 | // type: 152 | // 153 | // exists, err := redis.Bool(c.Do("EXISTS", "foo")) 154 | // if err != nil { 155 | // // handle error return from c.Do or type conversion error. 156 | // } 157 | // 158 | // The Scan function converts elements of a array reply to Go types: 159 | // 160 | // var value1 int 161 | // var value2 string 162 | // reply, err := redis.Values(c.Do("MGET", "key1", "key2")) 163 | // if err != nil { 164 | // // handle error 165 | // } 166 | // if _, err := redis.Scan(reply, &value1, &value2); err != nil { 167 | // // handle error 168 | // } 169 | package redis 170 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "log" 21 | ) 22 | 23 | // NewLoggingConn returns a logging wrapper around a connection. 24 | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { 25 | if prefix != "" { 26 | prefix = prefix + "." 27 | } 28 | return &loggingConn{conn, logger, prefix} 29 | } 30 | 31 | type loggingConn struct { 32 | Conn 33 | logger *log.Logger 34 | prefix string 35 | } 36 | 37 | func (c *loggingConn) Close() error { 38 | err := c.Conn.Close() 39 | var buf bytes.Buffer 40 | fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) 41 | c.logger.Output(2, buf.String()) 42 | return err 43 | } 44 | 45 | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { 46 | const chop = 32 47 | switch v := v.(type) { 48 | case []byte: 49 | if len(v) > chop { 50 | fmt.Fprintf(buf, "%q...", v[:chop]) 51 | } else { 52 | fmt.Fprintf(buf, "%q", v) 53 | } 54 | case string: 55 | if len(v) > chop { 56 | fmt.Fprintf(buf, "%q...", v[:chop]) 57 | } else { 58 | fmt.Fprintf(buf, "%q", v) 59 | } 60 | case []interface{}: 61 | if len(v) == 0 { 62 | buf.WriteString("[]") 63 | } else { 64 | sep := "[" 65 | fin := "]" 66 | if len(v) > chop { 67 | v = v[:chop] 68 | fin = "...]" 69 | } 70 | for _, vv := range v { 71 | buf.WriteString(sep) 72 | c.printValue(buf, vv) 73 | sep = ", " 74 | } 75 | buf.WriteString(fin) 76 | } 77 | default: 78 | fmt.Fprint(buf, v) 79 | } 80 | } 81 | 82 | func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { 83 | var buf bytes.Buffer 84 | fmt.Fprintf(&buf, "%s%s(", c.prefix, method) 85 | if method != "Receive" { 86 | buf.WriteString(commandName) 87 | for _, arg := range args { 88 | buf.WriteString(", ") 89 | c.printValue(&buf, arg) 90 | } 91 | } 92 | buf.WriteString(") -> (") 93 | if method != "Send" { 94 | c.printValue(&buf, reply) 95 | buf.WriteString(", ") 96 | } 97 | fmt.Fprintf(&buf, "%v)", err) 98 | c.logger.Output(3, buf.String()) 99 | } 100 | 101 | func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { 102 | reply, err := c.Conn.Do(commandName, args...) 103 | c.print("Do", commandName, args, reply, err) 104 | return reply, err 105 | } 106 | 107 | func (c *loggingConn) Send(commandName string, args ...interface{}) error { 108 | err := c.Conn.Send(commandName, args...) 109 | c.print("Send", commandName, args, nil, err) 110 | return err 111 | } 112 | 113 | func (c *loggingConn) Receive() (interface{}, error) { 114 | reply, err := c.Conn.Receive() 115 | c.print("Receive", "", nil, reply, err) 116 | return reply, err 117 | } 118 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import "errors" 18 | 19 | // Subscription represents a subscribe or unsubscribe notification. 20 | type Subscription struct { 21 | 22 | // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" 23 | Kind string 24 | 25 | // The channel that was changed. 26 | Channel string 27 | 28 | // The current number of subscriptions for connection. 29 | Count int 30 | } 31 | 32 | // Message represents a message notification. 33 | type Message struct { 34 | 35 | // The originating channel. 36 | Channel string 37 | 38 | // The message data. 39 | Data []byte 40 | } 41 | 42 | // PMessage represents a pmessage notification. 43 | type PMessage struct { 44 | 45 | // The matched pattern. 46 | Pattern string 47 | 48 | // The originating channel. 49 | Channel string 50 | 51 | // The message data. 52 | Data []byte 53 | } 54 | 55 | // Pong represents a pubsub pong notification. 56 | type Pong struct { 57 | Data string 58 | } 59 | 60 | // PubSubConn wraps a Conn with convenience methods for subscribers. 61 | type PubSubConn struct { 62 | Conn Conn 63 | } 64 | 65 | // Close closes the connection. 66 | func (c PubSubConn) Close() error { 67 | return c.Conn.Close() 68 | } 69 | 70 | // Subscribe subscribes the connection to the specified channels. 71 | func (c PubSubConn) Subscribe(channel ...interface{}) error { 72 | c.Conn.Send("SUBSCRIBE", channel...) 73 | return c.Conn.Flush() 74 | } 75 | 76 | // PSubscribe subscribes the connection to the given patterns. 77 | func (c PubSubConn) PSubscribe(channel ...interface{}) error { 78 | c.Conn.Send("PSUBSCRIBE", channel...) 79 | return c.Conn.Flush() 80 | } 81 | 82 | // Unsubscribe unsubscribes the connection from the given channels, or from all 83 | // of them if none is given. 84 | func (c PubSubConn) Unsubscribe(channel ...interface{}) error { 85 | c.Conn.Send("UNSUBSCRIBE", channel...) 86 | return c.Conn.Flush() 87 | } 88 | 89 | // PUnsubscribe unsubscribes the connection from the given patterns, or from all 90 | // of them if none is given. 91 | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { 92 | c.Conn.Send("PUNSUBSCRIBE", channel...) 93 | return c.Conn.Flush() 94 | } 95 | 96 | // Ping sends a PING to the server with the specified data. 97 | func (c PubSubConn) Ping(data string) error { 98 | c.Conn.Send("PING", data) 99 | return c.Conn.Flush() 100 | } 101 | 102 | // Receive returns a pushed message as a Subscription, Message, PMessage, Pong 103 | // or error. The return value is intended to be used directly in a type switch 104 | // as illustrated in the PubSubConn example. 105 | func (c PubSubConn) Receive() interface{} { 106 | reply, err := Values(c.Conn.Receive()) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | var kind string 112 | reply, err = Scan(reply, &kind) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | switch kind { 118 | case "message": 119 | var m Message 120 | if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { 121 | return err 122 | } 123 | return m 124 | case "pmessage": 125 | var pm PMessage 126 | if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { 127 | return err 128 | } 129 | return pm 130 | case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": 131 | s := Subscription{Kind: kind} 132 | if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { 133 | return err 134 | } 135 | return s 136 | case "pong": 137 | var p Pong 138 | if _, err := Scan(reply, &p.Data); err != nil { 139 | return err 140 | } 141 | return p 142 | } 143 | return errors.New("redigo: unknown pubsub notification") 144 | } 145 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | // Error represents an error returned in a command reply. 18 | type Error string 19 | 20 | func (err Error) Error() string { return string(err) } 21 | 22 | // Conn represents a connection to a Redis server. 23 | type Conn interface { 24 | // Close closes the connection. 25 | Close() error 26 | 27 | // Err returns a non-nil value if the connection is broken. The returned 28 | // value is either the first non-nil value returned from the underlying 29 | // network connection or a protocol parsing error. Applications should 30 | // close broken connections. 31 | Err() error 32 | 33 | // Do sends a command to the server and returns the received reply. 34 | Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | 36 | // Send writes the command to the client's output buffer. 37 | Send(commandName string, args ...interface{}) error 38 | 39 | // Flush flushes the output buffer to the Redis server. 40 | Flush() error 41 | 42 | // Receive receives a single reply from the Redis server 43 | Receive() (reply interface{}, err error) 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Script encapsulates the source, hash and key count for a Lua script. See 25 | // http://redis.io/commands/eval for information on scripts in Redis. 26 | type Script struct { 27 | keyCount int 28 | src string 29 | hash string 30 | } 31 | 32 | // NewScript returns a new script object. If keyCount is greater than or equal 33 | // to zero, then the count is automatically inserted in the EVAL command 34 | // argument list. If keyCount is less than zero, then the application supplies 35 | // the count as the first value in the keysAndArgs argument to the Do, Send and 36 | // SendHash methods. 37 | func NewScript(keyCount int, src string) *Script { 38 | h := sha1.New() 39 | io.WriteString(h, src) 40 | return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} 41 | } 42 | 43 | func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { 44 | var args []interface{} 45 | if s.keyCount < 0 { 46 | args = make([]interface{}, 1+len(keysAndArgs)) 47 | args[0] = spec 48 | copy(args[1:], keysAndArgs) 49 | } else { 50 | args = make([]interface{}, 2+len(keysAndArgs)) 51 | args[0] = spec 52 | args[1] = s.keyCount 53 | copy(args[2:], keysAndArgs) 54 | } 55 | return args 56 | } 57 | 58 | // Do evaluates the script. Under the covers, Do optimistically evaluates the 59 | // script using the EVALSHA command. If the command fails because the script is 60 | // not loaded, then Do evaluates the script using the EVAL command (thus 61 | // causing the script to load). 62 | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { 63 | v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) 64 | if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { 65 | v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) 66 | } 67 | return v, err 68 | } 69 | 70 | // SendHash evaluates the script without waiting for the reply. The script is 71 | // evaluated with the EVALSHA command. The application must ensure that the 72 | // script is loaded by a previous call to Send, Do or Load methods. 73 | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { 74 | return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) 75 | } 76 | 77 | // Send evaluates the script without waiting for the reply. 78 | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { 79 | return c.Send("EVAL", s.args(s.src, keysAndArgs)...) 80 | } 81 | 82 | // Load loads the script without evaluating it. 83 | func (s *Script) Load(c Conn) error { 84 | _, err := c.Do("SCRIPT", "LOAD", s.src) 85 | return err 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - go: 1.3 7 | - go: 1.4 8 | - go: 1.5 9 | - go: 1.6 10 | - go: 1.7 11 | - go: tip 12 | allow_failures: 13 | - go: tip 14 | 15 | script: 16 | - go get -t -v ./... 17 | - diff -u <(echo -n) <(gofmt -d .) 18 | - go vet $(go list ./... | grep -v /vendor/) 19 | - go test -v -race ./... 20 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/README.md: -------------------------------------------------------------------------------- 1 | context 2 | ======= 3 | [![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) 4 | 5 | gorilla/context is a general purpose registry for global request variables. 6 | 7 | > Note: gorilla/context, having been born well before `context.Context` existed, does not play well 8 | > with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`. 9 | 10 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/context 11 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package context 6 | 7 | import ( 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | mutex sync.RWMutex 15 | data = make(map[*http.Request]map[interface{}]interface{}) 16 | datat = make(map[*http.Request]int64) 17 | ) 18 | 19 | // Set stores a value for a given key in a given request. 20 | func Set(r *http.Request, key, val interface{}) { 21 | mutex.Lock() 22 | if data[r] == nil { 23 | data[r] = make(map[interface{}]interface{}) 24 | datat[r] = time.Now().Unix() 25 | } 26 | data[r][key] = val 27 | mutex.Unlock() 28 | } 29 | 30 | // Get returns a value stored for a given key in a given request. 31 | func Get(r *http.Request, key interface{}) interface{} { 32 | mutex.RLock() 33 | if ctx := data[r]; ctx != nil { 34 | value := ctx[key] 35 | mutex.RUnlock() 36 | return value 37 | } 38 | mutex.RUnlock() 39 | return nil 40 | } 41 | 42 | // GetOk returns stored value and presence state like multi-value return of map access. 43 | func GetOk(r *http.Request, key interface{}) (interface{}, bool) { 44 | mutex.RLock() 45 | if _, ok := data[r]; ok { 46 | value, ok := data[r][key] 47 | mutex.RUnlock() 48 | return value, ok 49 | } 50 | mutex.RUnlock() 51 | return nil, false 52 | } 53 | 54 | // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. 55 | func GetAll(r *http.Request) map[interface{}]interface{} { 56 | mutex.RLock() 57 | if context, ok := data[r]; ok { 58 | result := make(map[interface{}]interface{}, len(context)) 59 | for k, v := range context { 60 | result[k] = v 61 | } 62 | mutex.RUnlock() 63 | return result 64 | } 65 | mutex.RUnlock() 66 | return nil 67 | } 68 | 69 | // GetAllOk returns all stored values for the request as a map and a boolean value that indicates if 70 | // the request was registered. 71 | func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { 72 | mutex.RLock() 73 | context, ok := data[r] 74 | result := make(map[interface{}]interface{}, len(context)) 75 | for k, v := range context { 76 | result[k] = v 77 | } 78 | mutex.RUnlock() 79 | return result, ok 80 | } 81 | 82 | // Delete removes a value stored for a given key in a given request. 83 | func Delete(r *http.Request, key interface{}) { 84 | mutex.Lock() 85 | if data[r] != nil { 86 | delete(data[r], key) 87 | } 88 | mutex.Unlock() 89 | } 90 | 91 | // Clear removes all values stored for a given request. 92 | // 93 | // This is usually called by a handler wrapper to clean up request 94 | // variables at the end of a request lifetime. See ClearHandler(). 95 | func Clear(r *http.Request) { 96 | mutex.Lock() 97 | clear(r) 98 | mutex.Unlock() 99 | } 100 | 101 | // clear is Clear without the lock. 102 | func clear(r *http.Request) { 103 | delete(data, r) 104 | delete(datat, r) 105 | } 106 | 107 | // Purge removes request data stored for longer than maxAge, in seconds. 108 | // It returns the amount of requests removed. 109 | // 110 | // If maxAge <= 0, all request data is removed. 111 | // 112 | // This is only used for sanity check: in case context cleaning was not 113 | // properly set some request data can be kept forever, consuming an increasing 114 | // amount of memory. In case this is detected, Purge() must be called 115 | // periodically until the problem is fixed. 116 | func Purge(maxAge int) int { 117 | mutex.Lock() 118 | count := 0 119 | if maxAge <= 0 { 120 | count = len(data) 121 | data = make(map[*http.Request]map[interface{}]interface{}) 122 | datat = make(map[*http.Request]int64) 123 | } else { 124 | min := time.Now().Unix() - int64(maxAge) 125 | for r := range data { 126 | if datat[r] < min { 127 | clear(r) 128 | count++ 129 | } 130 | } 131 | } 132 | mutex.Unlock() 133 | return count 134 | } 135 | 136 | // ClearHandler wraps an http.Handler and clears request values at the end 137 | // of a request lifetime. 138 | func ClearHandler(h http.Handler) http.Handler { 139 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 140 | defer Clear(r) 141 | h.ServeHTTP(w, r) 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package context stores values shared during a request lifetime. 7 | 8 | Note: gorilla/context, having been born well before `context.Context` existed, 9 | does not play well > with the shallow copying of the request that 10 | [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) 11 | (added to net/http Go 1.7 onwards) performs. You should either use *just* 12 | gorilla/context, or moving forward, the new `http.Request.Context()`. 13 | 14 | For example, a router can set variables extracted from the URL and later 15 | application handlers can access those values, or it can be used to store 16 | sessions values to be saved at the end of a request. There are several 17 | others common uses. 18 | 19 | The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: 20 | 21 | http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 22 | 23 | Here's the basic usage: first define the keys that you will need. The key 24 | type is interface{} so a key can be of any type that supports equality. 25 | Here we define a key using a custom int type to avoid name collisions: 26 | 27 | package foo 28 | 29 | import ( 30 | "github.com/gorilla/context" 31 | ) 32 | 33 | type key int 34 | 35 | const MyKey key = 0 36 | 37 | Then set a variable. Variables are bound to an http.Request object, so you 38 | need a request instance to set a value: 39 | 40 | context.Set(r, MyKey, "bar") 41 | 42 | The application can later access the variable using the same key you provided: 43 | 44 | func MyHandler(w http.ResponseWriter, r *http.Request) { 45 | // val is "bar". 46 | val := context.Get(r, foo.MyKey) 47 | 48 | // returns ("bar", true) 49 | val, ok := context.GetOk(r, foo.MyKey) 50 | // ... 51 | } 52 | 53 | And that's all about the basic usage. We discuss some other ideas below. 54 | 55 | Any type can be stored in the context. To enforce a given type, make the key 56 | private and wrap Get() and Set() to accept and return values of a specific 57 | type: 58 | 59 | type key int 60 | 61 | const mykey key = 0 62 | 63 | // GetMyKey returns a value for this package from the request values. 64 | func GetMyKey(r *http.Request) SomeType { 65 | if rv := context.Get(r, mykey); rv != nil { 66 | return rv.(SomeType) 67 | } 68 | return nil 69 | } 70 | 71 | // SetMyKey sets a value for this package in the request values. 72 | func SetMyKey(r *http.Request, val SomeType) { 73 | context.Set(r, mykey, val) 74 | } 75 | 76 | Variables must be cleared at the end of a request, to remove all values 77 | that were stored. This can be done in an http.Handler, after a request was 78 | served. Just call Clear() passing the request: 79 | 80 | context.Clear(r) 81 | 82 | ...or use ClearHandler(), which conveniently wraps an http.Handler to clear 83 | variables at the end of a request lifetime. 84 | 85 | The Routers from the packages gorilla/mux and gorilla/pat call Clear() 86 | so if you are using either of them you don't need to clear the context manually. 87 | */ 88 | package context 89 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/securecookie/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - go: 1.3 7 | - go: 1.4 8 | - go: 1.5 9 | - go: 1.6 10 | - go: 1.7 11 | - go: tip 12 | allow_failures: 13 | - go: tip 14 | 15 | script: 16 | - go get -t -v ./... 17 | - diff -u <(echo -n) <(gofmt -d .) 18 | - go vet $(go list ./... | grep -v /vendor/) 19 | - go test -v -race ./... 20 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/securecookie/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/securecookie/README.md: -------------------------------------------------------------------------------- 1 | securecookie 2 | ============ 3 | [![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie) 4 | 5 | securecookie encodes and decodes authenticated and optionally encrypted 6 | cookie values. 7 | 8 | Secure cookies can't be forged, because their values are validated using HMAC. 9 | When encrypted, the content is also inaccessible to malicious eyes. It is still 10 | recommended that sensitive data not be stored in cookies, and that HTTPS be used 11 | to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack). 12 | 13 | ## Examples 14 | 15 | To use it, first create a new SecureCookie instance: 16 | 17 | ```go 18 | // Hash keys should be at least 32 bytes long 19 | var hashKey = []byte("very-secret") 20 | // Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long. 21 | // Shorter keys may weaken the encryption used. 22 | var blockKey = []byte("a-lot-secret") 23 | var s = securecookie.New(hashKey, blockKey) 24 | ``` 25 | 26 | The hashKey is required, used to authenticate the cookie value using HMAC. 27 | It is recommended to use a key with 32 or 64 bytes. 28 | 29 | The blockKey is optional, used to encrypt the cookie value -- set it to nil 30 | to not use encryption. If set, the length must correspond to the block size 31 | of the encryption algorithm. For AES, used by default, valid lengths are 32 | 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. 33 | 34 | Strong keys can be created using the convenience function GenerateRandomKey(). 35 | 36 | Once a SecureCookie instance is set, use it to encode a cookie value: 37 | 38 | ```go 39 | func SetCookieHandler(w http.ResponseWriter, r *http.Request) { 40 | value := map[string]string{ 41 | "foo": "bar", 42 | } 43 | if encoded, err := s.Encode("cookie-name", value); err == nil { 44 | cookie := &http.Cookie{ 45 | Name: "cookie-name", 46 | Value: encoded, 47 | Path: "/", 48 | Secure: true, 49 | HttpOnly: true, 50 | } 51 | http.SetCookie(w, cookie) 52 | } 53 | } 54 | ``` 55 | 56 | Later, use the same SecureCookie instance to decode and validate a cookie 57 | value: 58 | 59 | ```go 60 | func ReadCookieHandler(w http.ResponseWriter, r *http.Request) { 61 | if cookie, err := r.Cookie("cookie-name"); err == nil { 62 | value := make(map[string]string) 63 | if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil { 64 | fmt.Fprintf(w, "The value of foo is %q", value["foo"]) 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | We stored a map[string]string, but secure cookies can hold any value that 71 | can be encoded using `encoding/gob`. To store custom types, they must be 72 | registered first using gob.Register(). For basic types this is not needed; 73 | it works out of the box. An optional JSON encoder that uses `encoding/json` is 74 | available for types compatible with JSON. 75 | 76 | ## License 77 | 78 | BSD licensed. See the LICENSE file for details. 79 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/securecookie/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package securecookie encodes and decodes authenticated and optionally 7 | encrypted cookie values. 8 | 9 | Secure cookies can't be forged, because their values are validated using HMAC. 10 | When encrypted, the content is also inaccessible to malicious eyes. 11 | 12 | To use it, first create a new SecureCookie instance: 13 | 14 | var hashKey = []byte("very-secret") 15 | var blockKey = []byte("a-lot-secret") 16 | var s = securecookie.New(hashKey, blockKey) 17 | 18 | The hashKey is required, used to authenticate the cookie value using HMAC. 19 | It is recommended to use a key with 32 or 64 bytes. 20 | 21 | The blockKey is optional, used to encrypt the cookie value -- set it to nil 22 | to not use encryption. If set, the length must correspond to the block size 23 | of the encryption algorithm. For AES, used by default, valid lengths are 24 | 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. 25 | 26 | Strong keys can be created using the convenience function GenerateRandomKey(). 27 | 28 | Once a SecureCookie instance is set, use it to encode a cookie value: 29 | 30 | func SetCookieHandler(w http.ResponseWriter, r *http.Request) { 31 | value := map[string]string{ 32 | "foo": "bar", 33 | } 34 | if encoded, err := s.Encode("cookie-name", value); err == nil { 35 | cookie := &http.Cookie{ 36 | Name: "cookie-name", 37 | Value: encoded, 38 | Path: "/", 39 | } 40 | http.SetCookie(w, cookie) 41 | } 42 | } 43 | 44 | Later, use the same SecureCookie instance to decode and validate a cookie 45 | value: 46 | 47 | func ReadCookieHandler(w http.ResponseWriter, r *http.Request) { 48 | if cookie, err := r.Cookie("cookie-name"); err == nil { 49 | value := make(map[string]string) 50 | if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil { 51 | fmt.Fprintf(w, "The value of foo is %q", value["foo"]) 52 | } 53 | } 54 | } 55 | 56 | We stored a map[string]string, but secure cookies can hold any value that 57 | can be encoded using encoding/gob. To store custom types, they must be 58 | registered first using gob.Register(). For basic types this is not needed; 59 | it works out of the box. 60 | */ 61 | package securecookie 62 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/securecookie/fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package securecookie 4 | 5 | var hashKey = []byte("very-secret12345") 6 | var blockKey = []byte("a-lot-secret1234") 7 | var s = New(hashKey, blockKey) 8 | 9 | type Cookie struct { 10 | B bool 11 | I int 12 | S string 13 | } 14 | 15 | func Fuzz(data []byte) int { 16 | datas := string(data) 17 | var c Cookie 18 | if err := s.Decode("fuzz", datas, &c); err != nil { 19 | return 0 20 | } 21 | if _, err := s.Encode("fuzz", c); err != nil { 22 | panic(err) 23 | } 24 | return 1 25 | } 26 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/sessions/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - go: 1.3 7 | - go: 1.4 8 | - go: 1.5 9 | - go: 1.6 10 | - go: 1.7 11 | - go: tip 12 | allow_failures: 13 | - go: tip 14 | 15 | install: 16 | - # skip 17 | 18 | script: 19 | - go get -t -v ./... 20 | - diff -u <(echo -n) <(gofmt -d .) 21 | - go vet $(go list ./... | grep -v /vendor/) 22 | - go test -v -race ./... 23 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/sessions/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/sessions/README.md: -------------------------------------------------------------------------------- 1 | sessions 2 | ======== 3 | [![GoDoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) [![Build Status](https://travis-ci.org/gorilla/sessions.png?branch=master)](https://travis-ci.org/gorilla/sessions) 4 | 5 | gorilla/sessions provides cookie and filesystem sessions and infrastructure for 6 | custom session backends. 7 | 8 | The key features are: 9 | 10 | * Simple API: use it as an easy way to set signed (and optionally 11 | encrypted) cookies. 12 | * Built-in backends to store sessions in cookies or the filesystem. 13 | * Flash messages: session values that last until read. 14 | * Convenient way to switch session persistency (aka "remember me") and set 15 | other attributes. 16 | * Mechanism to rotate authentication and encryption keys. 17 | * Multiple sessions per request, even using different backends. 18 | * Interfaces and infrastructure for custom session backends: sessions from 19 | different stores can be retrieved and batch-saved using a common API. 20 | 21 | Let's start with an example that shows the sessions API in a nutshell: 22 | 23 | ```go 24 | import ( 25 | "net/http" 26 | "github.com/gorilla/sessions" 27 | ) 28 | 29 | var store = sessions.NewCookieStore([]byte("something-very-secret")) 30 | 31 | func MyHandler(w http.ResponseWriter, r *http.Request) { 32 | // Get a session. We're ignoring the error resulted from decoding an 33 | // existing session: Get() always returns a session, even if empty. 34 | session, _ := store.Get(r, "session-name") 35 | // Set some session values. 36 | session.Values["foo"] = "bar" 37 | session.Values[42] = 43 38 | // Save it before we write to the response/return from the handler. 39 | session.Save(r, w) 40 | } 41 | ``` 42 | 43 | First we initialize a session store calling `NewCookieStore()` and passing a 44 | secret key used to authenticate the session. Inside the handler, we call 45 | `store.Get()` to retrieve an existing session or a new one. Then we set some 46 | session values in session.Values, which is a `map[interface{}]interface{}`. 47 | And finally we call `session.Save()` to save the session in the response. 48 | 49 | Important Note: If you aren't using gorilla/mux, you need to wrap your handlers 50 | with 51 | [`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler) 52 | as or else you will leak memory! An easy way to do this is to wrap the top-level 53 | mux when calling http.ListenAndServe: 54 | 55 | More examples are available [on the Gorilla 56 | website](http://www.gorillatoolkit.org/pkg/sessions). 57 | 58 | ## Store Implementations 59 | 60 | Other implementations of the `sessions.Store` interface: 61 | 62 | * [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB 63 | * [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt 64 | * [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase 65 | * [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS 66 | * [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache 67 | * [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine 68 | * [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB 69 | * [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL 70 | * [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL 71 | * [github.com/boj/redistore](https://github.com/boj/redistore) - Redis 72 | * [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB 73 | * [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak 74 | * [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite 75 | * [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite) 76 | 77 | ## License 78 | 79 | BSD licensed. See the LICENSE file for details. 80 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/sessions/lex.go: -------------------------------------------------------------------------------- 1 | // This file contains code adapted from the Go standard library 2 | // https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go 3 | 4 | package sessions 5 | 6 | import "strings" 7 | 8 | var isTokenTable = [127]bool{ 9 | '!': true, 10 | '#': true, 11 | '$': true, 12 | '%': true, 13 | '&': true, 14 | '\'': true, 15 | '*': true, 16 | '+': true, 17 | '-': true, 18 | '.': true, 19 | '0': true, 20 | '1': true, 21 | '2': true, 22 | '3': true, 23 | '4': true, 24 | '5': true, 25 | '6': true, 26 | '7': true, 27 | '8': true, 28 | '9': true, 29 | 'A': true, 30 | 'B': true, 31 | 'C': true, 32 | 'D': true, 33 | 'E': true, 34 | 'F': true, 35 | 'G': true, 36 | 'H': true, 37 | 'I': true, 38 | 'J': true, 39 | 'K': true, 40 | 'L': true, 41 | 'M': true, 42 | 'N': true, 43 | 'O': true, 44 | 'P': true, 45 | 'Q': true, 46 | 'R': true, 47 | 'S': true, 48 | 'T': true, 49 | 'U': true, 50 | 'W': true, 51 | 'V': true, 52 | 'X': true, 53 | 'Y': true, 54 | 'Z': true, 55 | '^': true, 56 | '_': true, 57 | '`': true, 58 | 'a': true, 59 | 'b': true, 60 | 'c': true, 61 | 'd': true, 62 | 'e': true, 63 | 'f': true, 64 | 'g': true, 65 | 'h': true, 66 | 'i': true, 67 | 'j': true, 68 | 'k': true, 69 | 'l': true, 70 | 'm': true, 71 | 'n': true, 72 | 'o': true, 73 | 'p': true, 74 | 'q': true, 75 | 'r': true, 76 | 's': true, 77 | 't': true, 78 | 'u': true, 79 | 'v': true, 80 | 'w': true, 81 | 'x': true, 82 | 'y': true, 83 | 'z': true, 84 | '|': true, 85 | '~': true, 86 | } 87 | 88 | func isToken(r rune) bool { 89 | i := int(r) 90 | return i < len(isTokenTable) && isTokenTable[i] 91 | } 92 | 93 | func isNotToken(r rune) bool { 94 | return !isToken(r) 95 | } 96 | 97 | func isCookieNameValid(raw string) bool { 98 | if raw == "" { 99 | return false 100 | } 101 | return strings.IndexFunc(raw, isNotToken) < 0 102 | } 103 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | .idea/ 25 | *.iml -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - go: 1.4 7 | - go: 1.5 8 | - go: 1.6 9 | - go: tip 10 | allow_failures: 11 | - go: tip 12 | 13 | script: 14 | - go get -t -v ./... 15 | - diff -u <(echo -n) <(gofmt -d .) 16 | - go vet $(go list ./... | grep -v /vendor/) 17 | - go test -v -race ./... 18 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Gorilla WebSocket authors for copyright 2 | # purposes. 3 | # 4 | # Please keep the list sorted. 5 | 6 | Gary Burd 7 | Joachim Bauch 8 | 9 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/README.md: -------------------------------------------------------------------------------- 1 | # Gorilla WebSocket 2 | 3 | Gorilla WebSocket is a [Go](http://golang.org/) implementation of the 4 | [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. 5 | 6 | ### Documentation 7 | 8 | * [API Reference](http://godoc.org/github.com/gorilla/websocket) 9 | * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) 10 | * [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) 11 | * [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) 12 | * [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) 13 | 14 | ### Status 15 | 16 | The Gorilla WebSocket package provides a complete and tested implementation of 17 | the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The 18 | package API is stable. 19 | 20 | ### Installation 21 | 22 | go get github.com/gorilla/websocket 23 | 24 | ### Protocol Compliance 25 | 26 | The Gorilla WebSocket package passes the server tests in the [Autobahn Test 27 | Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn 28 | subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). 29 | 30 | ### Gorilla WebSocket compared with other packages 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
    github.com/gorillagolang.org/x/net
    RFC 6455 Features
    Passes Autobahn Test SuiteYesNo
    Receive fragmented messageYesNo, see note 1
    Send close messageYesNo
    Send pings and receive pongsYesNo
    Get the type of a received data messageYesYes, see note 2
    Other Features
    Read message using io.ReaderYesNo, see note 3
    Write message using io.WriteCloserYesNo, see note 3
    49 | 50 | Notes: 51 | 52 | 1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). 53 | 2. The application can get the type of a received data message by implementing 54 | a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) 55 | function. 56 | 3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. 57 | Read returns when the input buffer is full or a frame boundary is 58 | encountered. Each call to Write sends a single frame message. The Gorilla 59 | io.Reader and io.WriteCloser operate on a single WebSocket message. 60 | 61 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/compression.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "compress/flate" 9 | "errors" 10 | "io" 11 | "strings" 12 | ) 13 | 14 | func decompressNoContextTakeover(r io.Reader) io.Reader { 15 | const tail = 16 | // Add four bytes as specified in RFC 17 | "\x00\x00\xff\xff" + 18 | // Add final block to squelch unexpected EOF error from flate reader. 19 | "\x01\x00\x00\xff\xff" 20 | 21 | return flate.NewReader(io.MultiReader(r, strings.NewReader(tail))) 22 | } 23 | 24 | func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) { 25 | tw := &truncWriter{w: w} 26 | fw, err := flate.NewWriter(tw, 3) 27 | return &flateWrapper{fw: fw, tw: tw}, err 28 | } 29 | 30 | // truncWriter is an io.Writer that writes all but the last four bytes of the 31 | // stream to another io.Writer. 32 | type truncWriter struct { 33 | w io.WriteCloser 34 | n int 35 | p [4]byte 36 | } 37 | 38 | func (w *truncWriter) Write(p []byte) (int, error) { 39 | n := 0 40 | 41 | // fill buffer first for simplicity. 42 | if w.n < len(w.p) { 43 | n = copy(w.p[w.n:], p) 44 | p = p[n:] 45 | w.n += n 46 | if len(p) == 0 { 47 | return n, nil 48 | } 49 | } 50 | 51 | m := len(p) 52 | if m > len(w.p) { 53 | m = len(w.p) 54 | } 55 | 56 | if nn, err := w.w.Write(w.p[:m]); err != nil { 57 | return n + nn, err 58 | } 59 | 60 | copy(w.p[:], w.p[m:]) 61 | copy(w.p[len(w.p)-m:], p[len(p)-m:]) 62 | nn, err := w.w.Write(p[:len(p)-m]) 63 | return n + nn, err 64 | } 65 | 66 | type flateWrapper struct { 67 | fw *flate.Writer 68 | tw *truncWriter 69 | } 70 | 71 | func (w *flateWrapper) Write(p []byte) (int, error) { 72 | return w.fw.Write(p) 73 | } 74 | 75 | func (w *flateWrapper) Close() error { 76 | err1 := w.fw.Flush() 77 | if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { 78 | return errors.New("websocket: internal error, unexpected bytes at end of flate stream") 79 | } 80 | err2 := w.tw.w.Close() 81 | if err1 != nil { 82 | return err1 83 | } 84 | return err2 85 | } 86 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/conn_read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.5 6 | 7 | package websocket 8 | 9 | import "io" 10 | 11 | func (c *Conn) read(n int) ([]byte, error) { 12 | p, err := c.br.Peek(n) 13 | if err == io.EOF { 14 | err = errUnexpectedEOF 15 | } 16 | c.br.Discard(len(p)) 17 | return p, err 18 | } 19 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/conn_read_legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !go1.5 6 | 7 | package websocket 8 | 9 | import "io" 10 | 11 | func (c *Conn) read(n int) ([]byte, error) { 12 | p, err := c.br.Peek(n) 13 | if err == io.EOF { 14 | err = errUnexpectedEOF 15 | } 16 | if len(p) > 0 { 17 | // advance over the bytes just read 18 | io.ReadFull(c.br, p) 19 | } 20 | return p, err 21 | } 22 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | ) 11 | 12 | // WriteJSON is deprecated, use c.WriteJSON instead. 13 | func WriteJSON(c *Conn, v interface{}) error { 14 | return c.WriteJSON(v) 15 | } 16 | 17 | // WriteJSON writes the JSON encoding of v to the connection. 18 | // 19 | // See the documentation for encoding/json Marshal for details about the 20 | // conversion of Go values to JSON. 21 | func (c *Conn) WriteJSON(v interface{}) error { 22 | w, err := c.NextWriter(TextMessage) 23 | if err != nil { 24 | return err 25 | } 26 | err1 := json.NewEncoder(w).Encode(v) 27 | err2 := w.Close() 28 | if err1 != nil { 29 | return err1 30 | } 31 | return err2 32 | } 33 | 34 | // ReadJSON is deprecated, use c.ReadJSON instead. 35 | func ReadJSON(c *Conn, v interface{}) error { 36 | return c.ReadJSON(v) 37 | } 38 | 39 | // ReadJSON reads the next JSON-encoded message from the connection and stores 40 | // it in the value pointed to by v. 41 | // 42 | // See the documentation for the encoding/json Unmarshal function for details 43 | // about the conversion of JSON to a Go value. 44 | func (c *Conn) ReadJSON(v interface{}) error { 45 | _, r, err := c.NextReader() 46 | if err != nil { 47 | return err 48 | } 49 | err = json.NewDecoder(r).Decode(v) 50 | if err == io.EOF { 51 | // One value is expected in the message. 52 | err = io.ErrUnexpectedEOF 53 | } 54 | return err 55 | } 56 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/sha1" 10 | "encoding/base64" 11 | "io" 12 | "net/http" 13 | "strings" 14 | ) 15 | 16 | var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 17 | 18 | func computeAcceptKey(challengeKey string) string { 19 | h := sha1.New() 20 | h.Write([]byte(challengeKey)) 21 | h.Write(keyGUID) 22 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 23 | } 24 | 25 | func generateChallengeKey() (string, error) { 26 | p := make([]byte, 16) 27 | if _, err := io.ReadFull(rand.Reader, p); err != nil { 28 | return "", err 29 | } 30 | return base64.StdEncoding.EncodeToString(p), nil 31 | } 32 | 33 | // Octet types from RFC 2616. 34 | var octetTypes [256]byte 35 | 36 | const ( 37 | isTokenOctet = 1 << iota 38 | isSpaceOctet 39 | ) 40 | 41 | func init() { 42 | // From RFC 2616 43 | // 44 | // OCTET = 45 | // CHAR = 46 | // CTL = 47 | // CR = 48 | // LF = 49 | // SP = 50 | // HT = 51 | // <"> = 52 | // CRLF = CR LF 53 | // LWS = [CRLF] 1*( SP | HT ) 54 | // TEXT = 55 | // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> 56 | // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT 57 | // token = 1* 58 | // qdtext = > 59 | 60 | for c := 0; c < 256; c++ { 61 | var t byte 62 | isCtl := c <= 31 || c == 127 63 | isChar := 0 <= c && c <= 127 64 | isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 65 | if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { 66 | t |= isSpaceOctet 67 | } 68 | if isChar && !isCtl && !isSeparator { 69 | t |= isTokenOctet 70 | } 71 | octetTypes[c] = t 72 | } 73 | } 74 | 75 | func skipSpace(s string) (rest string) { 76 | i := 0 77 | for ; i < len(s); i++ { 78 | if octetTypes[s[i]]&isSpaceOctet == 0 { 79 | break 80 | } 81 | } 82 | return s[i:] 83 | } 84 | 85 | func nextToken(s string) (token, rest string) { 86 | i := 0 87 | for ; i < len(s); i++ { 88 | if octetTypes[s[i]]&isTokenOctet == 0 { 89 | break 90 | } 91 | } 92 | return s[:i], s[i:] 93 | } 94 | 95 | func nextTokenOrQuoted(s string) (value string, rest string) { 96 | if !strings.HasPrefix(s, "\"") { 97 | return nextToken(s) 98 | } 99 | s = s[1:] 100 | for i := 0; i < len(s); i++ { 101 | switch s[i] { 102 | case '"': 103 | return s[:i], s[i+1:] 104 | case '\\': 105 | p := make([]byte, len(s)-1) 106 | j := copy(p, s[:i]) 107 | escape := true 108 | for i = i + 1; i < len(s); i++ { 109 | b := s[i] 110 | switch { 111 | case escape: 112 | escape = false 113 | p[j] = b 114 | j += 1 115 | case b == '\\': 116 | escape = true 117 | case b == '"': 118 | return string(p[:j]), s[i+1:] 119 | default: 120 | p[j] = b 121 | j += 1 122 | } 123 | } 124 | return "", "" 125 | } 126 | } 127 | return "", "" 128 | } 129 | 130 | // tokenListContainsValue returns true if the 1#token header with the given 131 | // name contains token. 132 | func tokenListContainsValue(header http.Header, name string, value string) bool { 133 | headers: 134 | for _, s := range header[name] { 135 | for { 136 | var t string 137 | t, s = nextToken(skipSpace(s)) 138 | if t == "" { 139 | continue headers 140 | } 141 | s = skipSpace(s) 142 | if s != "" && s[0] != ',' { 143 | continue headers 144 | } 145 | if strings.EqualFold(t, value) { 146 | return true 147 | } 148 | if s == "" { 149 | continue headers 150 | } 151 | s = s[1:] 152 | } 153 | } 154 | return false 155 | } 156 | 157 | // parseExtensiosn parses WebSocket extensions from a header. 158 | func parseExtensions(header http.Header) []map[string]string { 159 | 160 | // From RFC 6455: 161 | // 162 | // Sec-WebSocket-Extensions = extension-list 163 | // extension-list = 1#extension 164 | // extension = extension-token *( ";" extension-param ) 165 | // extension-token = registered-token 166 | // registered-token = token 167 | // extension-param = token [ "=" (token | quoted-string) ] 168 | // ;When using the quoted-string syntax variant, the value 169 | // ;after quoted-string unescaping MUST conform to the 170 | // ;'token' ABNF. 171 | 172 | var result []map[string]string 173 | headers: 174 | for _, s := range header["Sec-Websocket-Extensions"] { 175 | for { 176 | var t string 177 | t, s = nextToken(skipSpace(s)) 178 | if t == "" { 179 | continue headers 180 | } 181 | ext := map[string]string{"": t} 182 | for { 183 | s = skipSpace(s) 184 | if !strings.HasPrefix(s, ";") { 185 | break 186 | } 187 | var k string 188 | k, s = nextToken(skipSpace(s[1:])) 189 | if k == "" { 190 | continue headers 191 | } 192 | s = skipSpace(s) 193 | var v string 194 | if strings.HasPrefix(s, "=") { 195 | v, s = nextTokenOrQuoted(skipSpace(s[1:])) 196 | s = skipSpace(s) 197 | } 198 | if s != "" && s[0] != ',' && s[0] != ';' { 199 | continue headers 200 | } 201 | ext[k] = v 202 | } 203 | if s != "" && s[0] != ',' { 204 | continue headers 205 | } 206 | result = append(result, ext) 207 | if s == "" { 208 | continue headers 209 | } 210 | s = s[1:] 211 | } 212 | } 213 | return result 214 | } 215 | -------------------------------------------------------------------------------- /vendor/github.com/pjebs/tokbox/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 PJ Engineering and Business Solutions Pty. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/pjebs/tokbox/README.md: -------------------------------------------------------------------------------- 1 | Tokbox Golang [![GoDoc](http://godoc.org/github.com/pjebs/tokbox?status.svg)](http://godoc.org/github.com/pjebs/tokbox) 2 | ============= 3 | 4 | This Library is for creating sessions and tokens for the Tokbox Video, Voice & Messaging Platform. 5 | [See Tokbox website](https://tokbox.com/) 6 | 7 | It is a hybrid library (supports **Google App Engine** and **Stand-Alone** binary). 8 | It supports **multi-threading** for faster generation of tokens. 9 | 10 | **WARNING:** This library uses the **deprecated** api which is only valid until July 2017. I will update this library to the [new API](https://www.tokbox.com/developer/rest/#authentication) before that date. 11 | 12 | Install 13 | ------- 14 | 15 | ```shell 16 | go get -u github.com/pjebs/tokbox 17 | ``` 18 | 19 | Usage 20 | ----- 21 | 22 | ```go 23 | import "github.com/pjebs/tokbox" 24 | 25 | //setup the api to use your credentials 26 | tb := tokbox.New("","") 27 | 28 | //create a session 29 | session, err := tb.NewSession("", tokbox.P2P) //no location, peer2peer enabled 30 | 31 | //create a token 32 | token, err := session.Token(tokbox.Publisher, "", tokbox.Hours24) //type publisher, no connection data, expire in 24 hours 33 | 34 | //Or create multiple tokens 35 | tokens := session.Tokens(5, true, tokbox.Publisher, "", tokbox.Hours24) //5 tokens, multi-thread token generation, type publisher, no connection data, expire in 24 hours. Returns a []string 36 | 37 | ``` 38 | 39 | See the unit test for a more detailed example. 40 | 41 | Settings 42 | ---------- 43 | 44 | ```go 45 | type MediaMode string 46 | 47 | const ( 48 | /** 49 | * The session will send streams using the OpenTok Media Router. 50 | */ 51 | MediaRouter MediaMode = "disabled" 52 | /** 53 | * The session will attempt to send streams directly between clients. If clients cannot connect 54 | * due to firewall restrictions, the session uses the OpenTok TURN server to relay streams. 55 | */ 56 | P2P = "enabled" 57 | ) 58 | 59 | ``` 60 | 61 | **MediaMode** is the second argument in `NewSession` method. 62 | 63 | 64 | ```go 65 | type Role string 66 | 67 | const ( 68 | /** 69 | * A publisher can publish streams, subscribe to streams, and signal. 70 | */ 71 | Publisher Role = "publisher" 72 | /** 73 | * A subscriber can only subscribe to streams. 74 | */ 75 | Subscriber = "subscriber" 76 | /** 77 | * In addition to the privileges granted to a publisher, in clients using the OpenTok.js 2.2 78 | * library, a moderator can call the forceUnpublish() and 79 | * forceDisconnect() method of the Session object. 80 | */ 81 | Moderator = "moderator" 82 | ) 83 | 84 | ``` 85 | 86 | **Role** is the first argument in `Token` method. 87 | 88 | ```go 89 | const ( 90 | Days30 = 2592000 //30 * 24 * 60 * 60 91 | Weeks1 = 604800 //7 * 24 * 60 * 60 92 | Hours24 = 86400 //24 * 60 * 60 93 | Hours2 = 7200 //60 * 60 * 2 94 | Hours1 = 3600 //60 * 60 95 | ) 96 | 97 | ``` 98 | 99 | **Expiration** value forms the third argument in `Token` method. It dictates how long a token is valid for. The unit is in (seconds) up to a maximum of 30 days. 100 | 101 | 102 | Methods 103 | ---------- 104 | 105 | func (t *Tokbox) NewSession(location string, mm MediaMode) (*Session, error) 106 | 107 | Creates a new session or returns an error. A session represents a 'virtual chat room' where participants can 'sit in' and communicate with one another. A session can not be deregistered. If you no longer require the session, just discard it's details. 108 | 109 | *location string* 110 | 111 | The *location* setting is optional, and generally you should keep it as `"".` This setting is an IP address that TokBox will use to situate the session in its global network. If no location hint is passed in (which is recommended), the session uses a media server based on the location of the first client connecting to the session. Pass a location hint in only if you know the general geographic region (and a representative IP address) and you think the first client connecting may not be in that region. If you need to specify an IP address, replace *location* with an IP address that is representative of the geographical location for the session. ([Tokbox - REST API reference](https://tokbox.com/opentok/api/#session_id_production)) 112 | 113 | *mm MediaMode* 114 | 115 | `P2P` will direct clients to transfer video-audio data between each other directly (if possible). 116 | 117 | `MediaRouter` directs data to go through Tokbox's Media Router servers. Integrates **Intelligent Quality Control** technology to improve user-experience (albeit at higher pricing). ([Tokbox - REST API reference](https://tokbox.com/opentok/api/#session_id_production)) 118 | 119 | 120 | func (s *Session) Token(role Role, connectionData string, expiration int64) (string, error) 121 | 122 | Generates a token for a corresponding session. Returns a string representing the token value or returns an error. A token represents a 'ticket' allowing participants to 'sit in' a session. The permitted range of activities is determined by the `role` setting. 123 | 124 | *role Role* 125 | 126 | `Publisher` - allows participant to broadcast their own audio and video feed to other participants in the session. They can also listen to and watch broadcasts by other members of the session. 127 | 128 | `Subscriber` - allows participants to **only** listen to and watch broadcasts by other participants in the session with **Publisher** rights. 129 | 130 | *connectionData string* 131 | 132 | `connectionData` - Extra arbitrary data that can be read by other clients. ([Tokbox - Generating Tokens](https://tokbox.com/opentok/libraries/server/php/)) 133 | 134 | *expiration int64* 135 | 136 | `expiration` - How long the token is valid for. The unit is in (seconds) up to a maximum of 30 days. See above for built-in enum values, or use your own. 137 | 138 | func (s *Session) Tokens(n int, multithread bool, role Role, connectionData string, expiration int64) []string 139 | 140 | Generates multiple (`n`) tokens in one go. Returns a `[]string.` All tokens generated have the same settings as dictated by *role*, *connectionData* and *expiration*. See above for more details. Since the function repeatedly calls the `Token` method, any error in token generation is ignored. Therefore it may be prudent to check if the length of the returned `[]string` matches `n.` 141 | 142 | *multithread bool* 143 | 144 | If `true,` the function strives to generate the tokens concurrently (if multiple CPU cores are available). Preferred if *many, many, many* tokens need to be generated in one go. 145 | 146 | 147 | Credits: 148 | -------- 149 | (This library is based on the older tokbox library – no longer in active development) 150 | 151 | https://github.com/cioc/tokbox by Charles Cary 152 | 153 | 154 | -------------------------------------------------------------------------------- /vendor/github.com/pjebs/tokbox/gae.go: -------------------------------------------------------------------------------- 1 | // +build appengine 2 | 3 | package tokbox 4 | 5 | import ( 6 | "golang.org/x/net/context" 7 | "google.golang.org/appengine/urlfetch" 8 | "net/http" 9 | ) 10 | 11 | func client(ctx *context.Context) *http.Client { 12 | if ctx == nil { 13 | return &http.Client{} 14 | } else { 15 | return urlfetch.Client(*ctx) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vendor/github.com/pjebs/tokbox/standalone.go: -------------------------------------------------------------------------------- 1 | // +build !appengine 2 | 3 | package tokbox 4 | 5 | import ( 6 | "golang.org/x/net/context" 7 | "net/http" 8 | ) 9 | 10 | func client(ctx *context.Context) *http.Client { 11 | return &http.Client{} 12 | } 13 | -------------------------------------------------------------------------------- /vendor/github.com/pjebs/tokbox/tokbox.go: -------------------------------------------------------------------------------- 1 | package tokbox 2 | 3 | import ( 4 | "bytes" 5 | 6 | "net/http" 7 | "net/url" 8 | 9 | "encoding/base64" 10 | "encoding/xml" 11 | 12 | "crypto/hmac" 13 | "crypto/sha1" 14 | 15 | "fmt" 16 | "math/rand" 17 | "strings" 18 | "time" 19 | 20 | "sync" 21 | 22 | "golang.org/x/net/context" 23 | ) 24 | 25 | const ( 26 | apiHost = "https://api.opentok.com/hl" 27 | apiSession = "/session/create" 28 | ) 29 | 30 | const ( 31 | Days30 = 2592000 //30 * 24 * 60 * 60 32 | Weeks1 = 604800 //7 * 24 * 60 * 60 33 | Hours24 = 86400 //24 * 60 * 60 34 | Hours2 = 7200 //60 * 60 * 2 35 | Hours1 = 3600 //60 * 60 36 | ) 37 | 38 | type MediaMode string 39 | 40 | const ( 41 | /** 42 | * The session will send streams using the OpenTok Media Router. 43 | */ 44 | MediaRouter MediaMode = "disabled" 45 | /** 46 | * The session will attempt send streams directly between clients. If clients cannot connect 47 | * due to firewall restrictions, the session uses the OpenTok TURN server to relay streams. 48 | */ 49 | P2P = "enabled" 50 | ) 51 | 52 | type Role string 53 | 54 | const ( 55 | /** 56 | * A publisher can publish streams, subscribe to streams, and signal. 57 | */ 58 | Publisher Role = "publisher" 59 | /** 60 | * A subscriber can only subscribe to streams. 61 | */ 62 | Subscriber = "subscriber" 63 | /** 64 | * In addition to the privileges granted to a publisher, in clients using the OpenTok.js 2.2 65 | * library, a moderator can call the forceUnpublish() and 66 | * forceDisconnect() method of the Session object. 67 | */ 68 | Moderator = "moderator" 69 | ) 70 | 71 | type sessions struct { 72 | Sessions []Session `xml:"Session"` 73 | } 74 | 75 | type Tokbox struct { 76 | apiKey string 77 | partnerSecret string 78 | BetaUrl string //Endpoint for Beta Programs 79 | } 80 | 81 | type Session struct { 82 | SessionId string `xml:"session_id"` 83 | PartnerId string `xml:"partner_id"` 84 | CreateDt string `xml:"create_dt"` 85 | SessionStatus string `xml:"session_status"` 86 | T *Tokbox 87 | } 88 | 89 | func New(apikey, partnerSecret string) *Tokbox { 90 | return &Tokbox{apikey, partnerSecret, ""} 91 | } 92 | 93 | func (t *Tokbox) NewSession(location string, mm MediaMode, ctx ...*context.Context) (*Session, error) { 94 | params := url.Values{} 95 | 96 | if len(location) > 0 { 97 | params.Add("location", location) 98 | } 99 | 100 | params.Add("p2p.preference", string(mm)) 101 | 102 | var endpoint string 103 | if t.BetaUrl == "" { 104 | endpoint = apiHost 105 | } else { 106 | endpoint = t.BetaUrl 107 | } 108 | req, err := http.NewRequest("POST", endpoint+apiSession, strings.NewReader(params.Encode())) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | authHeader := t.apiKey + ":" + t.partnerSecret 114 | req.Header.Add("X-TB-PARTNER-AUTH", authHeader) 115 | 116 | if len(ctx) == 0 { 117 | ctx = append(ctx, nil) 118 | } 119 | res, err := client(ctx[0]).Do(req) 120 | if err != nil { 121 | return nil, err 122 | } 123 | defer res.Body.Close() 124 | 125 | if res.StatusCode != 200 { 126 | return nil, fmt.Errorf("Tokbox returns error code: %v", res.StatusCode) 127 | } 128 | 129 | var s sessions 130 | if err = xml.NewDecoder(res.Body).Decode(&s); err != nil { 131 | return nil, err 132 | } 133 | 134 | if len(s.Sessions) < 1 { 135 | return nil, fmt.Errorf("Tokbox did not return a session") 136 | } 137 | 138 | o := s.Sessions[0] 139 | o.T = t 140 | return &o, nil 141 | } 142 | 143 | func (s *Session) Token(role Role, connectionData string, expiration int64) (string, error) { 144 | now := time.Now().UTC().Unix() 145 | 146 | dataStr := "" 147 | dataStr += "session_id=" + url.QueryEscape(s.SessionId) 148 | dataStr += "&create_time=" + url.QueryEscape(fmt.Sprintf("%d", now)) 149 | if expiration > 0 { 150 | dataStr += "&expire_time=" + url.QueryEscape(fmt.Sprintf("%d", now+expiration)) 151 | } 152 | if len(role) > 0 { 153 | dataStr += "&role=" + url.QueryEscape(string(role)) 154 | } 155 | if len(connectionData) > 0 { 156 | dataStr += "&connection_data=" + url.QueryEscape(connectionData) 157 | } 158 | dataStr += "&nonce=" + url.QueryEscape(fmt.Sprintf("%d", rand.Intn(999999))) 159 | 160 | h := hmac.New(sha1.New, []byte(s.T.partnerSecret)) 161 | n, err := h.Write([]byte(dataStr)) 162 | if err != nil { 163 | return "", err 164 | } 165 | if n != len(dataStr) { 166 | return "", fmt.Errorf("hmac not enough bytes written %d != %d", n, len(dataStr)) 167 | } 168 | 169 | preCoded := "" 170 | preCoded += "partner_id=" + s.T.apiKey 171 | preCoded += "&sig=" + fmt.Sprintf("%x:%s", h.Sum(nil), dataStr) 172 | 173 | var buf bytes.Buffer 174 | encoder := base64.NewEncoder(base64.StdEncoding, &buf) 175 | encoder.Write([]byte(preCoded)) 176 | encoder.Close() 177 | return fmt.Sprintf("T1==%s", buf.String()), nil 178 | } 179 | 180 | func (s *Session) Tokens(n int, multithread bool, role Role, connectionData string, expiration int64) []string { 181 | ret := []string{} 182 | 183 | if multithread { 184 | var w sync.WaitGroup 185 | var lock sync.Mutex 186 | w.Add(n) 187 | 188 | for i := 0; i < n; i++ { 189 | go func(role Role, connectionData string, expiration int64) { 190 | a, e := s.Token(role, connectionData, expiration) 191 | if e == nil { 192 | lock.Lock() 193 | ret = append(ret, a) 194 | lock.Unlock() 195 | } 196 | w.Done() 197 | }(role, connectionData, expiration) 198 | 199 | } 200 | 201 | w.Wait() 202 | return ret 203 | } else { 204 | for i := 0; i < n; i++ { 205 | 206 | a, e := s.Token(role, connectionData, expiration) 207 | if e == nil { 208 | ret = append(ret, a) 209 | } 210 | } 211 | return ret 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /vendor/github.com/rs/cors/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.3 4 | - 1.4 5 | -------------------------------------------------------------------------------- /vendor/github.com/rs/cors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Olivier Poitrey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/github.com/rs/cors/README.md: -------------------------------------------------------------------------------- 1 | # Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors) [![Coverage](http://gocover.io/_badge/github.com/rs/cors)](http://gocover.io/github.com/rs/cors) 2 | 3 | CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. 4 | 5 | ## Getting Started 6 | 7 | After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "net/http" 14 | 15 | "github.com/rs/cors" 16 | ) 17 | 18 | func main() { 19 | mux := http.NewServeMux() 20 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 21 | w.Header().Set("Content-Type", "application/json") 22 | w.Write([]byte("{\"hello\": \"world\"}")) 23 | }) 24 | 25 | // cors.Default() setup the middleware with default options being 26 | // all origins accepted with simple methods (GET, POST). See 27 | // documentation below for more options. 28 | handler := cors.Default().Handler(mux) 29 | http.ListenAndServe(":8080", handler) 30 | } 31 | ``` 32 | 33 | Install `cors`: 34 | 35 | go get github.com/rs/cors 36 | 37 | Then run your server: 38 | 39 | go run server.go 40 | 41 | The server now runs on `localhost:8080`: 42 | 43 | $ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/ 44 | HTTP/1.1 200 OK 45 | Access-Control-Allow-Origin: foo.com 46 | Content-Type: application/json 47 | Date: Sat, 25 Oct 2014 03:43:57 GMT 48 | Content-Length: 18 49 | 50 | {"hello": "world"} 51 | 52 | ### More Examples 53 | 54 | * `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go) 55 | * [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go) 56 | * [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go) 57 | * [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go) 58 | * [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go) 59 | 60 | ## Parameters 61 | 62 | Parameters are passed to the middleware thru the `cors.New` method as follow: 63 | 64 | ```go 65 | c := cors.New(cors.Options{ 66 | AllowedOrigins: []string{"http://foo.com"}, 67 | AllowCredentials: true, 68 | }) 69 | 70 | // Insert the middleware 71 | handler = c.Handler(handler) 72 | ``` 73 | 74 | * **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. An origin may contain a wildcard (`*`) to replace 0 or more characters (i.e.: `http://*.domain.com`). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin. The default value is `*`. 75 | * **AllowOriginFunc** `func (origin string) bool`: A custom function to validate the origin. It take the origin as argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` is ignored 76 | * **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`). 77 | * **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. 78 | * **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification 79 | * **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`. 80 | * **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age. 81 | * **OptionsPassthrough** `bool`: Instructs preflight to let other potential next handlers to process the `OPTIONS` method. Turn this on if your application handles `OPTIONS`. 82 | * **Debug** `bool`: Debugging flag adds additional output to debug server side CORS issues. 83 | 84 | See [API documentation](http://godoc.org/github.com/rs/cors) for more info. 85 | 86 | ## Benchmarks 87 | 88 | BenchmarkWithout 20000000 64.6 ns/op 8 B/op 1 allocs/op 89 | BenchmarkDefault 3000000 469 ns/op 114 B/op 2 allocs/op 90 | BenchmarkAllowedOrigin 3000000 608 ns/op 114 B/op 2 allocs/op 91 | BenchmarkPreflight 20000000 73.2 ns/op 0 B/op 0 allocs/op 92 | BenchmarkPreflightHeader 20000000 73.6 ns/op 0 B/op 0 allocs/op 93 | BenchmarkParseHeaderList 2000000 847 ns/op 184 B/op 6 allocs/op 94 | BenchmarkParse…Single 5000000 290 ns/op 32 B/op 3 allocs/op 95 | BenchmarkParse…Normalized 2000000 776 ns/op 160 B/op 6 allocs/op 96 | 97 | ## Licenses 98 | 99 | All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). 100 | -------------------------------------------------------------------------------- /vendor/github.com/rs/cors/utils.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import "strings" 4 | 5 | const toLower = 'a' - 'A' 6 | 7 | type converter func(string) string 8 | 9 | type wildcard struct { 10 | prefix string 11 | suffix string 12 | } 13 | 14 | func (w wildcard) match(s string) bool { 15 | return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) 16 | } 17 | 18 | // convert converts a list of string using the passed converter function 19 | func convert(s []string, c converter) []string { 20 | out := []string{} 21 | for _, i := range s { 22 | out = append(out, c(i)) 23 | } 24 | return out 25 | } 26 | 27 | // parseHeaderList tokenize + normalize a string containing a list of headers 28 | func parseHeaderList(headerList string) []string { 29 | l := len(headerList) 30 | h := make([]byte, 0, l) 31 | upper := true 32 | // Estimate the number headers in order to allocate the right splice size 33 | t := 0 34 | for i := 0; i < l; i++ { 35 | if headerList[i] == ',' { 36 | t++ 37 | } 38 | } 39 | headers := make([]string, 0, t) 40 | for i := 0; i < l; i++ { 41 | b := headerList[i] 42 | if b >= 'a' && b <= 'z' { 43 | if upper { 44 | h = append(h, b-toLower) 45 | } else { 46 | h = append(h, b) 47 | } 48 | } else if b >= 'A' && b <= 'Z' { 49 | if !upper { 50 | h = append(h, b+toLower) 51 | } else { 52 | h = append(h, b) 53 | } 54 | } else if b == '-' || (b >= '0' && b <= '9') { 55 | h = append(h, b) 56 | } 57 | 58 | if b == ' ' || b == ',' || i == l-1 { 59 | if len(h) > 0 { 60 | // Flush the found header 61 | headers = append(headers, string(h)) 62 | h = h[:0] 63 | upper = true 64 | } 65 | } else { 66 | upper = b == '-' 67 | } 68 | } 69 | return headers 70 | } 71 | -------------------------------------------------------------------------------- /vendor/github.com/rs/xhandler/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.5 4 | - tip 5 | matrix: 6 | allow_failures: 7 | - go: tip 8 | -------------------------------------------------------------------------------- /vendor/github.com/rs/xhandler/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Olivier Poitrey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/github.com/rs/xhandler/README.md: -------------------------------------------------------------------------------- 1 | # XHandler 2 | 3 | [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/xhandler) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/xhandler/master/LICENSE) [![Build Status](https://travis-ci.org/rs/xhandler.svg?branch=master)](https://travis-ci.org/rs/xhandler) [![Coverage](http://gocover.io/_badge/github.com/rs/xhandler)](http://gocover.io/github.com/rs/xhandler) 4 | 5 | XHandler is a bridge between [net/context](https://godoc.org/golang.org/x/net/context) and `http.Handler`. 6 | 7 | It lets you enforce `net/context` in your handlers without sacrificing compatibility with existing `http.Handlers` nor imposing a specific router. 8 | 9 | Thanks to `net/context` deadline management, `xhandler` is able to enforce a per request deadline and will cancel the context when the client closes the connection unexpectedly. 10 | 11 | You may create your own `net/context` aware handler pretty much the same way as you would do with http.Handler. 12 | 13 | Read more about xhandler on [Dailymotion engineering blog](http://engineering.dailymotion.com/our-way-to-go/). 14 | 15 | ## Installing 16 | 17 | go get -u github.com/rs/xhandler 18 | 19 | ## Usage 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "log" 26 | "net/http" 27 | "time" 28 | 29 | "github.com/rs/cors" 30 | "github.com/rs/xhandler" 31 | "golang.org/x/net/context" 32 | ) 33 | 34 | type myMiddleware struct { 35 | next xhandler.HandlerC 36 | } 37 | 38 | func (h myMiddleware) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { 39 | ctx = context.WithValue(ctx, "test", "World") 40 | h.next.ServeHTTPC(ctx, w, r) 41 | } 42 | 43 | func main() { 44 | c := xhandler.Chain{} 45 | 46 | // Add close notifier handler so context is cancelled when the client closes 47 | // the connection 48 | c.UseC(xhandler.CloseHandler) 49 | 50 | // Add timeout handler 51 | c.UseC(xhandler.TimeoutHandler(2 * time.Second)) 52 | 53 | // Middleware putting something in the context 54 | c.UseC(func(next xhandler.HandlerC) xhandler.HandlerC { 55 | return myMiddleware{next: next} 56 | }) 57 | 58 | // Mix it with a non-context-aware middleware handler 59 | c.Use(cors.Default().Handler) 60 | 61 | // Final handler (using handlerFuncC), reading from the context 62 | xh := xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 63 | value := ctx.Value("test").(string) 64 | w.Write([]byte("Hello " + value)) 65 | }) 66 | 67 | // Bridge context aware handlers with http.Handler using xhandler.Handle() 68 | http.Handle("/test", c.Handler(xh)) 69 | 70 | if err := http.ListenAndServe(":8080", nil); err != nil { 71 | log.Fatal(err) 72 | } 73 | } 74 | ``` 75 | 76 | ### Using xmux 77 | 78 | Xhandler comes with an optional context aware [muxer](https://github.com/rs/xmux) forked from [httprouter](https://github.com/julienschmidt/httprouter): 79 | 80 | ```go 81 | package main 82 | 83 | import ( 84 | "fmt" 85 | "log" 86 | "net/http" 87 | "time" 88 | 89 | "github.com/rs/xhandler" 90 | "github.com/rs/xmux" 91 | "golang.org/x/net/context" 92 | ) 93 | 94 | func main() { 95 | c := xhandler.Chain{} 96 | 97 | // Append a context-aware middleware handler 98 | c.UseC(xhandler.CloseHandler) 99 | 100 | // Another context-aware middleware handler 101 | c.UseC(xhandler.TimeoutHandler(2 * time.Second)) 102 | 103 | mux := xmux.New() 104 | 105 | // Use c.Handler to terminate the chain with your final handler 106 | mux.GET("/welcome/:name", xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) { 107 | fmt.Fprintf(w, "Welcome %s!", xmux.Params(ctx).Get("name")) 108 | })) 109 | 110 | if err := http.ListenAndServe(":8080", c.Handler(mux)); err != nil { 111 | log.Fatal(err) 112 | } 113 | } 114 | ``` 115 | 116 | See [xmux](https://github.com/rs/xmux) for more examples. 117 | 118 | ## Context Aware Middleware 119 | 120 | Here is a list of `net/context` aware middleware handlers implementing `xhandler.HandlerC` interface. 121 | 122 | Feel free to put up a PR linking your middleware if you have built one: 123 | 124 | | Middleware | Author | Description | 125 | | ---------- | ------ | ----------- | 126 | | [xmux](https://github.com/rs/xmux) | [Olivier Poitrey](https://github.com/rs) | HTTP request muxer | 127 | | [xlog](https://github.com/rs/xlog) | [Olivier Poitrey](https://github.com/rs) | HTTP handler logger | 128 | | [xstats](https://github.com/rs/xstats) | [Olivier Poitrey](https://github.com/rs) | A generic client for service instrumentation | 129 | | [xaccess](https://github.com/rs/xaccess) | [Olivier Poitrey](https://github.com/rs) | HTTP handler access logger with [xlog](https://github.com/rs/xlog) and [xstats](https://github.com/rs/xstats) | 130 | | [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support | 131 | 132 | ## Licenses 133 | 134 | All source code is licensed under the [MIT License](https://raw.github.com/rs/xhandler/master/LICENSE). 135 | -------------------------------------------------------------------------------- /vendor/github.com/rs/xhandler/chain.go: -------------------------------------------------------------------------------- 1 | package xhandler 2 | 3 | import ( 4 | "net/http" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // Chain is an helper to chain middleware handlers together for an easier 10 | // management. 11 | type Chain []func(next HandlerC) HandlerC 12 | 13 | // UseC appends a context-aware handler to the middleware chain. 14 | func (c *Chain) UseC(f func(next HandlerC) HandlerC) { 15 | *c = append(*c, f) 16 | } 17 | 18 | // Use appends a standard http.Handler to the middleware chain without 19 | // lossing track of the context when inserted between two context aware handlers. 20 | // 21 | // Caveat: the f function will be called on each request so you are better to put 22 | // any initialization sequence outside of this function. 23 | func (c *Chain) Use(f func(next http.Handler) http.Handler) { 24 | xf := func(next HandlerC) HandlerC { 25 | return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 26 | n := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | next.ServeHTTPC(ctx, w, r) 28 | }) 29 | f(n).ServeHTTP(w, r) 30 | }) 31 | } 32 | *c = append(*c, xf) 33 | } 34 | 35 | // Handler wraps the provided final handler with all the middleware appended to 36 | // the chain and return a new standard http.Handler instance. 37 | // The context.Background() context is injected automatically. 38 | func (c Chain) Handler(xh HandlerC) http.Handler { 39 | ctx := context.Background() 40 | return c.HandlerCtx(ctx, xh) 41 | } 42 | 43 | // HandlerFC is an helper to provide a function (HandlerFuncC) to Handler(). 44 | // 45 | // HandlerFC is equivalent to: 46 | // c.Handler(xhandler.HandlerFuncC(xhc)) 47 | func (c Chain) HandlerFC(xhf HandlerFuncC) http.Handler { 48 | ctx := context.Background() 49 | return c.HandlerCtx(ctx, HandlerFuncC(xhf)) 50 | } 51 | 52 | // HandlerH is an helper to provide a standard http handler (http.HandlerFunc) 53 | // to Handler(). Your final handler won't have access the context though. 54 | func (c Chain) HandlerH(h http.Handler) http.Handler { 55 | ctx := context.Background() 56 | return c.HandlerCtx(ctx, HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 57 | h.ServeHTTP(w, r) 58 | })) 59 | } 60 | 61 | // HandlerF is an helper to provide a standard http handler function 62 | // (http.HandlerFunc) to Handler(). Your final handler won't have access 63 | // the context though. 64 | func (c Chain) HandlerF(hf http.HandlerFunc) http.Handler { 65 | ctx := context.Background() 66 | return c.HandlerCtx(ctx, HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 67 | hf(w, r) 68 | })) 69 | } 70 | 71 | // HandlerCtx wraps the provided final handler with all the middleware appended to 72 | // the chain and return a new standard http.Handler instance. 73 | func (c Chain) HandlerCtx(ctx context.Context, xh HandlerC) http.Handler { 74 | return New(ctx, c.HandlerC(xh)) 75 | } 76 | 77 | // HandlerC wraps the provided final handler with all the middleware appended to 78 | // the chain and returns a HandlerC instance. 79 | func (c Chain) HandlerC(xh HandlerC) HandlerC { 80 | for i := len(c) - 1; i >= 0; i-- { 81 | xh = c[i](xh) 82 | } 83 | return xh 84 | } 85 | 86 | // HandlerCF wraps the provided final handler func with all the middleware appended to 87 | // the chain and returns a HandlerC instance. 88 | // 89 | // HandlerCF is equivalent to: 90 | // c.HandlerC(xhandler.HandlerFuncC(xhc)) 91 | func (c Chain) HandlerCF(xhc HandlerFuncC) HandlerC { 92 | return c.HandlerC(HandlerFuncC(xhc)) 93 | } 94 | -------------------------------------------------------------------------------- /vendor/github.com/rs/xhandler/middleware.go: -------------------------------------------------------------------------------- 1 | package xhandler 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // CloseHandler returns a Handler cancelling the context when the client 11 | // connection close unexpectedly. 12 | func CloseHandler(next HandlerC) HandlerC { 13 | return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 14 | // Cancel the context if the client closes the connection 15 | if wcn, ok := w.(http.CloseNotifier); ok { 16 | var cancel context.CancelFunc 17 | ctx, cancel = context.WithCancel(ctx) 18 | defer cancel() 19 | 20 | notify := wcn.CloseNotify() 21 | go func() { 22 | select { 23 | case <-notify: 24 | cancel() 25 | case <-ctx.Done(): 26 | } 27 | }() 28 | } 29 | 30 | next.ServeHTTPC(ctx, w, r) 31 | }) 32 | } 33 | 34 | // TimeoutHandler returns a Handler which adds a timeout to the context. 35 | // 36 | // Child handlers have the responsability to obey the context deadline and to return 37 | // an appropriate error (or not) response in case of timeout. 38 | func TimeoutHandler(timeout time.Duration) func(next HandlerC) HandlerC { 39 | return func(next HandlerC) HandlerC { 40 | return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 41 | ctx, _ = context.WithTimeout(ctx, timeout) 42 | next.ServeHTTPC(ctx, w, r) 43 | }) 44 | } 45 | } 46 | 47 | // If is a special handler that will skip insert the condNext handler only if a condition 48 | // applies at runtime. 49 | func If(cond func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool, condNext func(next HandlerC) HandlerC) func(next HandlerC) HandlerC { 50 | return func(next HandlerC) HandlerC { 51 | return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 52 | if cond(ctx, w, r) { 53 | condNext(next).ServeHTTPC(ctx, w, r) 54 | } else { 55 | next.ServeHTTPC(ctx, w, r) 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vendor/github.com/rs/xhandler/xhandler.go: -------------------------------------------------------------------------------- 1 | // Package xhandler provides a bridge between http.Handler and net/context. 2 | // 3 | // xhandler enforces net/context in your handlers without sacrificing 4 | // compatibility with existing http.Handlers nor imposing a specific router. 5 | // 6 | // Thanks to net/context deadline management, xhandler is able to enforce 7 | // a per request deadline and will cancel the context in when the client close 8 | // the connection unexpectedly. 9 | // 10 | // You may create net/context aware middlewares pretty much the same way as 11 | // you would do with http.Handler. 12 | package xhandler 13 | 14 | import ( 15 | "net/http" 16 | 17 | "golang.org/x/net/context" 18 | ) 19 | 20 | // HandlerC is a net/context aware http.Handler 21 | type HandlerC interface { 22 | ServeHTTPC(context.Context, http.ResponseWriter, *http.Request) 23 | } 24 | 25 | // HandlerFuncC type is an adapter to allow the use of ordinary functions 26 | // as a xhandler.Handler. If f is a function with the appropriate signature, 27 | // xhandler.HandlerFuncC(f) is a xhandler.Handler object that calls f. 28 | type HandlerFuncC func(context.Context, http.ResponseWriter, *http.Request) 29 | 30 | // ServeHTTPC calls f(ctx, w, r). 31 | func (f HandlerFuncC) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { 32 | f(ctx, w, r) 33 | } 34 | 35 | // New creates a conventional http.Handler injecting the provided root 36 | // context to sub handlers. This handler is used as a bridge between conventional 37 | // http.Handler and context aware handlers. 38 | func New(ctx context.Context, h HandlerC) http.Handler { 39 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 | h.ServeHTTPC(ctx, w, r) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /vendor/goji.io/.travis.yml: -------------------------------------------------------------------------------- 1 | go_import_path: goji.io 2 | language: go 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | - go: 1.5 8 | - go: 1.5.1 9 | - go: 1.5.2 10 | - go: 1.5.3 11 | - go: 1.6 12 | - go: tip 13 | 14 | script: 15 | - go test -cover -race ./... 16 | -------------------------------------------------------------------------------- /vendor/goji.io/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, 2016 Carl Jackson (carl@avtok.com) 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/goji.io/README.md: -------------------------------------------------------------------------------- 1 | Goji 2 | ==== 3 | 4 | [![GoDoc](https://godoc.org/goji.io?status.svg)](https://godoc.org/goji.io) [![Build Status](https://travis-ci.org/goji/goji.svg?branch=master)](https://travis-ci.org/goji/goji) 5 | 6 | Goji is a HTTP request multiplexer, similar to [`net/http.ServeMux`][servemux]. 7 | It compares incoming requests to a list of registered [Patterns][pattern], and 8 | dispatches to the [Handler][handler] that corresponds to the first matching 9 | Pattern. Goji also supports [Middleware][middleware] (composable shared 10 | functionality applied to every request) and uses the de facto standard 11 | [`x/net/context`][context] to store request-scoped values. 12 | 13 | [servemux]: https://golang.org/pkg/net/http/#ServeMux 14 | [pattern]: https://godoc.org/goji.io#Pattern 15 | [handler]: https://godoc.org/goji.io#Handler 16 | [middleware]: https://godoc.org/goji.io#Mux.Use 17 | [context]: https://godoc.org/golang.org/x/net/context 18 | 19 | 20 | Quick Start 21 | ----------- 22 | 23 | ```go 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | "net/http" 29 | 30 | "goji.io" 31 | "goji.io/pat" 32 | "golang.org/x/net/context" 33 | ) 34 | 35 | func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) { 36 | name := pat.Param(ctx, "name") 37 | fmt.Fprintf(w, "Hello, %s!", name) 38 | } 39 | 40 | func main() { 41 | mux := goji.NewMux() 42 | mux.HandleFuncC(pat.Get("/hello/:name"), hello) 43 | 44 | http.ListenAndServe("localhost:8000", mux) 45 | } 46 | ``` 47 | 48 | Please refer to [Goji's GoDoc Documentation][godoc] for a full API reference. 49 | 50 | [godoc]: https://godoc.org/goji.io 51 | 52 | 53 | Stability 54 | --------- 55 | 56 | Goji's API is stable, and guarantees to never break compatibility with existing 57 | code (under similar rules to the Go project's [guidelines][compat]). Goji is 58 | suitable for use in production. 59 | 60 | One possible exception to the above compatibility guarantees surrounds the 61 | inclusion of the `x/net/context` package in the standard library for Go 1.7. 62 | When this happens, Goji may switch its package imports to use the standard 63 | library's version of the package. Note that, while this is a backwards 64 | incompatible change, the impact on clients is expected to be minimal: 65 | applications will simply have to change the import path of the `context` 66 | package. More discussion about this migration can be found on the Goji mailing 67 | list. 68 | 69 | [compat]: https://golang.org/doc/go1compat 70 | 71 | 72 | Community / Contributing 73 | ------------------------ 74 | 75 | Goji maintains a mailing list, [gojiberries][berries], where you should feel 76 | welcome to ask questions about the project (no matter how simple!), to announce 77 | projects or libraries built on top of Goji, or to talk about Goji more 78 | generally. Goji's author (Carl Jackson) also loves to hear from users directly 79 | at his personal email address, which is available on his GitHub profile page. 80 | 81 | Contributions to Goji are welcome, however please be advised that due to Goji's 82 | stability guarantees interface changes are unlikely to be accepted. 83 | 84 | All interactions in the Goji community will be held to the high standard of the 85 | broader Go community's [Code of Conduct][conduct]. 86 | 87 | [berries]: https://groups.google.com/forum/#!forum/gojiberries 88 | [conduct]: https://golang.org/conduct 89 | -------------------------------------------------------------------------------- /vendor/goji.io/dispatch.go: -------------------------------------------------------------------------------- 1 | package goji 2 | 3 | import ( 4 | "net/http" 5 | 6 | "goji.io/internal" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | type dispatch struct{} 11 | 12 | func (d dispatch) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { 13 | h := ctx.Value(internal.Handler) 14 | if h == nil { 15 | http.NotFound(w, r) 16 | } else { 17 | h.(Handler).ServeHTTPC(ctx, w, r) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vendor/goji.io/goji.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package goji is a minimalistic and flexible HTTP request multiplexer. 3 | 4 | Goji itself has very few features: it is first and foremost a standard set of 5 | interfaces for writing web applications. Several subpackages are distributed 6 | with Goji to provide standard production-ready implementations of several of the 7 | interfaces, however users are also encouraged to implement the interfaces on 8 | their own, especially if their needs are unusual. 9 | */ 10 | package goji 11 | 12 | import ( 13 | "net/http" 14 | 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | /* 19 | Pattern determines whether a given request matches some criteria. Goji users 20 | looking for a concrete type that implements this interface should consider 21 | Goji's "pat" sub-package, which implements a small domain specific language for 22 | HTTP routing. 23 | 24 | Patterns typically only examine a small portion of incoming requests, most 25 | commonly the HTTP method and the URL's RawPath. As an optimization, Goji can 26 | elide calls to your Pattern for requests it knows cannot match. Pattern authors 27 | who wish to take advantage of this functionality (and in some cases an 28 | asymptotic performance improvement) can augment their Pattern implementations 29 | with any of the following methods: 30 | 31 | // HTTPMethods returns a set of HTTP methods that this Pattern matches, 32 | // or nil if it's not possible to determine which HTTP methods might be 33 | // matched. Put another way, requests with HTTP methods not in the 34 | // returned set are guaranteed to never match this Pattern. 35 | HTTPMethods() map[string]struct{} 36 | 37 | // PathPrefix returns a string which all RawPaths that match this 38 | // Pattern must have as a prefix. Put another way, requests with 39 | // RawPaths that do not contain the returned string as a prefix are 40 | // guaranteed to never match this Pattern. 41 | PathPrefix() string 42 | 43 | The presence or lack of these performance improvements should be viewed as an 44 | implementation detail and are not part of Goji's API compatibility guarantee. It 45 | is the responsibility of Pattern authors to ensure that their Match function 46 | always returns correct results, even if these optimizations are not performed. 47 | 48 | All operations on Patterns must be safe for concurrent use by multiple 49 | goroutines. 50 | */ 51 | type Pattern interface { 52 | // Match examines the request and request context to determine if the 53 | // request is a match. If so, it returns a non-nil context.Context 54 | // (likely one derived from the input Context, and perhaps simply the 55 | // input Context unchanged). The returned context may be used to store 56 | // request-scoped data, such as variables extracted from the Request. 57 | // 58 | // Match must not mutate the passed request. 59 | Match(context.Context, *http.Request) context.Context 60 | } 61 | 62 | /* 63 | Handler is a context-aware variant of net/http.Handler. It has the same 64 | semantics as http.Handler, except that a request-scoped context is additionally 65 | passed to the handler function. 66 | */ 67 | type Handler interface { 68 | ServeHTTPC(context.Context, http.ResponseWriter, *http.Request) 69 | } 70 | 71 | /* 72 | HandlerFunc is a context-aware variant of net/http.HandlerFunc. It has the same 73 | semantics as http.HandlerFunc, except that a request-scoped context is 74 | additionally passed to the function. 75 | 76 | HandlerFunc implements both the Handler and http.Handler interfaces. 77 | */ 78 | type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request) 79 | 80 | /* 81 | ServeHTTP implements net/http.Handler. It calls the underlying function with a 82 | context of context.TODO in order to ease the conversion of non-context-aware 83 | Handlers to context-aware ones using static analysis. 84 | */ 85 | func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { 86 | h(context.TODO(), w, r) 87 | } 88 | 89 | /* 90 | ServeHTTPC implements Handler. 91 | */ 92 | func (h HandlerFunc) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { 93 | h(ctx, w, r) 94 | } 95 | -------------------------------------------------------------------------------- /vendor/goji.io/handle.go: -------------------------------------------------------------------------------- 1 | package goji 2 | 3 | import ( 4 | "net/http" 5 | 6 | "goji.io/internal" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | /* 11 | Handle adds a new route to the Mux. Requests that match the given Pattern will 12 | be dispatched to the given http.Handler. If the http.Handler also supports 13 | Handler, that interface will be used instead. 14 | 15 | Routing is performed in the order in which routes are added: the first route 16 | with a matching Pattern will be used. In particular, Goji guarantees that 17 | routing is performed in a manner that is indistinguishable from the following 18 | algorithm: 19 | 20 | // Assume routes is a slice that every call to Handle appends to 21 | for route := range routes { 22 | // For performance, Patterns can opt out of this call to Match. 23 | // See the documentation for Pattern for more. 24 | if ctx2 := route.pattern.Match(ctx, r); ctx2 != nil { 25 | route.handler.ServeHTTPC(ctx2, w, r) 26 | break 27 | } 28 | } 29 | 30 | It is not safe to concurrently register routes from multiple goroutines, or to 31 | register routes concurrently with requests. 32 | */ 33 | func (m *Mux) Handle(p Pattern, h http.Handler) { 34 | gh, ok := h.(Handler) 35 | if !ok { 36 | gh = internal.ContextWrapper{Handler: h} 37 | } 38 | m.router.add(p, gh) 39 | } 40 | 41 | /* 42 | HandleFunc adds a new route to the Mux. It is equivalent to calling Handle on a 43 | handler wrapped with http.HandlerFunc, and is provided only for convenience. 44 | */ 45 | func (m *Mux) HandleFunc(p Pattern, h func(http.ResponseWriter, *http.Request)) { 46 | m.Handle(p, http.HandlerFunc(h)) 47 | } 48 | 49 | /* 50 | HandleC adds a new context-aware route to the Mux. See the documentation for 51 | Handle for more information about the semantics of routing. 52 | 53 | It is not safe to concurrently register routes from multiple goroutines, or to 54 | register routes concurrently with requests. 55 | */ 56 | func (m *Mux) HandleC(p Pattern, h Handler) { 57 | m.router.add(p, h) 58 | } 59 | 60 | /* 61 | HandleFuncC adds a new context-aware route to the Mux. It is equivalent to 62 | calling HandleC on a handler wrapped with HandlerFunc, and is provided for 63 | convenience. 64 | */ 65 | func (m *Mux) HandleFuncC(p Pattern, h func(context.Context, http.ResponseWriter, *http.Request)) { 66 | m.HandleC(p, HandlerFunc(h)) 67 | } 68 | -------------------------------------------------------------------------------- /vendor/goji.io/internal/context.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | // ContextKey is a type used for Goji's context.Context keys. 4 | type ContextKey int 5 | 6 | var ( 7 | // Path is the context key used to store the path Goji uses for its 8 | // PathPrefix optimization. 9 | Path interface{} = ContextKey(0) 10 | // Pattern is the context key used to store the Pattern that Goji last 11 | // matched. 12 | Pattern interface{} = ContextKey(1) 13 | // Handler is the context key used to store the Handler that Goji last 14 | // mached (and will therefore dispatch to at the end of the middleware 15 | // stack). 16 | Handler interface{} = ContextKey(2) 17 | ) 18 | -------------------------------------------------------------------------------- /vendor/goji.io/internal/http.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "net/http" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // ContextWrapper is a standard bridge type from http.Handlers to context-aware 10 | // Handlers. It is included here so that the middleware subpackage can use it to 11 | // unwrap Handlers. 12 | type ContextWrapper struct { 13 | http.Handler 14 | } 15 | 16 | // ServeHTTPC implements goji.Handler. 17 | func (c ContextWrapper) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { 18 | c.Handler.ServeHTTP(w, r) 19 | } 20 | -------------------------------------------------------------------------------- /vendor/goji.io/internal/internal.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package internal is a private package that allows Goji to expose a less 3 | confusing interface to its users. This package must not be used outside of Goji; 4 | every piece of its functionality has been exposed by one of Goji's subpackages. 5 | 6 | The problem this package solves is to allow Goji to internally agree on types 7 | and secret values between its packages without introducing import cycles. Goji 8 | needs to agree on these types and values in order to organize its public API 9 | into audience-specific subpackages (for instance, a package for pattern authors, 10 | a package for middleware authors, and a main package for routing users) without 11 | exposing implementation details in any of the packages. 12 | */ 13 | package internal 14 | -------------------------------------------------------------------------------- /vendor/goji.io/middleware.go: -------------------------------------------------------------------------------- 1 | package goji 2 | 3 | import ( 4 | "net/http" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | /* 10 | Use appends a middleware to the Mux's middleware stack. 11 | 12 | Middleware are composable pieces of functionality that augment Handlers. Common 13 | examples of middleware include request loggers, authentication checkers, and 14 | metrics gatherers. 15 | 16 | Middleware are evaluated in the reverse order in which they were added, but the 17 | resulting Handlers execute in "normal" order (i.e., the Handler returned by the 18 | first Middleware to be added gets called first). 19 | 20 | For instance, given middleware A, B, and C, added in that order, Goji will 21 | behave similarly to this snippet: 22 | 23 | augmentedHandler := A(B(C(yourHandler))) 24 | augmentedHandler.ServeHTTPC(ctx, w, r) 25 | 26 | Assuming each of A, B, and C look something like this: 27 | 28 | func A(inner goji.Handler) goji.Handler { 29 | log.Print("A: called") 30 | mw := func(ctx context.Context, w http.ResponseWriter, r *http.Request) { 31 | log.Print("A: before") 32 | inner.ServeHTTPC(ctx, w, r) 33 | log.Print("A: after") 34 | } 35 | return goji.HandlerFunc(mw) 36 | } 37 | 38 | we'd expect to see the following in the log: 39 | 40 | C: called 41 | B: called 42 | A: called 43 | --- 44 | A: before 45 | B: before 46 | C: before 47 | yourHandler: called 48 | C: after 49 | B: after 50 | A: after 51 | 52 | Note that augmentedHandler may be called many times. Put another way, you will 53 | see many invocations of the portion of the log below the divider, and perhaps 54 | only see the portion above the divider a single time. Also note that as an 55 | implementation detail, net/http-style middleware will be called once per 56 | request, even though the Goji-style middleware around them might only ever be 57 | called a single time. 58 | 59 | Middleware in Goji is called after routing has been performed. Therefore it is 60 | possible to examine any routing information placed into the context by Patterns, 61 | or to view or modify the Handler that will be routed to. Middleware authors 62 | should read the documentation for the "middleware" subpackage for more 63 | information about how this is done. 64 | 65 | The http.Handler returned by the given middleware must be safe for concurrent 66 | use by multiple goroutines. It is not safe to concurrently register middleware 67 | from multiple goroutines, or to register middleware concurrently with requests. 68 | */ 69 | func (m *Mux) Use(middleware func(http.Handler) http.Handler) { 70 | m.middleware = append(m.middleware, func(h Handler) Handler { 71 | return outerBridge{middleware, h} 72 | }) 73 | m.buildChain() 74 | } 75 | 76 | /* 77 | UseC appends a context-aware middleware to the Mux's middleware stack. See the 78 | documentation for Use for more information about the semantics of middleware. 79 | 80 | The Handler returned by the given middleware must be safe for concurrent use by 81 | multiple goroutines. It is not safe to concurrently register middleware from 82 | multiple goroutines, or to register middleware concurrently with requests. 83 | */ 84 | func (m *Mux) UseC(middleware func(Handler) Handler) { 85 | m.middleware = append(m.middleware, middleware) 86 | m.buildChain() 87 | } 88 | 89 | // Pre-compile a Handler for us to use during dispatch. Yes, this means that 90 | // adding middleware is quadratic, but it (a) happens during configuration time, 91 | // not at "runtime", and (b) n should ~always be small. 92 | func (m *Mux) buildChain() { 93 | m.handler = dispatch{} 94 | for i := len(m.middleware) - 1; i >= 0; i-- { 95 | m.handler = m.middleware[i](m.handler) 96 | } 97 | } 98 | 99 | type innerBridge struct { 100 | inner Handler 101 | ctx context.Context 102 | } 103 | 104 | func (b innerBridge) ServeHTTP(w http.ResponseWriter, r *http.Request) { 105 | b.inner.ServeHTTPC(b.ctx, w, r) 106 | } 107 | 108 | type outerBridge struct { 109 | mware func(http.Handler) http.Handler 110 | inner Handler 111 | } 112 | 113 | func (b outerBridge) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { 114 | b.mware(innerBridge{b.inner, ctx}).ServeHTTP(w, r) 115 | } 116 | -------------------------------------------------------------------------------- /vendor/goji.io/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package middleware contains utilities for Goji Middleware authors. 3 | 4 | Unless you are writing middleware for your application, you should avoid 5 | importing this package. Instead, use the abstractions provided by your 6 | middleware package. 7 | */ 8 | package middleware 9 | 10 | import ( 11 | "net/http" 12 | 13 | "goji.io" 14 | "goji.io/internal" 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | /* 19 | Pattern returns the most recently matched Pattern, or nil if no pattern was 20 | matched. 21 | */ 22 | func Pattern(ctx context.Context) goji.Pattern { 23 | p := ctx.Value(internal.Pattern) 24 | if p == nil { 25 | return nil 26 | } 27 | return p.(goji.Pattern) 28 | } 29 | 30 | /* 31 | SetPattern returns a new context in which the given Pattern is used as the most 32 | recently matched pattern. 33 | */ 34 | func SetPattern(ctx context.Context, p goji.Pattern) context.Context { 35 | return context.WithValue(ctx, internal.Pattern, p) 36 | } 37 | 38 | /* 39 | Handler returns the handler corresponding to the most recently matched Pattern, 40 | or nil if no pattern was matched. 41 | 42 | Internally, Goji converts net/http.Handlers into goji.Handlers using a wrapper 43 | object. Users who wish to retrieve the original http.Handler they passed to Goji 44 | may call UnwrapHandler. 45 | 46 | The handler returned by this function is the one that will be dispatched to at 47 | the end of the middleware stack. If the returned Handler is nil, http.NotFound 48 | will be used instead. 49 | */ 50 | func Handler(ctx context.Context) goji.Handler { 51 | h := ctx.Value(internal.Handler) 52 | if h == nil { 53 | return nil 54 | } 55 | return h.(goji.Handler) 56 | } 57 | 58 | /* 59 | SetHandler returns a new context in which the given Handler was most recently 60 | matched and which consequently will be dispatched to. 61 | */ 62 | func SetHandler(ctx context.Context, h goji.Handler) context.Context { 63 | return context.WithValue(ctx, internal.Handler, h) 64 | } 65 | 66 | /* 67 | UnwrapHandler extracts the original http.Handler from a Goji-wrapped Handler 68 | object, or returns nil if the given Handler has not been wrapped in this way. 69 | 70 | This function is necessary because Goji uses goji.Handler as its native data 71 | type internally, and uses a wrapper struct to convert all http.Handlers it is 72 | passed into goji.Handlers. 73 | */ 74 | func UnwrapHandler(h goji.Handler) http.Handler { 75 | if cw, ok := h.(internal.ContextWrapper); ok { 76 | return cw.Handler 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /vendor/goji.io/mux.go: -------------------------------------------------------------------------------- 1 | package goji 2 | 3 | import ( 4 | "net/http" 5 | 6 | "goji.io/internal" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | /* 11 | Mux is a HTTP multiplexer / router similar to net/http.ServeMux. 12 | 13 | Muxes multiplex traffic between many Handlers by selecting the first applicable 14 | Pattern. They then call a common middleware stack, finally passing control to 15 | the selected Handler. See the documentation on the Handle function for more 16 | information about how routing is performed, the documentation on the Pattern 17 | type for more information about request matching, and the documentation for the 18 | Use method for more about middleware. 19 | 20 | Muxes cannot be configured concurrently from multiple goroutines, nor can they 21 | be configured concurrently with requests. 22 | */ 23 | type Mux struct { 24 | handler Handler 25 | middleware []func(Handler) Handler 26 | router router 27 | root bool 28 | } 29 | 30 | /* 31 | NewMux returns a new Mux with no configured middleware or routes. 32 | */ 33 | func NewMux() *Mux { 34 | m := SubMux() 35 | m.root = true 36 | return m 37 | } 38 | 39 | /* 40 | SubMux returns a new Mux with no configured middleware or routes, and which 41 | inherits routing information from the passed context. This is especially useful 42 | when using one Mux as a Handler registered to another "parent" Mux. 43 | 44 | For example, a common pattern is to organize applications in a way that mirrors 45 | the structure of its URLs: a photo-sharing site might have URLs that start with 46 | "/users/" and URLs that start with "/albums/", and might be organized using 47 | three Muxes: 48 | 49 | root := NewMux() 50 | users := SubMux() 51 | root.HandleC(pat.New("/users/*"), users) 52 | albums := SubMux() 53 | root.HandleC(pat.New("/albums/*"), albums) 54 | 55 | // e.g., GET /users/carl 56 | users.HandleC(pat.Get("/:name"), renderProfile) 57 | // e.g., POST /albums/ 58 | albums.HandleC(pat.Post("/"), newAlbum) 59 | */ 60 | func SubMux() *Mux { 61 | m := &Mux{} 62 | m.buildChain() 63 | return m 64 | } 65 | 66 | /* 67 | ServeHTTP implements net/http.Handler. It uses context.TODO as the root context 68 | in order to ease the conversion of non-context-aware Handlers to context-aware 69 | ones using static analysis. 70 | 71 | Users who know that their mux sits at the top of the request hierarchy should 72 | consider creating a small helper http.Handler that calls this Mux's ServeHTTPC 73 | function with context.Background. 74 | */ 75 | func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 76 | m.ServeHTTPC(context.TODO(), w, r) 77 | } 78 | 79 | /* 80 | ServeHTTPC implements Handler. 81 | */ 82 | func (m *Mux) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { 83 | if m.root { 84 | ctx = context.WithValue(ctx, internal.Path, r.URL.EscapedPath()) 85 | } 86 | ctx = m.router.route(ctx, r) 87 | m.handler.ServeHTTPC(ctx, w, r) 88 | } 89 | 90 | var _ http.Handler = &Mux{} 91 | var _ Handler = &Mux{} 92 | -------------------------------------------------------------------------------- /vendor/goji.io/pat/match.go: -------------------------------------------------------------------------------- 1 | package pat 2 | 3 | import ( 4 | "sort" 5 | 6 | "goji.io/internal" 7 | "goji.io/pattern" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | type match struct { 12 | context.Context 13 | pat *Pattern 14 | matches []string 15 | } 16 | 17 | func (m match) Value(key interface{}) interface{} { 18 | switch key { 19 | case pattern.AllVariables: 20 | var vs map[pattern.Variable]interface{} 21 | if vsi := m.Context.Value(key); vsi == nil { 22 | if len(m.pat.pats) == 0 { 23 | return nil 24 | } 25 | vs = make(map[pattern.Variable]interface{}, len(m.matches)) 26 | } else { 27 | vs = vsi.(map[pattern.Variable]interface{}) 28 | } 29 | 30 | for _, p := range m.pat.pats { 31 | vs[p.name] = m.matches[p.idx] 32 | } 33 | return vs 34 | case internal.Path: 35 | if len(m.matches) == len(m.pat.pats)+1 { 36 | return m.matches[len(m.matches)-1] 37 | } 38 | return "" 39 | } 40 | 41 | if k, ok := key.(pattern.Variable); ok { 42 | i := sort.Search(len(m.pat.pats), func(i int) bool { 43 | return m.pat.pats[i].name >= k 44 | }) 45 | if i < len(m.pat.pats) && m.pat.pats[i].name == k { 46 | return m.matches[m.pat.pats[i].idx] 47 | } 48 | } 49 | 50 | return m.Context.Value(key) 51 | } 52 | -------------------------------------------------------------------------------- /vendor/goji.io/pat/methods.go: -------------------------------------------------------------------------------- 1 | package pat 2 | 3 | /* 4 | Delete returns a Pat route that only matches the DELETE HTTP method. 5 | */ 6 | func Delete(pat string) *Pattern { 7 | return newWithMethods(pat, "DELETE") 8 | } 9 | 10 | /* 11 | Get returns a Pat route that only matches the GET and HEAD HTTP method. HEAD 12 | requests are handled transparently by net/http. 13 | */ 14 | func Get(pat string) *Pattern { 15 | return newWithMethods(pat, "GET", "HEAD") 16 | } 17 | 18 | /* 19 | Head returns a Pat route that only matches the HEAD HTTP method. 20 | */ 21 | func Head(pat string) *Pattern { 22 | return newWithMethods(pat, "HEAD") 23 | } 24 | 25 | /* 26 | Options returns a Pat route that only matches the OPTIONS HTTP method. 27 | */ 28 | func Options(pat string) *Pattern { 29 | return newWithMethods(pat, "OPTIONS") 30 | } 31 | 32 | /* 33 | Patch returns a Pat route that only matches the PATCH HTTP method. 34 | */ 35 | func Patch(pat string) *Pattern { 36 | return newWithMethods(pat, "PATCH") 37 | } 38 | 39 | /* 40 | Post returns a Pat route that only matches the POST HTTP method. 41 | */ 42 | func Post(pat string) *Pattern { 43 | return newWithMethods(pat, "POST") 44 | } 45 | 46 | /* 47 | Put returns a Pat route that only matches the PUT HTTP method. 48 | */ 49 | func Put(pat string) *Pattern { 50 | return newWithMethods(pat, "PUT") 51 | } 52 | -------------------------------------------------------------------------------- /vendor/goji.io/pat/url.go: -------------------------------------------------------------------------------- 1 | package pat 2 | 3 | import "net/url" 4 | 5 | // Stolen (with modifications) from net/url in the Go stdlib 6 | 7 | func ishex(c byte) bool { 8 | switch { 9 | case '0' <= c && c <= '9': 10 | return true 11 | case 'a' <= c && c <= 'f': 12 | return true 13 | case 'A' <= c && c <= 'F': 14 | return true 15 | } 16 | return false 17 | } 18 | 19 | func unhex(c byte) byte { 20 | switch { 21 | case '0' <= c && c <= '9': 22 | return c - '0' 23 | case 'a' <= c && c <= 'f': 24 | return c - 'a' + 10 25 | case 'A' <= c && c <= 'F': 26 | return c - 'A' + 10 27 | } 28 | return 0 29 | } 30 | 31 | func unescape(s string) (string, error) { 32 | // Count %, check that they're well-formed. 33 | n := 0 34 | for i := 0; i < len(s); { 35 | switch s[i] { 36 | case '%': 37 | n++ 38 | if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { 39 | s = s[i:] 40 | if len(s) > 3 { 41 | s = s[:3] 42 | } 43 | return "", url.EscapeError(s) 44 | } 45 | i += 3 46 | default: 47 | i++ 48 | } 49 | } 50 | 51 | if n == 0 { 52 | return s, nil 53 | } 54 | 55 | t := make([]byte, len(s)-2*n) 56 | j := 0 57 | for i := 0; i < len(s); { 58 | switch s[i] { 59 | case '%': 60 | t[j] = unhex(s[i+1])<<4 | unhex(s[i+2]) 61 | j++ 62 | i += 3 63 | default: 64 | t[j] = s[i] 65 | j++ 66 | i++ 67 | } 68 | } 69 | return string(t), nil 70 | } 71 | -------------------------------------------------------------------------------- /vendor/goji.io/pattern.go: -------------------------------------------------------------------------------- 1 | package goji 2 | 3 | // httpMethods is an internal interface for the HTTPMethods pattern 4 | // optimization. See the documentation on Pattern for more. 5 | type httpMethods interface { 6 | HTTPMethods() map[string]struct{} 7 | } 8 | 9 | // pathPrefix is an internal interface for the PathPrefix pattern optimization. 10 | // See the documentation on Pattern for more. 11 | type pathPrefix interface { 12 | PathPrefix() string 13 | } 14 | -------------------------------------------------------------------------------- /vendor/goji.io/pattern/pattern.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package pattern contains utilities for Goji Pattern authors. 3 | 4 | Goji users should not import this package. Instead, use the utilities provided 5 | by your Pattern package. If you are looking for an implementation of Pattern, 6 | try Goji's pat subpackage, which contains a simple domain specific language for 7 | specifying routes. 8 | 9 | For Pattern authors, use of this subpackage is entirely optional. Nevertheless, 10 | authors who wish to take advantage of Goji's PathPrefix optimization or who wish 11 | to standardize on a few common interfaces may find this package useful. 12 | */ 13 | package pattern 14 | 15 | import ( 16 | "goji.io/internal" 17 | "golang.org/x/net/context" 18 | ) 19 | 20 | /* 21 | Variable is a standard type for the names of Pattern-bound variables, e.g. 22 | variables extracted from the URL. Pass the name of a variable, cast to this 23 | type, to context.Context.Value to retrieve the value bound to that name. 24 | */ 25 | type Variable string 26 | 27 | type allVariables struct{} 28 | 29 | /* 30 | AllVariables is a standard value which, when passed to context.Context.Value, 31 | returns all variable bindings present in the context, with bindings in newer 32 | contexts overriding values deeper in the stack. The concrete type 33 | 34 | map[Variable]interface{} 35 | 36 | is used for this purpose. If no variables are bound, nil should be returned 37 | instead of an empty map. 38 | */ 39 | var AllVariables = allVariables{} 40 | 41 | /* 42 | Path returns the path that the Goji router uses to perform the PathPrefix 43 | optimization. While this function does not distinguish between the absence of a 44 | path and an empty path, Goji will automatically extract a path from the request 45 | if none is present. 46 | 47 | By convention, paths are stored in their escaped form (i.e., the value returned 48 | by net/url.URL.EscapedPath, and not URL.Path) to ensure that Patterns have as 49 | much discretion as possible (e.g., to behave differently for '/' and '%2f'). 50 | */ 51 | func Path(ctx context.Context) string { 52 | pi := ctx.Value(internal.Path) 53 | if pi == nil { 54 | return "" 55 | } 56 | return pi.(string) 57 | } 58 | 59 | /* 60 | SetPath returns a new context in which the given path is used by the Goji Router 61 | when performing the PathPrefix optimization. See Path for more information about 62 | the intended semantics of this path. 63 | */ 64 | func SetPath(ctx context.Context, path string) context.Context { 65 | return context.WithValue(ctx, internal.Path, path) 66 | } 67 | -------------------------------------------------------------------------------- /vendor/goji.io/router.go: -------------------------------------------------------------------------------- 1 | package goji 2 | 3 | import ( 4 | "goji.io/internal" 5 | "golang.org/x/net/context" 6 | ) 7 | 8 | type match struct { 9 | context.Context 10 | p Pattern 11 | h Handler 12 | } 13 | 14 | func (m match) Value(key interface{}) interface{} { 15 | switch key { 16 | case internal.Pattern: 17 | return m.p 18 | case internal.Handler: 19 | return m.h 20 | default: 21 | return m.Context.Value(key) 22 | } 23 | } 24 | 25 | var _ context.Context = match{} 26 | -------------------------------------------------------------------------------- /vendor/goji.io/router_simple.go: -------------------------------------------------------------------------------- 1 | // +build goji_router_simple 2 | 3 | package goji 4 | 5 | import ( 6 | "net/http" 7 | 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | /* 12 | This is the simplest correct router implementation for Goji. 13 | */ 14 | 15 | type router []route 16 | 17 | type route struct { 18 | Pattern 19 | Handler 20 | } 21 | 22 | func (rt *router) add(p Pattern, h Handler) { 23 | *rt = append(*rt, route{p, h}) 24 | } 25 | 26 | func (rt *router) route(ctx context.Context, r *http.Request) context.Context { 27 | for _, route := range *rt { 28 | if ctx := route.Match(ctx, r); ctx != nil { 29 | return &match{ctx, route.Pattern, route.Handler} 30 | } 31 | } 32 | return &match{Context: ctx} 33 | } 34 | -------------------------------------------------------------------------------- /vendor/goji.io/router_trie.go: -------------------------------------------------------------------------------- 1 | // +build !goji_router_simple 2 | 3 | package goji 4 | 5 | import ( 6 | "net/http" 7 | "sort" 8 | "strings" 9 | 10 | "goji.io/internal" 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | type router struct { 15 | routes []route 16 | methods map[string]*trieNode 17 | wildcard trieNode 18 | } 19 | 20 | type route struct { 21 | Pattern 22 | Handler 23 | } 24 | 25 | type child struct { 26 | prefix string 27 | node *trieNode 28 | } 29 | 30 | type trieNode struct { 31 | routes []int 32 | children []child 33 | } 34 | 35 | func (rt *router) add(p Pattern, h Handler) { 36 | i := len(rt.routes) 37 | rt.routes = append(rt.routes, route{p, h}) 38 | 39 | var prefix string 40 | if pp, ok := p.(pathPrefix); ok { 41 | prefix = pp.PathPrefix() 42 | } 43 | 44 | var methods map[string]struct{} 45 | if hm, ok := p.(httpMethods); ok { 46 | methods = hm.HTTPMethods() 47 | } 48 | if methods == nil { 49 | rt.wildcard.add(prefix, i) 50 | for _, sub := range rt.methods { 51 | sub.add(prefix, i) 52 | } 53 | } else { 54 | if rt.methods == nil { 55 | rt.methods = make(map[string]*trieNode) 56 | } 57 | 58 | for method := range methods { 59 | if _, ok := rt.methods[method]; !ok { 60 | rt.methods[method] = rt.wildcard.clone() 61 | } 62 | rt.methods[method].add(prefix, i) 63 | } 64 | } 65 | } 66 | 67 | func (rt *router) route(ctx context.Context, r *http.Request) context.Context { 68 | tn := &rt.wildcard 69 | if tn2, ok := rt.methods[r.Method]; ok { 70 | tn = tn2 71 | } 72 | 73 | path := ctx.Value(internal.Path).(string) 74 | for path != "" { 75 | i := sort.Search(len(tn.children), func(i int) bool { 76 | return path[0] <= tn.children[i].prefix[0] 77 | }) 78 | if i == len(tn.children) || !strings.HasPrefix(path, tn.children[i].prefix) { 79 | break 80 | } 81 | 82 | path = path[len(tn.children[i].prefix):] 83 | tn = tn.children[i].node 84 | } 85 | for _, i := range tn.routes { 86 | if ctx := rt.routes[i].Match(ctx, r); ctx != nil { 87 | return &match{ctx, rt.routes[i].Pattern, rt.routes[i].Handler} 88 | } 89 | } 90 | return &match{Context: ctx} 91 | } 92 | 93 | // We can be a teensy bit more efficient here: we're maintaining a sorted list, 94 | // so we know exactly where to insert the new element. But since that involves 95 | // more bookkeeping and makes the code messier, let's cross that bridge when we 96 | // come to it. 97 | type byPrefix []child 98 | 99 | func (b byPrefix) Len() int { 100 | return len(b) 101 | } 102 | func (b byPrefix) Less(i, j int) bool { 103 | return b[i].prefix < b[j].prefix 104 | } 105 | func (b byPrefix) Swap(i, j int) { 106 | b[i], b[j] = b[j], b[i] 107 | } 108 | 109 | func longestPrefix(a, b string) string { 110 | mlen := len(a) 111 | if len(b) < mlen { 112 | mlen = len(b) 113 | } 114 | for i := 0; i < mlen; i++ { 115 | if a[i] != b[i] { 116 | return a[:i] 117 | } 118 | } 119 | return a[:mlen] 120 | } 121 | 122 | func (tn *trieNode) add(prefix string, idx int) { 123 | if len(prefix) == 0 { 124 | tn.routes = append(tn.routes, idx) 125 | for i := range tn.children { 126 | tn.children[i].node.add(prefix, idx) 127 | } 128 | return 129 | } 130 | 131 | ch := prefix[0] 132 | i := sort.Search(len(tn.children), func(i int) bool { 133 | return ch <= tn.children[i].prefix[0] 134 | }) 135 | 136 | if i == len(tn.children) || ch != tn.children[i].prefix[0] { 137 | routes := append([]int(nil), tn.routes...) 138 | tn.children = append(tn.children, child{ 139 | prefix: prefix, 140 | node: &trieNode{routes: append(routes, idx)}, 141 | }) 142 | } else { 143 | lp := longestPrefix(prefix, tn.children[i].prefix) 144 | 145 | if tn.children[i].prefix == lp { 146 | tn.children[i].node.add(prefix[len(lp):], idx) 147 | return 148 | } 149 | 150 | split := new(trieNode) 151 | split.children = []child{ 152 | {tn.children[i].prefix[len(lp):], tn.children[i].node}, 153 | } 154 | split.routes = append([]int(nil), tn.routes...) 155 | split.add(prefix[len(lp):], idx) 156 | 157 | tn.children[i].prefix = lp 158 | tn.children[i].node = split 159 | } 160 | 161 | sort.Sort(byPrefix(tn.children)) 162 | } 163 | 164 | func (tn *trieNode) clone() *trieNode { 165 | clone := new(trieNode) 166 | clone.routes = append(clone.routes, tn.routes...) 167 | clone.children = append(clone.children, tn.children...) 168 | for i := range clone.children { 169 | clone.children[i].node = tn.children[i].node.clone() 170 | } 171 | return clone 172 | } 173 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /vendor/gopkg.in/boj/redistore.v1/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | -------------------------------------------------------------------------------- /vendor/gopkg.in/boj/redistore.v1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Brian Jones 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/gopkg.in/boj/redistore.v1/README.md: -------------------------------------------------------------------------------- 1 | # redistore 2 | 3 | [![Build Status](https://drone.io/github.com/boj/redistore/status.png)](https://drone.io/github.com/boj/redistore/latest) 4 | 5 | A session store backend for [gorilla/sessions](http://www.gorillatoolkit.org/pkg/sessions) - [src](https://github.com/gorilla/sessions). 6 | 7 | ## Requirements 8 | 9 | Depends on the [Redigo](https://github.com/garyburd/redigo) Redis library. 10 | 11 | ## Installation 12 | 13 | go get gopkg.in/boj/redistore.v1 14 | 15 | ## Documentation 16 | 17 | Available on [godoc.org](http://www.godoc.org/gopkg.in/boj/redistore.v1). 18 | 19 | See http://www.gorillatoolkit.org/pkg/sessions for full documentation on underlying interface. 20 | 21 | ### Example 22 | 23 | // Fetch new store. 24 | store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key")) 25 | if err != nil { 26 | panic(err) 27 | } 28 | defer store.Close() 29 | 30 | // Get a session. 31 | session, err = store.Get(req, "session-key") 32 | if err != nil { 33 | log.Error(err.Error()) 34 | } 35 | 36 | // Add a value. 37 | session.Values["foo"] = "bar" 38 | 39 | // Save. 40 | if err = sessions.Save(req, rsp); err != nil { 41 | t.Fatalf("Error saving session: %v", err) 42 | } 43 | 44 | // Delete session. 45 | session.Options.MaxAge = -1 46 | if err = sessions.Save(req, rsp); err != nil { 47 | t.Fatalf("Error saving session: %v", err) 48 | } 49 | 50 | // Change session storage configuration for MaxAge = 10 days. 51 | store.SetMaxAge(10*24*3600) 52 | 53 | -------------------------------------------------------------------------------- /vendor/gopkg.in/boj/redistore.v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package redistore is a session store backend for gorilla/sessions 3 | */ 4 | package redistore 5 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const path = require('path') 3 | const webpack = require('webpack') 4 | const unixFormatter = require('eslint/lib/formatters/unix') 5 | 6 | const isProduction = process.env.NODE_ENV === 'production' 7 | 8 | module.exports = { 9 | devtool: 'eval', 10 | entry: _.compact([ 11 | 'babel-polyfill', 12 | './src/index', 13 | ]), 14 | output: { 15 | path: path.join(__dirname, 'static'), 16 | filename: 'bundle.js', 17 | publicPath: '/static/', 18 | }, 19 | plugins: _.compact([ 20 | isProduction && new webpack.optimize.DedupePlugin(), 21 | isProduction && new webpack.optimize.UglifyJsPlugin({ 22 | minimize: true, 23 | output: { 24 | comments: false, 25 | }, 26 | }), 27 | !isProduction && new webpack.HotModuleReplacementPlugin(), 28 | new webpack.DefinePlugin({ 29 | BASE_URL: JSON.stringify(isProduction 30 | ? 'https://blarg-im.herokuapp.com' 31 | : 'http://localhost:8000' 32 | ), 33 | API_BASE_URL: JSON.stringify(isProduction 34 | ? 'https://blarg-im.herokuapp.com/v1' 35 | : 'http://localhost:8000/v1' 36 | ), 37 | WS_URL: JSON.stringify(isProduction 38 | ? 'wss://blarg-im.herokuapp.com/v1/ws' 39 | : 'ws://localhost:8000/v1/ws' 40 | ), 41 | }), 42 | ]), 43 | module: { 44 | loaders: [{ 45 | test: /\.jsx?$/, 46 | loaders: ['babel', 'eslint'], 47 | exclude: /node_modules/, 48 | }, { 49 | test: /\.scss$/, 50 | loaders: ['style', 'css', 'sass'], 51 | }], 52 | }, 53 | sassLoader: { 54 | includePaths: [path.resolve(__dirname, './src/style')], 55 | }, 56 | resolve: { 57 | root: path.resolve(__dirname, './src'), 58 | alias: { 59 | components: 'components', 60 | handlers: 'handlers', 61 | actions: 'actions', 62 | }, 63 | extensions: ['', '.js', '.jsx', '.json'], 64 | }, 65 | eslint: { 66 | formatter: unixFormatter, 67 | }, 68 | } 69 | --------------------------------------------------------------------------------