Refactor to make the maintanance simpler
- Fixed issue with webhooks
This commit is contained in:
+289
@@ -0,0 +1,289 @@
|
||||
// Copyright 2014 Claudemiro Alves Feitosa Neto. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
|
||||
"ipe/channel"
|
||||
"ipe/connection"
|
||||
"ipe/events"
|
||||
"ipe/subscription"
|
||||
)
|
||||
|
||||
// An App
|
||||
type Application struct {
|
||||
sync.Mutex
|
||||
|
||||
Name string
|
||||
AppID string
|
||||
Key string
|
||||
Secret string
|
||||
OnlySSL bool
|
||||
ApplicationDisabled bool
|
||||
UserEvents bool
|
||||
WebHooks bool
|
||||
URLWebHook string
|
||||
|
||||
channels map[string]*channel.Channel `json:"-"`
|
||||
connections map[string]*connection.Connection `json:"-"`
|
||||
|
||||
Stats *expvar.Map `json:"-"`
|
||||
}
|
||||
|
||||
func NewApplication(
|
||||
name,
|
||||
appID,
|
||||
key,
|
||||
secret string,
|
||||
onlySSL,
|
||||
disabled,
|
||||
userEvents,
|
||||
webHooks bool,
|
||||
webHookURL string,
|
||||
) *Application {
|
||||
|
||||
a := &Application{
|
||||
Name: name,
|
||||
AppID: appID,
|
||||
Key: key,
|
||||
Secret: secret,
|
||||
OnlySSL: onlySSL,
|
||||
ApplicationDisabled: disabled,
|
||||
UserEvents: userEvents,
|
||||
WebHooks: webHooks,
|
||||
URLWebHook: webHookURL,
|
||||
}
|
||||
|
||||
a.connections = make(map[string]*connection.Connection)
|
||||
a.channels = make(map[string]*channel.Channel)
|
||||
a.Stats = expvar.NewMap(fmt.Sprintf("%s (%s)", a.Name, a.AppID))
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// Channels returns the full list of channels
|
||||
func (a *Application) Channels() []*channel.Channel {
|
||||
var channels []*channel.Channel
|
||||
|
||||
for _, c := range a.channels {
|
||||
channels = append(channels, c)
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
// Only Presence channels
|
||||
func (a *Application) PresenceChannels() []*channel.Channel {
|
||||
var channels []*channel.Channel
|
||||
|
||||
for _, c := range a.channels {
|
||||
if c.IsPresence() {
|
||||
channels = append(channels, c)
|
||||
}
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
// Only Private channels
|
||||
func (a *Application) PrivateChannels() []*channel.Channel {
|
||||
var channels []*channel.Channel
|
||||
|
||||
for _, c := range a.channels {
|
||||
if c.IsPrivate() {
|
||||
channels = append(channels, c)
|
||||
}
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
// Only Public channels
|
||||
func (a *Application) PublicChannels() []*channel.Channel {
|
||||
var channels []*channel.Channel
|
||||
|
||||
for _, c := range a.channels {
|
||||
if c.IsPublic() {
|
||||
channels = append(channels, c)
|
||||
}
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
// Disconnect Socket
|
||||
func (a *Application) Disconnect(socketID string) {
|
||||
log.Infof("disconnecting socket %+v", socketID)
|
||||
|
||||
conn, err := a.FindConnection(socketID)
|
||||
|
||||
if err != nil {
|
||||
log.Infof("socket not found, %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Unsubscribe from channels
|
||||
for _, c := range a.channels {
|
||||
if c.IsSubscribed(conn) {
|
||||
if err := c.Unsubscribe(conn); err != nil {
|
||||
log.Errorf("error while calling Channel.Unsubscribe, %+v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from Application
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
_, exists := a.connections[conn.SocketID]
|
||||
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
delete(a.connections, conn.SocketID)
|
||||
|
||||
a.Stats.Add("TotalConnections", -1)
|
||||
}
|
||||
|
||||
// Connect a new Subscriber
|
||||
func (a *Application) Connect(conn *connection.Connection) {
|
||||
log.Infof("adding a new Connection %s to Application %s", conn.SocketID, a.Name)
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
a.connections[conn.SocketID] = conn
|
||||
|
||||
a.Stats.Add("TotalConnections", 1)
|
||||
}
|
||||
|
||||
// Find a Connection on this Application
|
||||
func (a *Application) FindConnection(socketID string) (*connection.Connection, error) {
|
||||
conn, exists := a.connections[socketID]
|
||||
|
||||
if exists {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("connection not found")
|
||||
}
|
||||
|
||||
// DeleteChannel removes the Channel from Application
|
||||
func (a *Application) RemoveChannel(c *channel.Channel) {
|
||||
log.Infof("remove the Channel %s from Application %s", c.ID, a.Name)
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
delete(a.channels, c.ID)
|
||||
|
||||
if c.IsPresence() {
|
||||
a.Stats.Add("TotalPresenceChannels", -1)
|
||||
}
|
||||
|
||||
if c.IsPrivate() {
|
||||
a.Stats.Add("TotalPrivateChannels", -1)
|
||||
}
|
||||
|
||||
if c.IsPublic() {
|
||||
a.Stats.Add("TotalPublicChannels", -1)
|
||||
}
|
||||
|
||||
a.Stats.Add("TotalChannels", -1)
|
||||
}
|
||||
|
||||
// Add a new Channel to this APP
|
||||
func (a *Application) AddChannel(c *channel.Channel) {
|
||||
log.Infof("adding a new Channel %s to Application %s", c.ID, a.Name)
|
||||
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
a.channels[c.ID] = c
|
||||
|
||||
if c.IsPresence() {
|
||||
a.Stats.Add("TotalPresenceChannels", 1)
|
||||
}
|
||||
|
||||
if c.IsPrivate() {
|
||||
a.Stats.Add("TotalPrivateChannels", 1)
|
||||
}
|
||||
|
||||
if c.IsPublic() {
|
||||
a.Stats.Add("TotalPublicChannels", 1)
|
||||
}
|
||||
|
||||
a.Stats.Add("TotalChannels", 1)
|
||||
}
|
||||
|
||||
// Returns a Channel from this Application
|
||||
// If not found then the Channel is created and added to this Application
|
||||
func (a *Application) FindOrCreateChannelByChannelID(n string) *channel.Channel {
|
||||
c, err := a.FindChannelByChannelID(n)
|
||||
|
||||
if err != nil {
|
||||
c = channel.New(
|
||||
n,
|
||||
channel.WithChannelOccupiedListener(func(c *channel.Channel, s *subscription.Subscription) {
|
||||
a.TriggerChannelOccupiedHook(c)
|
||||
}),
|
||||
channel.WithChannelVacatedListener(func(c *channel.Channel, s *subscription.Subscription) {
|
||||
a.TriggerChannelVacatedHook(c)
|
||||
}),
|
||||
channel.WithMemberAddedListener(func(c *channel.Channel, s *subscription.Subscription) {
|
||||
a.TriggerMemberAddedHook(c, s)
|
||||
}),
|
||||
channel.WithMemberRemovedListener(func(c *channel.Channel, s *subscription.Subscription) {
|
||||
a.TriggerMemberRemovedHook(c, s)
|
||||
}),
|
||||
channel.WithClientEventListener(func(c *channel.Channel, s *subscription.Subscription, event string, data interface{}) {
|
||||
a.TriggerClientEventHook(c, s, event, data)
|
||||
}),
|
||||
)
|
||||
a.AddChannel(c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Find the Channel by Channel ID
|
||||
func (a *Application) FindChannelByChannelID(n string) (*channel.Channel, error) {
|
||||
c, exists := a.channels[n]
|
||||
|
||||
if exists {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("channel does not exists")
|
||||
}
|
||||
|
||||
func (a *Application) Publish(c *channel.Channel, event events.Raw, ignore string) error {
|
||||
a.Stats.Add("TotalUniqueMessages", 1)
|
||||
|
||||
return c.Publish(event, ignore)
|
||||
}
|
||||
|
||||
func (a *Application) Unsubscribe(c *channel.Channel, conn *connection.Connection) error {
|
||||
err := c.Unsubscribe(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.IsOccupied() {
|
||||
a.RemoveChannel(c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) Subscribe(c *channel.Channel, conn *connection.Connection, data string) error {
|
||||
return c.Subscribe(conn, data)
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
// Copyright 2014 Claudemiro Alves Feitosa Neto. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
channel2 "ipe/channel"
|
||||
"ipe/connection"
|
||||
"ipe/mocks"
|
||||
)
|
||||
|
||||
var id = 0
|
||||
|
||||
func newTestApp() *Application {
|
||||
a := NewApplication("Test", strconv.Itoa(id), "123", "123", false, false, true, false, "")
|
||||
id++
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
app.Connect(connection.New("socketID", mocks.MockSocket{}))
|
||||
|
||||
if len(app.connections) != 1 {
|
||||
t.Errorf("len(Application.connections) == %d, wants %d", len(app.connections), 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDisconnect(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
app.Connect(connection.New("socketID", mocks.MockSocket{}))
|
||||
app.Disconnect("socketID")
|
||||
|
||||
if len(app.connections) != 0 {
|
||||
t.Errorf("len(Application.connections) == %d, wants %d", len(app.connections), 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFindConnection(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
app.Connect(connection.New("socketID", mocks.MockSocket{}))
|
||||
|
||||
if _, err := app.FindConnection("socketID"); err != nil {
|
||||
t.Errorf("Application.FindConnection('socketID') == _, %q, wants %v", err, nil)
|
||||
}
|
||||
|
||||
if _, err := app.FindConnection("NotFound"); err == nil {
|
||||
t.Errorf("Application.FindConnection('socketID') == _, %q, wants !nil", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFindChannelByChannelID(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
channel := channel2.New("ID")
|
||||
app.AddChannel(channel)
|
||||
|
||||
if _, err := app.FindChannelByChannelID("ID"); err != nil {
|
||||
t.Errorf("Application.FindChannelByChannelID('ID') == _, %q, wants %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOrCreateChannelByChannelID(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
if len(app.channels) != 0 {
|
||||
t.Errorf("len(Application.channels) == %d, wants %d", len(app.channels), 0)
|
||||
}
|
||||
|
||||
app.FindOrCreateChannelByChannelID("ID")
|
||||
|
||||
if len(app.channels) != 1 {
|
||||
t.Errorf("len(Application.channels) == %d, wants %d", len(app.channels), 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemoveChannel(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
if len(app.channels) != 0 {
|
||||
t.Errorf("len(Application.channels) == %d, wants %d", len(app.channels), 0)
|
||||
}
|
||||
|
||||
channel := channel2.New("ID")
|
||||
app.AddChannel(channel)
|
||||
|
||||
if len(app.channels) != 1 {
|
||||
t.Errorf("len(Application.channels) == %d, wants %d", len(app.channels), 1)
|
||||
}
|
||||
|
||||
app.RemoveChannel(channel)
|
||||
|
||||
if len(app.channels) != 0 {
|
||||
t.Errorf("len(Application.channels) == %d, wants %d", len(app.channels), 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_add_channels(t *testing.T) {
|
||||
|
||||
app := newTestApp()
|
||||
|
||||
// Public
|
||||
|
||||
if len(app.PublicChannels()) != 0 {
|
||||
t.Errorf("len(Application.PublicChannels()) == %d, wants %d", len(app.PublicChannels()), 0)
|
||||
}
|
||||
|
||||
app.AddChannel(channel2.New("ID"))
|
||||
|
||||
if len(app.PublicChannels()) != 1 {
|
||||
t.Errorf("len(Application.PublicChannels()) == %d, wants %d", len(app.PublicChannels()), 1)
|
||||
}
|
||||
|
||||
// Presence
|
||||
|
||||
if len(app.PresenceChannels()) != 0 {
|
||||
t.Errorf("len(Application.PresenceChannels()) == %d, wants %d", len(app.PresenceChannels()), 0)
|
||||
}
|
||||
|
||||
app.AddChannel(channel2.New("presence-test"))
|
||||
|
||||
if len(app.PresenceChannels()) != 1 {
|
||||
t.Errorf("len(Application.PresenceChannels()) == %d, wants %d", len(app.PresenceChannels()), 1)
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
if len(app.PrivateChannels()) != 0 {
|
||||
t.Errorf("len(Application.PrivateChannels()) == %d, wants %d", len(app.PrivateChannels()), 0)
|
||||
}
|
||||
|
||||
app.AddChannel(channel2.New("private-test"))
|
||||
|
||||
if len(app.PrivateChannels()) != 1 {
|
||||
t.Errorf("len(Application.PrivateChannels()) == %d, wants %d", len(app.PrivateChannels()), 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_AllChannels(t *testing.T) {
|
||||
app := newTestApp()
|
||||
app.AddChannel(channel2.New("private-test"))
|
||||
app.AddChannel(channel2.New("presence-test"))
|
||||
app.AddChannel(channel2.New("test"))
|
||||
|
||||
if len(app.channels) != 3 {
|
||||
t.Errorf("len(Application.channels) == %d, wants %d", len(app.channels), 3)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_New_Subscriber(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
if len(app.connections) != 0 {
|
||||
t.Errorf("len(Application.connections) == %d, wants %d", len(app.connections), 0)
|
||||
}
|
||||
|
||||
conn := connection.New("1", mocks.MockSocket{})
|
||||
app.Connect(conn)
|
||||
|
||||
if len(app.connections) != 1 {
|
||||
t.Errorf("len(Application.connections) == %d, wants %d", len(app.connections), 1)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_find_subscriber(t *testing.T) {
|
||||
app := newTestApp()
|
||||
conn := connection.New("1", mocks.MockSocket{})
|
||||
app.Connect(conn)
|
||||
|
||||
conn, err := app.FindConnection("1")
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if conn.SocketID != "1" {
|
||||
t.Errorf("conn.SocketID == %s, wants %s", conn.SocketID, "1")
|
||||
}
|
||||
|
||||
// Find a wrong subscriber
|
||||
|
||||
conn, err = app.FindConnection("DoesNotExists")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("err == %q, wants !nil", err)
|
||||
}
|
||||
|
||||
if conn != nil {
|
||||
t.Errorf("conn == %q, wants nil", conn)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_find_or_create_channels(t *testing.T) {
|
||||
app := newTestApp()
|
||||
|
||||
// Public
|
||||
if len(app.PublicChannels()) != 0 {
|
||||
t.Errorf("len(Application.PublicChannels()) == %d, wants %d", len(app.PublicChannels()), 0)
|
||||
}
|
||||
|
||||
c := app.FindOrCreateChannelByChannelID("id")
|
||||
|
||||
if len(app.PublicChannels()) != 1 {
|
||||
t.Errorf("len(Application.PublicChannels()) == %d, wants %d", len(app.PublicChannels()), 1)
|
||||
}
|
||||
|
||||
if c.ID != "id" {
|
||||
t.Errorf("c.id == %s, wants %s", c.ID, "id")
|
||||
}
|
||||
|
||||
// Presence
|
||||
if len(app.PresenceChannels()) != 0 {
|
||||
t.Errorf("len(Application.PresenceChannels()) == %d, wants %d", len(app.PresenceChannels()), 0)
|
||||
}
|
||||
|
||||
c = app.FindOrCreateChannelByChannelID("presence-test")
|
||||
|
||||
if len(app.PresenceChannels()) != 1 {
|
||||
t.Errorf("len(Application.PresenceChannels()) == %d, wants %d", len(app.PresenceChannels()), 1)
|
||||
}
|
||||
|
||||
if c.ID != "presence-test" {
|
||||
t.Errorf("c.id == %s, wants %s", c.ID, "presence-test")
|
||||
}
|
||||
|
||||
// Private
|
||||
if len(app.PrivateChannels()) != 0 {
|
||||
t.Errorf("len(Application.PrivateChannels()) == %d, wants %d", len(app.PrivateChannels()), 0)
|
||||
}
|
||||
|
||||
c = app.FindOrCreateChannelByChannelID("private-test")
|
||||
|
||||
if len(app.PrivateChannels()) != 1 {
|
||||
t.Errorf("len(Application.PrivateChannels()) == %d, wants %d", len(app.PrivateChannels()), 1)
|
||||
}
|
||||
|
||||
if c.ID != "private-test" {
|
||||
t.Errorf("c.id == %s, wants %s", c.ID, "private-test")
|
||||
}
|
||||
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
// Copyright 2014 Claudemiro Alves Feitosa Neto. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
|
||||
"ipe/channel"
|
||||
"ipe/subscription"
|
||||
"ipe/utils"
|
||||
)
|
||||
|
||||
const maxTimeout = 3 * time.Second
|
||||
|
||||
// A webHook is sent as a HTTP POST request to the url which you specify.
|
||||
// The POST request payload (body) contains a JSON document, and follows the following format:
|
||||
// {
|
||||
// "time_ms": 1327078148132
|
||||
// "events": [
|
||||
// { "name": "event_name", "some": "data" }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// Security
|
||||
// Encryption
|
||||
//
|
||||
// You may use a HTTP or a HTTPS url for WebHooks. In most cases HTTP is sufficient, but HTTPS can be useful if your data is sensitive or if you wish to protect against replay attacks for example.
|
||||
// Authentication
|
||||
//
|
||||
// Since anyone could in principle send WebHooks to your application, it’s important to verify that these WebHooks originated from Pusher. Valid WebHooks will therefore contain these headers which contain a HMAC signature of the webHook payload (body):
|
||||
//
|
||||
// X-Pusher-Key: The App Key.
|
||||
// X-Pusher-Signature: A HMAC SHA256 hex digest formed by signing the POST payload (body) with the token’s secret.
|
||||
type webHook struct {
|
||||
TimeMs int64 `json:"time_ms"`
|
||||
Events []hookEvent `json:"events"`
|
||||
}
|
||||
|
||||
type hookEvent struct {
|
||||
Name string `json:"name"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
SocketID string `json:"socket_id,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
func newChannelOcuppiedHook(channel *channel.Channel) hookEvent {
|
||||
return hookEvent{Name: "channel_occupied", Channel: channel.ID}
|
||||
}
|
||||
|
||||
func newChannelVacatedHook(channel *channel.Channel) hookEvent {
|
||||
return hookEvent{Name: "channel_vacated", Channel: channel.ID}
|
||||
}
|
||||
|
||||
func newMemberAddedHook(channel *channel.Channel, s *subscription.Subscription) hookEvent {
|
||||
return hookEvent{Name: "member_added", Channel: channel.ID, UserID: s.ID}
|
||||
}
|
||||
|
||||
func newMemberRemovedHook(channel *channel.Channel, s *subscription.Subscription) hookEvent {
|
||||
return hookEvent{Name: "member_removed", Channel: channel.ID, UserID: s.ID}
|
||||
}
|
||||
|
||||
func newClientHook(channel *channel.Channel, s *subscription.Subscription, event string, data interface{}) hookEvent {
|
||||
return hookEvent{Name: "client_event", Channel: channel.ID, Event: event, Data: data, SocketID: s.Connection.SocketID}
|
||||
}
|
||||
|
||||
// channel_occupied
|
||||
// { "name": "channel_occupied", "channel": "test_channel" }
|
||||
func (a *Application) TriggerChannelOccupiedHook(c *channel.Channel) {
|
||||
event := newChannelOcuppiedHook(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := triggerHook(ctx, a, event); err != nil {
|
||||
log.Errorf("triggering webhook %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// channel_vacated
|
||||
// { "name": "channel_vacated", "channel": "test_channel" }
|
||||
func (a *Application) TriggerChannelVacatedHook(c *channel.Channel) {
|
||||
event := newChannelVacatedHook(c)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := triggerHook(ctx, a, event); err != nil {
|
||||
log.Errorf("triggering webhook %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// "name": "client_event",
|
||||
// "channel": "name of the channel the event was published on",
|
||||
// "event": "name of the event",
|
||||
// "data": "data associated with the event",
|
||||
// "socket_id": "socket_id of the sending socket",
|
||||
// "user_id": "user_id associated with the sending socket" # Only for presence channels
|
||||
// }
|
||||
func (a *Application) TriggerClientEventHook(c *channel.Channel, s *subscription.Subscription, clientEvent string, data interface{}) {
|
||||
event := newClientHook(c, s, clientEvent, data)
|
||||
|
||||
if c.IsPresence() {
|
||||
event.UserID = s.ID
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := triggerHook(ctx, a, event); err != nil {
|
||||
log.Errorf("triggering webhook %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// "name": "member_added",
|
||||
// "channel": "presence-your_channel_name",
|
||||
// "user_id": "a_user_id"
|
||||
// }
|
||||
func (a *Application) TriggerMemberAddedHook(c *channel.Channel, s *subscription.Subscription) {
|
||||
event := newMemberAddedHook(c, s)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := triggerHook(ctx, a, event); err != nil {
|
||||
log.Errorf("triggering webhook %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// "name": "member_removed",
|
||||
// "channel": "presence-your_channel_name",
|
||||
// "user_id": "a_user_id"
|
||||
// }
|
||||
func (a *Application) TriggerMemberRemovedHook(c *channel.Channel, s *subscription.Subscription) {
|
||||
event := newMemberRemovedHook(c, s)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := triggerHook(ctx, a, event); err != nil {
|
||||
log.Errorf("triggering webhook %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func triggerHook(ctx context.Context, a *Application, event hookEvent) error {
|
||||
if !a.WebHooks {
|
||||
log.Infof("webhook are not enabled for app: %s", a.Name)
|
||||
return fmt.Errorf("webhooks are not enabled for app: %s", a.Name)
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
log.Infof("Triggering %s event", event.Name)
|
||||
|
||||
hook := webHook{TimeMs: time.Now().Unix()}
|
||||
|
||||
hook.Events = append(hook.Events, event)
|
||||
|
||||
var js []byte
|
||||
var err error
|
||||
|
||||
js, err = json.Marshal(hook)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error decoding json: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
|
||||
req, err = http.NewRequest("POST", a.URLWebHook, bytes.NewReader(js))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating request: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
req.Header.Set("User-Agent", "Ipe UA; (+https://github.com/dimiro1/ipe)")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Pusher-Key", a.Key)
|
||||
req.Header.Set("X-Pusher-Signature", utils.HashMAC(js, []byte(a.Secret)))
|
||||
|
||||
log.V(1).Infof("%+v", req.Header)
|
||||
log.V(1).Infof("%+v", string(js))
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
// See: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#close_http_resp_body
|
||||
if resp != nil {
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Errorf("error closing response body %+v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error posting %s event: %+v", event.Name, err)
|
||||
}
|
||||
|
||||
// Successfully terminated
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user