mirror of
https://github.com/FluuxIO/go-xmpp.git
synced 2025-11-07 10:43:44 -08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8794ea6ed8 | ||
|
|
7e596fc33c | ||
|
|
6f9808fe16 | ||
|
|
7b1f83f6b7 | ||
|
|
6005a964ba | ||
|
|
d3b45b42a5 | ||
|
|
6e65ba2a0b | ||
|
|
76f59be5ed | ||
|
|
80d2e0fa1e | ||
|
|
2e864ff7f6 | ||
|
|
3de99e0e0e | ||
|
|
e531370dc9 |
@@ -2,4 +2,5 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/processone/mpg123 v1.0.0/go.mod h1:X/FeL+h8vD1bYsG9tIWV3M2c4qNTZOficyvPVBP08go=
|
||||
github.com/processone/soundcloud v1.0.0/go.mod h1:kDLeWpkRtN3C8kIReQdxoiRi92P9xR6yW6qLOJnNWfY=
|
||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
60
client.go
60
client.go
@@ -31,6 +31,18 @@ type Event struct {
|
||||
State ConnState
|
||||
Description string
|
||||
StreamError string
|
||||
SMState SMState
|
||||
}
|
||||
|
||||
// SMState holds Stream Management information regarding the session that can be
|
||||
// used to resume session after disconnect
|
||||
type SMState struct {
|
||||
// Stream Management ID
|
||||
Id string
|
||||
// Inbound stanza count
|
||||
Inbound uint
|
||||
// TODO Store location for IP affinity
|
||||
// TODO Store max and timestamp, to check if we should retry resumption or not
|
||||
}
|
||||
|
||||
// EventHandler is use to pass events about state of the connection to
|
||||
@@ -52,6 +64,13 @@ func (em EventManager) updateState(state ConnState) {
|
||||
}
|
||||
}
|
||||
|
||||
func (em EventManager) disconnected(state SMState) {
|
||||
em.CurrentState = StateDisconnected
|
||||
if em.Handler != nil {
|
||||
em.Handler(Event{State: em.CurrentState, SMState: state})
|
||||
}
|
||||
}
|
||||
|
||||
func (em EventManager) streamError(error, desc string) {
|
||||
em.CurrentState = StateStreamError
|
||||
if em.Handler != nil {
|
||||
@@ -128,7 +147,15 @@ func NewClient(config Config, r *Router) (c *Client, err error) {
|
||||
}
|
||||
|
||||
// Connect triggers actual TCP connection, based on previously defined parameters.
|
||||
// Connect simply triggers resumption, with an empty session state.
|
||||
func (c *Client) Connect() error {
|
||||
var state SMState
|
||||
return c.Resume(state)
|
||||
}
|
||||
|
||||
// Resume attempts resuming a Stream Managed session, based on the provided stream management
|
||||
// state.
|
||||
func (c *Client) Resume(state SMState) error {
|
||||
var err error
|
||||
|
||||
c.conn, err = net.DialTimeout("tcp", c.config.Address, time.Duration(c.config.ConnectTimeout)*time.Second)
|
||||
@@ -138,30 +165,34 @@ func (c *Client) Connect() error {
|
||||
c.updateState(StateConnected)
|
||||
|
||||
// Client is ok, we now open XMPP session
|
||||
if c.conn, c.Session, err = NewSession(c.conn, c.config); err != nil {
|
||||
if c.conn, c.Session, err = NewSession(c.conn, c.config, state); err != nil {
|
||||
return err
|
||||
}
|
||||
c.updateState(StateSessionEstablished)
|
||||
|
||||
// Start the keepalive go routine
|
||||
keepaliveQuit := make(chan struct{})
|
||||
go keepalive(c.conn, keepaliveQuit)
|
||||
// Start the receiver go routine
|
||||
state = c.Session.SMState
|
||||
go c.recv(state, keepaliveQuit)
|
||||
|
||||
// We're connected and can now receive and send messages.
|
||||
//fmt.Fprintf(client.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", "chat", "Online")
|
||||
// TODO: Do we always want to send initial presence automatically ?
|
||||
// Do we need an option to avoid that or do we rely on client to send the presence itself ?
|
||||
fmt.Fprintf(c.Session.streamLogger, "<presence/>")
|
||||
|
||||
// Start the keepalive go routine
|
||||
keepaliveQuit := make(chan struct{})
|
||||
go keepalive(c.conn, keepaliveQuit)
|
||||
// Start the receiver go routine
|
||||
go c.recv(keepaliveQuit)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Disconnect() {
|
||||
_ = c.SendRaw("</stream:stream>")
|
||||
// TODO: Add a way to wait for stream close acknowledgement from the server for clean disconnect
|
||||
_ = c.conn.Close()
|
||||
conn := c.conn
|
||||
if conn != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SetHandler(handler EventHandler) {
|
||||
@@ -206,12 +237,12 @@ func (c *Client) sendWithLogger(packet string) error {
|
||||
// Go routines
|
||||
|
||||
// Loop: Receive data from server
|
||||
func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
|
||||
func (c *Client) recv(state SMState, keepaliveQuit chan<- struct{}) (err error) {
|
||||
for {
|
||||
val, err := stanza.NextPacket(c.Session.decoder)
|
||||
if err != nil {
|
||||
close(keepaliveQuit)
|
||||
c.updateState(StateDisconnected)
|
||||
c.disconnected(state)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -222,6 +253,15 @@ func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
|
||||
close(keepaliveQuit)
|
||||
c.streamError(packet.Error.Local, packet.Text)
|
||||
return errors.New("stream error: " + packet.Error.Local)
|
||||
// Process Stream management nonzas
|
||||
case stanza.SMRequest:
|
||||
answer := stanza.SMAnswer{XMLName: xml.Name{
|
||||
Space: stanza.NSStreamManagement,
|
||||
Local: "a",
|
||||
}, H: state.Inbound}
|
||||
c.Send(answer)
|
||||
default:
|
||||
state.Inbound++
|
||||
}
|
||||
|
||||
c.router.route(c, val)
|
||||
|
||||
198
cmd/fluuxmpp/README.md
Normal file
198
cmd/fluuxmpp/README.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# fluuxmpp
|
||||
|
||||
fluuxIO's XMPP command-line tool
|
||||
|
||||
## Installation
|
||||
|
||||
To install `fluuxmpp` in your Go path:
|
||||
|
||||
```
|
||||
$ go get -u gosrc.io/xmpp/cmd/fluuxmpp
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ fluuxmpp --help
|
||||
fluuxIO's xmpp comandline tool
|
||||
|
||||
Usage:
|
||||
fluuxmpp [command]
|
||||
|
||||
Available Commands:
|
||||
check is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires
|
||||
help Help about any command
|
||||
send is a command-line tool to send to send XMPP messages to users
|
||||
|
||||
Flags:
|
||||
-h, --help help for fluuxmpp
|
||||
|
||||
Use "fluuxmpp [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
### check tls
|
||||
|
||||
```
|
||||
$ fluuxmpp check --help
|
||||
is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires
|
||||
|
||||
Usage:
|
||||
fluuxmpp check <host[:port]> [flags]
|
||||
|
||||
Examples:
|
||||
fluuxmpp check chat.sum7.eu:5222 --domain meckerspace.de
|
||||
|
||||
Flags:
|
||||
-d, --domain string domain if host handle multiple domains
|
||||
-h, --help help for check
|
||||
```
|
||||
|
||||
### sending messages
|
||||
|
||||
```
|
||||
$ fluuxmpp send --help
|
||||
is a command-line tool to send to send XMPP messages to users
|
||||
|
||||
Usage:
|
||||
fluuxmpp send <recipient,> [message] [flags]
|
||||
|
||||
Examples:
|
||||
fluuxmpp send to@chat.sum7.eu "Hello World!"
|
||||
|
||||
Flags:
|
||||
--addr string host[:port]
|
||||
--config string config file (default is ~/.config/fluuxmpp.yml)
|
||||
-h, --help help for send
|
||||
--jid string using jid (required)
|
||||
-m, --muc recipient is a muc (join it before sending messages)
|
||||
--password string using password for your jid (required)
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### check tls
|
||||
|
||||
If you server is on standard port and XMPP domains matches the hostname you can simply use:
|
||||
|
||||
```
|
||||
$ fluuxmpp check chat.sum7.eu
|
||||
info All checks passed
|
||||
⇢ address="chat.sum7.eu" domain=""
|
||||
⇢ main.go:43 main.runCheck
|
||||
⇢ 2019-07-16T22:01:39.765+02:00
|
||||
```
|
||||
|
||||
You can also pass the port and the XMPP domain if different from the server hostname:
|
||||
|
||||
```
|
||||
$ fluuxmpp check chat.sum7.eu:5222 --domain meckerspace.de
|
||||
info All checks passed
|
||||
⇢ address="chat.sum7.eu:5222" domain="meckerspace.de"
|
||||
⇢ main.go:43 main.runCheck
|
||||
⇢ 2019-07-16T22:01:33.270+02:00
|
||||
```
|
||||
|
||||
Error code will be non-zero in case of error. You can thus use it directly with your usual
|
||||
monitoring scripts.
|
||||
|
||||
|
||||
### sending messages
|
||||
|
||||
Message from arguments:
|
||||
```bash
|
||||
$ fluuxmpp send to@example.org "Hello World!"
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:42:43.310+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Hello World!" to="to@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:42:43.310+02:00
|
||||
```
|
||||
|
||||
Message from STDIN:
|
||||
```bash
|
||||
$ journalctl -f | fluuxmpp send to@example.org -
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:40:03.177+02:00
|
||||
info send message
|
||||
⇢ muc=false text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="to@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:40:03.178+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Jul 17 23:36:46 RECHNERNAME systemd[755]: Started Fetch mails." to="to@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:40:03.178+02:00
|
||||
^C
|
||||
```
|
||||
|
||||
|
||||
Multiple recipients:
|
||||
```bash
|
||||
$ fluuxmpp send to1@example.org,to2@example.org "Multiple recipient"
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:47:57.650+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Multiple recipient" to="to1@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:47:57.651+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Multiple recipient" to="to2@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:47:57.652+02:00
|
||||
```
|
||||
|
||||
Send to MUC:
|
||||
```bash
|
||||
journalctl -f | fluuxmpp send testit@conference.chat.sum7.eu - --muc
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:52:56.269+02:00
|
||||
info send message
|
||||
⇢ muc=true text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="testit@conference.chat.sum7.eu"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:52:56.270+02:00
|
||||
info send message
|
||||
⇢ muc=true text="Jul 17 23:48:58 RECHNERNAME systemd[755]: mail.service: Succeeded." to="testit@conference.chat.sum7.eu"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:52:56.277+02:00
|
||||
^C
|
||||
```
|
||||
|
||||
## Authentification
|
||||
|
||||
### Configuration file
|
||||
|
||||
In `/etc/`, `~/.config` and `.` (here).
|
||||
You could create the file name `fluuxmpp` with you favorite file extension (e.g. `toml`, `yml`).
|
||||
|
||||
e.g. ~/.config/fluuxmpp.toml
|
||||
```toml
|
||||
jid = "bot@example.org"
|
||||
password = "secret"
|
||||
|
||||
addr = "example.com:5222"
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
```bash
|
||||
export FLUXXMPP_JID='bot@example.org';
|
||||
export FLUXXMPP_PASSWORD='secret';
|
||||
|
||||
export FLUXXMPP_ADDR='example.com:5222';
|
||||
|
||||
fluuxmpp send to@example.org "Hello Welt";
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Warning: This should not be used for production systems, as all users on the system
|
||||
can read the running processes, and their parameters (and thus the password).
|
||||
|
||||
```bash
|
||||
fluuxmpp send to@example.org "Hello World!" --jid bot@example.org --password secret --addr example.com:5222;
|
||||
```
|
||||
@@ -1,11 +1,18 @@
|
||||
# TODO
|
||||
|
||||
## Issues
|
||||
## check
|
||||
### Features
|
||||
|
||||
- Use a config file to define the checks to perform as client on an XMPP server.
|
||||
|
||||
## send
|
||||
|
||||
### Issues
|
||||
|
||||
- Remove global variable (like mucToleave)
|
||||
- Does not report error when trying to connect to a non open port (for example localhost with no server running).
|
||||
|
||||
## Features
|
||||
### Features
|
||||
|
||||
- configuration
|
||||
- allow unencrypted
|
||||
@@ -6,15 +6,11 @@ import (
|
||||
"gosrc.io/xmpp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.AddHook(&hook{})
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
var domain = ""
|
||||
var cmd = &cobra.Command{
|
||||
Use: "xmpp-check <host[:port]>",
|
||||
Example: "xmpp-check chat.sum7.eu:5222 --domain meckerspace.de",
|
||||
var cmdCheck = &cobra.Command{
|
||||
Use: "check <host[:port]>",
|
||||
Short: "is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires",
|
||||
Example: "fluuxmpp check chat.sum7.eu:5222 --domain meckerspace.de",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runCheck(args[0], domain)
|
||||
@@ -22,7 +18,8 @@ var cmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd.Flags().StringVarP(&domain, "domain", "d", "", "domain if host handle multiple domains")
|
||||
cmdRoot.AddCommand(cmdCheck)
|
||||
cmdCheck.Flags().StringVarP(&domain, "domain", "d", "", "domain if host handle multiple domains")
|
||||
}
|
||||
|
||||
func runCheck(address, domain string) {
|
||||
5
cmd/fluuxmpp/doc.go
Normal file
5
cmd/fluuxmpp/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
|
||||
fluuxmpp: fluuxIO's xmpp comandline tool
|
||||
*/
|
||||
package main
|
||||
19
cmd/fluuxmpp/main.go
Normal file
19
cmd/fluuxmpp/main.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/bdlm/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// cmdRoot represents the base command when called without any subcommands
|
||||
var cmdRoot = &cobra.Command{
|
||||
Use: "fluuxmpp",
|
||||
Short: "fluuxIO's xmpp comandline tool",
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.AddHook(&hook{})
|
||||
if err := cmdRoot.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,10 @@ var configFile = ""
|
||||
// FIXME: Remove global variables
|
||||
var isMUCRecipient = false
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "sendxmpp <recipient,> [message]",
|
||||
Example: `sendxmpp to@chat.sum7.eu "Hello World!"`,
|
||||
var cmdSend = &cobra.Command{
|
||||
Use: "send <recipient,> [message]",
|
||||
Short: "is a command-line tool to send to send XMPP messages to users",
|
||||
Example: `fluuxmpp send to@chat.sum7.eu "Hello World!"`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: sendxmpp,
|
||||
}
|
||||
@@ -95,28 +96,30 @@ func sendxmpp(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
cmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is ~/.config/fluxxmpp.yml)")
|
||||
cmdRoot.AddCommand(cmdSend)
|
||||
|
||||
cmd.Flags().StringP("jid", "", "", "using jid (required)")
|
||||
viper.BindPFlag("jid", cmd.Flags().Lookup("jid"))
|
||||
cobra.OnInitialize(initConfigFile)
|
||||
cmdSend.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is ~/.config/fluuxmpp.yml)")
|
||||
|
||||
cmd.Flags().StringP("password", "", "", "using password for your jid (required)")
|
||||
viper.BindPFlag("password", cmd.Flags().Lookup("password"))
|
||||
cmdSend.Flags().StringP("jid", "", "", "using jid (required)")
|
||||
viper.BindPFlag("jid", cmdSend.Flags().Lookup("jid"))
|
||||
|
||||
cmd.Flags().StringP("addr", "", "", "host[:port]")
|
||||
viper.BindPFlag("addr", cmd.Flags().Lookup("addr"))
|
||||
cmdSend.Flags().StringP("password", "", "", "using password for your jid (required)")
|
||||
viper.BindPFlag("password", cmdSend.Flags().Lookup("password"))
|
||||
|
||||
cmd.Flags().BoolVarP(&isMUCRecipient, "muc", "m", false, "recipient is a muc (join it before sending messages)")
|
||||
cmdSend.Flags().StringP("addr", "", "", "host[:port]")
|
||||
viper.BindPFlag("addr", cmdSend.Flags().Lookup("addr"))
|
||||
|
||||
cmdSend.Flags().BoolVarP(&isMUCRecipient, "muc", "m", false, "recipient is a muc (join it before sending messages)")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
func initConfigFile() {
|
||||
if configFile != "" {
|
||||
viper.SetConfigFile(configFile)
|
||||
}
|
||||
|
||||
viper.SetConfigName("fluxxmpp")
|
||||
viper.SetConfigName("fluuxmpp")
|
||||
viper.AddConfigPath("/etc/")
|
||||
viper.AddConfigPath("$HOME/.config")
|
||||
viper.AddConfigPath(".")
|
||||
@@ -1,131 +0,0 @@
|
||||
# sendXMPP
|
||||
|
||||
sendxmpp is a tool to send messages from command-line.
|
||||
|
||||
## Installation
|
||||
|
||||
To install `sendxmpp` in your Go path:
|
||||
|
||||
```
|
||||
$ go get -u gosrc.io/xmpp/cmd/sendxmpp
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ sendxmpp --help
|
||||
Usage:
|
||||
sendxmpp <recipient,> [message] [flags]
|
||||
|
||||
Examples:
|
||||
sendxmpp to@chat.sum7.eu "Hello World!"
|
||||
|
||||
Flags:
|
||||
--addr string host[:port]
|
||||
--config string config file (default is ~/.config/fluxxmpp.yml)
|
||||
-h, --help help for sendxmpp
|
||||
--jid string using jid (required)
|
||||
-m, --muc recipient is a muc (join it before sending messages)
|
||||
--password string using password for your jid (required)
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
Message from arguments:
|
||||
```bash
|
||||
$ sendxmpp to@example.org "Hello World!"
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:42:43.310+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Hello World!" to="to@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:42:43.310+02:00
|
||||
```
|
||||
|
||||
Message from STDIN:
|
||||
```bash
|
||||
$ journalctl -f | sendxmpp to@example.org -
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:40:03.177+02:00
|
||||
info send message
|
||||
⇢ muc=false text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="to@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:40:03.178+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Jul 17 23:36:46 RECHNERNAME systemd[755]: Started Fetch mails." to="to@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:40:03.178+02:00
|
||||
^C
|
||||
```
|
||||
|
||||
|
||||
Multiple recipients:
|
||||
```bash
|
||||
$ sendxmpp to1@example.org,to2@example.org "Multiple recipient"
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:47:57.650+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Multiple recipient" to="to1@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:47:57.651+02:00
|
||||
info send message
|
||||
⇢ muc=false text="Multiple recipient" to="to2@example.org"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:47:57.652+02:00
|
||||
```
|
||||
|
||||
Send to MUC:
|
||||
```bash
|
||||
journalctl -f | sendxmpp testit@conference.chat.sum7.eu - --muc
|
||||
info client connected
|
||||
⇢ cmd.go:56 main.glob..func1.1
|
||||
⇢ 2019-07-17T23:52:56.269+02:00
|
||||
info send message
|
||||
⇢ muc=true text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="testit@conference.chat.sum7.eu"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:52:56.270+02:00
|
||||
info send message
|
||||
⇢ muc=true text="Jul 17 23:48:58 RECHNERNAME systemd[755]: mail.service: Succeeded." to="testit@conference.chat.sum7.eu"
|
||||
⇢ send.go:31 main.send
|
||||
⇢ 2019-07-17T23:52:56.277+02:00
|
||||
^C
|
||||
```
|
||||
|
||||
### Authentification
|
||||
|
||||
#### Configuration file
|
||||
|
||||
In `/etc/`, `~/.config` and `.` (here).
|
||||
You could create the file name `fluxxmpp` with you favorite file extenion (e.g. `toml`, `yml`).
|
||||
|
||||
e.g. ~/.config/fluxxmpp.toml
|
||||
```toml
|
||||
jid = "bot@example.org"
|
||||
password = "secret"
|
||||
|
||||
addr = "example.com:5222"
|
||||
```
|
||||
|
||||
#### Environment variables
|
||||
|
||||
```bash
|
||||
export FLUXXMPP_JID='bot@example.org';
|
||||
export FLUXXMPP_PASSWORD='secret';
|
||||
|
||||
export FLUXXMPP_ADDR='example.com:5222';
|
||||
|
||||
sendxmpp to@example.org "Hello Welt";
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
Warning: This should not be used for production systems, as all users on the system
|
||||
can read the running processes, and their parameters (and thus the password).
|
||||
|
||||
```bash
|
||||
sendxmpp to@example.org "Hello World!" --jid bot@example.org --password secret --addr example.com:5222;
|
||||
```
|
||||
@@ -1,6 +0,0 @@
|
||||
/*
|
||||
|
||||
sendxmpp is a command-line tool to send to send XMPP messages to users
|
||||
|
||||
*/
|
||||
package main
|
||||
@@ -1,12 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/bdlm/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.AddHook(&hook{})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
# XMPP Check
|
||||
|
||||
XMPP check is a tool to check TLS certificate on a remote server.
|
||||
|
||||
## Installation
|
||||
|
||||
To install `xmpp-check` in your Go path:
|
||||
|
||||
```
|
||||
$ go get -u gosrc.io/xmpp/cmd/xmpp-check
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ xmpp-check --help
|
||||
Usage:
|
||||
xmpp-check <host[:port]> [flags]
|
||||
|
||||
Examples:
|
||||
xmpp-check chat.sum7.eu:5222 --domain meckerspace.de
|
||||
|
||||
Flags:
|
||||
-d, --domain string domain if host handle multiple domains
|
||||
-h, --help help for xmpp-check
|
||||
```
|
||||
|
||||
If you server is on standard port and XMPP domains matches the hostname you can simply use:
|
||||
|
||||
```
|
||||
$ xmpp-check chat.sum7.eu
|
||||
info All checks passed
|
||||
⇢ address="chat.sum7.eu" domain=""
|
||||
⇢ main.go:43 main.runCheck
|
||||
⇢ 2019-07-16T22:01:39.765+02:00
|
||||
```
|
||||
|
||||
You can also pass the port and the XMPP domain if different from the server hostname:
|
||||
|
||||
```
|
||||
$ xmpp-check chat.sum7.eu:5222 --domain meckerspace.de
|
||||
info All checks passed
|
||||
⇢ address="chat.sum7.eu:5222" domain="meckerspace.de"
|
||||
⇢ main.go:43 main.runCheck
|
||||
⇢ 2019-07-16T22:01:33.270+02:00
|
||||
```
|
||||
|
||||
Error code will be non-zero in case of error. You can thus use it directly with your usual
|
||||
monitoring scripts.
|
||||
@@ -1,3 +0,0 @@
|
||||
# TODO
|
||||
|
||||
- Use a config file to define the checks to perform as client on an XMPP server.
|
||||
@@ -1,6 +0,0 @@
|
||||
/*
|
||||
|
||||
xmpp-check is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires.
|
||||
|
||||
*/
|
||||
package main
|
||||
@@ -1,34 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/bdlm/log"
|
||||
stdLogger "github.com/bdlm/std/logger"
|
||||
)
|
||||
|
||||
type hook struct{}
|
||||
|
||||
func (h *hook) Fire(entry *log.Entry) error {
|
||||
switch entry.Level {
|
||||
case log.PanicLevel:
|
||||
entry.Logger.Out = os.Stderr
|
||||
case log.FatalLevel:
|
||||
entry.Logger.Out = os.Stderr
|
||||
case log.ErrorLevel:
|
||||
entry.Logger.Out = os.Stderr
|
||||
case log.WarnLevel:
|
||||
entry.Logger.Out = os.Stdout
|
||||
case log.InfoLevel:
|
||||
entry.Logger.Out = os.Stdout
|
||||
case log.DebugLevel:
|
||||
entry.Logger.Out = os.Stdout
|
||||
default:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *hook) Levels() []stdLogger.Level {
|
||||
return log.AllLevels
|
||||
}
|
||||
29
component.go
29
component.go
@@ -66,52 +66,67 @@ func NewComponent(opts ComponentOptions, r *Router) (*Component, error) {
|
||||
// Connect triggers component connection to XMPP server component port.
|
||||
// TODO: Failed handshake should be a permanent error
|
||||
func (c *Component) Connect() error {
|
||||
var state SMState
|
||||
return c.Resume(state)
|
||||
}
|
||||
func (c *Component) Resume(sm SMState) error {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if conn, err = net.DialTimeout("tcp", c.Address, time.Duration(5)*time.Second); err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = conn
|
||||
c.updateState(StateConnected)
|
||||
|
||||
// 1. Send stream open tag
|
||||
if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, stanza.NSComponent, stanza.NSStream); err != nil {
|
||||
return errors.New("cannot send stream open " + err.Error())
|
||||
c.updateState(StateStreamError)
|
||||
return NewConnError(errors.New("cannot send stream open "+err.Error()), false)
|
||||
}
|
||||
c.decoder = xml.NewDecoder(conn)
|
||||
|
||||
// 2. Initialize xml decoder and extract streamID from reply
|
||||
streamId, err := stanza.InitStream(c.decoder)
|
||||
if err != nil {
|
||||
return errors.New("cannot init decoder " + err.Error())
|
||||
c.updateState(StateStreamError)
|
||||
return NewConnError(errors.New("cannot init decoder "+err.Error()), false)
|
||||
}
|
||||
|
||||
// 3. Authentication
|
||||
if _, err := fmt.Fprintf(conn, "<handshake>%s</handshake>", c.handshake(streamId)); err != nil {
|
||||
return errors.New("cannot send handshake " + err.Error())
|
||||
c.updateState(StateStreamError)
|
||||
return NewConnError(errors.New("cannot send handshake "+err.Error()), false)
|
||||
}
|
||||
|
||||
// 4. Check server response for authentication
|
||||
val, err := stanza.NextPacket(c.decoder)
|
||||
if err != nil {
|
||||
return err
|
||||
c.updateState(StateDisconnected)
|
||||
return NewConnError(err, true)
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case stanza.StreamError:
|
||||
return errors.New("handshake failed " + v.Error.Local)
|
||||
c.streamError("conflict", "no auth loop")
|
||||
return NewConnError(errors.New("handshake failed "+v.Error.Local), true)
|
||||
case stanza.Handshake:
|
||||
// Start the receiver go routine
|
||||
c.updateState(StateSessionEstablished)
|
||||
go c.recv()
|
||||
return nil
|
||||
default:
|
||||
return errors.New("expecting handshake result, got " + v.Name())
|
||||
c.updateState(StateStreamError)
|
||||
return NewConnError(errors.New("expecting handshake result, got "+v.Name()), true)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Component) Disconnect() {
|
||||
_ = c.SendRaw("</stream:stream>")
|
||||
// TODO: Add a way to wait for stream close acknowledgement from the server for clean disconnect
|
||||
_ = c.conn.Close()
|
||||
conn := c.conn
|
||||
if conn != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Component) SetHandler(handler EventHandler) {
|
||||
|
||||
@@ -23,3 +23,10 @@ func TestHandshake(t *testing.T) {
|
||||
func TestGenerateHandshake(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// Test that NewStreamManager can accept a Component.
|
||||
//
|
||||
// This validates that Component conforms to StreamClient interface.
|
||||
func TestStreamManager(t *testing.T) {
|
||||
NewStreamManager(&Component{}, nil)
|
||||
}
|
||||
|
||||
17
router.go
17
router.go
@@ -98,7 +98,7 @@ type Handler interface {
|
||||
type Route struct {
|
||||
handler Handler
|
||||
// Matchers are used to "specialize" routes and focus on specific packet features
|
||||
matchers []matcher
|
||||
matchers []Matcher
|
||||
}
|
||||
|
||||
func (r *Route) Handler(handler Handler) *Route {
|
||||
@@ -122,8 +122,8 @@ func (r *Route) HandlerFunc(f HandlerFunc) *Route {
|
||||
return r.Handler(f)
|
||||
}
|
||||
|
||||
// addMatcher adds a matcher to the route
|
||||
func (r *Route) addMatcher(m matcher) *Route {
|
||||
// AddMatcher adds a matcher to the route
|
||||
func (r *Route) AddMatcher(m Matcher) *Route {
|
||||
r.matchers = append(r.matchers, m)
|
||||
return r
|
||||
}
|
||||
@@ -170,7 +170,7 @@ func (n nameMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
||||
// It matches on the Local part of the xml.Name
|
||||
func (r *Route) Packet(name string) *Route {
|
||||
name = strings.ToLower(name)
|
||||
return r.addMatcher(nameMatcher(name))
|
||||
return r.AddMatcher(nameMatcher(name))
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
@@ -204,7 +204,7 @@ func (r *Route) StanzaType(types ...string) *Route {
|
||||
for k, v := range types {
|
||||
types[k] = strings.ToLower(v)
|
||||
}
|
||||
return r.addMatcher(nsTypeMatcher(types))
|
||||
return r.AddMatcher(nsTypeMatcher(types))
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
@@ -229,14 +229,15 @@ func (r *Route) IQNamespaces(namespaces ...string) *Route {
|
||||
for k, v := range namespaces {
|
||||
namespaces[k] = strings.ToLower(v)
|
||||
}
|
||||
return r.addMatcher(nsIQMatcher(namespaces))
|
||||
return r.AddMatcher(nsIQMatcher(namespaces))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Matchers
|
||||
|
||||
// Matchers are used to "specialize" routes and focus on specific packet features
|
||||
type matcher interface {
|
||||
// Matchers are used to "specialize" routes and focus on specific packet features.
|
||||
// You can register attach them to a route via the AddMatcher method.
|
||||
type Matcher interface {
|
||||
Match(stanza.Packet, *RouteMatch) bool
|
||||
}
|
||||
|
||||
|
||||
78
session.go
78
session.go
@@ -17,6 +17,7 @@ type Session struct {
|
||||
// Session info
|
||||
BindJid string // Jabber ID as provided by XMPP server
|
||||
StreamId string
|
||||
SMState SMState
|
||||
Features stanza.StreamFeatures
|
||||
TlsEnabled bool
|
||||
lastPacketId int
|
||||
@@ -29,14 +30,19 @@ type Session struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewSession(conn net.Conn, o Config) (net.Conn, *Session, error) {
|
||||
func NewSession(conn net.Conn, o Config, state SMState) (net.Conn, *Session, error) {
|
||||
s := new(Session)
|
||||
s.SMState = state
|
||||
s.init(conn, o)
|
||||
|
||||
// starttls
|
||||
var tlsConn net.Conn
|
||||
tlsConn = s.startTlsIfSupported(conn, o.parsedJid.Domain, o)
|
||||
|
||||
if s.err != nil {
|
||||
return nil, nil, NewConnError(s.err, true)
|
||||
}
|
||||
|
||||
if !s.TlsEnabled && !o.Insecure {
|
||||
err := fmt.Errorf("failed to negotiate TLS session : %s", s.err)
|
||||
return nil, nil, NewConnError(err, true)
|
||||
@@ -50,10 +56,18 @@ func NewSession(conn net.Conn, o Config) (net.Conn, *Session, error) {
|
||||
s.auth(o)
|
||||
s.reset(tlsConn, tlsConn, o)
|
||||
|
||||
// bind resource and 'start' XMPP session
|
||||
// attempt resumption
|
||||
if s.resume(o) {
|
||||
return tlsConn, s, s.err
|
||||
}
|
||||
|
||||
// otherwise, bind resource and 'start' XMPP session
|
||||
s.bind(o)
|
||||
s.rfc3921Session(o)
|
||||
|
||||
// Enable stream management if supported
|
||||
s.EnableStreamManagement(o)
|
||||
|
||||
return tlsConn, s, s.err
|
||||
}
|
||||
|
||||
@@ -131,7 +145,6 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string, o Config) ne
|
||||
}
|
||||
|
||||
if !o.TLSConfig.InsecureSkipVerify {
|
||||
// We check that cert matches hostname
|
||||
s.err = tlsConn.VerifyHostname(domain)
|
||||
}
|
||||
|
||||
@@ -158,6 +171,38 @@ func (s *Session) auth(o Config) {
|
||||
s.err = authSASL(s.streamLogger, s.decoder, s.Features, o.parsedJid.Node, o.Password)
|
||||
}
|
||||
|
||||
// Attempt to resume session using stream management
|
||||
func (s *Session) resume(o Config) bool {
|
||||
if !s.Features.DoesStreamManagement() {
|
||||
return false
|
||||
}
|
||||
if s.SMState.Id == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Fprintf(s.streamLogger, "<resume xmlns='%s' h='%d' previd='%s'/>",
|
||||
stanza.NSStreamManagement, s.SMState.Inbound, s.SMState.Id)
|
||||
|
||||
var packet stanza.Packet
|
||||
packet, s.err = stanza.NextPacket(s.decoder)
|
||||
if s.err == nil {
|
||||
switch p := packet.(type) {
|
||||
case stanza.SMResumed:
|
||||
if p.PrevId != s.SMState.Id {
|
||||
s.err = errors.New("session resumption: mismatched id")
|
||||
s.SMState = SMState{}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case stanza.SMFailed:
|
||||
default:
|
||||
s.err = errors.New("unexpected reply to SM resume")
|
||||
}
|
||||
}
|
||||
s.SMState = SMState{}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Session) bind(o Config) {
|
||||
if s.err != nil {
|
||||
return
|
||||
@@ -205,3 +250,30 @@ func (s *Session) rfc3921Session(o Config) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable stream management, with session resumption, if supported.
|
||||
func (s *Session) EnableStreamManagement(o Config) {
|
||||
if s.err != nil {
|
||||
return
|
||||
}
|
||||
if !s.Features.DoesStreamManagement() {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(s.streamLogger, "<enable xmlns='%s' resume='true'/>", stanza.NSStreamManagement)
|
||||
|
||||
var packet stanza.Packet
|
||||
packet, s.err = stanza.NextPacket(s.decoder)
|
||||
if s.err == nil {
|
||||
switch p := packet.(type) {
|
||||
case stanza.SMEnabled:
|
||||
s.SMState = SMState{Id: p.Id}
|
||||
case stanza.SMFailed:
|
||||
// TODO: Store error in SMState, for later inspection
|
||||
default:
|
||||
s.err = errors.New("unexpected reply to SM enable")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package stanza
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -99,7 +98,6 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var xmppError Err
|
||||
err = d.DecodeElement(&xmppError, &tt)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
iq.Error = xmppError
|
||||
|
||||
@@ -63,6 +63,8 @@ func NextPacket(p *xml.Decoder) (Packet, error) {
|
||||
return decodeClient(p, se)
|
||||
case NSComponent:
|
||||
return decodeComponent(p, se)
|
||||
case NSStreamManagement:
|
||||
return sm.decode(p, se)
|
||||
default:
|
||||
return nil, errors.New("unknown namespace " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
@@ -133,7 +135,7 @@ func decodeClient(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// decodeClient decodes all known packets in the component namespace.
|
||||
// decodeComponent decodes all known packets in the component namespace.
|
||||
func decodeComponent(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||
switch se.Name.Local {
|
||||
case "handshake": // handshake is used to authenticate components
|
||||
|
||||
121
stanza/stream_management.go
Normal file
121
stanza/stream_management.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package stanza
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
NSStreamManagement = "urn:xmpp:sm:3"
|
||||
)
|
||||
|
||||
// Enabled as defined in Stream Management spec
|
||||
// Reference: https://xmpp.org/extensions/xep-0198.html#enable
|
||||
type SMEnabled struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:sm:3 enabled"`
|
||||
Id string `xml:"id,attr,omitempty"`
|
||||
Location string `xml:"location,attr,omitempty"`
|
||||
Resume string `xml:"resume,attr,omitempty"`
|
||||
Max uint `xml:"max,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (SMEnabled) Name() string {
|
||||
return "Stream Management: enabled"
|
||||
}
|
||||
|
||||
// Request as defined in Stream Management spec
|
||||
// Reference: https://xmpp.org/extensions/xep-0198.html#acking
|
||||
type SMRequest struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:sm:3 r"`
|
||||
}
|
||||
|
||||
func (SMRequest) Name() string {
|
||||
return "Stream Management: request"
|
||||
}
|
||||
|
||||
// Answer as defined in Stream Management spec
|
||||
// Reference: https://xmpp.org/extensions/xep-0198.html#acking
|
||||
type SMAnswer struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:sm:3 a"`
|
||||
H uint `xml:"h,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (SMAnswer) Name() string {
|
||||
return "Stream Management: answer"
|
||||
}
|
||||
|
||||
// Resumed as defined in Stream Management spec
|
||||
// Reference: https://xmpp.org/extensions/xep-0198.html#acking
|
||||
type SMResumed struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:sm:3 resumed"`
|
||||
PrevId string `xml:"previd,attr,omitempty"`
|
||||
H uint `xml:"h,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (SMResumed) Name() string {
|
||||
return "Stream Management: resumed"
|
||||
}
|
||||
|
||||
// Failed as defined in Stream Management spec
|
||||
// Reference: https://xmpp.org/extensions/xep-0198.html#acking
|
||||
type SMFailed struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:sm:3 failed"`
|
||||
// TODO: Handle decoding error cause (need custom parsing).
|
||||
}
|
||||
|
||||
func (SMFailed) Name() string {
|
||||
return "Stream Management: failed"
|
||||
}
|
||||
|
||||
type smDecoder struct{}
|
||||
|
||||
var sm smDecoder
|
||||
|
||||
// decode decodes all known nonza in the stream management namespace.
|
||||
func (s smDecoder) decode(p *xml.Decoder, se xml.StartElement) (Packet, error) {
|
||||
switch se.Name.Local {
|
||||
case "enabled":
|
||||
return s.decodeEnabled(p, se)
|
||||
case "resumed":
|
||||
return s.decodeResumed(p, se)
|
||||
case "r":
|
||||
return s.decodeRequest(p, se)
|
||||
case "h":
|
||||
return s.decodeAnswer(p, se)
|
||||
case "failed":
|
||||
return s.decodeFailed(p, se)
|
||||
default:
|
||||
return nil, errors.New("unexpected XMPP packet " +
|
||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
||||
}
|
||||
}
|
||||
|
||||
func (smDecoder) decodeEnabled(p *xml.Decoder, se xml.StartElement) (SMEnabled, error) {
|
||||
var packet SMEnabled
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
func (smDecoder) decodeResumed(p *xml.Decoder, se xml.StartElement) (SMResumed, error) {
|
||||
var packet SMResumed
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
func (smDecoder) decodeRequest(p *xml.Decoder, se xml.StartElement) (SMRequest, error) {
|
||||
var packet SMRequest
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
func (smDecoder) decodeAnswer(p *xml.Decoder, se xml.StartElement) (SMAnswer, error) {
|
||||
var packet SMAnswer
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
|
||||
func (smDecoder) decodeFailed(p *xml.Decoder, se xml.StartElement) (SMFailed, error) {
|
||||
var packet SMFailed
|
||||
err := p.DecodeElement(&packet, &se)
|
||||
return packet, err
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
// set callback and trigger reconnection.
|
||||
type StreamClient interface {
|
||||
Connect() error
|
||||
Resume(state SMState) error
|
||||
Send(packet stanza.Packet) error
|
||||
SendRaw(packet string) error
|
||||
Disconnect()
|
||||
@@ -78,7 +79,7 @@ func (sm *StreamManager) Run() error {
|
||||
sm.Metrics.setLoginTime()
|
||||
case StateDisconnected:
|
||||
// Reconnect on disconnection
|
||||
sm.connect()
|
||||
sm.resume(e.SMState)
|
||||
case StateStreamError:
|
||||
sm.client.Disconnect()
|
||||
// Only try reconnecting if we have not been kicked by another session to avoid connection loop.
|
||||
@@ -106,8 +107,13 @@ func (sm *StreamManager) Stop() {
|
||||
sm.wg.Done()
|
||||
}
|
||||
|
||||
// connect manages the reconnection loop and apply the define backoff to avoid overloading the server.
|
||||
func (sm *StreamManager) connect() error {
|
||||
var state SMState
|
||||
return sm.resume(state)
|
||||
}
|
||||
|
||||
// resume manages the reconnection loop and apply the define backoff to avoid overloading the server.
|
||||
func (sm *StreamManager) resume(state SMState) error {
|
||||
var backoff backoff // TODO: Group backoff calculation features with connection manager?
|
||||
|
||||
for {
|
||||
@@ -115,7 +121,7 @@ func (sm *StreamManager) connect() error {
|
||||
// TODO: Make it possible to define logger to log disconnect and reconnection attempts
|
||||
sm.Metrics = initMetrics()
|
||||
|
||||
if err = sm.client.Connect(); err != nil {
|
||||
if err = sm.client.Resume(state); err != nil {
|
||||
var actualErr ConnError
|
||||
if xerrors.As(err, &actualErr) {
|
||||
if actualErr.Permanent {
|
||||
@@ -146,7 +152,7 @@ type Metrics struct {
|
||||
ConnectTime time.Duration
|
||||
// LoginTime returns the between client initiation of the TCP/IP
|
||||
// connection to the server and the return of the login result.
|
||||
// This includes ConnectTime, but also XMPP level protocol negociation
|
||||
// This includes ConnectTime, but also XMPP level protocol negotiation
|
||||
// like starttls.
|
||||
LoginTime time.Duration
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user