mirror of
https://github.com/42wim/matterbridge.git
synced 2024-11-21 10:12:00 -08:00
Refactor and update RocketChat bridge (#707)
* Add support for editing/deleting messages * Add support for uploading files * Add support for avatars * Use the Rocket.Chat.Go.SDK * Use the rest and streaming api
This commit is contained in:
commit
716751cf76
69
bridge/rocketchat/handlers.go
Normal file
69
bridge/rocketchat/handlers.go
Normal file
@ -0,0 +1,69 @@
|
||||
package brocketchat
|
||||
|
||||
import (
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
)
|
||||
|
||||
func (b *Brocketchat) handleRocket() {
|
||||
messages := make(chan *config.Message)
|
||||
if b.GetString("WebhookBindAddress") != "" {
|
||||
b.Log.Debugf("Choosing webhooks based receiving")
|
||||
go b.handleRocketHook(messages)
|
||||
} else {
|
||||
b.Log.Debugf("Choosing login/password based receiving")
|
||||
go b.handleRocketClient(messages)
|
||||
}
|
||||
for message := range messages {
|
||||
message.Account = b.Account
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", message)
|
||||
b.Remote <- *message
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Brocketchat) handleRocketHook(messages chan *config.Message) {
|
||||
for {
|
||||
message := b.rh.Receive()
|
||||
b.Log.Debugf("Receiving from rockethook %#v", message)
|
||||
// do not loop
|
||||
if message.UserName == b.GetString("Nick") {
|
||||
continue
|
||||
}
|
||||
messages <- &config.Message{
|
||||
UserID: message.UserID,
|
||||
Username: message.UserName,
|
||||
Text: message.Text,
|
||||
Channel: message.ChannelName,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Brocketchat) handleRocketClient(messages chan *config.Message) {
|
||||
for message := range b.messageChan {
|
||||
b.Log.Debugf("message %#v", message)
|
||||
m := message
|
||||
if b.skipMessage(&m) {
|
||||
b.Log.Debugf("Skipped message: %#v", message)
|
||||
continue
|
||||
}
|
||||
|
||||
rmsg := &config.Message{Text: message.Msg,
|
||||
Username: message.User.UserName,
|
||||
Channel: b.getChannelName(message.RoomID),
|
||||
Account: b.Account,
|
||||
UserID: message.User.ID,
|
||||
ID: message.ID,
|
||||
}
|
||||
messages <- rmsg
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Brocketchat) handleUploadFile(msg *config.Message) error {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if err := b.uploadFile(&fi, b.getChannelID(msg.Channel)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
198
bridge/rocketchat/helpers.go
Normal file
198
bridge/rocketchat/helpers.go
Normal file
@ -0,0 +1,198 @@
|
||||
package brocketchat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/hook/rockethook"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/realtime"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/rest"
|
||||
"github.com/nelsonken/gomf"
|
||||
)
|
||||
|
||||
func (b *Brocketchat) doConnectWebhookBind() error {
|
||||
switch {
|
||||
case b.GetString("WebhookURL") != "":
|
||||
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
DisableServer: true})
|
||||
b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
|
||||
case b.GetString("Login") != "":
|
||||
b.Log.Info("Connecting using login/password (sending)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
||||
b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) doConnectWebhookURL() error {
|
||||
b.Log.Info("Connecting using webhookurl (sending)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
DisableServer: true})
|
||||
if b.GetString("Login") != "" {
|
||||
b.Log.Info("Connecting using login/password (receiving)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) apiLogin() error {
|
||||
b.Log.Debugf("handling apiLogin()")
|
||||
credentials := &models.UserCredentials{Email: b.GetString("login"), Password: b.GetString("password")}
|
||||
myURL, err := url.Parse(b.GetString("server"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := realtime.NewClient(myURL, b.GetBool("debug"))
|
||||
b.c = client
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
restclient := rest.NewClient(myURL, b.GetBool("debug"))
|
||||
user, err := b.c.Login(credentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.user = user
|
||||
b.r = restclient
|
||||
err = b.r.Login(credentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Log.Info("Connection succeeded")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) getChannelName(id string) string {
|
||||
b.RLock()
|
||||
defer b.RUnlock()
|
||||
if name, ok := b.channelMap[id]; ok {
|
||||
return name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Brocketchat) getChannelID(name string) string {
|
||||
b.RLock()
|
||||
defer b.RUnlock()
|
||||
for k, v := range b.channelMap {
|
||||
if v == name {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Brocketchat) skipMessage(message *models.Message) bool {
|
||||
return message.User.ID == b.user.ID
|
||||
}
|
||||
|
||||
func (b *Brocketchat) uploadFile(fi *config.FileInfo, channel string) error {
|
||||
fb := gomf.New()
|
||||
if err := fb.WriteField("description", fi.Comment); err != nil {
|
||||
return err
|
||||
}
|
||||
sp := strings.Split(fi.Name, ".")
|
||||
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
||||
if !strings.Contains(mtype, "image") && !strings.Contains(mtype, "video") {
|
||||
return nil
|
||||
}
|
||||
if err := fb.WriteFile("file", fi.Name, mtype, *fi.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := fb.GetHTTPRequest(context.TODO(), b.GetString("server")+"/api/v1/rooms.upload/"+channel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("X-Auth-Token", b.user.Token)
|
||||
req.Header.Add("X-User-Id", b.user.ID)
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
b.Log.Errorf("failed: %#v", string(body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendWebhook uses the configured WebhookURL to send the message
|
||||
func (b *Brocketchat) sendWebhook(msg *config.Message) error {
|
||||
// skip events
|
||||
if msg.Event != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
if msg.Extra != nil {
|
||||
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
||||
for _, rmsg := range helper.HandleExtra(msg, b.General) {
|
||||
rmsg := rmsg // scopelint
|
||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{
|
||||
IconURL: iconURL,
|
||||
Channel: rmsg.Channel,
|
||||
UserName: rmsg.Username,
|
||||
Text: rmsg.Text,
|
||||
Props: make(map[string]interface{}),
|
||||
}
|
||||
if err := b.mh.Send(matterMessage); err != nil {
|
||||
b.Log.Errorf("sendWebhook failed: %s ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// webhook doesn't support file uploads, so we add the url manually
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text += fi.URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
iconURL := config.GetIconURL(msg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{
|
||||
IconURL: iconURL,
|
||||
Channel: msg.Channel,
|
||||
UserName: msg.Username,
|
||||
Text: msg.Text,
|
||||
}
|
||||
if msg.Avatar != "" {
|
||||
matterMessage.IconURL = msg.Avatar
|
||||
}
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
b.Log.Info(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,21 +1,37 @@
|
||||
package brocketchat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/hook/rockethook"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/realtime"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/rest"
|
||||
)
|
||||
|
||||
type Brocketchat struct {
|
||||
mh *matterhook.Client
|
||||
rh *rockethook.Client
|
||||
c *realtime.Client
|
||||
r *rest.Client
|
||||
*bridge.Config
|
||||
messageChan chan models.Message
|
||||
channelMap map[string]string
|
||||
user *models.User
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
return &Brocketchat{Config: cfg}
|
||||
b := &Brocketchat{Config: cfg}
|
||||
b.messageChan = make(chan models.Message)
|
||||
b.channelMap = make(map[string]string)
|
||||
b.Log.Debugf("enabling rocketchat")
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Command(cmd string) string {
|
||||
@ -23,70 +39,118 @@ func (b *Brocketchat) Command(cmd string) string {
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Connect() error {
|
||||
b.Log.Info("Connecting webhooks")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
DisableServer: true})
|
||||
b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
|
||||
go b.handleRocketHook()
|
||||
if b.GetString("WebhookBindAddress") != "" {
|
||||
if err := b.doConnectWebhookBind(); err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleRocket()
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case b.GetString("WebhookURL") != "":
|
||||
if err := b.doConnectWebhookURL(); err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleRocket()
|
||||
return nil
|
||||
case b.GetString("Login") != "":
|
||||
b.Log.Info("Connecting using login/password (sending and receiving)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleRocket()
|
||||
}
|
||||
if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" &&
|
||||
b.GetString("Login") == "" {
|
||||
return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Login/Password/Server configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Disconnect() error {
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
|
||||
if b.c == nil {
|
||||
return nil
|
||||
}
|
||||
id, err := b.c.GetChannelId(channel.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Lock()
|
||||
b.channelMap[id] = channel.Name
|
||||
b.Unlock()
|
||||
mychannel := &models.Channel{ID: id, Name: channel.Name}
|
||||
if err := b.c.JoinChannel(id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.c.SubscribeToMessageStream(mychannel, b.messageChan); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
||||
// ignore delete messages
|
||||
channel := &models.Channel{ID: b.getChannelID(msg.Channel), Name: msg.Channel}
|
||||
|
||||
// Delete message
|
||||
if msg.Event == config.EventMsgDelete {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
}
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
return msg.ID, b.c.DeleteMessage(&models.Message{ID: msg.ID})
|
||||
}
|
||||
|
||||
// Use webhook to send the message
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
return "", b.sendWebhook(&msg)
|
||||
}
|
||||
|
||||
// Prepend nick if configured
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
|
||||
// Edit message if we have an ID
|
||||
if msg.ID != "" {
|
||||
return msg.ID, b.c.EditMessage(&models.Message{ID: msg.ID, Msg: msg.Text, RoomID: b.getChannelID(msg.Channel)})
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
rmsg := rmsg // scopelint
|
||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text}
|
||||
b.mh.Send(matterMessage)
|
||||
smsg := &models.Message{
|
||||
RoomID: b.getChannelID(rmsg.Channel),
|
||||
Msg: rmsg.Username + rmsg.Text,
|
||||
PostMessage: models.PostMessage{
|
||||
Avatar: rmsg.Avatar,
|
||||
Alias: rmsg.Username,
|
||||
},
|
||||
}
|
||||
if _, err := b.c.SendMessage(smsg); err != nil {
|
||||
b.Log.Errorf("SendMessage failed: %s", err)
|
||||
}
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text += fi.URL
|
||||
}
|
||||
}
|
||||
return "", b.handleUploadFile(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL}
|
||||
matterMessage.Channel = msg.Channel
|
||||
matterMessage.UserName = msg.Username
|
||||
matterMessage.Type = ""
|
||||
matterMessage.Text = msg.Text
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
b.Log.Info(err)
|
||||
smsg := &models.Message{
|
||||
RoomID: channel.ID,
|
||||
Msg: msg.Text,
|
||||
PostMessage: models.PostMessage{
|
||||
Avatar: msg.Avatar,
|
||||
Alias: msg.Username,
|
||||
},
|
||||
}
|
||||
|
||||
rmsg, err := b.c.SendMessage(smsg)
|
||||
if rmsg == nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) handleRocketHook() {
|
||||
for {
|
||||
message := b.rh.Receive()
|
||||
b.Log.Debugf("Receiving from rockethook %#v", message)
|
||||
// do not loop
|
||||
if message.UserName == b.GetString("Nick") {
|
||||
continue
|
||||
}
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.UserName, b.Account)
|
||||
b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID}
|
||||
}
|
||||
return rmsg.ID, err
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -3,6 +3,7 @@ module github.com/42wim/matterbridge
|
||||
require (
|
||||
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
||||
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 // indirect
|
||||
github.com/Jeffail/gabs v1.1.1 // indirect
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
|
||||
github.com/bwmarrin/discordgo v0.19.0
|
||||
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
|
||||
@ -10,6 +11,7 @@ require (
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
|
||||
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc // indirect
|
||||
github.com/google/gops v0.3.5
|
||||
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
|
||||
github.com/gorilla/schema v1.0.2
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
@ -23,6 +25,7 @@ require (
|
||||
github.com/lrstanley/girc v0.0.0-20190102153329-c1e59a02f488
|
||||
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
|
||||
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
|
||||
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
|
||||
github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
|
||||
github.com/matterbridge/gozulipbot v0.0.0-20190212232658-7aa251978a18
|
||||
@ -31,6 +34,7 @@ require (
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
|
||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
||||
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
|
||||
github.com/nicksnyder/go-i18n v1.4.0 // indirect
|
||||
github.com/nlopes/slack v0.5.0
|
||||
github.com/onsi/ginkgo v1.6.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@ -2,6 +2,8 @@ github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557 h1:IZtuWGfzQnKnCSu
|
||||
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557/go.mod h1:jL0YSXMs/txjtGJ4PWrmETOk6KUHMDPMshgQZlTeB3Y=
|
||||
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 h1:v/zr4ns/4sSahF9KBm4Uc933bLsEEv7LuT63CJ019yo=
|
||||
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E=
|
||||
github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 h1:xZBoq249G9MSt+XuY7sVQzcfONJ6IQuwpCK+KAaOpnY=
|
||||
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
|
||||
github.com/alexcesaro/log v0.0.0-20150915221235-61e686294e58 h1:MkpmYfld/S8kXqTYI68DfL8/hHXjHogL120Dy00TIxc=
|
||||
@ -27,6 +29,8 @@ github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc h1:wdhDSKrkYy24mcf
|
||||
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/gops v0.3.5 h1:SIWvPLiYvy5vMwjxB3rVFTE4QBhUFj2KKWr3Xm7CKhw=
|
||||
github.com/google/gops v0.3.5/go.mod h1:pMQgrscwEK/aUSW1IFSaBPbJX82FPHWaSoJw1axQfD0=
|
||||
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo=
|
||||
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/schema v1.0.2 h1:sAgNfOcNYvdDSrzGHVy9nzCQahG+qmsg+nE8dK85QRA=
|
||||
@ -66,6 +70,8 @@ github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408
|
||||
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d h1:F+Sr+C0ojSlYQ37BLylQtSFmyQULe3jbAygcyXQ9mVs=
|
||||
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A=
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 h1:KzDEcy8eDbTx881giW8a6llsAck3e2bJvMyKvh1IK+k=
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
|
||||
github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea h1:kaADGqpK4gGO2BpzEyJrBxq2Jc57Rsar4i2EUxcACUc=
|
||||
@ -88,6 +94,8 @@ github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 h1:oKIteT
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff h1:HLGD5/9UxxfEuO9DtP8gnTmNtMxbPyhYltfxsITel8g=
|
||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff/go.mod h1:B8jLfIIPn2sKyWr0D7cL2v7tnrDD5z291s2Zypdu89E=
|
||||
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9 h1:mp6tU1r0xLostUGLkTspf/9/AiHuVD7ptyXhySkDEsE=
|
||||
github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9/go.mod h1:A5SRAcpTemjGgIuBq6Kic2yHcoeUFWUinOAlMP/i9xo=
|
||||
github.com/nicksnyder/go-i18n v1.4.0 h1:AgLl+Yq7kg5OYlzCgu9cKTZOyI4tD/NgukKqLqC8E+I=
|
||||
github.com/nicksnyder/go-i18n v1.4.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
|
||||
github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0=
|
||||
|
@ -893,6 +893,21 @@ ShowTopicChange=false
|
||||
#REQUIRED
|
||||
|
||||
[rocketchat.rockme]
|
||||
#The rocketchat hostname. (prefix it with http or https)
|
||||
#REQUIRED (when not using webhooks)
|
||||
Server="https://yourrocketchatserver.domain.com:443"
|
||||
|
||||
#login/pass of your bot.
|
||||
#Use a dedicated user for this and not your own!
|
||||
#REQUIRED (when not using webhooks)
|
||||
Login="yourlogin"
|
||||
Password="yourpass"
|
||||
|
||||
#### Settings for webhook matterbridge.
|
||||
#USE DEDICATED BOT USER WHEN POSSIBLE! This allows you to use advanced features like message editing/deleting and uploads
|
||||
#You don't need to configure this, if you have configured the settings
|
||||
#above.
|
||||
|
||||
#Url is your incoming webhook url as specified in rocketchat
|
||||
#Read #https://rocket.chat/docs/administrator-guides/integrations/#how-to-create-a-new-incoming-webhook
|
||||
#See administration - integrations - new integration - incoming webhook
|
||||
@ -917,6 +932,8 @@ NoTLS=false
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
#### End settings for webhook matterbridge.
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
|
19
vendor/github.com/Jeffail/gabs/LICENSE
generated
vendored
Normal file
19
vendor/github.com/Jeffail/gabs/LICENSE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2014 Ashley Jeffs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
315
vendor/github.com/Jeffail/gabs/README.md
generated
vendored
Normal file
315
vendor/github.com/Jeffail/gabs/README.md
generated
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
![Gabs](gabs_logo.png "Gabs")
|
||||
|
||||
Gabs is a small utility for dealing with dynamic or unknown JSON structures in
|
||||
golang. It's pretty much just a helpful wrapper around the golang
|
||||
`json.Marshal/json.Unmarshal` behaviour and `map[string]interface{}` objects.
|
||||
It does nothing spectacular except for being fabulous.
|
||||
|
||||
https://godoc.org/github.com/Jeffail/gabs
|
||||
|
||||
## How to install:
|
||||
|
||||
``` bash
|
||||
go get github.com/Jeffail/gabs
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
### Parsing and searching JSON
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
import "github.com/Jeffail/gabs"
|
||||
|
||||
jsonParsed, err := gabs.ParseJSON([]byte(`{
|
||||
"outter":{
|
||||
"inner":{
|
||||
"value1":10,
|
||||
"value2":22
|
||||
},
|
||||
"alsoInner":{
|
||||
"value1":20
|
||||
}
|
||||
}
|
||||
}`))
|
||||
|
||||
var value float64
|
||||
var ok bool
|
||||
|
||||
value, ok = jsonParsed.Path("outter.inner.value1").Data().(float64)
|
||||
// value == 10.0, ok == true
|
||||
|
||||
value, ok = jsonParsed.Search("outter", "inner", "value1").Data().(float64)
|
||||
// value == 10.0, ok == true
|
||||
|
||||
value, ok = jsonParsed.Path("does.not.exist").Data().(float64)
|
||||
// value == 0.0, ok == false
|
||||
|
||||
exists := jsonParsed.Exists("outter", "inner", "value1")
|
||||
// exists == true
|
||||
|
||||
exists := jsonParsed.Exists("does", "not", "exist")
|
||||
// exists == false
|
||||
|
||||
exists := jsonParsed.ExistsP("does.not.exist")
|
||||
// exists == false
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### Iterating objects
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonParsed, _ := gabs.ParseJSON([]byte(`{"object":{ "first": 1, "second": 2, "third": 3 }}`))
|
||||
|
||||
// S is shorthand for Search
|
||||
children, _ := jsonParsed.S("object").ChildrenMap()
|
||||
for key, child := range children {
|
||||
fmt.Printf("key: %v, value: %v\n", key, child.Data().(string))
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### Iterating arrays
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ "first", "second", "third" ]}`))
|
||||
|
||||
// S is shorthand for Search
|
||||
children, _ := jsonParsed.S("array").Children()
|
||||
for _, child := range children {
|
||||
fmt.Println(child.Data().(string))
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
first
|
||||
second
|
||||
third
|
||||
```
|
||||
|
||||
Children() will return all children of an array in order. This also works on
|
||||
objects, however, the children will be returned in a random order.
|
||||
|
||||
### Searching through arrays
|
||||
|
||||
If your JSON structure contains arrays you can still search the fields of the
|
||||
objects within the array, this returns a JSON array containing the results for
|
||||
each element.
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ {"value":1}, {"value":2}, {"value":3} ]}`))
|
||||
fmt.Println(jsonParsed.Path("array.value").String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
[1,2,3]
|
||||
```
|
||||
|
||||
### Generating JSON
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonObj := gabs.New()
|
||||
// or gabs.Consume(jsonObject) to work on an existing map[string]interface{}
|
||||
|
||||
jsonObj.Set(10, "outter", "inner", "value")
|
||||
jsonObj.SetP(20, "outter.inner.value2")
|
||||
jsonObj.Set(30, "outter", "inner2", "value3")
|
||||
|
||||
fmt.Println(jsonObj.String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{"outter":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
|
||||
```
|
||||
|
||||
To pretty-print:
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
fmt.Println(jsonObj.StringIndent("", " "))
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{
|
||||
"outter": {
|
||||
"inner": {
|
||||
"value": 10,
|
||||
"value2": 20
|
||||
},
|
||||
"inner2": {
|
||||
"value3": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Generating Arrays
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonObj := gabs.New()
|
||||
|
||||
jsonObj.Array("foo", "array")
|
||||
// Or .ArrayP("foo.array")
|
||||
|
||||
jsonObj.ArrayAppend(10, "foo", "array")
|
||||
jsonObj.ArrayAppend(20, "foo", "array")
|
||||
jsonObj.ArrayAppend(30, "foo", "array")
|
||||
|
||||
fmt.Println(jsonObj.String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{"foo":{"array":[10,20,30]}}
|
||||
```
|
||||
|
||||
Working with arrays by index:
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonObj := gabs.New()
|
||||
|
||||
// Create an array with the length of 3
|
||||
jsonObj.ArrayOfSize(3, "foo")
|
||||
|
||||
jsonObj.S("foo").SetIndex("test1", 0)
|
||||
jsonObj.S("foo").SetIndex("test2", 1)
|
||||
|
||||
// Create an embedded array with the length of 3
|
||||
jsonObj.S("foo").ArrayOfSizeI(3, 2)
|
||||
|
||||
jsonObj.S("foo").Index(2).SetIndex(1, 0)
|
||||
jsonObj.S("foo").Index(2).SetIndex(2, 1)
|
||||
jsonObj.S("foo").Index(2).SetIndex(3, 2)
|
||||
|
||||
fmt.Println(jsonObj.String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{"foo":["test1","test2",[1,2,3]]}
|
||||
```
|
||||
|
||||
### Converting back to JSON
|
||||
|
||||
This is the easiest part:
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonParsedObj, _ := gabs.ParseJSON([]byte(`{
|
||||
"outter":{
|
||||
"values":{
|
||||
"first":10,
|
||||
"second":11
|
||||
}
|
||||
},
|
||||
"outter2":"hello world"
|
||||
}`))
|
||||
|
||||
jsonOutput := jsonParsedObj.String()
|
||||
// Becomes `{"outter":{"values":{"first":10,"second":11}},"outter2":"hello world"}`
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
And to serialize a specific segment is as simple as:
|
||||
|
||||
``` go
|
||||
...
|
||||
|
||||
jsonParsedObj := gabs.ParseJSON([]byte(`{
|
||||
"outter":{
|
||||
"values":{
|
||||
"first":10,
|
||||
"second":11
|
||||
}
|
||||
},
|
||||
"outter2":"hello world"
|
||||
}`))
|
||||
|
||||
jsonOutput := jsonParsedObj.Search("outter").String()
|
||||
// Becomes `{"values":{"first":10,"second":11}}`
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### Merge two containers
|
||||
|
||||
You can merge a JSON structure into an existing one, where collisions will be
|
||||
converted into a JSON array.
|
||||
|
||||
``` go
|
||||
jsonParsed1, _ := ParseJSON([]byte(`{"outter": {"value1": "one"}}`))
|
||||
jsonParsed2, _ := ParseJSON([]byte(`{"outter": {"inner": {"value3": "three"}}, "outter2": {"value2": "two"}}`))
|
||||
|
||||
jsonParsed1.Merge(jsonParsed2)
|
||||
// Becomes `{"outter":{"inner":{"value3":"three"},"value1":"one"},"outter2":{"value2":"two"}}`
|
||||
```
|
||||
|
||||
Arrays are merged:
|
||||
|
||||
``` go
|
||||
jsonParsed1, _ := ParseJSON([]byte(`{"array": ["one"]}`))
|
||||
jsonParsed2, _ := ParseJSON([]byte(`{"array": ["two"]}`))
|
||||
|
||||
jsonParsed1.Merge(jsonParsed2)
|
||||
// Becomes `{"array":["one", "two"]}`
|
||||
```
|
||||
|
||||
### Parsing Numbers
|
||||
|
||||
Gabs uses the `json` package under the bonnet, which by default will parse all
|
||||
number values into `float64`. If you need to parse `Int` values then you should
|
||||
use a `json.Decoder` (https://golang.org/pkg/encoding/json/#Decoder):
|
||||
|
||||
``` go
|
||||
sample := []byte(`{"test":{"int":10, "float":6.66}}`)
|
||||
dec := json.NewDecoder(bytes.NewReader(sample))
|
||||
dec.UseNumber()
|
||||
|
||||
val, err := gabs.ParseJSONDecoder(dec)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
intValue, err := val.Path("test.int").Data().(json.Number).Int64()
|
||||
```
|
581
vendor/github.com/Jeffail/gabs/gabs.go
generated
vendored
Normal file
581
vendor/github.com/Jeffail/gabs/gabs.go
generated
vendored
Normal file
@ -0,0 +1,581 @@
|
||||
/*
|
||||
Copyright (c) 2014 Ashley Jeffs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package gabs implements a simplified wrapper around creating and parsing JSON.
|
||||
package gabs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
// ErrOutOfBounds - Index out of bounds.
|
||||
ErrOutOfBounds = errors.New("out of bounds")
|
||||
|
||||
// ErrNotObjOrArray - The target is not an object or array type.
|
||||
ErrNotObjOrArray = errors.New("not an object or array")
|
||||
|
||||
// ErrNotObj - The target is not an object type.
|
||||
ErrNotObj = errors.New("not an object")
|
||||
|
||||
// ErrNotArray - The target is not an array type.
|
||||
ErrNotArray = errors.New("not an array")
|
||||
|
||||
// ErrPathCollision - Creating a path failed because an element collided with an existing value.
|
||||
ErrPathCollision = errors.New("encountered value collision whilst building path")
|
||||
|
||||
// ErrInvalidInputObj - The input value was not a map[string]interface{}.
|
||||
ErrInvalidInputObj = errors.New("invalid input object")
|
||||
|
||||
// ErrInvalidInputText - The input data could not be parsed.
|
||||
ErrInvalidInputText = errors.New("input text could not be parsed")
|
||||
|
||||
// ErrInvalidPath - The filepath was not valid.
|
||||
ErrInvalidPath = errors.New("invalid file path")
|
||||
|
||||
// ErrInvalidBuffer - The input buffer contained an invalid JSON string
|
||||
ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
|
||||
)
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Container - an internal structure that holds a reference to the core interface map of the parsed
|
||||
// json. Use this container to move context.
|
||||
type Container struct {
|
||||
object interface{}
|
||||
}
|
||||
|
||||
// Data - Return the contained data as an interface{}.
|
||||
func (g *Container) Data() interface{} {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
return g.object
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Path - Search for a value using dot notation.
|
||||
func (g *Container) Path(path string) *Container {
|
||||
return g.Search(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// Search - Attempt to find and return an object within the JSON structure by specifying the
|
||||
// hierarchy of field names to locate the target. If the search encounters an array and has not
|
||||
// reached the end target then it will iterate each object of the array for the target and return
|
||||
// all of the results in a JSON array.
|
||||
func (g *Container) Search(hierarchy ...string) *Container {
|
||||
var object interface{}
|
||||
|
||||
object = g.Data()
|
||||
for target := 0; target < len(hierarchy); target++ {
|
||||
if mmap, ok := object.(map[string]interface{}); ok {
|
||||
object, ok = mmap[hierarchy[target]]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
} else if marray, ok := object.([]interface{}); ok {
|
||||
tmpArray := []interface{}{}
|
||||
for _, val := range marray {
|
||||
tmpGabs := &Container{val}
|
||||
res := tmpGabs.Search(hierarchy[target:]...)
|
||||
if res != nil {
|
||||
tmpArray = append(tmpArray, res.Data())
|
||||
}
|
||||
}
|
||||
if len(tmpArray) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &Container{tmpArray}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return &Container{object}
|
||||
}
|
||||
|
||||
// S - Shorthand method, does the same thing as Search.
|
||||
func (g *Container) S(hierarchy ...string) *Container {
|
||||
return g.Search(hierarchy...)
|
||||
}
|
||||
|
||||
// Exists - Checks whether a path exists.
|
||||
func (g *Container) Exists(hierarchy ...string) bool {
|
||||
return g.Search(hierarchy...) != nil
|
||||
}
|
||||
|
||||
// ExistsP - Checks whether a dot notation path exists.
|
||||
func (g *Container) ExistsP(path string) bool {
|
||||
return g.Exists(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// Index - Attempt to find and return an object within a JSON array by index.
|
||||
func (g *Container) Index(index int) *Container {
|
||||
if array, ok := g.Data().([]interface{}); ok {
|
||||
if index >= len(array) {
|
||||
return &Container{nil}
|
||||
}
|
||||
return &Container{array[index]}
|
||||
}
|
||||
return &Container{nil}
|
||||
}
|
||||
|
||||
// Children - Return a slice of all the children of the array. This also works for objects, however,
|
||||
// the children returned for an object will NOT be in order and you lose the names of the returned
|
||||
// objects this way.
|
||||
func (g *Container) Children() ([]*Container, error) {
|
||||
if array, ok := g.Data().([]interface{}); ok {
|
||||
children := make([]*Container, len(array))
|
||||
for i := 0; i < len(array); i++ {
|
||||
children[i] = &Container{array[i]}
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||
children := []*Container{}
|
||||
for _, obj := range mmap {
|
||||
children = append(children, &Container{obj})
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
return nil, ErrNotObjOrArray
|
||||
}
|
||||
|
||||
// ChildrenMap - Return a map of all the children of an object.
|
||||
func (g *Container) ChildrenMap() (map[string]*Container, error) {
|
||||
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||
children := map[string]*Container{}
|
||||
for name, obj := range mmap {
|
||||
children[name] = &Container{obj}
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
return nil, ErrNotObj
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be
|
||||
// constructed, and if a collision occurs with a non object type whilst iterating the path an error
|
||||
// is returned.
|
||||
func (g *Container) Set(value interface{}, path ...string) (*Container, error) {
|
||||
if len(path) == 0 {
|
||||
g.object = value
|
||||
return g, nil
|
||||
}
|
||||
var object interface{}
|
||||
if g.object == nil {
|
||||
g.object = map[string]interface{}{}
|
||||
}
|
||||
object = g.object
|
||||
for target := 0; target < len(path); target++ {
|
||||
if mmap, ok := object.(map[string]interface{}); ok {
|
||||
if target == len(path)-1 {
|
||||
mmap[path[target]] = value
|
||||
} else if mmap[path[target]] == nil {
|
||||
mmap[path[target]] = map[string]interface{}{}
|
||||
}
|
||||
object = mmap[path[target]]
|
||||
} else {
|
||||
return &Container{nil}, ErrPathCollision
|
||||
}
|
||||
}
|
||||
return &Container{object}, nil
|
||||
}
|
||||
|
||||
// SetP - Does the same as Set, but using a dot notation JSON path.
|
||||
func (g *Container) SetP(value interface{}, path string) (*Container, error) {
|
||||
return g.Set(value, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// SetIndex - Set a value of an array element based on the index.
|
||||
func (g *Container) SetIndex(value interface{}, index int) (*Container, error) {
|
||||
if array, ok := g.Data().([]interface{}); ok {
|
||||
if index >= len(array) {
|
||||
return &Container{nil}, ErrOutOfBounds
|
||||
}
|
||||
array[index] = value
|
||||
return &Container{array[index]}, nil
|
||||
}
|
||||
return &Container{nil}, ErrNotArray
|
||||
}
|
||||
|
||||
// Object - Create a new JSON object at a path. Returns an error if the path contains a collision
|
||||
// with a non object type.
|
||||
func (g *Container) Object(path ...string) (*Container, error) {
|
||||
return g.Set(map[string]interface{}{}, path...)
|
||||
}
|
||||
|
||||
// ObjectP - Does the same as Object, but using a dot notation JSON path.
|
||||
func (g *Container) ObjectP(path string) (*Container, error) {
|
||||
return g.Object(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ObjectI - Create a new JSON object at an array index. Returns an error if the object is not an
|
||||
// array or the index is out of bounds.
|
||||
func (g *Container) ObjectI(index int) (*Container, error) {
|
||||
return g.SetIndex(map[string]interface{}{}, index)
|
||||
}
|
||||
|
||||
// Array - Create a new JSON array at a path. Returns an error if the path contains a collision with
|
||||
// a non object type.
|
||||
func (g *Container) Array(path ...string) (*Container, error) {
|
||||
return g.Set([]interface{}{}, path...)
|
||||
}
|
||||
|
||||
// ArrayP - Does the same as Array, but using a dot notation JSON path.
|
||||
func (g *Container) ArrayP(path string) (*Container, error) {
|
||||
return g.Array(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayI - Create a new JSON array at an array index. Returns an error if the object is not an
|
||||
// array or the index is out of bounds.
|
||||
func (g *Container) ArrayI(index int) (*Container, error) {
|
||||
return g.SetIndex([]interface{}{}, index)
|
||||
}
|
||||
|
||||
// ArrayOfSize - Create a new JSON array of a particular size at a path. Returns an error if the
|
||||
// path contains a collision with a non object type.
|
||||
func (g *Container) ArrayOfSize(size int, path ...string) (*Container, error) {
|
||||
a := make([]interface{}, size)
|
||||
return g.Set(a, path...)
|
||||
}
|
||||
|
||||
// ArrayOfSizeP - Does the same as ArrayOfSize, but using a dot notation JSON path.
|
||||
func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) {
|
||||
return g.ArrayOfSize(size, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayOfSizeI - Create a new JSON array of a particular size at an array index. Returns an error
|
||||
// if the object is not an array or the index is out of bounds.
|
||||
func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) {
|
||||
a := make([]interface{}, size)
|
||||
return g.SetIndex(a, index)
|
||||
}
|
||||
|
||||
// Delete - Delete an element at a JSON path, an error is returned if the element does not exist.
|
||||
func (g *Container) Delete(path ...string) error {
|
||||
var object interface{}
|
||||
|
||||
if g.object == nil {
|
||||
return ErrNotObj
|
||||
}
|
||||
object = g.object
|
||||
for target := 0; target < len(path); target++ {
|
||||
if mmap, ok := object.(map[string]interface{}); ok {
|
||||
if target == len(path)-1 {
|
||||
if _, ok := mmap[path[target]]; ok {
|
||||
delete(mmap, path[target])
|
||||
} else {
|
||||
return ErrNotObj
|
||||
}
|
||||
}
|
||||
object = mmap[path[target]]
|
||||
} else {
|
||||
return ErrNotObj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteP - Does the same as Delete, but using a dot notation JSON path.
|
||||
func (g *Container) DeleteP(path string) error {
|
||||
return g.Delete(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// Merge - Merges two gabs-containers
|
||||
func (g *Container) Merge(toMerge *Container) error {
|
||||
var recursiveFnc func(map[string]interface{}, []string) error
|
||||
recursiveFnc = func(mmap map[string]interface{}, path []string) error {
|
||||
for key, value := range mmap {
|
||||
newPath := append(path, key)
|
||||
if g.Exists(newPath...) {
|
||||
target := g.Search(newPath...)
|
||||
switch t := value.(type) {
|
||||
case map[string]interface{}:
|
||||
switch targetV := target.Data().(type) {
|
||||
case map[string]interface{}:
|
||||
if err := recursiveFnc(t, newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
case []interface{}:
|
||||
g.Set(append(targetV, t), newPath...)
|
||||
default:
|
||||
newSlice := append([]interface{}{}, targetV)
|
||||
g.Set(append(newSlice, t), newPath...)
|
||||
}
|
||||
case []interface{}:
|
||||
for _, valueOfSlice := range t {
|
||||
if err := g.ArrayAppend(valueOfSlice, newPath...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
switch targetV := target.Data().(type) {
|
||||
case []interface{}:
|
||||
g.Set(append(targetV, t), newPath...)
|
||||
default:
|
||||
newSlice := append([]interface{}{}, targetV)
|
||||
g.Set(append(newSlice, t), newPath...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// path doesn't exist. So set the value
|
||||
if _, err := g.Set(value, newPath...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if mmap, ok := toMerge.Data().(map[string]interface{}); ok {
|
||||
return recursiveFnc(mmap, []string{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
Array modification/search - Keeping these options simple right now, no need for anything more
|
||||
complicated since you can just cast to []interface{}, modify and then reassign with Set.
|
||||
*/
|
||||
|
||||
// ArrayAppend - Append a value onto a JSON array. If the target is not a JSON array then it will be
|
||||
// converted into one, with its contents as the first element of the array.
|
||||
func (g *Container) ArrayAppend(value interface{}, path ...string) error {
|
||||
if array, ok := g.Search(path...).Data().([]interface{}); ok {
|
||||
array = append(array, value)
|
||||
_, err := g.Set(array, path...)
|
||||
return err
|
||||
}
|
||||
|
||||
newArray := []interface{}{}
|
||||
if d := g.Search(path...).Data(); d != nil {
|
||||
newArray = append(newArray, d)
|
||||
}
|
||||
newArray = append(newArray, value)
|
||||
|
||||
_, err := g.Set(newArray, path...)
|
||||
return err
|
||||
}
|
||||
|
||||
// ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayAppendP(value interface{}, path string) error {
|
||||
return g.ArrayAppend(value, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayRemove - Remove an element from a JSON array.
|
||||
func (g *Container) ArrayRemove(index int, path ...string) error {
|
||||
if index < 0 {
|
||||
return ErrOutOfBounds
|
||||
}
|
||||
array, ok := g.Search(path...).Data().([]interface{})
|
||||
if !ok {
|
||||
return ErrNotArray
|
||||
}
|
||||
if index < len(array) {
|
||||
array = append(array[:index], array[index+1:]...)
|
||||
} else {
|
||||
return ErrOutOfBounds
|
||||
}
|
||||
_, err := g.Set(array, path...)
|
||||
return err
|
||||
}
|
||||
|
||||
// ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayRemoveP(index int, path string) error {
|
||||
return g.ArrayRemove(index, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayElement - Access an element from a JSON array.
|
||||
func (g *Container) ArrayElement(index int, path ...string) (*Container, error) {
|
||||
if index < 0 {
|
||||
return &Container{nil}, ErrOutOfBounds
|
||||
}
|
||||
array, ok := g.Search(path...).Data().([]interface{})
|
||||
if !ok {
|
||||
return &Container{nil}, ErrNotArray
|
||||
}
|
||||
if index < len(array) {
|
||||
return &Container{array[index]}, nil
|
||||
}
|
||||
return &Container{nil}, ErrOutOfBounds
|
||||
}
|
||||
|
||||
// ArrayElementP - Access an element from a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
|
||||
return g.ArrayElement(index, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayCount - Count the number of elements in a JSON array.
|
||||
func (g *Container) ArrayCount(path ...string) (int, error) {
|
||||
if array, ok := g.Search(path...).Data().([]interface{}); ok {
|
||||
return len(array), nil
|
||||
}
|
||||
return 0, ErrNotArray
|
||||
}
|
||||
|
||||
// ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayCountP(path string) (int, error) {
|
||||
return g.ArrayCount(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Bytes - Converts the contained object back to a JSON []byte blob.
|
||||
func (g *Container) Bytes() []byte {
|
||||
if g.Data() != nil {
|
||||
if bytes, err := json.Marshal(g.object); err == nil {
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
return []byte("{}")
|
||||
}
|
||||
|
||||
// BytesIndent - Converts the contained object to a JSON []byte blob formatted with prefix, indent.
|
||||
func (g *Container) BytesIndent(prefix string, indent string) []byte {
|
||||
if g.object != nil {
|
||||
if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil {
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
return []byte("{}")
|
||||
}
|
||||
|
||||
// String - Converts the contained object to a JSON formatted string.
|
||||
func (g *Container) String() string {
|
||||
return string(g.Bytes())
|
||||
}
|
||||
|
||||
// StringIndent - Converts the contained object back to a JSON formatted string with prefix, indent.
|
||||
func (g *Container) StringIndent(prefix string, indent string) string {
|
||||
return string(g.BytesIndent(prefix, indent))
|
||||
}
|
||||
|
||||
// EncodeOpt is a functional option for the EncodeJSON method.
|
||||
type EncodeOpt func(e *json.Encoder)
|
||||
|
||||
// EncodeOptHTMLEscape sets the encoder to escape the JSON for html.
|
||||
func EncodeOptHTMLEscape(doEscape bool) EncodeOpt {
|
||||
return func(e *json.Encoder) {
|
||||
e.SetEscapeHTML(doEscape)
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeOptIndent sets the encoder to indent the JSON output.
|
||||
func EncodeOptIndent(prefix string, indent string) EncodeOpt {
|
||||
return func(e *json.Encoder) {
|
||||
e.SetIndent(prefix, indent)
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeJSON - Encodes the contained object back to a JSON formatted []byte
|
||||
// using a variant list of modifier functions for the encoder being used.
|
||||
// Functions for modifying the output are prefixed with EncodeOpt, e.g.
|
||||
// EncodeOptHTMLEscape.
|
||||
func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte {
|
||||
var b bytes.Buffer
|
||||
encoder := json.NewEncoder(&b)
|
||||
encoder.SetEscapeHTML(false) // Do not escape by default.
|
||||
for _, opt := range encodeOpts {
|
||||
opt(encoder)
|
||||
}
|
||||
if err := encoder.Encode(g.object); err != nil {
|
||||
return []byte("{}")
|
||||
}
|
||||
result := b.Bytes()
|
||||
if len(result) > 0 {
|
||||
result = result[:len(result)-1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// New - Create a new gabs JSON object.
|
||||
func New() *Container {
|
||||
return &Container{map[string]interface{}{}}
|
||||
}
|
||||
|
||||
// Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object.
|
||||
func Consume(root interface{}) (*Container, error) {
|
||||
return &Container{root}, nil
|
||||
}
|
||||
|
||||
// ParseJSON - Convert a string into a representation of the parsed JSON.
|
||||
func ParseJSON(sample []byte) (*Container, error) {
|
||||
var gabs Container
|
||||
|
||||
if err := json.Unmarshal(sample, &gabs.object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gabs, nil
|
||||
}
|
||||
|
||||
// ParseJSONDecoder - Convert a json.Decoder into a representation of the parsed JSON.
|
||||
func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
|
||||
var gabs Container
|
||||
|
||||
if err := decoder.Decode(&gabs.object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gabs, nil
|
||||
}
|
||||
|
||||
// ParseJSONFile - Read a file and convert into a representation of the parsed JSON.
|
||||
func ParseJSONFile(path string) (*Container, error) {
|
||||
if len(path) > 0 {
|
||||
cBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container, err := ParseJSON(cBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return container, nil
|
||||
}
|
||||
return nil, ErrInvalidPath
|
||||
}
|
||||
|
||||
// ParseJSONBuffer - Read the contents of a buffer into a representation of the parsed JSON.
|
||||
func ParseJSONBuffer(buffer io.Reader) (*Container, error) {
|
||||
var gabs Container
|
||||
jsonDecoder := json.NewDecoder(buffer)
|
||||
if err := jsonDecoder.Decode(&gabs.object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gabs, nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
BIN
vendor/github.com/Jeffail/gabs/gabs_logo.png
generated
vendored
Normal file
BIN
vendor/github.com/Jeffail/gabs/gabs_logo.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
24
vendor/github.com/gopackage/ddp/.gitignore
generated
vendored
Normal file
24
vendor/github.com/gopackage/ddp/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
13
vendor/github.com/gopackage/ddp/LICENSE
generated
vendored
Normal file
13
vendor/github.com/gopackage/ddp/LICENSE
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright (c) 2015, Metamech LLC.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
3
vendor/github.com/gopackage/ddp/README.md
generated
vendored
Normal file
3
vendor/github.com/gopackage/ddp/README.md
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# ddp
|
||||
|
||||
MeteorJS DDP library for Golang
|
79
vendor/github.com/gopackage/ddp/ddp.go
generated
vendored
Normal file
79
vendor/github.com/gopackage/ddp/ddp.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
// Package ddp implements the MeteorJS DDP protocol over websockets. Fallback
|
||||
// to longpolling is NOT supported (and is not planned on ever being supported
|
||||
// by this library). We will try to model the library after `net/http` - right
|
||||
// now the library is barebones and doesn't provide the pluggability of http.
|
||||
// However, that's the goal for the package eventually.
|
||||
package ddp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// debugLog is true if we should log debugging information about the connection
|
||||
var debugLog = true
|
||||
|
||||
// The main file contains common utility types.
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// idManager provides simple incrementing IDs for ddp messages.
|
||||
type idManager struct {
|
||||
// nextID is the next ID for API calls
|
||||
nextID uint64
|
||||
// idMutex is a mutex to protect ID updates
|
||||
idMutex *sync.Mutex
|
||||
}
|
||||
|
||||
// newidManager creates a new instance and sets up resources.
|
||||
func newidManager() *idManager {
|
||||
return &idManager{idMutex: new(sync.Mutex)}
|
||||
}
|
||||
|
||||
// newID issues a new ID for use in calls.
|
||||
func (id *idManager) newID() string {
|
||||
id.idMutex.Lock()
|
||||
next := id.nextID
|
||||
id.nextID++
|
||||
id.idMutex.Unlock()
|
||||
return fmt.Sprintf("%x", next)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// pingTracker tracks in-flight pings.
|
||||
type pingTracker struct {
|
||||
handler func(error)
|
||||
timeout time.Duration
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Call represents an active RPC call.
|
||||
type Call struct {
|
||||
ID string // The uuid for this method call
|
||||
ServiceMethod string // The name of the service and method to call.
|
||||
Args interface{} // The argument to the function (*struct).
|
||||
Reply interface{} // The reply from the function (*struct).
|
||||
Error error // After completion, the error status.
|
||||
Done chan *Call // Strobes when call is complete.
|
||||
Owner *Client // Client that owns the method call
|
||||
}
|
||||
|
||||
// done removes the call from any owners and strobes the done channel with itself.
|
||||
func (call *Call) done() {
|
||||
delete(call.Owner.calls, call.ID)
|
||||
select {
|
||||
case call.Done <- call:
|
||||
// ok
|
||||
default:
|
||||
// We don't want to block here. It is the caller's responsibility to make
|
||||
// sure the channel has enough buffer space. See comment in Go().
|
||||
if debugLog {
|
||||
log.Println("rpc: discarding Call reply due to insufficient Done chan capacity")
|
||||
}
|
||||
}
|
||||
}
|
654
vendor/github.com/gopackage/ddp/ddp_client.go
generated
vendored
Normal file
654
vendor/github.com/gopackage/ddp/ddp_client.go
generated
vendored
Normal file
@ -0,0 +1,654 @@
|
||||
package ddp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
DISCONNECTED = iota
|
||||
DIALING
|
||||
CONNECTING
|
||||
CONNECTED
|
||||
)
|
||||
|
||||
type ConnectionListener interface {
|
||||
Connected()
|
||||
}
|
||||
|
||||
type ConnectionNotifier interface {
|
||||
AddConnectionListener(listener ConnectionListener)
|
||||
}
|
||||
|
||||
type StatusListener interface {
|
||||
Status(status int)
|
||||
}
|
||||
|
||||
type StatusNotifier interface {
|
||||
AddStatusListener(listener StatusListener)
|
||||
}
|
||||
|
||||
// Client represents a DDP client connection. The DDP client establish a DDP
|
||||
// session and acts as a message pump for other tools.
|
||||
type Client struct {
|
||||
// HeartbeatInterval is the time between heartbeats to send
|
||||
HeartbeatInterval time.Duration
|
||||
// HeartbeatTimeout is the time for a heartbeat ping to timeout
|
||||
HeartbeatTimeout time.Duration
|
||||
// ReconnectInterval is the time between reconnections on bad connections
|
||||
ReconnectInterval time.Duration
|
||||
|
||||
// writeStats controls statistics gathering for current websocket writes.
|
||||
writeSocketStats *WriterStats
|
||||
// writeStats controls statistics gathering for overall client writes.
|
||||
writeStats *WriterStats
|
||||
// writeLog controls logging for client writes.
|
||||
writeLog *WriterLogger
|
||||
// readStats controls statistics gathering for current websocket reads.
|
||||
readSocketStats *ReaderStats
|
||||
// readStats controls statistics gathering for overall client reads.
|
||||
readStats *ReaderStats
|
||||
// readLog control logging for clietn reads.
|
||||
readLog *ReaderLogger
|
||||
// reconnects in the number of reconnections the client has made
|
||||
reconnects int64
|
||||
// pingsIn is the number of pings received from the server
|
||||
pingsIn int64
|
||||
// pingsOut is te number of pings sent by the client
|
||||
pingsOut int64
|
||||
|
||||
// session contains the DDP session token (can be used for reconnects and debugging).
|
||||
session string
|
||||
// version contains the negotiated DDP protocol version in use.
|
||||
version string
|
||||
// serverID the cluster node ID for the server we connected to
|
||||
serverID string
|
||||
// ws is the underlying websocket being used.
|
||||
ws *websocket.Conn
|
||||
// encoder is a JSON encoder to send outgoing packets to the websocket.
|
||||
encoder *json.Encoder
|
||||
// url the URL the websocket is connected to
|
||||
url string
|
||||
// origin is the origin for the websocket connection
|
||||
origin string
|
||||
// inbox is an incoming message channel
|
||||
inbox chan map[string]interface{}
|
||||
// errors is an incoming errors channel
|
||||
errors chan error
|
||||
// pingTimer is a timer for sending regular pings to the server
|
||||
pingTimer *time.Timer
|
||||
// pings tracks inflight pings based on each ping ID.
|
||||
pings map[string][]*pingTracker
|
||||
// calls tracks method invocations that are still in flight
|
||||
calls map[string]*Call
|
||||
// subs tracks active subscriptions. Map contains name->args
|
||||
subs map[string]*Call
|
||||
// collections contains all the collections currently subscribed
|
||||
collections map[string]Collection
|
||||
// connectionStatus is the current connection status of the client
|
||||
connectionStatus int
|
||||
// reconnectTimer is the timer tracking reconnections
|
||||
reconnectTimer *time.Timer
|
||||
// reconnectLock protects access to reconnection
|
||||
reconnectLock *sync.Mutex
|
||||
|
||||
// statusListeners will be informed when the connection status of the client changes
|
||||
statusListeners []StatusListener
|
||||
// connectionListeners will be informed when a connection to the server is established
|
||||
connectionListeners []ConnectionListener
|
||||
|
||||
// idManager tracks IDs for ddp messages
|
||||
idManager
|
||||
}
|
||||
|
||||
// NewClient creates a default client (using an internal websocket) to the
|
||||
// provided URL using the origin for the connection. The client will
|
||||
// automatically connect, upgrade to a websocket, and establish a DDP
|
||||
// connection session before returning the client. The client will
|
||||
// automatically and internally handle heartbeats and reconnects.
|
||||
//
|
||||
// TBD create an option to use an external websocket (aka htt.Transport)
|
||||
// TBD create an option to substitute heartbeat and reconnect behavior (aka http.Tranport)
|
||||
// TBD create an option to hijack the connection (aka http.Hijacker)
|
||||
// TBD create profiling features (aka net/http/pprof)
|
||||
func NewClient(url, origin string) *Client {
|
||||
c := &Client{
|
||||
HeartbeatInterval: time.Minute, // Meteor impl default + 10 (we ping last)
|
||||
HeartbeatTimeout: 15 * time.Second, // Meteor impl default
|
||||
ReconnectInterval: 5 * time.Second,
|
||||
collections: map[string]Collection{},
|
||||
url: url,
|
||||
origin: origin,
|
||||
inbox: make(chan map[string]interface{}, 100),
|
||||
errors: make(chan error, 100),
|
||||
pings: map[string][]*pingTracker{},
|
||||
calls: map[string]*Call{},
|
||||
subs: map[string]*Call{},
|
||||
connectionStatus: DISCONNECTED,
|
||||
reconnectLock: &sync.Mutex{},
|
||||
|
||||
// Stats
|
||||
writeSocketStats: NewWriterStats(nil),
|
||||
writeStats: NewWriterStats(nil),
|
||||
readSocketStats: NewReaderStats(nil),
|
||||
readStats: NewReaderStats(nil),
|
||||
|
||||
// Loggers
|
||||
writeLog: NewWriterTextLogger(nil),
|
||||
readLog: NewReaderTextLogger(nil),
|
||||
|
||||
idManager: *newidManager(),
|
||||
}
|
||||
c.encoder = json.NewEncoder(c.writeStats)
|
||||
c.SetSocketLogActive(false)
|
||||
|
||||
// We spin off an inbox processing goroutine
|
||||
go c.inboxManager()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Session returns the negotiated session token for the connection.
|
||||
func (c *Client) Session() string {
|
||||
return c.session
|
||||
}
|
||||
|
||||
// Version returns the negotiated protocol version in use by the client.
|
||||
func (c *Client) Version() string {
|
||||
return c.version
|
||||
}
|
||||
|
||||
// AddStatusListener in order to receive status change updates.
|
||||
func (c *Client) AddStatusListener(listener StatusListener) {
|
||||
c.statusListeners = append(c.statusListeners, listener)
|
||||
}
|
||||
|
||||
// AddConnectionListener in order to receive connection updates.
|
||||
func (c *Client) AddConnectionListener(listener ConnectionListener) {
|
||||
c.connectionListeners = append(c.connectionListeners, listener)
|
||||
}
|
||||
|
||||
// status updates all status listeners with the new client status.
|
||||
func (c *Client) status(status int) {
|
||||
if c.connectionStatus == status {
|
||||
return
|
||||
}
|
||||
c.connectionStatus = status
|
||||
for _, listener := range c.statusListeners {
|
||||
listener.Status(status)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect attempts to connect the client to the server.
|
||||
func (c *Client) Connect() error {
|
||||
c.status(DIALING)
|
||||
ws, err := websocket.Dial(c.url, "", c.origin)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
log.Println("Dial error", err)
|
||||
c.reconnectLater()
|
||||
return err
|
||||
}
|
||||
// Start DDP connection
|
||||
c.start(ws, NewConnect())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reconnect attempts to reconnect the client to the server on the existing
|
||||
// DDP session.
|
||||
//
|
||||
// TODO needs a reconnect backoff so we don't trash a down server
|
||||
// TODO reconnect should not allow more reconnects while a reconnection is already in progress.
|
||||
func (c *Client) Reconnect() {
|
||||
func() {
|
||||
c.reconnectLock.Lock()
|
||||
defer c.reconnectLock.Unlock()
|
||||
if c.reconnectTimer != nil {
|
||||
c.reconnectTimer.Stop()
|
||||
c.reconnectTimer = nil
|
||||
}
|
||||
}()
|
||||
|
||||
c.Close()
|
||||
|
||||
c.reconnects++
|
||||
|
||||
// Reconnect
|
||||
c.status(DIALING)
|
||||
ws, err := websocket.Dial(c.url, "", c.origin)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
log.Println("Dial error", err)
|
||||
c.reconnectLater()
|
||||
return
|
||||
}
|
||||
|
||||
c.start(ws, NewReconnect(c.session))
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// We resume inflight or ongoing subscriptions - we don't have to wait
|
||||
// for connection confirmation (messages can be pipelined).
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
// Send calls that haven't been confirmed - may not have been sent
|
||||
// and effects should be idempotent
|
||||
for _, call := range c.calls {
|
||||
c.Send(NewMethod(call.ID, call.ServiceMethod, call.Args.([]interface{})))
|
||||
}
|
||||
|
||||
// Resend subscriptions and patch up collections
|
||||
for _, sub := range c.subs {
|
||||
c.Send(NewSub(sub.ID, sub.ServiceMethod, sub.Args.([]interface{})))
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe subscribes to data updates.
|
||||
func (c *Client) Subscribe(subName string, done chan *Call, args ...interface{}) *Call {
|
||||
|
||||
if args == nil {
|
||||
args = []interface{}{}
|
||||
}
|
||||
call := new(Call)
|
||||
call.ID = c.newID()
|
||||
call.ServiceMethod = subName
|
||||
call.Args = args
|
||||
call.Owner = c
|
||||
|
||||
if done == nil {
|
||||
done = make(chan *Call, 10) // buffered.
|
||||
} else {
|
||||
// If caller passes done != nil, it must arrange that
|
||||
// done has enough buffer for the number of simultaneous
|
||||
// RPCs that will be using that channel. If the channel
|
||||
// is totally unbuffered, it's best not to run at all.
|
||||
if cap(done) == 0 {
|
||||
log.Panic("ddp.rpc: done channel is unbuffered")
|
||||
}
|
||||
}
|
||||
call.Done = done
|
||||
c.subs[call.ID] = call
|
||||
|
||||
// Save this subscription to the client so we can reconnect
|
||||
subArgs := make([]interface{}, len(args))
|
||||
copy(subArgs, args)
|
||||
|
||||
c.Send(NewSub(call.ID, subName, args))
|
||||
|
||||
return call
|
||||
}
|
||||
|
||||
// Sub sends a synchronous subscription request to the server.
|
||||
func (c *Client) Sub(subName string, args ...interface{}) error {
|
||||
call := <-c.Subscribe(subName, make(chan *Call, 1), args...).Done
|
||||
return call.Error
|
||||
}
|
||||
|
||||
// Go invokes the function asynchronously. It returns the Call structure representing
|
||||
// the invocation. The done channel will signal when the call is complete by returning
|
||||
// the same Call object. If done is nil, Go will allocate a new channel.
|
||||
// If non-nil, done must be buffered or Go will deliberately crash.
|
||||
//
|
||||
// Go and Call are modeled after the standard `net/rpc` package versions.
|
||||
func (c *Client) Go(serviceMethod string, done chan *Call, args ...interface{}) *Call {
|
||||
|
||||
if args == nil {
|
||||
args = []interface{}{}
|
||||
}
|
||||
call := new(Call)
|
||||
call.ID = c.newID()
|
||||
call.ServiceMethod = serviceMethod
|
||||
call.Args = args
|
||||
call.Owner = c
|
||||
if done == nil {
|
||||
done = make(chan *Call, 10) // buffered.
|
||||
} else {
|
||||
// If caller passes done != nil, it must arrange that
|
||||
// done has enough buffer for the number of simultaneous
|
||||
// RPCs that will be using that channel. If the channel
|
||||
// is totally unbuffered, it's best not to run at all.
|
||||
if cap(done) == 0 {
|
||||
log.Panic("ddp.rpc: done channel is unbuffered")
|
||||
}
|
||||
}
|
||||
call.Done = done
|
||||
c.calls[call.ID] = call
|
||||
|
||||
c.Send(NewMethod(call.ID, serviceMethod, args))
|
||||
|
||||
return call
|
||||
}
|
||||
|
||||
// Call invokes the named function, waits for it to complete, and returns its error status.
|
||||
func (c *Client) Call(serviceMethod string, args ...interface{}) (interface{}, error) {
|
||||
call := <-c.Go(serviceMethod, make(chan *Call, 1), args...).Done
|
||||
return call.Reply, call.Error
|
||||
}
|
||||
|
||||
// Ping sends a heartbeat signal to the server. The Ping doesn't look for
|
||||
// a response but may trigger the connection to reconnect if the ping timesout.
|
||||
// This is primarily useful for reviving an unresponsive Client connection.
|
||||
func (c *Client) Ping() {
|
||||
c.PingPong(c.newID(), c.HeartbeatTimeout, func(err error) {
|
||||
if err != nil {
|
||||
// Is there anything else we should or can do?
|
||||
c.reconnectLater()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PingPong sends a heartbeat signal to the server and calls the provided
|
||||
// function when a pong is received. An optional id can be sent to help
|
||||
// track the responses - or an empty string can be used. It is the
|
||||
// responsibility of the caller to respond to any errors that may occur.
|
||||
func (c *Client) PingPong(id string, timeout time.Duration, handler func(error)) {
|
||||
err := c.Send(NewPing(id))
|
||||
if err != nil {
|
||||
handler(err)
|
||||
return
|
||||
}
|
||||
c.pingsOut++
|
||||
pings, ok := c.pings[id]
|
||||
if !ok {
|
||||
pings = make([]*pingTracker, 0, 5)
|
||||
}
|
||||
tracker := &pingTracker{handler: handler, timeout: timeout, timer: time.AfterFunc(timeout, func() {
|
||||
handler(fmt.Errorf("ping timeout"))
|
||||
})}
|
||||
c.pings[id] = append(pings, tracker)
|
||||
}
|
||||
|
||||
// Send transmits messages to the server. The msg parameter must be json
|
||||
// encoder compatible.
|
||||
func (c *Client) Send(msg interface{}) error {
|
||||
return c.encoder.Encode(msg)
|
||||
}
|
||||
|
||||
// Close implements the io.Closer interface.
|
||||
func (c *Client) Close() {
|
||||
// Shutdown out all outstanding pings
|
||||
if c.pingTimer != nil {
|
||||
c.pingTimer.Stop()
|
||||
c.pingTimer = nil
|
||||
}
|
||||
|
||||
// Close websocket
|
||||
if c.ws != nil {
|
||||
c.ws.Close()
|
||||
c.ws = nil
|
||||
}
|
||||
for _, collection := range c.collections {
|
||||
collection.reset()
|
||||
}
|
||||
c.status(DISCONNECTED)
|
||||
}
|
||||
|
||||
// ResetStats resets the statistics for the client.
|
||||
func (c *Client) ResetStats() {
|
||||
c.readSocketStats.Reset()
|
||||
c.readStats.Reset()
|
||||
c.writeSocketStats.Reset()
|
||||
c.writeStats.Reset()
|
||||
c.reconnects = 0
|
||||
c.pingsIn = 0
|
||||
c.pingsOut = 0
|
||||
}
|
||||
|
||||
// Stats returns the read and write statistics of the client.
|
||||
func (c *Client) Stats() *ClientStats {
|
||||
return &ClientStats{
|
||||
Reads: c.readSocketStats.Snapshot(),
|
||||
TotalReads: c.readStats.Snapshot(),
|
||||
Writes: c.writeSocketStats.Snapshot(),
|
||||
TotalWrites: c.writeStats.Snapshot(),
|
||||
Reconnects: c.reconnects,
|
||||
PingsSent: c.pingsOut,
|
||||
PingsRecv: c.pingsIn,
|
||||
}
|
||||
}
|
||||
|
||||
// SocketLogActive returns the current logging status for the socket.
|
||||
func (c *Client) SocketLogActive() bool {
|
||||
return c.writeLog.Active
|
||||
}
|
||||
|
||||
// SetSocketLogActive to true to enable logging of raw socket data.
|
||||
func (c *Client) SetSocketLogActive(active bool) {
|
||||
c.writeLog.Active = active
|
||||
c.readLog.Active = active
|
||||
}
|
||||
|
||||
// CollectionByName retrieves a collection by it's name.
|
||||
func (c *Client) CollectionByName(name string) Collection {
|
||||
collection, ok := c.collections[name]
|
||||
if !ok {
|
||||
collection = NewCollection(name)
|
||||
c.collections[name] = collection
|
||||
}
|
||||
return collection
|
||||
}
|
||||
|
||||
// CollectionStats returns a snapshot of statistics for the currently known collections.
|
||||
func (c *Client) CollectionStats() []CollectionStats {
|
||||
stats := make([]CollectionStats, 0, len(c.collections))
|
||||
for name, collection := range c.collections {
|
||||
stats = append(stats, CollectionStats{Name: name, Count: len(collection.FindAll())})
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
// start starts a new client connection on the provided websocket
|
||||
func (c *Client) start(ws *websocket.Conn, connect *Connect) {
|
||||
|
||||
c.status(CONNECTING)
|
||||
|
||||
c.ws = ws
|
||||
c.writeLog.SetWriter(ws)
|
||||
c.writeSocketStats = NewWriterStats(c.writeLog)
|
||||
c.writeStats.SetWriter(c.writeSocketStats)
|
||||
c.readLog.SetReader(ws)
|
||||
c.readSocketStats = NewReaderStats(c.readLog)
|
||||
c.readStats.SetReader(c.readSocketStats)
|
||||
|
||||
// We spin off an inbox stuffing goroutine
|
||||
go c.inboxWorker(c.readStats)
|
||||
|
||||
c.Send(connect)
|
||||
}
|
||||
|
||||
// inboxManager pulls messages from the inbox and routes them to appropriate
|
||||
// handlers.
|
||||
func (c *Client) inboxManager() {
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.inbox:
|
||||
// Message!
|
||||
//log.Println("Got message", msg)
|
||||
mtype, ok := msg["msg"]
|
||||
if ok {
|
||||
switch mtype.(string) {
|
||||
// Connection management
|
||||
case "connected":
|
||||
c.status(CONNECTED)
|
||||
for _, collection := range c.collections {
|
||||
collection.init()
|
||||
}
|
||||
c.version = "1" // Currently the only version we support
|
||||
c.session = msg["session"].(string)
|
||||
// Start automatic heartbeats
|
||||
c.pingTimer = time.AfterFunc(c.HeartbeatInterval, func() {
|
||||
c.Ping()
|
||||
c.pingTimer.Reset(c.HeartbeatInterval)
|
||||
})
|
||||
// Notify connection listeners
|
||||
for _, listener := range c.connectionListeners {
|
||||
go listener.Connected()
|
||||
}
|
||||
case "failed":
|
||||
log.Fatalf("IM Failed to connect, we support version 1 but server supports %s", msg["version"])
|
||||
|
||||
// Heartbeats
|
||||
case "ping":
|
||||
// We received a ping - need to respond with a pong
|
||||
id, ok := msg["id"]
|
||||
if ok {
|
||||
c.Send(NewPong(id.(string)))
|
||||
} else {
|
||||
c.Send(NewPong(""))
|
||||
}
|
||||
c.pingsIn++
|
||||
case "pong":
|
||||
// We received a pong - we can clear the ping tracker and call its handler
|
||||
id, ok := msg["id"]
|
||||
var key string
|
||||
if ok {
|
||||
key = id.(string)
|
||||
}
|
||||
pings, ok := c.pings[key]
|
||||
if ok && len(pings) > 0 {
|
||||
ping := pings[0]
|
||||
pings = pings[1:]
|
||||
if len(key) == 0 || len(pings) > 0 {
|
||||
c.pings[key] = pings
|
||||
}
|
||||
ping.timer.Stop()
|
||||
ping.handler(nil)
|
||||
}
|
||||
|
||||
// Live Data
|
||||
case "nosub":
|
||||
log.Println("Subscription returned a nosub error", msg)
|
||||
// Clear related subscriptions
|
||||
sub, ok := msg["id"]
|
||||
if ok {
|
||||
id := sub.(string)
|
||||
runningSub := c.subs[id]
|
||||
|
||||
if runningSub != nil {
|
||||
runningSub.Error = errors.New("Subscription returned a nosub error")
|
||||
runningSub.done()
|
||||
delete(c.subs, id)
|
||||
}
|
||||
}
|
||||
case "ready":
|
||||
// Run 'done' callbacks on all ready subscriptions
|
||||
subs, ok := msg["subs"]
|
||||
if ok {
|
||||
for _, sub := range subs.([]interface{}) {
|
||||
call, ok := c.subs[sub.(string)]
|
||||
if ok {
|
||||
call.done()
|
||||
}
|
||||
}
|
||||
}
|
||||
case "added":
|
||||
c.collectionBy(msg).added(msg)
|
||||
case "changed":
|
||||
c.collectionBy(msg).changed(msg)
|
||||
case "removed":
|
||||
c.collectionBy(msg).removed(msg)
|
||||
case "addedBefore":
|
||||
c.collectionBy(msg).addedBefore(msg)
|
||||
case "movedBefore":
|
||||
c.collectionBy(msg).movedBefore(msg)
|
||||
|
||||
// RPC
|
||||
case "result":
|
||||
id, ok := msg["id"]
|
||||
if ok {
|
||||
call := c.calls[id.(string)]
|
||||
delete(c.calls, id.(string))
|
||||
e, ok := msg["error"]
|
||||
if ok {
|
||||
txt, _ := json.Marshal(e)
|
||||
call.Error = fmt.Errorf(string(txt))
|
||||
call.Reply = e
|
||||
} else {
|
||||
call.Reply = msg["result"]
|
||||
}
|
||||
call.done()
|
||||
}
|
||||
case "updated":
|
||||
// We currently don't do anything with updated status
|
||||
|
||||
default:
|
||||
// Ignore?
|
||||
log.Println("Server sent unexpected message", msg)
|
||||
}
|
||||
} else {
|
||||
// Current Meteor server sends an undocumented DDP message
|
||||
// (looks like clustering "hint"). We will register and
|
||||
// ignore rather than log an error.
|
||||
serverID, ok := msg["server_id"]
|
||||
if ok {
|
||||
switch ID := serverID.(type) {
|
||||
case string:
|
||||
c.serverID = ID
|
||||
default:
|
||||
log.Println("Server cluster node", serverID)
|
||||
}
|
||||
} else {
|
||||
log.Println("Server sent message with no `msg` field", msg)
|
||||
}
|
||||
}
|
||||
case err := <-c.errors:
|
||||
log.Println("Websocket error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) collectionBy(msg map[string]interface{}) Collection {
|
||||
n, ok := msg["collection"]
|
||||
if !ok {
|
||||
return NewMockCollection()
|
||||
}
|
||||
switch name := n.(type) {
|
||||
case string:
|
||||
return c.CollectionByName(name)
|
||||
default:
|
||||
return NewMockCollection()
|
||||
}
|
||||
}
|
||||
|
||||
// inboxWorker pulls messages from a websocket, decodes JSON packets, and
|
||||
// stuffs them into a message channel.
|
||||
func (c *Client) inboxWorker(ws io.Reader) {
|
||||
dec := json.NewDecoder(ws)
|
||||
for {
|
||||
var event interface{}
|
||||
|
||||
if err := dec.Decode(&event); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
c.errors <- err
|
||||
}
|
||||
if c.pingTimer != nil {
|
||||
c.pingTimer.Reset(c.HeartbeatInterval)
|
||||
}
|
||||
if event == nil {
|
||||
log.Println("Inbox worker found nil event. May be due to broken websocket. Reconnecting.")
|
||||
break
|
||||
} else {
|
||||
c.inbox <- event.(map[string]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
c.reconnectLater()
|
||||
}
|
||||
|
||||
// reconnectLater schedules a reconnect for later. We need to make sure that we don't
|
||||
// block, and that we don't reconnect more frequently than once every c.ReconnectInterval
|
||||
func (c *Client) reconnectLater() {
|
||||
c.Close()
|
||||
c.reconnectLock.Lock()
|
||||
defer c.reconnectLock.Unlock()
|
||||
if c.reconnectTimer == nil {
|
||||
c.reconnectTimer = time.AfterFunc(c.ReconnectInterval, c.Reconnect)
|
||||
}
|
||||
}
|
245
vendor/github.com/gopackage/ddp/ddp_collection.go
generated
vendored
Normal file
245
vendor/github.com/gopackage/ddp/ddp_collection.go
generated
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
package ddp
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Collection
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type Update map[string]interface{}
|
||||
type UpdateListener interface {
|
||||
CollectionUpdate(collection, operation, id string, doc Update)
|
||||
}
|
||||
|
||||
// Collection managed cached collection data sent from the server in a
|
||||
// livedata subscription.
|
||||
//
|
||||
// It would be great to build an entire mongo compatible local store (minimongo)
|
||||
type Collection interface {
|
||||
|
||||
// FindOne queries objects and returns the first match.
|
||||
FindOne(id string) Update
|
||||
// FindAll returns a map of all items in the cache - this is a hack
|
||||
// until we have time to build out a real minimongo interface.
|
||||
FindAll() map[string]Update
|
||||
// AddUpdateListener adds a channel that receives update messages.
|
||||
AddUpdateListener(listener UpdateListener)
|
||||
|
||||
// livedata updates
|
||||
added(msg Update)
|
||||
changed(msg Update)
|
||||
removed(msg Update)
|
||||
addedBefore(msg Update)
|
||||
movedBefore(msg Update)
|
||||
init() // init informs the collection that the connection to the server has begun/resumed
|
||||
reset() // reset informs the collection that the connection to the server has been lost
|
||||
}
|
||||
|
||||
// NewMockCollection creates an empty collection that does nothing.
|
||||
func NewMockCollection() Collection {
|
||||
return &MockCache{}
|
||||
}
|
||||
|
||||
// NewCollection creates a new collection - always KeyCache.
|
||||
func NewCollection(name string) Collection {
|
||||
return &KeyCache{name, make(map[string]Update), nil}
|
||||
}
|
||||
|
||||
// KeyCache caches items keyed on unique ID.
|
||||
type KeyCache struct {
|
||||
// The name of the collection
|
||||
Name string
|
||||
// items contains collection items by ID
|
||||
items map[string]Update
|
||||
// listeners contains all the listeners that should be notified of collection updates.
|
||||
listeners []UpdateListener
|
||||
// TODO(badslug): do we need to protect from multiple threads
|
||||
}
|
||||
|
||||
func (c *KeyCache) added(msg Update) {
|
||||
id, fields := parseUpdate(msg)
|
||||
if fields != nil {
|
||||
c.items[id] = fields
|
||||
c.notify("create", id, fields)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *KeyCache) changed(msg Update) {
|
||||
id, fields := parseUpdate(msg)
|
||||
if fields != nil {
|
||||
item, ok := c.items[id]
|
||||
if ok {
|
||||
for key, value := range fields {
|
||||
item[key] = value
|
||||
}
|
||||
c.items[id] = item
|
||||
c.notify("update", id, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *KeyCache) removed(msg Update) {
|
||||
id, _ := parseUpdate(msg)
|
||||
if len(id) > 0 {
|
||||
delete(c.items, id)
|
||||
c.notify("remove", id, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *KeyCache) addedBefore(msg Update) {
|
||||
// for keyed cache, ordered commands are a noop
|
||||
}
|
||||
|
||||
func (c *KeyCache) movedBefore(msg Update) {
|
||||
// for keyed cache, ordered commands are a noop
|
||||
}
|
||||
|
||||
// init prepares the collection for data updates (called when a new connection is
|
||||
// made or a connection/session is resumed).
|
||||
func (c *KeyCache) init() {
|
||||
// TODO start to patch up the current data with fresh server state
|
||||
}
|
||||
|
||||
func (c *KeyCache) reset() {
|
||||
// TODO we should mark the collection but maintain it's contents and then
|
||||
// patch up the current contents with the new contents when we receive them.
|
||||
//c.items = nil
|
||||
c.notify("reset", "", nil)
|
||||
}
|
||||
|
||||
// notify sends a Update to all UpdateListener's which should never block.
|
||||
func (c *KeyCache) notify(operation, id string, doc Update) {
|
||||
for _, listener := range c.listeners {
|
||||
listener.CollectionUpdate(c.Name, operation, id, doc)
|
||||
}
|
||||
}
|
||||
|
||||
// FindOne returns the item with matching id.
|
||||
func (c *KeyCache) FindOne(id string) Update {
|
||||
return c.items[id]
|
||||
}
|
||||
|
||||
// FindAll returns a dump of all items in the collection
|
||||
func (c *KeyCache) FindAll() map[string]Update {
|
||||
return c.items
|
||||
}
|
||||
|
||||
// AddUpdateListener adds a listener for changes on a collection.
|
||||
func (c *KeyCache) AddUpdateListener(listener UpdateListener) {
|
||||
c.listeners = append(c.listeners, listener)
|
||||
}
|
||||
|
||||
// OrderedCache caches items based on list order.
|
||||
// This is a placeholder, currently not implemented as the Meteor server
|
||||
// does not transmit ordered collections over DDP yet.
|
||||
type OrderedCache struct {
|
||||
// ranks contains ordered collection items for ordered collections
|
||||
items []interface{}
|
||||
}
|
||||
|
||||
func (c *OrderedCache) added(msg Update) {
|
||||
// for ordered cache, key commands are a noop
|
||||
}
|
||||
|
||||
func (c *OrderedCache) changed(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *OrderedCache) removed(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *OrderedCache) addedBefore(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *OrderedCache) movedBefore(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *OrderedCache) init() {
|
||||
|
||||
}
|
||||
|
||||
func (c *OrderedCache) reset() {
|
||||
|
||||
}
|
||||
|
||||
// FindOne returns the item with matching id.
|
||||
func (c *OrderedCache) FindOne(id string) Update {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindAll returns a dump of all items in the collection
|
||||
func (c *OrderedCache) FindAll() map[string]Update {
|
||||
return map[string]Update{}
|
||||
}
|
||||
|
||||
// AddUpdateListener does nothing.
|
||||
func (c *OrderedCache) AddUpdateListener(ch UpdateListener) {
|
||||
}
|
||||
|
||||
// MockCache implements the Collection interface but does nothing with the data.
|
||||
type MockCache struct {
|
||||
}
|
||||
|
||||
func (c *MockCache) added(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *MockCache) changed(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *MockCache) removed(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *MockCache) addedBefore(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *MockCache) movedBefore(msg Update) {
|
||||
|
||||
}
|
||||
|
||||
func (c *MockCache) init() {
|
||||
|
||||
}
|
||||
|
||||
func (c *MockCache) reset() {
|
||||
|
||||
}
|
||||
|
||||
// FindOne returns the item with matching id.
|
||||
func (c *MockCache) FindOne(id string) Update {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindAll returns a dump of all items in the collection
|
||||
func (c *MockCache) FindAll() map[string]Update {
|
||||
return map[string]Update{}
|
||||
}
|
||||
|
||||
// AddUpdateListener does nothing.
|
||||
func (c *MockCache) AddUpdateListener(ch UpdateListener) {
|
||||
}
|
||||
|
||||
// parseUpdate returns the ID and fields from a DDP Update document.
|
||||
func parseUpdate(up Update) (ID string, Fields Update) {
|
||||
key, ok := up["id"]
|
||||
if ok {
|
||||
switch id := key.(type) {
|
||||
case string:
|
||||
updates, ok := up["fields"]
|
||||
if ok {
|
||||
switch fields := updates.(type) {
|
||||
case map[string]interface{}:
|
||||
return id, Update(fields)
|
||||
default:
|
||||
// Don't know what to do...
|
||||
}
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
217
vendor/github.com/gopackage/ddp/ddp_ejson.go
generated
vendored
Normal file
217
vendor/github.com/gopackage/ddp/ddp_ejson.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
package ddp
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// EJSON document interface
|
||||
// ----------------------------------------------------------------------
|
||||
// https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md#appendix-ejson
|
||||
|
||||
// Doc provides hides the complexity of ejson documents.
|
||||
type Doc struct {
|
||||
root interface{}
|
||||
}
|
||||
|
||||
// NewDoc creates a new document from a generic json parsed document.
|
||||
func NewDoc(in interface{}) *Doc {
|
||||
doc := &Doc{in}
|
||||
return doc
|
||||
}
|
||||
|
||||
// Map locates a map[string]interface{} - json object - at a path
|
||||
// or returns nil if not found.
|
||||
func (d *Doc) Map(path string) map[string]interface{} {
|
||||
item := d.Item(path)
|
||||
if item != nil {
|
||||
switch m := item.(type) {
|
||||
case map[string]interface{}:
|
||||
return m
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Array locates an []interface{} - json array - at a path
|
||||
// or returns nil if not found.
|
||||
func (d *Doc) Array(path string) []interface{} {
|
||||
item := d.Item(path)
|
||||
if item != nil {
|
||||
switch m := item.(type) {
|
||||
case []interface{}:
|
||||
return m
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringArray locates an []string - json array of strings - at a path
|
||||
// or returns nil if not found. The string array will contain all string values
|
||||
// in the array and skip any non-string entries.
|
||||
func (d *Doc) StringArray(path string) []string {
|
||||
item := d.Item(path)
|
||||
if item != nil {
|
||||
switch m := item.(type) {
|
||||
case []interface{}:
|
||||
items := []string{}
|
||||
for _, item := range m {
|
||||
switch val := item.(type) {
|
||||
case string:
|
||||
items = append(items, val)
|
||||
}
|
||||
}
|
||||
return items
|
||||
case []string:
|
||||
return m
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string value located at the path or an empty string if not found.
|
||||
func (d *Doc) String(path string) string {
|
||||
item := d.Item(path)
|
||||
if item != nil {
|
||||
switch m := item.(type) {
|
||||
case string:
|
||||
return m
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Bool returns a boolean value located at the path or false if not found.
|
||||
func (d *Doc) Bool(path string) bool {
|
||||
item := d.Item(path)
|
||||
if item != nil {
|
||||
switch m := item.(type) {
|
||||
case bool:
|
||||
return m
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Float returns a float64 value located at the path or zero if not found.
|
||||
func (d *Doc) Float(path string) float64 {
|
||||
item := d.Item(path)
|
||||
if item != nil {
|
||||
switch m := item.(type) {
|
||||
case float64:
|
||||
return m
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Time returns a time value located at the path or nil if not found.
|
||||
func (d *Doc) Time(path string) time.Time {
|
||||
ticks := d.Float(path + ".$date")
|
||||
var t time.Time
|
||||
if ticks > 0 {
|
||||
sec := int64(ticks / 1000)
|
||||
t = time.Unix(int64(sec), 0)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Item locates a "raw" item at the provided path, returning
|
||||
// the item found or nil if not found.
|
||||
func (d *Doc) Item(path string) interface{} {
|
||||
item := d.root
|
||||
steps := strings.Split(path, ".")
|
||||
for _, step := range steps {
|
||||
// This is an intermediate step - we must be in a map
|
||||
switch m := item.(type) {
|
||||
case map[string]interface{}:
|
||||
item = m[step]
|
||||
case Update:
|
||||
item = m[step]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// Set a value for a path. Intermediate items are created as necessary.
|
||||
func (d *Doc) Set(path string, value interface{}) {
|
||||
item := d.root
|
||||
steps := strings.Split(path, ".")
|
||||
last := steps[len(steps)-1]
|
||||
steps = steps[:len(steps)-1]
|
||||
for _, step := range steps {
|
||||
// This is an intermediate step - we must be in a map
|
||||
switch m := item.(type) {
|
||||
case map[string]interface{}:
|
||||
item = m[step]
|
||||
if item == nil {
|
||||
item = map[string]interface{}{}
|
||||
m[step] = item
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
// Item is now the last map so we just set the value
|
||||
switch m := item.(type) {
|
||||
case map[string]interface{}:
|
||||
m[last] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Accounts password login support
|
||||
type Login struct {
|
||||
User *User `json:"user"`
|
||||
Password *Password `json:"password"`
|
||||
}
|
||||
|
||||
func NewEmailLogin(email, pass string) *Login {
|
||||
return &Login{User: &User{Email: email}, Password: NewPassword(pass)}
|
||||
}
|
||||
|
||||
func NewUsernameLogin(user, pass string) *Login {
|
||||
return &Login{User: &User{Username: user}, Password: NewPassword(pass)}
|
||||
}
|
||||
|
||||
type LoginResume struct {
|
||||
Token string `json:"resume"`
|
||||
}
|
||||
|
||||
func NewLoginResume(token string) *LoginResume {
|
||||
return &LoginResume{Token: token}
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
type Password struct {
|
||||
Digest string `json:"digest"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
}
|
||||
|
||||
func NewPassword(pass string) *Password {
|
||||
sha := sha256.New()
|
||||
io.WriteString(sha, pass)
|
||||
digest := sha.Sum(nil)
|
||||
return &Password{Digest: hex.EncodeToString(digest), Algorithm: "sha-256"}
|
||||
}
|
82
vendor/github.com/gopackage/ddp/ddp_messages.go
generated
vendored
Normal file
82
vendor/github.com/gopackage/ddp/ddp_messages.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
package ddp
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// DDP Messages
|
||||
//
|
||||
// Go structs representing DDP raw messages ready for JSON
|
||||
// encoding.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
// Message contains the common fields that all DDP messages use.
|
||||
type Message struct {
|
||||
Type string `json:"msg"`
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
// Connect represents a DDP connect message.
|
||||
type Connect struct {
|
||||
Message
|
||||
Version string `json:"version"`
|
||||
Support []string `json:"support"`
|
||||
Session string `json:"session,omitempty"`
|
||||
}
|
||||
|
||||
// NewConnect creates a new connect message
|
||||
func NewConnect() *Connect {
|
||||
return &Connect{Message: Message{Type: "connect"}, Version: "1", Support: []string{"1"}}
|
||||
}
|
||||
|
||||
// NewReconnect creates a new connect message with a session ID to resume.
|
||||
func NewReconnect(session string) *Connect {
|
||||
c := NewConnect()
|
||||
c.Session = session
|
||||
return c
|
||||
}
|
||||
|
||||
// Ping represents a DDP ping message.
|
||||
type Ping Message
|
||||
|
||||
// NewPing creates a new ping message with optional ID.
|
||||
func NewPing(id string) *Ping {
|
||||
return &Ping{Type: "ping", ID: id}
|
||||
}
|
||||
|
||||
// Pong represents a DDP pong message.
|
||||
type Pong Message
|
||||
|
||||
// NewPong creates a new pong message with optional ID.
|
||||
func NewPong(id string) *Pong {
|
||||
return &Pong{Type: "pong", ID: id}
|
||||
}
|
||||
|
||||
// Method is used to send a remote procedure call to the server.
|
||||
type Method struct {
|
||||
Message
|
||||
ServiceMethod string `json:"method"`
|
||||
Args []interface{} `json:"params"`
|
||||
}
|
||||
|
||||
// NewMethod creates a new method invocation object.
|
||||
func NewMethod(id, serviceMethod string, args []interface{}) *Method {
|
||||
return &Method{
|
||||
Message: Message{Type: "method", ID: id},
|
||||
ServiceMethod: serviceMethod,
|
||||
Args: args,
|
||||
}
|
||||
}
|
||||
|
||||
// Sub is used to send a subscription request to the server.
|
||||
type Sub struct {
|
||||
Message
|
||||
SubName string `json:"name"`
|
||||
Args []interface{} `json:"params"`
|
||||
}
|
||||
|
||||
// NewSub creates a new sub object.
|
||||
func NewSub(id, subName string, args []interface{}) *Sub {
|
||||
return &Sub{
|
||||
Message: Message{Type: "sub", ID: id},
|
||||
SubName: subName,
|
||||
Args: args,
|
||||
}
|
||||
}
|
321
vendor/github.com/gopackage/ddp/ddp_stats.go
generated
vendored
Normal file
321
vendor/github.com/gopackage/ddp/ddp_stats.go
generated
vendored
Normal file
@ -0,0 +1,321 @@
|
||||
package ddp
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Gather statistics about a DDP connection.
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// io utilities
|
||||
//
|
||||
// This is generic - should be moved into a stand alone lib
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// ReaderProxy provides common tooling for structs that manage an io.Reader.
|
||||
type ReaderProxy struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// NewReaderProxy creates a new proxy for the provided reader.
|
||||
func NewReaderProxy(reader io.Reader) *ReaderProxy {
|
||||
return &ReaderProxy{reader}
|
||||
}
|
||||
|
||||
// SetReader sets the reader on the proxy.
|
||||
func (r *ReaderProxy) SetReader(reader io.Reader) {
|
||||
r.reader = reader
|
||||
}
|
||||
|
||||
// WriterProxy provides common tooling for structs that manage an io.Writer.
|
||||
type WriterProxy struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewWriterProxy creates a new proxy for the provided writer.
|
||||
func NewWriterProxy(writer io.Writer) *WriterProxy {
|
||||
return &WriterProxy{writer}
|
||||
}
|
||||
|
||||
// SetWriter sets the writer on the proxy.
|
||||
func (w *WriterProxy) SetWriter(writer io.Writer) {
|
||||
w.writer = writer
|
||||
}
|
||||
|
||||
// Logging data types
|
||||
const (
|
||||
DataByte = iota // data is raw []byte
|
||||
DataText // data is string data
|
||||
)
|
||||
|
||||
// Logger logs data from i/o sources.
|
||||
type Logger struct {
|
||||
// Active is true if the logger should be logging reads
|
||||
Active bool
|
||||
// Truncate is >0 to indicate the number of characters to truncate output
|
||||
Truncate int
|
||||
|
||||
logger *log.Logger
|
||||
dtype int
|
||||
}
|
||||
|
||||
// NewLogger creates a new i/o logger.
|
||||
func NewLogger(logger *log.Logger, active bool, dataType int, truncate int) Logger {
|
||||
return Logger{logger: logger, Active: active, dtype: dataType, Truncate: truncate}
|
||||
}
|
||||
|
||||
// Log logs the current i/o operation and returns the read and error for
|
||||
// easy call chaining.
|
||||
func (l *Logger) Log(p []byte, n int, err error) (int, error) {
|
||||
if l.Active && err == nil {
|
||||
limit := n
|
||||
truncated := false
|
||||
if l.Truncate > 0 && l.Truncate < limit {
|
||||
limit = l.Truncate
|
||||
truncated = true
|
||||
}
|
||||
switch l.dtype {
|
||||
case DataText:
|
||||
if truncated {
|
||||
l.logger.Printf("[%d] %s...", n, string(p[:limit]))
|
||||
} else {
|
||||
l.logger.Printf("[%d] %s", n, string(p[:limit]))
|
||||
}
|
||||
case DataByte:
|
||||
fallthrough
|
||||
default:
|
||||
l.logger.Println(hex.Dump(p[:limit]))
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// ReaderLogger logs data from any io.Reader.
|
||||
// ReaderLogger wraps a Reader and passes data to the actual data consumer.
|
||||
type ReaderLogger struct {
|
||||
Logger
|
||||
ReaderProxy
|
||||
}
|
||||
|
||||
// NewReaderDataLogger creates an active binary data logger with a default
|
||||
// log.Logger and a '->' prefix.
|
||||
func NewReaderDataLogger(reader io.Reader) *ReaderLogger {
|
||||
logger := log.New(os.Stdout, "<- ", log.LstdFlags)
|
||||
return NewReaderLogger(reader, logger, true, DataByte, 0)
|
||||
}
|
||||
|
||||
// NewReaderTextLogger creates an active binary data logger with a default
|
||||
// log.Logger and a '->' prefix.
|
||||
func NewReaderTextLogger(reader io.Reader) *ReaderLogger {
|
||||
logger := log.New(os.Stdout, "<- ", log.LstdFlags)
|
||||
return NewReaderLogger(reader, logger, true, DataText, 80)
|
||||
}
|
||||
|
||||
// NewReaderLogger creates a Reader logger for the provided parameters.
|
||||
func NewReaderLogger(reader io.Reader, logger *log.Logger, active bool, dataType int, truncate int) *ReaderLogger {
|
||||
return &ReaderLogger{ReaderProxy: *NewReaderProxy(reader), Logger: NewLogger(logger, active, dataType, truncate)}
|
||||
}
|
||||
|
||||
// Read logs the read bytes and passes them to the wrapped reader.
|
||||
func (r *ReaderLogger) Read(p []byte) (int, error) {
|
||||
n, err := r.reader.Read(p)
|
||||
return r.Log(p, n, err)
|
||||
}
|
||||
|
||||
// WriterLogger logs data from any io.Writer.
|
||||
// WriterLogger wraps a Writer and passes data to the actual data producer.
|
||||
type WriterLogger struct {
|
||||
Logger
|
||||
WriterProxy
|
||||
}
|
||||
|
||||
// NewWriterDataLogger creates an active binary data logger with a default
|
||||
// log.Logger and a '->' prefix.
|
||||
func NewWriterDataLogger(writer io.Writer) *WriterLogger {
|
||||
logger := log.New(os.Stdout, "-> ", log.LstdFlags)
|
||||
return NewWriterLogger(writer, logger, true, DataByte, 0)
|
||||
}
|
||||
|
||||
// NewWriterTextLogger creates an active binary data logger with a default
|
||||
// log.Logger and a '->' prefix.
|
||||
func NewWriterTextLogger(writer io.Writer) *WriterLogger {
|
||||
logger := log.New(os.Stdout, "-> ", log.LstdFlags)
|
||||
return NewWriterLogger(writer, logger, true, DataText, 80)
|
||||
}
|
||||
|
||||
// NewWriterLogger creates a Reader logger for the provided parameters.
|
||||
func NewWriterLogger(writer io.Writer, logger *log.Logger, active bool, dataType int, truncate int) *WriterLogger {
|
||||
return &WriterLogger{WriterProxy: *NewWriterProxy(writer), Logger: NewLogger(logger, active, dataType, truncate)}
|
||||
}
|
||||
|
||||
// Write logs the written bytes and passes them to the wrapped writer.
|
||||
func (w *WriterLogger) Write(p []byte) (int, error) {
|
||||
if w.writer != nil {
|
||||
n, err := w.writer.Write(p)
|
||||
return w.Log(p, n, err)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Stats tracks statistics for i/o operations. Stats are produced from a
|
||||
// of a running stats agent.
|
||||
type Stats struct {
|
||||
// Bytes is the total number of bytes transferred.
|
||||
Bytes int64
|
||||
// Ops is the total number of i/o operations performed.
|
||||
Ops int64
|
||||
// Errors is the total number of i/o errors encountered.
|
||||
Errors int64
|
||||
// Runtime is the duration that stats have been gathered.
|
||||
Runtime time.Duration
|
||||
}
|
||||
|
||||
// ClientStats displays combined statistics for the Client.
|
||||
type ClientStats struct {
|
||||
// Reads provides statistics on the raw i/o network reads for the current connection.
|
||||
Reads *Stats
|
||||
// Reads provides statistics on the raw i/o network reads for the all client connections.
|
||||
TotalReads *Stats
|
||||
// Writes provides statistics on the raw i/o network writes for the current connection.
|
||||
Writes *Stats
|
||||
// Writes provides statistics on the raw i/o network writes for all the client connections.
|
||||
TotalWrites *Stats
|
||||
// Reconnects is the number of reconnections the client has made.
|
||||
Reconnects int64
|
||||
// PingsSent is the number of pings sent by the client
|
||||
PingsSent int64
|
||||
// PingsRecv is the number of pings received by the client
|
||||
PingsRecv int64
|
||||
}
|
||||
|
||||
// String produces a compact string representation of the client stats.
|
||||
func (stats *ClientStats) String() string {
|
||||
i := stats.Reads
|
||||
ti := stats.TotalReads
|
||||
o := stats.Writes
|
||||
to := stats.TotalWrites
|
||||
totalRun := (ti.Runtime * 1000000) / 1000000
|
||||
run := (i.Runtime * 1000000) / 1000000
|
||||
return fmt.Sprintf("bytes: %d/%d##%d/%d ops: %d/%d##%d/%d err: %d/%d##%d/%d reconnects: %d pings: %d/%d uptime: %v##%v",
|
||||
i.Bytes, o.Bytes,
|
||||
ti.Bytes, to.Bytes,
|
||||
i.Ops, o.Ops,
|
||||
ti.Ops, to.Ops,
|
||||
i.Errors, o.Errors,
|
||||
ti.Errors, to.Errors,
|
||||
stats.Reconnects,
|
||||
stats.PingsRecv, stats.PingsSent,
|
||||
run, totalRun)
|
||||
}
|
||||
|
||||
// CollectionStats combines statistics about a collection.
|
||||
type CollectionStats struct {
|
||||
Name string // Name of the collection
|
||||
Count int // Count is the total number of documents in the collection
|
||||
}
|
||||
|
||||
// String produces a compact string representation of the collection stat.
|
||||
func (s *CollectionStats) String() string {
|
||||
return fmt.Sprintf("%s[%d]", s.Name, s.Count)
|
||||
}
|
||||
|
||||
// StatsTracker provides the basic tooling for tracking i/o stats.
|
||||
type StatsTracker struct {
|
||||
bytes int64
|
||||
ops int64
|
||||
errors int64
|
||||
start time.Time
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
// NewStatsTracker create a new stats tracker with start time set to now.
|
||||
func NewStatsTracker() *StatsTracker {
|
||||
return &StatsTracker{start: time.Now(), lock: new(sync.Mutex)}
|
||||
}
|
||||
|
||||
// Op records an i/o operation. The parameters are passed through to
|
||||
// allow easy chaining.
|
||||
func (t *StatsTracker) Op(n int, err error) (int, error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.ops++
|
||||
if err == nil {
|
||||
t.bytes += int64(n)
|
||||
} else {
|
||||
if err == io.EOF {
|
||||
// I don't think we should log EOF stats as an error
|
||||
} else {
|
||||
t.errors++
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Snapshot takes a snapshot of the current reader statistics.
|
||||
func (t *StatsTracker) Snapshot() *Stats {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
return t.snap()
|
||||
}
|
||||
|
||||
// Reset sets all of the stats to initial values.
|
||||
func (t *StatsTracker) Reset() *Stats {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
stats := t.snap()
|
||||
t.bytes = 0
|
||||
t.ops = 0
|
||||
t.errors = 0
|
||||
t.start = time.Now()
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func (t *StatsTracker) snap() *Stats {
|
||||
return &Stats{Bytes: t.bytes, Ops: t.ops, Errors: t.errors, Runtime: time.Since(t.start)}
|
||||
}
|
||||
|
||||
// ReaderStats tracks statistics on any io.Reader.
|
||||
// ReaderStats wraps a Reader and passes data to the actual data consumer.
|
||||
type ReaderStats struct {
|
||||
StatsTracker
|
||||
ReaderProxy
|
||||
}
|
||||
|
||||
// NewReaderStats creates a ReaderStats object for the provided reader.
|
||||
func NewReaderStats(reader io.Reader) *ReaderStats {
|
||||
return &ReaderStats{ReaderProxy: *NewReaderProxy(reader), StatsTracker: *NewStatsTracker()}
|
||||
}
|
||||
|
||||
// Read passes through a read collecting statistics and logging activity.
|
||||
func (r *ReaderStats) Read(p []byte) (int, error) {
|
||||
return r.Op(r.reader.Read(p))
|
||||
}
|
||||
|
||||
// WriterStats tracks statistics on any io.Writer.
|
||||
// WriterStats wraps a Writer and passes data to the actual data producer.
|
||||
type WriterStats struct {
|
||||
StatsTracker
|
||||
WriterProxy
|
||||
}
|
||||
|
||||
// NewWriterStats creates a WriterStats object for the provided writer.
|
||||
func NewWriterStats(writer io.Writer) *WriterStats {
|
||||
return &WriterStats{WriterProxy: *NewWriterProxy(writer), StatsTracker: *NewStatsTracker()}
|
||||
}
|
||||
|
||||
// Write passes through a write collecting statistics.
|
||||
func (w *WriterStats) Write(p []byte) (int, error) {
|
||||
if w.writer != nil {
|
||||
return w.Op(w.writer.Write(p))
|
||||
}
|
||||
return 0, nil
|
||||
}
|
39
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
generated
vendored
Normal file
39
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/channel.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Channel struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
Fname string `json:"fname,omitempty"`
|
||||
Type string `json:"t"`
|
||||
Msgs int `json:"msgs"`
|
||||
|
||||
ReadOnly bool `json:"ro,omitempty"`
|
||||
SysMes bool `json:"sysMes,omitempty"`
|
||||
Default bool `json:"default"`
|
||||
Broadcast bool `json:"broadcast,omitempty"`
|
||||
|
||||
Timestamp *time.Time `json:"ts,omitempty"`
|
||||
UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
|
||||
|
||||
User *User `json:"u,omitempty"`
|
||||
LastMessage *Message `json:"lastMessage,omitempty"`
|
||||
|
||||
// Lm interface{} `json:"lm"`
|
||||
// CustomFields struct {
|
||||
// } `json:"customFields,omitempty"`
|
||||
}
|
||||
|
||||
type ChannelSubscription struct {
|
||||
ID string `json:"_id"`
|
||||
Alert bool `json:"alert"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"fname"`
|
||||
Open bool `json:"open"`
|
||||
RoomId string `json:"rid"`
|
||||
Type string `json:"c"`
|
||||
User User `json:"u"`
|
||||
Roles []string `json:"roles"`
|
||||
Unread float64 `json:"unread"`
|
||||
}
|
133
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
generated
vendored
Normal file
133
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/info.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Info struct {
|
||||
Version string `json:"version"`
|
||||
|
||||
Build struct {
|
||||
NodeVersion string `json:"nodeVersion"`
|
||||
Arch string `json:"arch"`
|
||||
Platform string `json:"platform"`
|
||||
Cpus int `json:"cpus"`
|
||||
} `json:"build"`
|
||||
|
||||
Commit struct {
|
||||
Hash string `json:"hash"`
|
||||
Date string `json:"date"`
|
||||
Author string `json:"author"`
|
||||
Subject string `json:"subject"`
|
||||
Tag string `json:"tag"`
|
||||
Branch string `json:"branch"`
|
||||
} `json:"commit"`
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
Count int `json:"count"`
|
||||
Offset int `json:"offset"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type Directory struct {
|
||||
Result []struct {
|
||||
ID string `json:"_id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Emails []struct {
|
||||
Address string `json:"address"`
|
||||
Verified bool `json:"verified"`
|
||||
} `json:"emails"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
} `json:"result"`
|
||||
|
||||
Pagination
|
||||
}
|
||||
|
||||
type Spotlight struct {
|
||||
Users []User `json:"users"`
|
||||
Rooms []Channel `json:"rooms"`
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
ID string `json:"_id"`
|
||||
UniqueID string `json:"uniqueId"`
|
||||
Version string `json:"version"`
|
||||
|
||||
ActiveUsers int `json:"activeUsers"`
|
||||
NonActiveUsers int `json:"nonActiveUsers"`
|
||||
OnlineUsers int `json:"onlineUsers"`
|
||||
AwayUsers int `json:"awayUsers"`
|
||||
OfflineUsers int `json:"offlineUsers"`
|
||||
TotalUsers int `json:"totalUsers"`
|
||||
|
||||
TotalRooms int `json:"totalRooms"`
|
||||
TotalChannels int `json:"totalChannels"`
|
||||
TotalPrivateGroups int `json:"totalPrivateGroups"`
|
||||
TotalDirect int `json:"totalDirect"`
|
||||
TotlalLivechat int `json:"totlalLivechat"`
|
||||
TotalMessages int `json:"totalMessages"`
|
||||
TotalChannelMessages int `json:"totalChannelMessages"`
|
||||
TotalPrivateGroupMessages int `json:"totalPrivateGroupMessages"`
|
||||
TotalDirectMessages int `json:"totalDirectMessages"`
|
||||
TotalLivechatMessages int `json:"totalLivechatMessages"`
|
||||
|
||||
InstalledAt time.Time `json:"installedAt"`
|
||||
LastLogin time.Time `json:"lastLogin"`
|
||||
LastMessageSentAt time.Time `json:"lastMessageSentAt"`
|
||||
LastSeenSubscription time.Time `json:"lastSeenSubscription"`
|
||||
|
||||
Os struct {
|
||||
Type string `json:"type"`
|
||||
Platform string `json:"platform"`
|
||||
Arch string `json:"arch"`
|
||||
Release string `json:"release"`
|
||||
Uptime int `json:"uptime"`
|
||||
Loadavg []float64 `json:"loadavg"`
|
||||
Totalmem int64 `json:"totalmem"`
|
||||
Freemem int `json:"freemem"`
|
||||
Cpus []struct {
|
||||
Model string `json:"model"`
|
||||
Speed int `json:"speed"`
|
||||
Times struct {
|
||||
User int `json:"user"`
|
||||
Nice int `json:"nice"`
|
||||
Sys int `json:"sys"`
|
||||
Idle int `json:"idle"`
|
||||
Irq int `json:"irq"`
|
||||
} `json:"times"`
|
||||
} `json:"cpus"`
|
||||
} `json:"os"`
|
||||
|
||||
Process struct {
|
||||
NodeVersion string `json:"nodeVersion"`
|
||||
Pid int `json:"pid"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
} `json:"process"`
|
||||
|
||||
Deploy struct {
|
||||
Method string `json:"method"`
|
||||
Platform string `json:"platform"`
|
||||
} `json:"deploy"`
|
||||
|
||||
Migration struct {
|
||||
ID string `json:"_id"`
|
||||
Version int `json:"version"`
|
||||
Locked bool `json:"locked"`
|
||||
LockedAt time.Time `json:"lockedAt"`
|
||||
BuildAt time.Time `json:"buildAt"`
|
||||
} `json:"migration"`
|
||||
|
||||
InstanceCount int `json:"instanceCount"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"_updatedAt"`
|
||||
}
|
||||
|
||||
type StatisticsInfo struct {
|
||||
Statistics Statistics `json:"statistics"`
|
||||
}
|
||||
|
||||
type StatisticsList struct {
|
||||
Statistics []Statistics `json:"statistics"`
|
||||
|
||||
Pagination
|
||||
}
|
75
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
generated
vendored
Normal file
75
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/message.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Message struct {
|
||||
ID string `json:"_id"`
|
||||
RoomID string `json:"rid"`
|
||||
Msg string `json:"msg"`
|
||||
EditedBy string `json:"editedBy,omitempty"`
|
||||
|
||||
Groupable bool `json:"groupable,omitempty"`
|
||||
|
||||
EditedAt *time.Time `json:"editedAt,omitempty"`
|
||||
Timestamp *time.Time `json:"ts,omitempty"`
|
||||
UpdatedAt *time.Time `json:"_updatedAt,omitempty"`
|
||||
|
||||
Mentions []User `json:"mentions,omitempty"`
|
||||
User *User `json:"u,omitempty"`
|
||||
PostMessage
|
||||
|
||||
// Bot interface{} `json:"bot"`
|
||||
// CustomFields interface{} `json:"customFields"`
|
||||
// Channels []interface{} `json:"channels"`
|
||||
// SandstormSessionID interface{} `json:"sandstormSessionId"`
|
||||
}
|
||||
|
||||
// PostMessage Payload for postmessage rest API
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
|
||||
type PostMessage struct {
|
||||
RoomID string `json:"roomId,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
ParseUrls bool `json:"parseUrls,omitempty"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Emoji string `json:"emoji,omitempty"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Attachments []Attachment `json:"attachments,omitempty"`
|
||||
}
|
||||
|
||||
// Attachment Payload for postmessage rest API
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
|
||||
type Attachment struct {
|
||||
Color string `json:"color,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Timestamp string `json:"ts,omitempty"`
|
||||
ThumbURL string `json:"thumb_url,omitempty"`
|
||||
MessageLink string `json:"message_link,omitempty"`
|
||||
Collapsed bool `json:"collapsed"`
|
||||
|
||||
AuthorName string `json:"author_name,omitempty"`
|
||||
AuthorLink string `json:"author_link,omitempty"`
|
||||
AuthorIcon string `json:"author_icon,omitempty"`
|
||||
|
||||
Title string `json:"title,omitempty"`
|
||||
TitleLink string `json:"title_link,omitempty"`
|
||||
TitleLinkDownload string `json:"title_link_download,omitempty"`
|
||||
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
|
||||
AudioURL string `json:"audio_url,omitempty"`
|
||||
VideoURL string `json:"video_url,omitempty"`
|
||||
|
||||
Fields []AttachmentField `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// AttachmentField Payload for postmessage rest API
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/
|
||||
type AttachmentField struct {
|
||||
Short bool `json:"short"`
|
||||
Title string `json:"title"`
|
||||
Value string `json:"value"`
|
||||
}
|
7
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
generated
vendored
Normal file
7
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/permission.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
type Permission struct {
|
||||
ID string `json:"_id"`
|
||||
UpdatedAt string `json:"_updatedAt.$date"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
21
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
generated
vendored
Normal file
21
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/setting.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
type Setting struct {
|
||||
ID string `json:"_id"`
|
||||
Blocked bool `json:"blocked"`
|
||||
Group string `json:"group"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Public bool `json:"public"`
|
||||
Type string `json:"type"`
|
||||
PackageValue string `json:"packageValue"`
|
||||
Sorter int `json:"sorter"`
|
||||
Value string `json:"value"`
|
||||
ValueBool bool `json:"valueBool"`
|
||||
ValueInt float64 `json:"valueInt"`
|
||||
ValueSource string `json:"valueSource"`
|
||||
ValueAsset Asset `json:"asset"`
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
DefaultUrl string `json:"defaultUrl"`
|
||||
}
|
29
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
generated
vendored
Normal file
29
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/user.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package models
|
||||
|
||||
type User struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
UserName string `json:"username"`
|
||||
Status string `json:"status"`
|
||||
Token string `json:"token"`
|
||||
TokenExpires int64 `json:"tokenExpires"`
|
||||
}
|
||||
|
||||
type CreateUserRequest struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
CustomFields map[string]string `json:"customFields,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
UserID string `json:"userId"`
|
||||
Data struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
CustomFields map[string]string `json:"customFields,omitempty"`
|
||||
} `json:"data"`
|
||||
}
|
10
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
generated
vendored
Normal file
10
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/models/userCredentials.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
package models
|
||||
|
||||
type UserCredentials struct {
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"pass"`
|
||||
}
|
263
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
generated
vendored
Normal file
263
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/channels.go
generated
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
func (c *Client) GetChannelId(name string) (string, error) {
|
||||
rawResponse, err := c.ddp.Call("getRoomIdByNameOrId", name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//log.Println(rawResponse)
|
||||
|
||||
return rawResponse.(string), nil
|
||||
}
|
||||
|
||||
// GetChannelsIn returns list of channels
|
||||
// Optionally includes date to get all since last check or 0 to get all
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-rooms/
|
||||
func (c *Client) GetChannelsIn() ([]models.Channel, error) {
|
||||
rawResponse, err := c.ddp.Call("rooms/get", map[string]int{
|
||||
"$date": 0,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
|
||||
|
||||
chans, err := document.Children()
|
||||
|
||||
var channels []models.Channel
|
||||
|
||||
for _, i := range chans {
|
||||
channels = append(channels, models.Channel{
|
||||
ID: stringOrZero(i.Path("_id").Data()),
|
||||
//Default: stringOrZero(i.Path("default").Data()),
|
||||
Name: stringOrZero(i.Path("name").Data()),
|
||||
Type: stringOrZero(i.Path("t").Data()),
|
||||
})
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
// GetChannelSubscriptions gets users channel subscriptions
|
||||
// Optionally includes date to get all since last check or 0 to get all
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-subscriptions
|
||||
func (c *Client) GetChannelSubscriptions() ([]models.ChannelSubscription, error) {
|
||||
rawResponse, err := c.ddp.Call("subscriptions/get", map[string]int{
|
||||
"$date": 0,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
document, _ := gabs.Consume(rawResponse.(map[string]interface{})["update"])
|
||||
|
||||
channelSubs, err := document.Children()
|
||||
|
||||
var channelSubscriptions []models.ChannelSubscription
|
||||
|
||||
for _, sub := range channelSubs {
|
||||
channelSubscription := models.ChannelSubscription{
|
||||
ID: stringOrZero(sub.Path("_id").Data()),
|
||||
Alert: sub.Path("alert").Data().(bool),
|
||||
Name: stringOrZero(sub.Path("name").Data()),
|
||||
DisplayName: stringOrZero(sub.Path("fname").Data()),
|
||||
Open: sub.Path("open").Data().(bool),
|
||||
Type: stringOrZero(sub.Path("t").Data()),
|
||||
User: models.User{
|
||||
ID: stringOrZero(sub.Path("u._id").Data()),
|
||||
UserName: stringOrZero(sub.Path("u.username").Data()),
|
||||
},
|
||||
Unread: sub.Path("unread").Data().(float64),
|
||||
}
|
||||
|
||||
if sub.Path("roles").Data() != nil {
|
||||
var roles []string
|
||||
for _, role := range sub.Path("roles").Data().([]interface{}) {
|
||||
roles = append(roles, role.(string))
|
||||
}
|
||||
|
||||
channelSubscription.Roles = roles
|
||||
}
|
||||
|
||||
channelSubscriptions = append(channelSubscriptions, channelSubscription)
|
||||
}
|
||||
|
||||
return channelSubscriptions, nil
|
||||
}
|
||||
|
||||
// GetChannelRoles returns room roles
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-room-roles
|
||||
func (c *Client) GetChannelRoles(roomId string) error {
|
||||
_, err := c.ddp.Call("getRoomRoles", roomId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateChannel creates a channel
|
||||
// Takes name and users array
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-channels
|
||||
func (c *Client) CreateChannel(name string, users []string) error {
|
||||
_, err := c.ddp.Call("createChannel", name, users)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateGroup creates a private group
|
||||
// Takes group name and array of users
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/create-private-groups
|
||||
func (c *Client) CreateGroup(name string, users []string) error {
|
||||
_, err := c.ddp.Call("createPrivateGroup", name, users)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JoinChannel joins a channel
|
||||
// Takes roomId
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/joining-channels
|
||||
func (c *Client) JoinChannel(roomId string) error {
|
||||
_, err := c.ddp.Call("joinRoom", roomId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LeaveChannel leaves a channel
|
||||
// Takes roomId
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/leaving-rooms
|
||||
func (c *Client) LeaveChannel(roomId string) error {
|
||||
_, err := c.ddp.Call("leaveRoom", roomId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArchiveChannel archives the channel
|
||||
// Takes roomId
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/archive-rooms
|
||||
func (c *Client) ArchiveChannel(roomId string) error {
|
||||
_, err := c.ddp.Call("archiveRoom", roomId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnArchiveChannel unarchives the channel
|
||||
// Takes roomId
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unarchive-rooms
|
||||
func (c *Client) UnArchiveChannel(roomId string) error {
|
||||
_, err := c.ddp.Call("unarchiveRoom", roomId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteChannel deletes the channel
|
||||
// Takes roomId
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-rooms
|
||||
func (c *Client) DeleteChannel(roomId string) error {
|
||||
_, err := c.ddp.Call("eraseRoom", roomId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetChannelTopic sets channel topic
|
||||
// takes roomId and topic
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
|
||||
func (c *Client) SetChannelTopic(roomId string, topic string) error {
|
||||
_, err := c.ddp.Call("saveRoomSettings", roomId, "roomTopic", topic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetChannelType sets the channel type
|
||||
// takes roomId and roomType
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
|
||||
func (c *Client) SetChannelType(roomId string, roomType string) error {
|
||||
_, err := c.ddp.Call("saveRoomSettings", roomId, "roomType", roomType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetChannelJoinCode sets channel join code
|
||||
// takes roomId and joinCode
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
|
||||
func (c *Client) SetChannelJoinCode(roomId string, joinCode string) error {
|
||||
_, err := c.ddp.Call("saveRoomSettings", roomId, "joinCode", joinCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetChannelReadOnly sets channel as read only
|
||||
// takes roomId and boolean
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
|
||||
func (c *Client) SetChannelReadOnly(roomId string, readOnly bool) error {
|
||||
_, err := c.ddp.Call("saveRoomSettings", roomId, "readOnly", readOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetChannelDescription sets channels description
|
||||
// takes roomId and description
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/save-room-settings
|
||||
func (c *Client) SetChannelDescription(roomId string, description string) error {
|
||||
_, err := c.ddp.Call("saveRoomSettings", roomId, "roomDescription", description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
96
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
generated
vendored
Normal file
96
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/client.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
// Provides access to Rocket.Chat's realtime API via ddp
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gopackage/ddp"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
ddp *ddp.Client
|
||||
}
|
||||
|
||||
// Creates a new instance and connects to the websocket.
|
||||
func NewClient(serverURL *url.URL, debug bool) (*Client, error) {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
wsURL := "ws"
|
||||
port := 80
|
||||
|
||||
if serverURL.Scheme == "https" {
|
||||
wsURL = "wss"
|
||||
port = 443
|
||||
}
|
||||
|
||||
if len(serverURL.Port()) > 0 {
|
||||
port, _ = strconv.Atoi(serverURL.Port())
|
||||
}
|
||||
|
||||
wsURL = fmt.Sprintf("%s://%v:%v%s/websocket", wsURL, serverURL.Hostname(), port, serverURL.Path)
|
||||
|
||||
// log.Println("About to connect to:", wsURL, port, serverURL.Scheme)
|
||||
|
||||
c := new(Client)
|
||||
c.ddp = ddp.NewClient(wsURL, serverURL.String())
|
||||
|
||||
if debug {
|
||||
c.ddp.SetSocketLogActive(true)
|
||||
}
|
||||
|
||||
if err := c.ddp.Connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type statusListener struct {
|
||||
listener func(int)
|
||||
}
|
||||
|
||||
func (s statusListener) Status(status int) {
|
||||
s.listener(status)
|
||||
}
|
||||
|
||||
func (c *Client) AddStatusListener(listener func(int)) {
|
||||
c.ddp.AddStatusListener(statusListener{listener: listener})
|
||||
}
|
||||
|
||||
func (c *Client) Reconnect() {
|
||||
c.ddp.Reconnect()
|
||||
}
|
||||
|
||||
// ConnectionAway sets connection status to away
|
||||
func (c *Client) ConnectionAway() error {
|
||||
_, err := c.ddp.Call("UserPresence:away")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectionOnline sets connection status to online
|
||||
func (c *Client) ConnectionOnline() error {
|
||||
_, err := c.ddp.Call("UserPresence:online")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the ddp session
|
||||
func (c *Client) Close() {
|
||||
c.ddp.Close()
|
||||
}
|
||||
|
||||
// Some of the rocketchat objects need unique IDs specified by the client
|
||||
func (c *Client) newRandomId() string {
|
||||
return fmt.Sprintf("%f", rand.Float64())
|
||||
}
|
10
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
generated
vendored
Normal file
10
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/emoji.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
package realtime
|
||||
|
||||
func (c *Client) getCustomEmoji() error {
|
||||
_, err := c.ddp.Call("listEmojiCustom")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
21
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
generated
vendored
Normal file
21
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/events.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package realtime
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *Client) StartTyping(roomId string, username string) error {
|
||||
_, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) StopTyping(roomId string, username string) error {
|
||||
_, err := c.ddp.Call("stream-notify-room", fmt.Sprintf("%s/typing", roomId), username, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
240
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
generated
vendored
Normal file
240
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/messages.go
generated
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/gopackage/ddp"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
const (
|
||||
// RocketChat doesn't send the `added` event for new messages by default, only `changed`.
|
||||
send_added_event = true
|
||||
default_buffer_size = 100
|
||||
)
|
||||
|
||||
// LoadHistory loads history
|
||||
// Takes roomId
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/load-history
|
||||
func (c *Client) LoadHistory(roomId string) error {
|
||||
_, err := c.ddp.Call("loadHistory", roomId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessage sends message to channel
|
||||
// takes channel and message
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/send-message
|
||||
func (c *Client) SendMessage(m *models.Message) (*models.Message, error) {
|
||||
m.ID = c.newRandomId()
|
||||
|
||||
rawResponse, err := c.ddp.Call("sendMessage", m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return getMessageFromData(rawResponse.(map[string]interface{})), nil
|
||||
}
|
||||
|
||||
// EditMessage edits a message
|
||||
// takes message object
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/update-message
|
||||
func (c *Client) EditMessage(message *models.Message) error {
|
||||
_, err := c.ddp.Call("updateMessage", message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMessage deletes a message
|
||||
// takes a message object
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/delete-message
|
||||
func (c *Client) DeleteMessage(message *models.Message) error {
|
||||
_, err := c.ddp.Call("deleteMessage", map[string]string{
|
||||
"_id": message.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReactToMessage adds a reaction to a message
|
||||
// takes a message and emoji
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/set-reaction
|
||||
func (c *Client) ReactToMessage(message *models.Message, reaction string) error {
|
||||
_, err := c.ddp.Call("setReaction", reaction, message.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StarMessage stars message
|
||||
// takes a message object
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
|
||||
func (c *Client) StarMessage(message *models.Message) error {
|
||||
_, err := c.ddp.Call("starMessage", map[string]interface{}{
|
||||
"_id": message.ID,
|
||||
"rid": message.RoomID,
|
||||
"starred": true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnStarMessage unstars message
|
||||
// takes message object
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/star-message
|
||||
func (c *Client) UnStarMessage(message *models.Message) error {
|
||||
_, err := c.ddp.Call("starMessage", map[string]interface{}{
|
||||
"_id": message.ID,
|
||||
"rid": message.RoomID,
|
||||
"starred": false,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PinMessage pins a message
|
||||
// takes a message object
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/pin-message
|
||||
func (c *Client) PinMessage(message *models.Message) error {
|
||||
_, err := c.ddp.Call("pinMessage", message)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnPinMessage unpins message
|
||||
// takes a message object
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/unpin-messages
|
||||
func (c *Client) UnPinMessage(message *models.Message) error {
|
||||
_, err := c.ddp.Call("unpinMessage", message)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubscribeToMessageStream Subscribes to the message updates of a channel
|
||||
// Returns a buffered channel
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
|
||||
func (c *Client) SubscribeToMessageStream(channel *models.Channel, msgChannel chan models.Message) error {
|
||||
|
||||
if err := c.ddp.Sub("stream-room-messages", channel.ID, send_added_event); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//msgChannel := make(chan models.Message, default_buffer_size)
|
||||
c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(messageExtractor{msgChannel, "update"})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMessagesFromUpdateEvent(update ddp.Update) []models.Message {
|
||||
document, _ := gabs.Consume(update["args"])
|
||||
args, err := document.Children()
|
||||
|
||||
if err != nil {
|
||||
// log.Printf("Event arguments are in an unexpected format: %v", err)
|
||||
return make([]models.Message, 0)
|
||||
}
|
||||
|
||||
messages := make([]models.Message, len(args))
|
||||
|
||||
for i, arg := range args {
|
||||
messages[i] = *getMessageFromDocument(arg)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func getMessageFromData(data interface{}) *models.Message {
|
||||
// TODO: We should know what this will look like, we shouldn't need to use gabs
|
||||
document, _ := gabs.Consume(data)
|
||||
return getMessageFromDocument(document)
|
||||
}
|
||||
|
||||
func getMessageFromDocument(arg *gabs.Container) *models.Message {
|
||||
var ts *time.Time
|
||||
date := stringOrZero(arg.Path("ts.$date").Data())
|
||||
if len(date) > 0 {
|
||||
if ti, err := strconv.ParseFloat(date, 64); err == nil {
|
||||
t := time.Unix(int64(ti)/1e3, int64(ti)%1e3)
|
||||
ts = &t
|
||||
}
|
||||
}
|
||||
return &models.Message{
|
||||
ID: stringOrZero(arg.Path("_id").Data()),
|
||||
RoomID: stringOrZero(arg.Path("rid").Data()),
|
||||
Msg: stringOrZero(arg.Path("msg").Data()),
|
||||
Timestamp: ts,
|
||||
User: &models.User{
|
||||
ID: stringOrZero(arg.Path("u._id").Data()),
|
||||
UserName: stringOrZero(arg.Path("u.username").Data()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func stringOrZero(i interface{}) string {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch i.(type) {
|
||||
case string:
|
||||
return i.(string)
|
||||
case float64:
|
||||
return fmt.Sprintf("%f", i.(float64))
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type messageExtractor struct {
|
||||
messageChannel chan models.Message
|
||||
operation string
|
||||
}
|
||||
|
||||
func (u messageExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
|
||||
if operation == u.operation {
|
||||
msgs := getMessagesFromUpdateEvent(doc)
|
||||
for _, m := range msgs {
|
||||
u.messageChannel <- m
|
||||
}
|
||||
}
|
||||
}
|
54
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
generated
vendored
Normal file
54
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/permissions.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
// GetPermissions gets permissions
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-permissions
|
||||
func (c *Client) GetPermissions() ([]models.Permission, error) {
|
||||
rawResponse, err := c.ddp.Call("permissions/get")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
document, _ := gabs.Consume(rawResponse)
|
||||
|
||||
perms, _ := document.Children()
|
||||
|
||||
var permissions []models.Permission
|
||||
|
||||
for _, permission := range perms {
|
||||
var roles []string
|
||||
for _, role := range permission.Path("roles").Data().([]interface{}) {
|
||||
roles = append(roles, role.(string))
|
||||
}
|
||||
|
||||
permissions = append(permissions, models.Permission{
|
||||
ID: stringOrZero(permission.Path("_id").Data()),
|
||||
Roles: roles,
|
||||
})
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
// GetUserRoles gets current users roles
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-user-roles
|
||||
func (c *Client) GetUserRoles() error {
|
||||
rawResponse, err := c.ddp.Call("getUserRoles")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
document, _ := gabs.Consume(rawResponse)
|
||||
|
||||
_, err = document.Children()
|
||||
// TODO: Figure out if this function is even useful if so return it
|
||||
//log.Println(roles)
|
||||
|
||||
return nil
|
||||
}
|
53
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
generated
vendored
Normal file
53
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/settings.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
// GetPublicSettings gets public settings
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/get-public-settings
|
||||
func (c *Client) GetPublicSettings() ([]models.Setting, error) {
|
||||
rawResponse, err := c.ddp.Call("public-settings/get")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
document, _ := gabs.Consume(rawResponse)
|
||||
|
||||
sett, _ := document.Children()
|
||||
|
||||
var settings []models.Setting
|
||||
|
||||
for _, rawSetting := range sett {
|
||||
setting := models.Setting{
|
||||
ID: stringOrZero(rawSetting.Path("_id").Data()),
|
||||
Type: stringOrZero(rawSetting.Path("type").Data()),
|
||||
}
|
||||
|
||||
switch setting.Type {
|
||||
case "boolean":
|
||||
setting.ValueBool = rawSetting.Path("value").Data().(bool)
|
||||
case "string":
|
||||
setting.Value = stringOrZero(rawSetting.Path("value").Data())
|
||||
case "code":
|
||||
setting.Value = stringOrZero(rawSetting.Path("value").Data())
|
||||
case "color":
|
||||
setting.Value = stringOrZero(rawSetting.Path("value").Data())
|
||||
case "int":
|
||||
setting.ValueInt = rawSetting.Path("value").Data().(float64)
|
||||
case "asset":
|
||||
setting.ValueAsset = models.Asset{
|
||||
DefaultUrl: stringOrZero(rawSetting.Path("value").Data().(map[string]interface{})["defaultUrl"]),
|
||||
}
|
||||
|
||||
default:
|
||||
// log.Println(setting.Type, rawSetting.Path("value").Data())
|
||||
}
|
||||
|
||||
settings = append(settings, setting)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
41
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
generated
vendored
Normal file
41
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/subscriptions.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gopackage/ddp"
|
||||
)
|
||||
|
||||
// Subscribes to stream-notify-logged
|
||||
// Returns a buffered channel
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/subscriptions/stream-room-messages/
|
||||
func (c *Client) Sub(name string, args ...interface{}) (chan string, error) {
|
||||
|
||||
if args == nil {
|
||||
//log.Println("no args passed")
|
||||
if err := c.ddp.Sub(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := c.ddp.Sub(name, args[0], false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
msgChannel := make(chan string, default_buffer_size)
|
||||
c.ddp.CollectionByName("stream-room-messages").AddUpdateListener(genericExtractor{msgChannel, "update"})
|
||||
|
||||
return msgChannel, nil
|
||||
}
|
||||
|
||||
type genericExtractor struct {
|
||||
messageChannel chan string
|
||||
operation string
|
||||
}
|
||||
|
||||
func (u genericExtractor) CollectionUpdate(collection, operation, id string, doc ddp.Update) {
|
||||
if operation == u.operation {
|
||||
u.messageChannel <- fmt.Sprintf("%s -> update", collection)
|
||||
}
|
||||
}
|
103
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
generated
vendored
Normal file
103
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/realtime/users.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
package realtime
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
type ddpLoginRequest struct {
|
||||
User ddpUser `json:"user"`
|
||||
Password ddpPassword `json:"password"`
|
||||
}
|
||||
|
||||
type ddpTokenLoginRequest struct {
|
||||
Token string `json:"resume"`
|
||||
}
|
||||
|
||||
type ddpUser struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type ddpPassword struct {
|
||||
Digest string `json:"digest"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
}
|
||||
|
||||
// RegisterUser a new user on the server. This function does not need a logged in user. The registered user gets logged in
|
||||
// to set its username.
|
||||
func (c *Client) RegisterUser(credentials *models.UserCredentials) (*models.User, error) {
|
||||
|
||||
if _, err := c.ddp.Call("registerUser", credentials); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := c.Login(credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := c.ddp.Call("setUsername", credentials.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Login a user.
|
||||
// token shouldn't be nil, otherwise the password and the email are not allowed to be nil.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/realtime-api/method-calls/login/
|
||||
func (c *Client) Login(credentials *models.UserCredentials) (*models.User, error) {
|
||||
var request interface{}
|
||||
if credentials.Token != "" {
|
||||
request = ddpTokenLoginRequest{
|
||||
Token: credentials.Token,
|
||||
}
|
||||
} else {
|
||||
digest := sha256.Sum256([]byte(credentials.Password))
|
||||
request = ddpLoginRequest{
|
||||
User: ddpUser{Email: credentials.Email},
|
||||
Password: ddpPassword{
|
||||
Digest: hex.EncodeToString(digest[:]),
|
||||
Algorithm: "sha-256",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
rawResponse, err := c.ddp.Call("login", request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := getUserFromData(rawResponse.(map[string]interface{}))
|
||||
if credentials.Token == "" {
|
||||
credentials.ID, credentials.Token = user.ID, user.Token
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func getUserFromData(data interface{}) *models.User {
|
||||
document, _ := gabs.Consume(data)
|
||||
|
||||
expires, _ := strconv.ParseFloat(stringOrZero(document.Path("tokenExpires.$date").Data()), 64)
|
||||
return &models.User{
|
||||
ID: stringOrZero(document.Path("id").Data()),
|
||||
Token: stringOrZero(document.Path("token").Data()),
|
||||
TokenExpires: int64(expires),
|
||||
}
|
||||
}
|
||||
|
||||
// SetPresence set user presence
|
||||
func (c *Client) SetPresence(status string) error {
|
||||
_, err := c.ddp.Call("UserPresence:setDefaultStatus", status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
64
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
generated
vendored
Normal file
64
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/channels.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
type ChannelsResponse struct {
|
||||
Status
|
||||
models.Pagination
|
||||
Channels []models.Channel `json:"channels"`
|
||||
}
|
||||
|
||||
type ChannelResponse struct {
|
||||
Status
|
||||
Channel models.Channel `json:"channel"`
|
||||
}
|
||||
|
||||
// GetPublicChannels returns all channels that can be seen by the logged in user.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/channels/list
|
||||
func (c *Client) GetPublicChannels() (*ChannelsResponse, error) {
|
||||
response := new(ChannelsResponse)
|
||||
if err := c.Get("channels.list", nil, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetJoinedChannels returns all channels that the user has joined.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/channels/list-joined
|
||||
func (c *Client) GetJoinedChannels(params url.Values) (*ChannelsResponse, error) {
|
||||
response := new(ChannelsResponse)
|
||||
if err := c.Get("channels.list.joined", params, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// LeaveChannel leaves a channel. The id of the channel has to be not nil.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/channels/leave
|
||||
func (c *Client) LeaveChannel(channel *models.Channel) error {
|
||||
var body = fmt.Sprintf(`{ "roomId": "%s"}`, channel.ID)
|
||||
return c.Post("channels.leave", bytes.NewBufferString(body), new(ChannelResponse))
|
||||
}
|
||||
|
||||
// GetChannelInfo get information about a channel. That might be useful to update the usernames.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/channels/info
|
||||
func (c *Client) GetChannelInfo(channel *models.Channel) (*models.Channel, error) {
|
||||
response := new(ChannelResponse)
|
||||
if err := c.Get("channels.info", url.Values{"roomId": []string{channel.ID}}, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Channel, nil
|
||||
}
|
176
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
generated
vendored
Normal file
176
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/client.go
generated
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
//Package rest provides a RocketChat rest client.
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var (
|
||||
ResponseErr = fmt.Errorf("got false response")
|
||||
)
|
||||
|
||||
type Response interface {
|
||||
OK() error
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Protocol string
|
||||
Host string
|
||||
Path string
|
||||
Port string
|
||||
Version string
|
||||
|
||||
// Use this switch to see all network communication.
|
||||
Debug bool
|
||||
|
||||
auth *authInfo
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type authInfo struct {
|
||||
token string
|
||||
id string
|
||||
}
|
||||
|
||||
func (s Status) OK() error {
|
||||
if s.Success {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(s.Error) > 0 {
|
||||
return fmt.Errorf(s.Error)
|
||||
}
|
||||
|
||||
if s.Status == "success" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(s.Message) > 0 {
|
||||
return fmt.Errorf("status: %s, message: %s", s.Status, s.Message)
|
||||
}
|
||||
return ResponseErr
|
||||
}
|
||||
|
||||
// StatusResponse The base for the most of the json responses
|
||||
type StatusResponse struct {
|
||||
Status
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
|
||||
func NewClient(serverUrl *url.URL, debug bool) *Client {
|
||||
protocol := "http"
|
||||
port := "80"
|
||||
|
||||
if serverUrl.Scheme == "https" {
|
||||
protocol = "https"
|
||||
port = "443"
|
||||
}
|
||||
|
||||
if len(serverUrl.Port()) > 0 {
|
||||
port = serverUrl.Port()
|
||||
}
|
||||
|
||||
return &Client{Host: serverUrl.Hostname(), Path: serverUrl.Path, Port: port, Protocol: protocol, Version: "v1", Debug: debug}
|
||||
}
|
||||
|
||||
func (c *Client) getUrl() string {
|
||||
if len(c.Version) == 0 {
|
||||
c.Version = "v1"
|
||||
}
|
||||
return fmt.Sprintf("%v://%v:%v%s/api/%s", c.Protocol, c.Host, c.Port, c.Path, c.Version)
|
||||
}
|
||||
|
||||
// Get call Get
|
||||
func (c *Client) Get(api string, params url.Values, response Response) error {
|
||||
return c.doRequest(http.MethodGet, api, params, nil, response)
|
||||
}
|
||||
|
||||
// Post call as JSON
|
||||
func (c *Client) Post(api string, body io.Reader, response Response) error {
|
||||
return c.doRequest(http.MethodPost, api, nil, body, response)
|
||||
}
|
||||
|
||||
// PostForm call as Form Data
|
||||
func (c *Client) PostForm(api string, params url.Values, response Response) error {
|
||||
return c.doRequest(http.MethodPost, api, params, nil, response)
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(method, api string, params url.Values, body io.Reader, response Response) error {
|
||||
contentType := "application/x-www-form-urlencoded"
|
||||
if method == http.MethodPost {
|
||||
if body != nil {
|
||||
contentType = "application/json"
|
||||
} else if len(params) > 0 {
|
||||
body = bytes.NewBufferString(params.Encode())
|
||||
}
|
||||
}
|
||||
|
||||
request, err := http.NewRequest(method, c.getUrl()+"/"+api, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if method == http.MethodGet {
|
||||
if len(params) > 0 {
|
||||
request.URL.RawQuery = params.Encode()
|
||||
}
|
||||
} else {
|
||||
request.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
|
||||
if c.auth != nil {
|
||||
request.Header.Set("X-Auth-Token", c.auth.token)
|
||||
request.Header.Set("X-User-Id", c.auth.id)
|
||||
}
|
||||
|
||||
if c.Debug {
|
||||
log.Println(request)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if c.Debug {
|
||||
log.Println(string(bodyBytes))
|
||||
}
|
||||
|
||||
var parse bool
|
||||
if err == nil {
|
||||
if e := json.Unmarshal(bodyBytes, response); e == nil {
|
||||
parse = true
|
||||
}
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if parse {
|
||||
return response.OK()
|
||||
}
|
||||
return errors.New("Request error: " + resp.Status)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.OK()
|
||||
}
|
98
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
generated
vendored
Normal file
98
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/information.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
type InfoResponse struct {
|
||||
Status
|
||||
Info models.Info `json:"info"`
|
||||
}
|
||||
|
||||
// GetServerInfo a simple method, requires no authentication,
|
||||
// that returns information about the server including version information.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/info
|
||||
func (c *Client) GetServerInfo() (*models.Info, error) {
|
||||
response := new(InfoResponse)
|
||||
if err := c.Get("info", nil, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Info, nil
|
||||
}
|
||||
|
||||
type DirectoryResponse struct {
|
||||
Status
|
||||
models.Directory
|
||||
}
|
||||
|
||||
// GetDirectory a method, that searches by users or channels on all users and channels available on server.
|
||||
// It supports the Offset, Count, and Sort Query Parameters along with Query and Fields Query Parameters.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/directory
|
||||
func (c *Client) GetDirectory(params url.Values) (*models.Directory, error) {
|
||||
response := new(DirectoryResponse)
|
||||
if err := c.Get("directory", params, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Directory, nil
|
||||
}
|
||||
|
||||
type SpotlightResponse struct {
|
||||
Status
|
||||
models.Spotlight
|
||||
}
|
||||
|
||||
// GetSpotlight searches for users or rooms that are visible to the user.
|
||||
// WARNING: It will only return rooms that user didn’t join yet.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/spotlight
|
||||
func (c *Client) GetSpotlight(params url.Values) (*models.Spotlight, error) {
|
||||
response := new(SpotlightResponse)
|
||||
if err := c.Get("spotlight", params, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Spotlight, nil
|
||||
}
|
||||
|
||||
type StatisticsResponse struct {
|
||||
Status
|
||||
models.StatisticsInfo
|
||||
}
|
||||
|
||||
// GetStatistics
|
||||
// Statistics about the Rocket.Chat server.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/statistics
|
||||
func (c *Client) GetStatistics() (*models.StatisticsInfo, error) {
|
||||
response := new(StatisticsResponse)
|
||||
if err := c.Get("statistics", nil, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.StatisticsInfo, nil
|
||||
}
|
||||
|
||||
type StatisticsListResponse struct {
|
||||
Status
|
||||
models.StatisticsList
|
||||
}
|
||||
|
||||
// GetStatisticsList
|
||||
// Selectable statistics about the Rocket.Chat server.
|
||||
// It supports the Offset, Count and Sort Query Parameters along with just the Fields and Query Parameters.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/miscellaneous/statistics.list
|
||||
func (c *Client) GetStatisticsList(params url.Values) (*models.StatisticsList, error) {
|
||||
response := new(StatisticsListResponse)
|
||||
if err := c.Get("statistics.list", params, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.StatisticsList, nil
|
||||
}
|
67
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
generated
vendored
Normal file
67
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/messages.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
type MessagesResponse struct {
|
||||
Status
|
||||
Messages []models.Message `json:"messages"`
|
||||
}
|
||||
|
||||
type MessageResponse struct {
|
||||
Status
|
||||
Message models.Message `json:"message"`
|
||||
}
|
||||
|
||||
// Sends a message to a channel. The name of the channel has to be not nil.
|
||||
// The message will be html escaped.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
|
||||
func (c *Client) Send(channel *models.Channel, msg string) error {
|
||||
body := fmt.Sprintf(`{ "channel": "%s", "text": "%s"}`, channel.Name, html.EscapeString(msg))
|
||||
return c.Post("chat.postMessage", bytes.NewBufferString(body), new(MessageResponse))
|
||||
}
|
||||
|
||||
// PostMessage send a message to a channel. The channel or roomId has to be not nil.
|
||||
// The message will be json encode.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage
|
||||
func (c *Client) PostMessage(msg *models.PostMessage) (*MessageResponse, error) {
|
||||
body, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := new(MessageResponse)
|
||||
err = c.Post("chat.postMessage", bytes.NewBuffer(body), response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// Get messages from a channel. The channel id has to be not nil. Optionally a
|
||||
// count can be specified to limit the size of the returned messages.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/channels/history
|
||||
func (c *Client) GetMessages(channel *models.Channel, page *models.Pagination) ([]models.Message, error) {
|
||||
params := url.Values{
|
||||
"roomId": []string{channel.ID},
|
||||
}
|
||||
|
||||
if page != nil {
|
||||
params.Add("count", strconv.Itoa(page.Count))
|
||||
}
|
||||
|
||||
response := new(MessagesResponse)
|
||||
if err := c.Get("channels.history", params, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.Messages, nil
|
||||
}
|
145
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
generated
vendored
Normal file
145
vendor/github.com/matterbridge/Rocket.Chat.Go.SDK/rest/users.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/matterbridge/Rocket.Chat.Go.SDK/models"
|
||||
)
|
||||
|
||||
type logoutResponse struct {
|
||||
Status
|
||||
Data struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type logonResponse struct {
|
||||
Status
|
||||
Data struct {
|
||||
Token string `json:"authToken"`
|
||||
UserID string `json:"userID"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type CreateUserResponse struct {
|
||||
Status
|
||||
User struct {
|
||||
ID string `json:"_id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Services struct {
|
||||
Password struct {
|
||||
Bcrypt string `json:"bcrypt"`
|
||||
} `json:"password"`
|
||||
} `json:"services"`
|
||||
Username string `json:"username"`
|
||||
Emails []struct {
|
||||
Address string `json:"address"`
|
||||
Verified bool `json:"verified"`
|
||||
} `json:"emails"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Active bool `json:"active"`
|
||||
Roles []string `json:"roles"`
|
||||
UpdatedAt time.Time `json:"_updatedAt"`
|
||||
Name string `json:"name"`
|
||||
CustomFields map[string]string `json:"customFields"`
|
||||
} `json:"user"`
|
||||
}
|
||||
|
||||
// Login a user. The Email and the Password are mandatory. The auth token of the user is stored in the Client instance.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/authentication/login
|
||||
func (c *Client) Login(credentials *models.UserCredentials) error {
|
||||
if c.auth != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if credentials.ID != "" && credentials.Token != "" {
|
||||
c.auth = &authInfo{id: credentials.ID, token: credentials.Token}
|
||||
return nil
|
||||
}
|
||||
|
||||
response := new(logonResponse)
|
||||
data := url.Values{"user": {credentials.Email}, "password": {credentials.Password}}
|
||||
if err := c.PostForm("login", data, response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.auth = &authInfo{id: response.Data.UserID, token: response.Data.Token}
|
||||
credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateToken creates an access token for a user
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/users/createtoken/
|
||||
func (c *Client) CreateToken(userID, username string) (*models.UserCredentials, error) {
|
||||
response := new(logonResponse)
|
||||
data := url.Values{"userId": {userID}, "username": {username}}
|
||||
if err := c.PostForm("users.createToken", data, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credentials := &models.UserCredentials{}
|
||||
credentials.ID, credentials.Token = response.Data.UserID, response.Data.Token
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
// Logout a user. The function returns the response message of the server.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/authentication/logout
|
||||
func (c *Client) Logout() (string, error) {
|
||||
|
||||
if c.auth == nil {
|
||||
return "Was not logged in", nil
|
||||
}
|
||||
|
||||
response := new(logoutResponse)
|
||||
if err := c.Get("logout", nil, response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.Data.Message, nil
|
||||
}
|
||||
|
||||
// CreateUser being logged in with a user that has permission to do so.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/users/create
|
||||
func (c *Client) CreateUser(req *models.CreateUserRequest) (*CreateUserResponse, error) {
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := new(CreateUserResponse)
|
||||
err = c.Post("users.create", bytes.NewBuffer(body), response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// UpdateUser updates a user's data being logged in with a user that has permission to do so.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/users/update/
|
||||
func (c *Client) UpdateUser(req *models.UpdateUserRequest) (*CreateUserResponse, error) {
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := new(CreateUserResponse)
|
||||
err = c.Post("users.update", bytes.NewBuffer(body), response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// SetUserAvatar updates a user's avatar being logged in with a user that has permission to do so.
|
||||
// Currently only passing an URL is possible.
|
||||
//
|
||||
// https://rocket.chat/docs/developer-guides/rest-api/users/setavatar/
|
||||
func (c *Client) SetUserAvatar(userID, username, avatarURL string) (*Status, error) {
|
||||
body := fmt.Sprintf(`{ "userId": "%s","username": "%s","avatarUrl":"%s"}`, userID, username, avatarURL)
|
||||
response := new(Status)
|
||||
err := c.Post("users.setAvatar", bytes.NewBufferString(body), response)
|
||||
return response, err
|
||||
}
|
37
vendor/github.com/nelsonken/gomf/README.md
generated
vendored
Normal file
37
vendor/github.com/nelsonken/gomf/README.md
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
# golang 可多文件上传的request builder 库
|
||||
|
||||
## 测试方法
|
||||
|
||||
1. start php upload server: php -S 127.0.0.1:8080 ./
|
||||
2. run go test -v
|
||||
|
||||
## 使用方法
|
||||
|
||||
```go
|
||||
fb := gomf.New()
|
||||
fb.WriteField("name", "accountName")
|
||||
fb.WriteField("password", "pwd")
|
||||
fb.WriteFile("picture", "icon.png", "image/jpeg", []byte(strings.Repeat("0", 100)))
|
||||
|
||||
log.Println(fb.GetBuffer().String())
|
||||
|
||||
req, err := fb.GetHTTPRequest(context.Background(), "http://127.0.0.1:8080/up.php")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
|
||||
log.Println(res.StatusCode)
|
||||
log.Println(res.Status)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println(string(b))
|
||||
```
|
89
vendor/github.com/nelsonken/gomf/form_builder.go
generated
vendored
Normal file
89
vendor/github.com/nelsonken/gomf/form_builder.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
package gomf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
)
|
||||
|
||||
type FormBuilder struct {
|
||||
w *multipart.Writer
|
||||
b *bytes.Buffer
|
||||
}
|
||||
|
||||
func New() *FormBuilder {
|
||||
buf := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(buf)
|
||||
return &FormBuilder{
|
||||
w: writer,
|
||||
b: buf,
|
||||
}
|
||||
}
|
||||
|
||||
func (ufw *FormBuilder) WriteField(name, value string) error {
|
||||
w, err := ufw.w.CreateFormField(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteFile if contentType is empty-string, will auto convert to application/octet-stream
|
||||
func (ufw *FormBuilder) WriteFile(fieldName, fileName, contentType string, content []byte) error {
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
wx, err := ufw.w.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Type": []string{
|
||||
contentType,
|
||||
},
|
||||
"Content-Disposition": []string{
|
||||
fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fileName),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = wx.Write(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fb *FormBuilder) Close() error {
|
||||
return fb.w.Close()
|
||||
}
|
||||
|
||||
func (fb *FormBuilder) GetBuffer() *bytes.Buffer {
|
||||
return fb.b
|
||||
}
|
||||
|
||||
func (fb *FormBuilder) GetHTTPRequest(ctx context.Context, reqURL string) (*http.Request, error) {
|
||||
err := fb.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", reqURL, fb.b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", fb.w.FormDataContentType())
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
return req, nil
|
||||
}
|
33
vendor/github.com/nelsonken/gomf/up.php
generated
vendored
Normal file
33
vendor/github.com/nelsonken/gomf/up.php
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* PHP UPLOAD DEMO for gomf test
|
||||
* USAGE:
|
||||
* php -S 127.0.0.1:8080 -t ./
|
||||
*
|
||||
*/
|
||||
|
||||
print_r($_FILES);
|
||||
|
||||
if ($_FILES["picture"]["error"] > 0) {
|
||||
echo "Return Code: " . $_FILES["picture"]["error"] . "\n";
|
||||
} else {
|
||||
echo "Upload: " . $_FILES["picture"]["name"] . "\n";
|
||||
echo "Type: " . $_FILES["picture"]["type"] . "\n";
|
||||
echo "Size: " . ($_FILES["picture"]["size"] / 1024) . " Kb\n";
|
||||
echo "Temp file: " . $_FILES["picture"]["tmp_name"] . "\n>";
|
||||
|
||||
if (file_exists($_FILES["picture"]["name"]))
|
||||
{
|
||||
echo $_FILES["picture"]["name"] . " already exists. \n";
|
||||
}
|
||||
else
|
||||
{
|
||||
move_uploaded_file($_FILES["picture"]["tmp_name"], $_FILES["picture"]["name"]);
|
||||
echo "Stored in: " . $_FILES["picture"]["name"] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// DialError is an error that occurs while dialling a websocket server.
|
||||
type DialError struct {
|
||||
*Config
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *DialError) Error() string {
|
||||
return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// NewConfig creates a new WebSocket config for client connection.
|
||||
func NewConfig(server, origin string) (config *Config, err error) {
|
||||
config = new(Config)
|
||||
config.Version = ProtocolVersionHybi13
|
||||
config.Location, err = url.ParseRequestURI(server)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config.Origin, err = url.ParseRequestURI(origin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config.Header = http.Header(make(map[string][]string))
|
||||
return
|
||||
}
|
||||
|
||||
// NewClient creates a new WebSocket client connection over rwc.
|
||||
func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
|
||||
br := bufio.NewReader(rwc)
|
||||
bw := bufio.NewWriter(rwc)
|
||||
err = hybiClientHandshake(config, br, bw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buf := bufio.NewReadWriter(br, bw)
|
||||
ws = newHybiClientConn(config, buf, rwc)
|
||||
return
|
||||
}
|
||||
|
||||
// Dial opens a new client connection to a WebSocket.
|
||||
func Dial(url_, protocol, origin string) (ws *Conn, err error) {
|
||||
config, err := NewConfig(url_, origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if protocol != "" {
|
||||
config.Protocol = []string{protocol}
|
||||
}
|
||||
return DialConfig(config)
|
||||
}
|
||||
|
||||
var portMap = map[string]string{
|
||||
"ws": "80",
|
||||
"wss": "443",
|
||||
}
|
||||
|
||||
func parseAuthority(location *url.URL) string {
|
||||
if _, ok := portMap[location.Scheme]; ok {
|
||||
if _, _, err := net.SplitHostPort(location.Host); err != nil {
|
||||
return net.JoinHostPort(location.Host, portMap[location.Scheme])
|
||||
}
|
||||
}
|
||||
return location.Host
|
||||
}
|
||||
|
||||
// DialConfig opens a new client connection to a WebSocket with a config.
|
||||
func DialConfig(config *Config) (ws *Conn, err error) {
|
||||
var client net.Conn
|
||||
if config.Location == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketLocation}
|
||||
}
|
||||
if config.Origin == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketOrigin}
|
||||
}
|
||||
dialer := config.Dialer
|
||||
if dialer == nil {
|
||||
dialer = &net.Dialer{}
|
||||
}
|
||||
client, err = dialWithDialer(dialer, config)
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
ws, err = NewClient(config, client)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
goto Error
|
||||
}
|
||||
return
|
||||
|
||||
Error:
|
||||
return nil, &DialError{config, err}
|
||||
}
|
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
Normal file
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
|
||||
switch config.Location.Scheme {
|
||||
case "ws":
|
||||
conn, err = dialer.Dial("tcp", parseAuthority(config.Location))
|
||||
|
||||
case "wss":
|
||||
conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)
|
||||
|
||||
default:
|
||||
err = ErrBadScheme
|
||||
}
|
||||
return
|
||||
}
|
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
@ -0,0 +1,583 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
// This file implements a protocol of hybi draft.
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
closeStatusNormal = 1000
|
||||
closeStatusGoingAway = 1001
|
||||
closeStatusProtocolError = 1002
|
||||
closeStatusUnsupportedData = 1003
|
||||
closeStatusFrameTooLarge = 1004
|
||||
closeStatusNoStatusRcvd = 1005
|
||||
closeStatusAbnormalClosure = 1006
|
||||
closeStatusBadMessageData = 1007
|
||||
closeStatusPolicyViolation = 1008
|
||||
closeStatusTooBigData = 1009
|
||||
closeStatusExtensionMismatch = 1010
|
||||
|
||||
maxControlFramePayloadLength = 125
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadMaskingKey = &ProtocolError{"bad masking key"}
|
||||
ErrBadPongMessage = &ProtocolError{"bad pong message"}
|
||||
ErrBadClosingStatus = &ProtocolError{"bad closing status"}
|
||||
ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
|
||||
ErrNotImplemented = &ProtocolError{"not implemented"}
|
||||
|
||||
handshakeHeader = map[string]bool{
|
||||
"Host": true,
|
||||
"Upgrade": true,
|
||||
"Connection": true,
|
||||
"Sec-Websocket-Key": true,
|
||||
"Sec-Websocket-Origin": true,
|
||||
"Sec-Websocket-Version": true,
|
||||
"Sec-Websocket-Protocol": true,
|
||||
"Sec-Websocket-Accept": true,
|
||||
}
|
||||
)
|
||||
|
||||
// A hybiFrameHeader is a frame header as defined in hybi draft.
|
||||
type hybiFrameHeader struct {
|
||||
Fin bool
|
||||
Rsv [3]bool
|
||||
OpCode byte
|
||||
Length int64
|
||||
MaskingKey []byte
|
||||
|
||||
data *bytes.Buffer
|
||||
}
|
||||
|
||||
// A hybiFrameReader is a reader for hybi frame.
|
||||
type hybiFrameReader struct {
|
||||
reader io.Reader
|
||||
|
||||
header hybiFrameHeader
|
||||
pos int64
|
||||
length int
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
|
||||
n, err = frame.reader.Read(msg)
|
||||
if frame.header.MaskingKey != nil {
|
||||
for i := 0; i < n; i++ {
|
||||
msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
|
||||
frame.pos++
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
|
||||
|
||||
func (frame *hybiFrameReader) HeaderReader() io.Reader {
|
||||
if frame.header.data == nil {
|
||||
return nil
|
||||
}
|
||||
if frame.header.data.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return frame.header.data
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
|
||||
|
||||
func (frame *hybiFrameReader) Len() (n int) { return frame.length }
|
||||
|
||||
// A hybiFrameReaderFactory creates new frame reader based on its frame type.
|
||||
type hybiFrameReaderFactory struct {
|
||||
*bufio.Reader
|
||||
}
|
||||
|
||||
// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
|
||||
// See Section 5.2 Base Framing protocol for detail.
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
|
||||
func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
|
||||
hybiFrame := new(hybiFrameReader)
|
||||
frame = hybiFrame
|
||||
var header []byte
|
||||
var b byte
|
||||
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
|
||||
for i := 0; i < 3; i++ {
|
||||
j := uint(6 - i)
|
||||
hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
|
||||
}
|
||||
hybiFrame.header.OpCode = header[0] & 0x0f
|
||||
|
||||
// Second byte. Mask/Payload len(7bits)
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
mask := (b & 0x80) != 0
|
||||
b &= 0x7f
|
||||
lengthFields := 0
|
||||
switch {
|
||||
case b <= 125: // Payload length 7bits.
|
||||
hybiFrame.header.Length = int64(b)
|
||||
case b == 126: // Payload length 7+16bits
|
||||
lengthFields = 2
|
||||
case b == 127: // Payload length 7+64bits
|
||||
lengthFields = 8
|
||||
}
|
||||
for i := 0; i < lengthFields; i++ {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
|
||||
b &= 0x7f
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
|
||||
}
|
||||
if mask {
|
||||
// Masking key. 4 bytes.
|
||||
for i := 0; i < 4; i++ {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
|
||||
}
|
||||
}
|
||||
hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
|
||||
hybiFrame.header.data = bytes.NewBuffer(header)
|
||||
hybiFrame.length = len(header) + int(hybiFrame.header.Length)
|
||||
return
|
||||
}
|
||||
|
||||
// A HybiFrameWriter is a writer for hybi frame.
|
||||
type hybiFrameWriter struct {
|
||||
writer *bufio.Writer
|
||||
|
||||
header *hybiFrameHeader
|
||||
}
|
||||
|
||||
func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
|
||||
var header []byte
|
||||
var b byte
|
||||
if frame.header.Fin {
|
||||
b |= 0x80
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if frame.header.Rsv[i] {
|
||||
j := uint(6 - i)
|
||||
b |= 1 << j
|
||||
}
|
||||
}
|
||||
b |= frame.header.OpCode
|
||||
header = append(header, b)
|
||||
if frame.header.MaskingKey != nil {
|
||||
b = 0x80
|
||||
} else {
|
||||
b = 0
|
||||
}
|
||||
lengthFields := 0
|
||||
length := len(msg)
|
||||
switch {
|
||||
case length <= 125:
|
||||
b |= byte(length)
|
||||
case length < 65536:
|
||||
b |= 126
|
||||
lengthFields = 2
|
||||
default:
|
||||
b |= 127
|
||||
lengthFields = 8
|
||||
}
|
||||
header = append(header, b)
|
||||
for i := 0; i < lengthFields; i++ {
|
||||
j := uint((lengthFields - i - 1) * 8)
|
||||
b = byte((length >> j) & 0xff)
|
||||
header = append(header, b)
|
||||
}
|
||||
if frame.header.MaskingKey != nil {
|
||||
if len(frame.header.MaskingKey) != 4 {
|
||||
return 0, ErrBadMaskingKey
|
||||
}
|
||||
header = append(header, frame.header.MaskingKey...)
|
||||
frame.writer.Write(header)
|
||||
data := make([]byte, length)
|
||||
for i := range data {
|
||||
data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
|
||||
}
|
||||
frame.writer.Write(data)
|
||||
err = frame.writer.Flush()
|
||||
return length, err
|
||||
}
|
||||
frame.writer.Write(header)
|
||||
frame.writer.Write(msg)
|
||||
err = frame.writer.Flush()
|
||||
return length, err
|
||||
}
|
||||
|
||||
func (frame *hybiFrameWriter) Close() error { return nil }
|
||||
|
||||
type hybiFrameWriterFactory struct {
|
||||
*bufio.Writer
|
||||
needMaskingKey bool
|
||||
}
|
||||
|
||||
func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
|
||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
|
||||
if buf.needMaskingKey {
|
||||
frameHeader.MaskingKey, err = generateMaskingKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
|
||||
}
|
||||
|
||||
type hybiFrameHandler struct {
|
||||
conn *Conn
|
||||
payloadType byte
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
|
||||
if handler.conn.IsServerConn() {
|
||||
// The client MUST mask all frames sent to the server.
|
||||
if frame.(*hybiFrameReader).header.MaskingKey == nil {
|
||||
handler.WriteClose(closeStatusProtocolError)
|
||||
return nil, io.EOF
|
||||
}
|
||||
} else {
|
||||
// The server MUST NOT mask all frames.
|
||||
if frame.(*hybiFrameReader).header.MaskingKey != nil {
|
||||
handler.WriteClose(closeStatusProtocolError)
|
||||
return nil, io.EOF
|
||||
}
|
||||
}
|
||||
if header := frame.HeaderReader(); header != nil {
|
||||
io.Copy(ioutil.Discard, header)
|
||||
}
|
||||
switch frame.PayloadType() {
|
||||
case ContinuationFrame:
|
||||
frame.(*hybiFrameReader).header.OpCode = handler.payloadType
|
||||
case TextFrame, BinaryFrame:
|
||||
handler.payloadType = frame.PayloadType()
|
||||
case CloseFrame:
|
||||
return nil, io.EOF
|
||||
case PingFrame, PongFrame:
|
||||
b := make([]byte, maxControlFramePayloadLength)
|
||||
n, err := io.ReadFull(frame, b)
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, frame)
|
||||
if frame.PayloadType() == PingFrame {
|
||||
if _, err := handler.WritePong(b[:n]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
|
||||
handler.conn.wio.Lock()
|
||||
defer handler.conn.wio.Unlock()
|
||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(msg, uint16(status))
|
||||
_, err = w.Write(msg)
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
|
||||
handler.conn.wio.Lock()
|
||||
defer handler.conn.wio.Unlock()
|
||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = w.Write(msg)
|
||||
w.Close()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
|
||||
func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
if buf == nil {
|
||||
br := bufio.NewReader(rwc)
|
||||
bw := bufio.NewWriter(rwc)
|
||||
buf = bufio.NewReadWriter(br, bw)
|
||||
}
|
||||
ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
|
||||
frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
|
||||
frameWriterFactory: hybiFrameWriterFactory{
|
||||
buf.Writer, request == nil},
|
||||
PayloadType: TextFrame,
|
||||
defaultCloseStatus: closeStatusNormal}
|
||||
ws.frameHandler = &hybiFrameHandler{conn: ws}
|
||||
return ws
|
||||
}
|
||||
|
||||
// generateMaskingKey generates a masking key for a frame.
|
||||
func generateMaskingKey() (maskingKey []byte, err error) {
|
||||
maskingKey = make([]byte, 4)
|
||||
if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// generateNonce generates a nonce consisting of a randomly selected 16-byte
|
||||
// value that has been base64-encoded.
|
||||
func generateNonce() (nonce []byte) {
|
||||
key := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nonce = make([]byte, 24)
|
||||
base64.StdEncoding.Encode(nonce, key)
|
||||
return
|
||||
}
|
||||
|
||||
// removeZone removes IPv6 zone identifer from host.
|
||||
// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
|
||||
func removeZone(host string) string {
|
||||
if !strings.HasPrefix(host, "[") {
|
||||
return host
|
||||
}
|
||||
i := strings.LastIndex(host, "]")
|
||||
if i < 0 {
|
||||
return host
|
||||
}
|
||||
j := strings.LastIndex(host[:i], "%")
|
||||
if j < 0 {
|
||||
return host
|
||||
}
|
||||
return host[:j] + host[i:]
|
||||
}
|
||||
|
||||
// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
|
||||
// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
|
||||
func getNonceAccept(nonce []byte) (expected []byte, err error) {
|
||||
h := sha1.New()
|
||||
if _, err = h.Write(nonce); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = h.Write([]byte(websocketGUID)); err != nil {
|
||||
return
|
||||
}
|
||||
expected = make([]byte, 28)
|
||||
base64.StdEncoding.Encode(expected, h.Sum(nil))
|
||||
return
|
||||
}
|
||||
|
||||
// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
|
||||
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
|
||||
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
|
||||
|
||||
// According to RFC 6874, an HTTP client, proxy, or other
|
||||
// intermediary must remove any IPv6 zone identifier attached
|
||||
// to an outgoing URI.
|
||||
bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
|
||||
bw.WriteString("Upgrade: websocket\r\n")
|
||||
bw.WriteString("Connection: Upgrade\r\n")
|
||||
nonce := generateNonce()
|
||||
if config.handshakeData != nil {
|
||||
nonce = []byte(config.handshakeData["key"])
|
||||
}
|
||||
bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
|
||||
bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
|
||||
|
||||
if config.Version != ProtocolVersionHybi13 {
|
||||
return ErrBadProtocolVersion
|
||||
}
|
||||
|
||||
bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
|
||||
if len(config.Protocol) > 0 {
|
||||
bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
|
||||
}
|
||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||
err = config.Header.WriteSubset(bw, handshakeHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bw.WriteString("\r\n")
|
||||
if err = bw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 101 {
|
||||
return ErrBadStatus
|
||||
}
|
||||
if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
|
||||
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
|
||||
return ErrBadUpgrade
|
||||
}
|
||||
expectedAccept, err := getNonceAccept(nonce)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
|
||||
return ErrChallengeResponse
|
||||
}
|
||||
if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
|
||||
return ErrUnsupportedExtensions
|
||||
}
|
||||
offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
|
||||
if offeredProtocol != "" {
|
||||
protocolMatched := false
|
||||
for i := 0; i < len(config.Protocol); i++ {
|
||||
if config.Protocol[i] == offeredProtocol {
|
||||
protocolMatched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !protocolMatched {
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
config.Protocol = []string{offeredProtocol}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newHybiClientConn creates a client WebSocket connection after handshake.
|
||||
func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
|
||||
return newHybiConn(config, buf, rwc, nil)
|
||||
}
|
||||
|
||||
// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
|
||||
type hybiServerHandshaker struct {
|
||||
*Config
|
||||
accept []byte
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
|
||||
c.Version = ProtocolVersionHybi13
|
||||
if req.Method != "GET" {
|
||||
return http.StatusMethodNotAllowed, ErrBadRequestMethod
|
||||
}
|
||||
// HTTP version can be safely ignored.
|
||||
|
||||
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
|
||||
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
|
||||
return http.StatusBadRequest, ErrNotWebSocket
|
||||
}
|
||||
|
||||
key := req.Header.Get("Sec-Websocket-Key")
|
||||
if key == "" {
|
||||
return http.StatusBadRequest, ErrChallengeResponse
|
||||
}
|
||||
version := req.Header.Get("Sec-Websocket-Version")
|
||||
switch version {
|
||||
case "13":
|
||||
c.Version = ProtocolVersionHybi13
|
||||
default:
|
||||
return http.StatusBadRequest, ErrBadWebSocketVersion
|
||||
}
|
||||
var scheme string
|
||||
if req.TLS != nil {
|
||||
scheme = "wss"
|
||||
} else {
|
||||
scheme = "ws"
|
||||
}
|
||||
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
|
||||
if protocol != "" {
|
||||
protocols := strings.Split(protocol, ",")
|
||||
for i := 0; i < len(protocols); i++ {
|
||||
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
|
||||
}
|
||||
}
|
||||
c.accept, err = getNonceAccept([]byte(key))
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return http.StatusSwitchingProtocols, nil
|
||||
}
|
||||
|
||||
// Origin parses the Origin header in req.
|
||||
// If the Origin header is not set, it returns nil and nil.
|
||||
func Origin(config *Config, req *http.Request) (*url.URL, error) {
|
||||
var origin string
|
||||
switch config.Version {
|
||||
case ProtocolVersionHybi13:
|
||||
origin = req.Header.Get("Origin")
|
||||
}
|
||||
if origin == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return url.ParseRequestURI(origin)
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
|
||||
if len(c.Protocol) > 0 {
|
||||
if len(c.Protocol) != 1 {
|
||||
// You need choose a Protocol in Handshake func in Server.
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
}
|
||||
buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||
buf.WriteString("Upgrade: websocket\r\n")
|
||||
buf.WriteString("Connection: Upgrade\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
|
||||
if len(c.Protocol) > 0 {
|
||||
buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
|
||||
}
|
||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||
if c.Header != nil {
|
||||
err := c.Header.WriteSubset(buf, handshakeHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
return buf.Flush()
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
return newHybiServerConn(c.Config, buf, rwc, request)
|
||||
}
|
||||
|
||||
// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
|
||||
func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
return newHybiConn(config, buf, rwc, request)
|
||||
}
|
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) {
|
||||
var hs serverHandshaker = &hybiServerHandshaker{Config: config}
|
||||
code, err := hs.ReadHandshake(buf.Reader, req)
|
||||
if err == ErrBadWebSocketVersion {
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
|
||||
buf.WriteString("\r\n")
|
||||
buf.WriteString(err.Error())
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.WriteString(err.Error())
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
if handshake != nil {
|
||||
err = handshake(config, req)
|
||||
if err != nil {
|
||||
code = http.StatusForbidden
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
}
|
||||
err = hs.AcceptHandshake(buf.Writer)
|
||||
if err != nil {
|
||||
code = http.StatusBadRequest
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
conn = hs.NewServerConn(buf, rwc, req)
|
||||
return
|
||||
}
|
||||
|
||||
// Server represents a server of a WebSocket.
|
||||
type Server struct {
|
||||
// Config is a WebSocket configuration for new WebSocket connection.
|
||||
Config
|
||||
|
||||
// Handshake is an optional function in WebSocket handshake.
|
||||
// For example, you can check, or don't check Origin header.
|
||||
// Another example, you can select config.Protocol.
|
||||
Handshake func(*Config, *http.Request) error
|
||||
|
||||
// Handler handles a WebSocket connection.
|
||||
Handler
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
s.serveWebSocket(w, req)
|
||||
}
|
||||
|
||||
func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
|
||||
rwc, buf, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
panic("Hijack failed: " + err.Error())
|
||||
}
|
||||
// The server should abort the WebSocket connection if it finds
|
||||
// the client did not send a handshake that matches with protocol
|
||||
// specification.
|
||||
defer rwc.Close()
|
||||
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if conn == nil {
|
||||
panic("unexpected nil conn")
|
||||
}
|
||||
s.Handler(conn)
|
||||
}
|
||||
|
||||
// Handler is a simple interface to a WebSocket browser client.
|
||||
// It checks if Origin header is valid URL by default.
|
||||
// You might want to verify websocket.Conn.Config().Origin in the func.
|
||||
// If you use Server instead of Handler, you could call websocket.Origin and
|
||||
// check the origin in your Handshake func. So, if you want to accept
|
||||
// non-browser clients, which do not send an Origin header, set a
|
||||
// Server.Handshake that does not check the origin.
|
||||
type Handler func(*Conn)
|
||||
|
||||
func checkOrigin(config *Config, req *http.Request) (err error) {
|
||||
config.Origin, err = Origin(config, req)
|
||||
if err == nil && config.Origin == nil {
|
||||
return fmt.Errorf("null origin")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
s := Server{Handler: h, Handshake: checkOrigin}
|
||||
s.serveWebSocket(w, req)
|
||||
}
|
448
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
448
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
@ -0,0 +1,448 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package websocket implements a client and server for the WebSocket protocol
|
||||
// as specified in RFC 6455.
|
||||
//
|
||||
// This package currently lacks some features found in an alternative
|
||||
// and more actively maintained WebSocket package:
|
||||
//
|
||||
// https://godoc.org/github.com/gorilla/websocket
|
||||
//
|
||||
package websocket // import "golang.org/x/net/websocket"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ProtocolVersionHybi13 = 13
|
||||
ProtocolVersionHybi = ProtocolVersionHybi13
|
||||
SupportedProtocolVersion = "13"
|
||||
|
||||
ContinuationFrame = 0
|
||||
TextFrame = 1
|
||||
BinaryFrame = 2
|
||||
CloseFrame = 8
|
||||
PingFrame = 9
|
||||
PongFrame = 10
|
||||
UnknownFrame = 255
|
||||
|
||||
DefaultMaxPayloadBytes = 32 << 20 // 32MB
|
||||
)
|
||||
|
||||
// ProtocolError represents WebSocket protocol errors.
|
||||
type ProtocolError struct {
|
||||
ErrorString string
|
||||
}
|
||||
|
||||
func (err *ProtocolError) Error() string { return err.ErrorString }
|
||||
|
||||
var (
|
||||
ErrBadProtocolVersion = &ProtocolError{"bad protocol version"}
|
||||
ErrBadScheme = &ProtocolError{"bad scheme"}
|
||||
ErrBadStatus = &ProtocolError{"bad status"}
|
||||
ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"}
|
||||
ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"}
|
||||
ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
|
||||
ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
|
||||
ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"}
|
||||
ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"}
|
||||
ErrBadFrame = &ProtocolError{"bad frame"}
|
||||
ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"}
|
||||
ErrNotWebSocket = &ProtocolError{"not websocket protocol"}
|
||||
ErrBadRequestMethod = &ProtocolError{"bad method"}
|
||||
ErrNotSupported = &ProtocolError{"not supported"}
|
||||
)
|
||||
|
||||
// ErrFrameTooLarge is returned by Codec's Receive method if payload size
|
||||
// exceeds limit set by Conn.MaxPayloadBytes
|
||||
var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit")
|
||||
|
||||
// Addr is an implementation of net.Addr for WebSocket.
|
||||
type Addr struct {
|
||||
*url.URL
|
||||
}
|
||||
|
||||
// Network returns the network type for a WebSocket, "websocket".
|
||||
func (addr *Addr) Network() string { return "websocket" }
|
||||
|
||||
// Config is a WebSocket configuration
|
||||
type Config struct {
|
||||
// A WebSocket server address.
|
||||
Location *url.URL
|
||||
|
||||
// A Websocket client origin.
|
||||
Origin *url.URL
|
||||
|
||||
// WebSocket subprotocols.
|
||||
Protocol []string
|
||||
|
||||
// WebSocket protocol version.
|
||||
Version int
|
||||
|
||||
// TLS config for secure WebSocket (wss).
|
||||
TlsConfig *tls.Config
|
||||
|
||||
// Additional header fields to be sent in WebSocket opening handshake.
|
||||
Header http.Header
|
||||
|
||||
// Dialer used when opening websocket connections.
|
||||
Dialer *net.Dialer
|
||||
|
||||
handshakeData map[string]string
|
||||
}
|
||||
|
||||
// serverHandshaker is an interface to handle WebSocket server side handshake.
|
||||
type serverHandshaker interface {
|
||||
// ReadHandshake reads handshake request message from client.
|
||||
// Returns http response code and error if any.
|
||||
ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
|
||||
|
||||
// AcceptHandshake accepts the client handshake request and sends
|
||||
// handshake response back to client.
|
||||
AcceptHandshake(buf *bufio.Writer) (err error)
|
||||
|
||||
// NewServerConn creates a new WebSocket connection.
|
||||
NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
|
||||
}
|
||||
|
||||
// frameReader is an interface to read a WebSocket frame.
|
||||
type frameReader interface {
|
||||
// Reader is to read payload of the frame.
|
||||
io.Reader
|
||||
|
||||
// PayloadType returns payload type.
|
||||
PayloadType() byte
|
||||
|
||||
// HeaderReader returns a reader to read header of the frame.
|
||||
HeaderReader() io.Reader
|
||||
|
||||
// TrailerReader returns a reader to read trailer of the frame.
|
||||
// If it returns nil, there is no trailer in the frame.
|
||||
TrailerReader() io.Reader
|
||||
|
||||
// Len returns total length of the frame, including header and trailer.
|
||||
Len() int
|
||||
}
|
||||
|
||||
// frameReaderFactory is an interface to creates new frame reader.
|
||||
type frameReaderFactory interface {
|
||||
NewFrameReader() (r frameReader, err error)
|
||||
}
|
||||
|
||||
// frameWriter is an interface to write a WebSocket frame.
|
||||
type frameWriter interface {
|
||||
// Writer is to write payload of the frame.
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
// frameWriterFactory is an interface to create new frame writer.
|
||||
type frameWriterFactory interface {
|
||||
NewFrameWriter(payloadType byte) (w frameWriter, err error)
|
||||
}
|
||||
|
||||
type frameHandler interface {
|
||||
HandleFrame(frame frameReader) (r frameReader, err error)
|
||||
WriteClose(status int) (err error)
|
||||
}
|
||||
|
||||
// Conn represents a WebSocket connection.
|
||||
//
|
||||
// Multiple goroutines may invoke methods on a Conn simultaneously.
|
||||
type Conn struct {
|
||||
config *Config
|
||||
request *http.Request
|
||||
|
||||
buf *bufio.ReadWriter
|
||||
rwc io.ReadWriteCloser
|
||||
|
||||
rio sync.Mutex
|
||||
frameReaderFactory
|
||||
frameReader
|
||||
|
||||
wio sync.Mutex
|
||||
frameWriterFactory
|
||||
|
||||
frameHandler
|
||||
PayloadType byte
|
||||
defaultCloseStatus int
|
||||
|
||||
// MaxPayloadBytes limits the size of frame payload received over Conn
|
||||
// by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used.
|
||||
MaxPayloadBytes int
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface:
|
||||
// it reads data of a frame from the WebSocket connection.
|
||||
// if msg is not large enough for the frame data, it fills the msg and next Read
|
||||
// will read the rest of the frame data.
|
||||
// it reads Text frame or Binary frame.
|
||||
func (ws *Conn) Read(msg []byte) (n int, err error) {
|
||||
ws.rio.Lock()
|
||||
defer ws.rio.Unlock()
|
||||
again:
|
||||
if ws.frameReader == nil {
|
||||
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ws.frameReader == nil {
|
||||
goto again
|
||||
}
|
||||
}
|
||||
n, err = ws.frameReader.Read(msg)
|
||||
if err == io.EOF {
|
||||
if trailer := ws.frameReader.TrailerReader(); trailer != nil {
|
||||
io.Copy(ioutil.Discard, trailer)
|
||||
}
|
||||
ws.frameReader = nil
|
||||
goto again
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface:
|
||||
// it writes data as a frame to the WebSocket connection.
|
||||
func (ws *Conn) Write(msg []byte) (n int, err error) {
|
||||
ws.wio.Lock()
|
||||
defer ws.wio.Unlock()
|
||||
w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = w.Write(msg)
|
||||
w.Close()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close implements the io.Closer interface.
|
||||
func (ws *Conn) Close() error {
|
||||
err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
|
||||
err1 := ws.rwc.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
func (ws *Conn) IsClientConn() bool { return ws.request == nil }
|
||||
func (ws *Conn) IsServerConn() bool { return ws.request != nil }
|
||||
|
||||
// LocalAddr returns the WebSocket Origin for the connection for client, or
|
||||
// the WebSocket location for server.
|
||||
func (ws *Conn) LocalAddr() net.Addr {
|
||||
if ws.IsClientConn() {
|
||||
return &Addr{ws.config.Origin}
|
||||
}
|
||||
return &Addr{ws.config.Location}
|
||||
}
|
||||
|
||||
// RemoteAddr returns the WebSocket location for the connection for client, or
|
||||
// the Websocket Origin for server.
|
||||
func (ws *Conn) RemoteAddr() net.Addr {
|
||||
if ws.IsClientConn() {
|
||||
return &Addr{ws.config.Location}
|
||||
}
|
||||
return &Addr{ws.config.Origin}
|
||||
}
|
||||
|
||||
var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
|
||||
|
||||
// SetDeadline sets the connection's network read & write deadlines.
|
||||
func (ws *Conn) SetDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the connection's network read deadline.
|
||||
func (ws *Conn) SetReadDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetReadDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the connection's network write deadline.
|
||||
func (ws *Conn) SetWriteDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetWriteDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// Config returns the WebSocket config.
|
||||
func (ws *Conn) Config() *Config { return ws.config }
|
||||
|
||||
// Request returns the http request upgraded to the WebSocket.
|
||||
// It is nil for client side.
|
||||
func (ws *Conn) Request() *http.Request { return ws.request }
|
||||
|
||||
// Codec represents a symmetric pair of functions that implement a codec.
|
||||
type Codec struct {
|
||||
Marshal func(v interface{}) (data []byte, payloadType byte, err error)
|
||||
Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
|
||||
}
|
||||
|
||||
// Send sends v marshaled by cd.Marshal as single frame to ws.
|
||||
func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
|
||||
data, payloadType, err := cd.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.wio.Lock()
|
||||
defer ws.wio.Unlock()
|
||||
w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores
|
||||
// in v. The whole frame payload is read to an in-memory buffer; max size of
|
||||
// payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds
|
||||
// limit, ErrFrameTooLarge is returned; in this case frame is not read off wire
|
||||
// completely. The next call to Receive would read and discard leftover data of
|
||||
// previous oversized frame before processing next frame.
|
||||
func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
|
||||
ws.rio.Lock()
|
||||
defer ws.rio.Unlock()
|
||||
if ws.frameReader != nil {
|
||||
_, err = io.Copy(ioutil.Discard, ws.frameReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.frameReader = nil
|
||||
}
|
||||
again:
|
||||
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
frame, err = ws.frameHandler.HandleFrame(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if frame == nil {
|
||||
goto again
|
||||
}
|
||||
maxPayloadBytes := ws.MaxPayloadBytes
|
||||
if maxPayloadBytes == 0 {
|
||||
maxPayloadBytes = DefaultMaxPayloadBytes
|
||||
}
|
||||
if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) {
|
||||
// payload size exceeds limit, no need to call Unmarshal
|
||||
//
|
||||
// set frameReader to current oversized frame so that
|
||||
// the next call to this function can drain leftover
|
||||
// data before processing the next frame
|
||||
ws.frameReader = frame
|
||||
return ErrFrameTooLarge
|
||||
}
|
||||
payloadType := frame.PayloadType()
|
||||
data, err := ioutil.ReadAll(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cd.Unmarshal(data, payloadType, v)
|
||||
}
|
||||
|
||||
func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||
switch data := v.(type) {
|
||||
case string:
|
||||
return []byte(data), TextFrame, nil
|
||||
case []byte:
|
||||
return data, BinaryFrame, nil
|
||||
}
|
||||
return nil, UnknownFrame, ErrNotSupported
|
||||
}
|
||||
|
||||
func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||
switch data := v.(type) {
|
||||
case *string:
|
||||
*data = string(msg)
|
||||
return nil
|
||||
case *[]byte:
|
||||
*data = msg
|
||||
return nil
|
||||
}
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
/*
|
||||
Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
|
||||
To send/receive text frame, use string type.
|
||||
To send/receive binary frame, use []byte type.
|
||||
|
||||
Trivial usage:
|
||||
|
||||
import "websocket"
|
||||
|
||||
// receive text frame
|
||||
var message string
|
||||
websocket.Message.Receive(ws, &message)
|
||||
|
||||
// send text frame
|
||||
message = "hello"
|
||||
websocket.Message.Send(ws, message)
|
||||
|
||||
// receive binary frame
|
||||
var data []byte
|
||||
websocket.Message.Receive(ws, &data)
|
||||
|
||||
// send binary frame
|
||||
data = []byte{0, 1, 2}
|
||||
websocket.Message.Send(ws, data)
|
||||
|
||||
*/
|
||||
var Message = Codec{marshal, unmarshal}
|
||||
|
||||
func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||
msg, err = json.Marshal(v)
|
||||
return msg, TextFrame, err
|
||||
}
|
||||
|
||||
func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||
return json.Unmarshal(msg, v)
|
||||
}
|
||||
|
||||
/*
|
||||
JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
|
||||
|
||||
Trivial usage:
|
||||
|
||||
import "websocket"
|
||||
|
||||
type T struct {
|
||||
Msg string
|
||||
Count int
|
||||
}
|
||||
|
||||
// receive JSON type T
|
||||
var data T
|
||||
websocket.JSON.Receive(ws, &data)
|
||||
|
||||
// send JSON type T
|
||||
websocket.JSON.Send(ws, data)
|
||||
*/
|
||||
var JSON = Codec{jsonMarshal, jsonUnmarshal}
|
12
vendor/modules.txt
vendored
12
vendor/modules.txt
vendored
@ -1,5 +1,7 @@
|
||||
# github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
||||
github.com/42wim/go-gitter
|
||||
# github.com/Jeffail/gabs v1.1.1
|
||||
github.com/Jeffail/gabs
|
||||
# github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
|
||||
github.com/Philipp15b/go-steam
|
||||
github.com/Philipp15b/go-steam/protocol/steamlang
|
||||
@ -30,6 +32,8 @@ github.com/golang/protobuf/protoc-gen-go/descriptor
|
||||
github.com/google/gops/agent
|
||||
github.com/google/gops/internal
|
||||
github.com/google/gops/signal
|
||||
# github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4
|
||||
github.com/gopackage/ddp
|
||||
# github.com/gorilla/schema v1.0.2
|
||||
github.com/gorilla/schema
|
||||
# github.com/gorilla/websocket v1.4.0
|
||||
@ -66,6 +70,10 @@ github.com/labstack/gommon/random
|
||||
github.com/lrstanley/girc
|
||||
# github.com/magiconair/properties v1.8.0
|
||||
github.com/magiconair/properties
|
||||
# github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20190210153444-cc9d05784d5d
|
||||
github.com/matterbridge/Rocket.Chat.Go.SDK/models
|
||||
github.com/matterbridge/Rocket.Chat.Go.SDK/realtime
|
||||
github.com/matterbridge/Rocket.Chat.Go.SDK/rest
|
||||
# github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
|
||||
github.com/matterbridge/go-xmpp
|
||||
# github.com/matterbridge/gomatrix v0.0.0-20190102230110-6f9631ca6dea
|
||||
@ -91,6 +99,8 @@ github.com/mitchellh/mapstructure
|
||||
github.com/mreiferson/go-httpclient
|
||||
# github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff
|
||||
github.com/mrexodia/wray
|
||||
# github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9
|
||||
github.com/nelsonken/gomf
|
||||
# github.com/nicksnyder/go-i18n v1.4.0
|
||||
github.com/nicksnyder/go-i18n/i18n
|
||||
github.com/nicksnyder/go-i18n/i18n/bundle
|
||||
@ -184,6 +194,8 @@ golang.org/x/crypto/curve25519
|
||||
golang.org/x/crypto/ed25519
|
||||
golang.org/x/crypto/internal/chacha20
|
||||
golang.org/x/crypto/ed25519/internal/edwards25519
|
||||
# golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37
|
||||
golang.org/x/net/websocket
|
||||
# golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
|
Loading…
Reference in New Issue
Block a user