├── Makefile ├── .gitignore ├── CONTRIBUTING.md ├── examples ├── channel.go ├── basic_publish.go ├── filtered_channels.go ├── users.go ├── all_channels.go └── multi_publish.go ├── README.md ├── signature_test.go ├── client_test.go ├── signature.go └── client.go /Makefile: -------------------------------------------------------------------------------- 1 | EXAMPLES := $(wildcard examples/*.go) 2 | 3 | examples: */**.go 4 | for example in $(EXAMPLES); do \ 5 | go run $$example; \ 6 | done 7 | 8 | fmt: */**.go 9 | gofmt -w -l -tabs=false -tabwidth=4 */**.go *.go 10 | 11 | test: */**.go 12 | go test ./... 13 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # The basics 2 | 3 | * Fork 4 | * Test 5 | * FMT 6 | * Pull Request 7 | 8 | # Using the Makefile 9 | 10 | ``` 11 | # Run all examples to see if they work. 12 | make examples 13 | ``` 14 | 15 | ``` 16 | # Format all go files, shortcut for go fmt ./... 17 | make fmt 18 | ``` 19 | 20 | ``` 21 | # Run all tests, shortcut for go test ./... 22 | make test 23 | ``` 24 | -------------------------------------------------------------------------------- /examples/channel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/timonv/pusher" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | client := pusher.NewClient("4115", "23ed642e81512118260e", "cd72de5494540704dcf1", false) 11 | 12 | done := make(chan bool) 13 | 14 | go func() { 15 | channel, err := client.Channel("common", nil) 16 | if err != nil { 17 | fmt.Printf("Error %s\n", err) 18 | } else { 19 | fmt.Println(channel) 20 | } 21 | done <- true 22 | }() 23 | 24 | select { 25 | case <-done: 26 | fmt.Println("Done :-)") 27 | case <-time.After(1 * time.Minute): 28 | fmt.Println("Timeout :-(") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/basic_publish.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/timonv/pusher" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | client := pusher.NewClient("34420", "87bdfd3a6320e83b9289", "f25dfe88fb26ebf75139", false) 11 | 12 | done := make(chan bool) 13 | 14 | go func() { 15 | err := client.Publish("test", "test", "test") 16 | if err != nil { 17 | fmt.Printf("Error %s\n", err) 18 | } else { 19 | fmt.Println("Message Published!") 20 | } 21 | done <- true 22 | }() 23 | 24 | select { 25 | case <-done: 26 | fmt.Println("Done :-)") 27 | case <-time.After(1 * time.Minute): 28 | fmt.Println("Timeout :-(") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/filtered_channels.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/timonv/pusher" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | client := pusher.NewClient("4115", "23ed642e81512118260e", "cd72de5494540704dcf1", false) 11 | 12 | done := make(chan bool) 13 | 14 | go func() { 15 | queryParameters := map[string]string{ 16 | "info": "user_count", 17 | "filter_by_prefix": "presence-", 18 | } 19 | channels, err := client.Channels(queryParameters) 20 | if err != nil { 21 | fmt.Printf("Error %s\n", err) 22 | } else { 23 | fmt.Println(channels) 24 | } 25 | done <- true 26 | }() 27 | 28 | select { 29 | case <-done: 30 | fmt.Println("Done :-)") 31 | case <-time.After(1 * time.Minute): 32 | fmt.Println("Timeout :-(") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/users.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/timonv/pusher" 6 | "sort" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | client := pusher.NewClient("4115", "23ed642e81512118260e", "cd72de5494540704dcf1", false) 12 | 13 | done := make(chan bool) 14 | 15 | go func() { 16 | users, err := client.Users("common") 17 | if err != nil { 18 | fmt.Printf("Error %s\n", err) 19 | } else { 20 | ids := []int{} 21 | for k := range users.List { 22 | ids = append(ids, k) 23 | } 24 | sort.Ints(ids) 25 | fmt.Println("User Count:", len(ids)) 26 | fmt.Println(ids) 27 | } 28 | done <- true 29 | }() 30 | 31 | select { 32 | case <-done: 33 | fmt.Println("Done :-)") 34 | case <-time.After(1 * time.Minute): 35 | fmt.Println("Timeout :-(") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/all_channels.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/timonv/pusher" 6 | "sort" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | client := pusher.NewClient("4115", "23ed642e81512118260e", "cd72de5494540704dcf1", false) 12 | 13 | done := make(chan bool) 14 | 15 | go func() { 16 | channels, err := client.AllChannels() 17 | if err != nil { 18 | fmt.Printf("Error %s\n", err) 19 | } else { 20 | names := []string{} 21 | for k := range channels.List { 22 | names = append(names, k) 23 | } 24 | sort.Strings(names) 25 | fmt.Println("Channel Count:", len(names)) 26 | fmt.Println(names) 27 | } 28 | done <- true 29 | }() 30 | 31 | select { 32 | case <-done: 33 | fmt.Println("Done :-)") 34 | case <-time.After(1 * time.Minute): 35 | fmt.Println("Timeout :-(") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/multi_publish.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/timonv/pusher" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | workers := 100 11 | messageCount := 5000 12 | messages := make(chan string) 13 | done := make(chan bool) 14 | 15 | client := pusher.NewClient("34420", "87bdfd3a6320e83b9289", "f25dfe88fb26ebf75139", false) 16 | 17 | for i := 0; i < workers; i++ { 18 | go func() { 19 | for data := range messages { 20 | err := client.Publish(data, "test", "test") 21 | if err != nil { 22 | fmt.Printf("E", err) 23 | } else { 24 | fmt.Print(".") 25 | } 26 | } 27 | }() 28 | } 29 | 30 | go func() { 31 | for i := 0; i < messageCount; i++ { 32 | messages <- "test" 33 | } 34 | done <- true 35 | close(messages) 36 | close(done) 37 | }() 38 | 39 | select { 40 | case <-done: 41 | fmt.Println("\nDone :-)") 42 | case <-time.After(1 * time.Minute): 43 | fmt.Println("\nTimeout :-(") 44 | } 45 | 46 | fmt.Println("") 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Go Lang Pusher Library 2 | ======================== 3 | 4 | So much to write, so little information to tell you right now :) 5 | 6 | 7 | ## Example 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "github.com/timonv/pusher" 15 | "time" 16 | ) 17 | 18 | func main() { 19 | client := pusher.NewClient("appId", "key", "secret", false) 20 | 21 | done := make(chan bool) 22 | 23 | go func() { 24 | err := client.Publish("test", "test", "test") 25 | if err != nil { 26 | fmt.Printf("Error %s\n", err) 27 | } else { 28 | fmt.Print("Message Published!") 29 | } 30 | done <- true 31 | }() 32 | 33 | // A basic timeout to make sure we don't wait forever 34 | select { 35 | case <-done: 36 | fmt.Println("\nDone") 37 | case <-time.After(1 * time.Minute): 38 | fmt.Println("\n:-( Timeout") 39 | } 40 | } 41 | ``` 42 | 43 | 44 | ## License 45 | 46 | MIT: Timon Vonk and Josh Kalderimis http://timon-josh.mit-license.org -------------------------------------------------------------------------------- /signature_test.go: -------------------------------------------------------------------------------- 1 | package pusher 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSign(t *testing.T) { 8 | signature := &Signature{ 9 | "key", 10 | "secret", 11 | "POST", 12 | "/some/path", 13 | "1234", 14 | "1.0", 15 | []byte("content"), 16 | map[string]string{"query": "params", "go": "here"}, 17 | } 18 | 19 | expected := "5da41b658c67bb135898072d6d325e7a98e5f790d9c7c70cc5e210173d81be52" 20 | sig := signature.Sign() 21 | if expected != sig { 22 | t.Errorf("Sign(): Expected %s, got %s", expected, sig) 23 | } 24 | } 25 | 26 | func TestEncodedQuery(t *testing.T) { 27 | signature := &Signature{ 28 | "key", 29 | "secret", 30 | "POST", 31 | "/some/path", 32 | "1234", 33 | "1.0", 34 | []byte("content"), 35 | map[string]string{"query": "params", "go": "here"}, 36 | } 37 | 38 | expected := "auth_key=key&auth_signature=5da41b658c67bb135898072d6d325e7a98e5f790d9c7c70cc5e210173d81be52&auth_timestamp=1234&auth_version=1.0&body_md5=9a0364b9e99bb480dd25e1f0284c8555&go=here&query=params" 39 | encodedQuery := signature.EncodedQuery() 40 | if expected != encodedQuery { 41 | t.Errorf("EncodedQuery(): Expected %s, got %s", expected, encodedQuery) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package pusher 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func setupTestServer(handler http.Handler) (server *httptest.Server) { 14 | server = httptest.NewServer(handler) 15 | return 16 | } 17 | 18 | func verifyRequest(t *testing.T, prefix string, req *http.Request, method, path string) (payload Payload) { 19 | if method != req.Method { 20 | t.Errorf("%s: Expected method %s, got %s", prefix, method, req.Method) 21 | } 22 | if path != req.URL.Path { 23 | t.Errorf("%s: Expected path '%s', got '%s'", prefix, path, req.URL.Path) 24 | } 25 | 26 | err := json.NewDecoder(req.Body).Decode(&payload) 27 | if err != nil { 28 | fmt.Println("Got error:", err) 29 | } 30 | 31 | return 32 | } 33 | 34 | func stringSlicesEqual(a, b []string) bool { 35 | if len(a) != len(b) { 36 | return false 37 | } 38 | 39 | for i := range a { 40 | if a[i] != b[i] { 41 | return false 42 | } 43 | } 44 | 45 | return true 46 | } 47 | 48 | func TestPublish(t *testing.T) { 49 | server := setupTestServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) { 50 | w.WriteHeader(200) 51 | fmt.Fprintf(w, "{}") 52 | 53 | payload := verifyRequest(t, "Publish()", request, "POST", "/apps/1/events") 54 | 55 | if payload.Name != "event" { 56 | t.Errorf("Publish(): Expected body[name] = \"event\", got %q", payload.Name) 57 | } 58 | if !reflect.DeepEqual(payload.Channels, []string{"mychannel", "c2"}) { 59 | t.Errorf("Publish(): Expected body[channels] = [mychannel c2], got %+v", payload.Channels) 60 | } 61 | })) 62 | defer server.Close() 63 | 64 | url, _ := url.Parse(server.URL) 65 | 66 | client := NewClient("1", "key", "secret") 67 | client.Host = url.Host 68 | err := client.Publish("data", "event", "mychannel", "c2") 69 | 70 | if err != nil { 71 | t.Errorf("Publish(): %v", err) 72 | } 73 | } 74 | 75 | func TestFields(t *testing.T) { 76 | client := NewClient("1", "key", "secret") 77 | 78 | if client.appid != "1" { 79 | t.Errorf("appid not set correctly") 80 | } 81 | 82 | if client.key != "key" { 83 | t.Errorf("key not set correctly") 84 | } 85 | 86 | if client.secret != "secret" { 87 | t.Errorf("secret not set correctly") 88 | } 89 | } 90 | 91 | func TestDefaultHost(t *testing.T) { 92 | client := NewClient("1", "key", "secret") 93 | 94 | if client.Host != "api.pusherapp.com" { 95 | t.Errorf("Host not set correctly") 96 | } 97 | } 98 | 99 | func TestDefaultScheme(t *testing.T) { 100 | client := NewClient("1", "key", "secret") 101 | 102 | if client.Scheme != "http" { 103 | t.Errorf("Scheme not set correctly") 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /signature.go: -------------------------------------------------------------------------------- 1 | package pusher 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "fmt" 9 | "io" 10 | "net/url" 11 | "sort" 12 | "strings" 13 | ) 14 | 15 | type Signature struct { 16 | key, secret string 17 | method, path, timestamp, authVersion string 18 | content []byte 19 | queryParameters map[string]string 20 | } 21 | 22 | type AuthPart struct { 23 | key, value string 24 | } 25 | 26 | type OrderedAuthParts []*AuthPart 27 | 28 | func (s OrderedAuthParts) Len() int { return len(s) } 29 | func (s OrderedAuthParts) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 30 | func (s OrderedAuthParts) Less(i, j int) bool { return s[i].key < s[j].key } 31 | 32 | func (s *Signature) Sign() string { 33 | authParts := []*AuthPart{ 34 | {"auth_key", s.key}, 35 | {"auth_timestamp", s.timestamp}, 36 | {"auth_version", s.authVersion}, 37 | {"body_md5", s.md5Content()}, 38 | } 39 | 40 | for k := range s.queryParameters { 41 | authParts = append(authParts, &AuthPart{k, s.queryParameters[k]}) 42 | } 43 | 44 | sort.Sort(OrderedAuthParts(authParts)) 45 | 46 | sortedAuthParts := []string{} 47 | for index := range authParts { 48 | newPart := fmt.Sprintf("%s=%s", authParts[index].key, authParts[index].value) 49 | sortedAuthParts = append(sortedAuthParts, newPart) 50 | } 51 | 52 | authPartsQueryString := strings.Join(sortedAuthParts, "&") 53 | completeAuthParts := fmt.Sprintf("%s\n%s\n%s", s.method, s.path, authPartsQueryString) 54 | 55 | return s.hmacSha256(completeAuthParts) 56 | } 57 | 58 | func (s *Signature) EncodedQuery() string { 59 | query := url.Values{ 60 | "auth_key": {s.key}, 61 | "auth_timestamp": {s.timestamp}, 62 | "auth_version": {s.authVersion}, 63 | "body_md5": {s.md5Content()}, 64 | "auth_signature": {s.Sign()}, 65 | } 66 | for k := range s.queryParameters { 67 | query.Add(k, s.queryParameters[k]) 68 | } 69 | return query.Encode() 70 | } 71 | 72 | func (s *Signature) auth_key() string { 73 | return "auth_key=" + s.key 74 | } 75 | 76 | func (s *Signature) auth_timestamp() string { 77 | return "auth_timestamp=" + s.timestamp 78 | } 79 | 80 | func (s *Signature) auth_version() string { 81 | return "auth_version=" + s.authVersion 82 | } 83 | 84 | func (s *Signature) body_md5() string { 85 | return "body_md5=" + s.md5Content() 86 | } 87 | 88 | func (s *Signature) md5Content() string { 89 | return s.md5(s.content) 90 | } 91 | 92 | func (s *Signature) md5(content []byte) string { 93 | hash := md5.New() 94 | hash.Write(content) 95 | return hex.EncodeToString(hash.Sum(nil)) 96 | } 97 | 98 | func (s *Signature) hmacSha256(content string) string { 99 | hash := hmac.New(sha256.New, []byte(s.secret)) 100 | io.WriteString(hash, content) 101 | return hex.EncodeToString(hash.Sum(nil)) 102 | } 103 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package pusher 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | var HttpClient = http.Client{} 15 | 16 | const AuthVersion = "1.0" 17 | 18 | type Client struct { 19 | appid, key, secret string 20 | secure bool 21 | Host string 22 | Scheme string 23 | } 24 | 25 | type Payload struct { 26 | Name string `json:"name"` 27 | Channels []string `json:"channels"` 28 | Data string `json:"data"` 29 | } 30 | 31 | type ChannelList struct { 32 | List map[string]ChannelInfo `json:"channels"` 33 | } 34 | 35 | func (c *ChannelList) String() string { 36 | format := "[channel count: %d, list: %+v]" 37 | return fmt.Sprintf(format, len(c.List), c.List) 38 | } 39 | 40 | type ChannelInfo struct { 41 | UserCount int `json:"user_count"` 42 | } 43 | 44 | type UserList struct { 45 | List []UserInfo `json:"users"` 46 | } 47 | 48 | type UserInfo struct { 49 | Id int `json:"id"` 50 | } 51 | 52 | type Channel struct { 53 | Name string 54 | Occupied bool `json:"occupied"` 55 | UserCount int `json:"user_count",omitempty` 56 | SubscriptionCount int `json:"subscription_count",omitempty` 57 | } 58 | 59 | func (c *Channel) String() string { 60 | format := "[name: %s, occupied: %t, user count: %d, subscription count: %d]" 61 | return fmt.Sprintf(format, c.Name, c.Occupied, c.UserCount, c.SubscriptionCount) 62 | } 63 | 64 | func NewClient(appid, key, secret string) *Client { 65 | return &Client{ 66 | appid: appid, 67 | key: key, 68 | secret: secret, 69 | Host: "api.pusherapp.com", 70 | Scheme: "http", 71 | } 72 | } 73 | 74 | func (c *Client) Publish(data, event string, channels ...string) error { 75 | timestamp := c.stringTimestamp() 76 | 77 | content, err := c.jsonifyData(data, event, channels) 78 | if err != nil { 79 | return fmt.Errorf("pusher: Publish failed: %s", err) 80 | } 81 | 82 | signature := Signature{c.key, c.secret, "POST", c.publishPath(), timestamp, AuthVersion, content, nil} 83 | 84 | err = c.post(content, c.fullUrl(c.publishPath()), signature.EncodedQuery()) 85 | 86 | return err 87 | } 88 | 89 | func (c *Client) AllChannels() (*ChannelList, error) { 90 | return c.Channels(nil) 91 | } 92 | 93 | func (c *Client) Channels(queryParameters map[string]string) (*ChannelList, error) { 94 | timestamp := c.stringTimestamp() 95 | 96 | signature := Signature{c.key, c.secret, "GET", c.channelsPath(), timestamp, AuthVersion, nil, queryParameters} 97 | 98 | body, err := c.get(c.fullUrl(c.channelsPath()), signature.EncodedQuery()) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | var channels *ChannelList 104 | err = c.parseResponse(body, &channels) 105 | 106 | if err != nil { 107 | return nil, fmt.Errorf("pusher: Channels failed: %s", err) 108 | } 109 | 110 | return channels, nil 111 | } 112 | 113 | func (c *Client) Channel(name string, queryParameters map[string]string) (*Channel, error) { 114 | timestamp := c.stringTimestamp() 115 | 116 | urlPath := c.channelPath(name) 117 | 118 | signature := Signature{c.key, c.secret, "GET", urlPath, timestamp, AuthVersion, nil, queryParameters} 119 | 120 | body, err := c.get(c.fullUrl(urlPath), signature.EncodedQuery()) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | var channel *Channel 126 | err = c.parseResponse(body, &channel) 127 | 128 | if err != nil { 129 | return nil, fmt.Errorf("pusher: Channel failed: %s", err) 130 | } 131 | 132 | channel.Name = name 133 | 134 | return channel, nil 135 | } 136 | 137 | func (c *Client) Users(channelName string) (*UserList, error) { 138 | timestamp := c.stringTimestamp() 139 | 140 | signature := Signature{c.key, c.secret, "GET", c.usersPath(channelName), timestamp, AuthVersion, nil, nil} 141 | 142 | body, err := c.get(c.fullUrl(c.usersPath(channelName)), signature.EncodedQuery()) 143 | if err != nil { 144 | return nil, err 145 | } 146 | fmt.Println(body) 147 | 148 | var users *UserList 149 | err = c.parseResponse(body, &users) 150 | 151 | if err != nil { 152 | return nil, fmt.Errorf("pusher: Users failed: %s", err) 153 | } 154 | 155 | return users, nil 156 | } 157 | 158 | func (c *Client) post(content []byte, fullUrl string, query string) error { 159 | buffer := bytes.NewBuffer(content) 160 | 161 | postUrl, err := url.Parse(fullUrl) 162 | if err != nil { 163 | return err 164 | } 165 | 166 | postUrl.Scheme = c.Scheme 167 | postUrl.RawQuery = query 168 | 169 | resp, err := HttpClient.Post(postUrl.String(), "application/json", buffer) 170 | if err != nil { 171 | return fmt.Errorf("pusher: POST failed: %s", err) 172 | } 173 | 174 | defer resp.Body.Close() 175 | 176 | if resp.StatusCode >= 400 { 177 | b, _ := ioutil.ReadAll(resp.Body) 178 | return fmt.Errorf("pusher: POST failed: %s", b) 179 | } 180 | 181 | return nil 182 | } 183 | 184 | func (c *Client) get(fullUrl string, query string) (string, error) { 185 | getUrl, err := url.Parse(fullUrl) 186 | if err != nil { 187 | return "", fmt.Errorf("pusher: GET failed: %s", err) 188 | } 189 | 190 | getUrl.Scheme = c.Scheme 191 | getUrl.RawQuery = query 192 | 193 | resp, err := HttpClient.Get(getUrl.String()) 194 | if err != nil { 195 | return "", fmt.Errorf("pusher: GET failed: %s", err) 196 | } 197 | 198 | defer resp.Body.Close() 199 | 200 | if resp.StatusCode >= 400 { 201 | b, _ := ioutil.ReadAll(resp.Body) 202 | return "", fmt.Errorf("pusher: GET failed: %s", b) 203 | } 204 | 205 | fullBody, err := ioutil.ReadAll(resp.Body) 206 | 207 | if err != nil { 208 | return "", fmt.Errorf("pusher: GET failed: %s", err) 209 | } 210 | 211 | return string(fullBody), nil 212 | } 213 | 214 | func (c *Client) jsonifyData(data, event string, channels []string) ([]byte, error) { 215 | content := Payload{event, channels, data} 216 | b, err := json.Marshal(content) 217 | if err != nil { 218 | return nil, err 219 | } 220 | return b, nil 221 | } 222 | 223 | func (c *Client) parseResponse(body string, response interface{}) error { 224 | err := json.Unmarshal([]byte(body), &response) 225 | if err != nil { 226 | return err 227 | } 228 | return nil 229 | } 230 | 231 | func (c *Client) publishPath() string { 232 | return fmt.Sprintf("/apps/%s/events", c.appid) 233 | } 234 | 235 | func (c *Client) channelsPath() string { 236 | return fmt.Sprintf("/apps/%s/channels", c.appid) 237 | } 238 | 239 | func (c *Client) channelPath(name string) string { 240 | return fmt.Sprintf("/apps/%s/channels/%s", c.appid, name) 241 | } 242 | 243 | func (c *Client) usersPath(channelName string) string { 244 | return fmt.Sprintf("/apps/%s/channels/%s/users", c.appid, channelName) 245 | } 246 | 247 | func (c *Client) fullUrl(path string) string { 248 | return fmt.Sprintf("http://%s%s", c.Host, path) 249 | } 250 | 251 | func (c *Client) stringTimestamp() string { 252 | t := time.Now() 253 | return strconv.FormatInt(t.Unix(), 10) 254 | } 255 | --------------------------------------------------------------------------------