mirror of
https://github.com/FluuxIO/go-xmpp.git
synced 2025-03-26 01:19:25 -07:00
WIP : Support for XEP-313
This commit is contained in:
parent
ce71bc5c76
commit
7cea390519
19
client.go
19
client.go
@ -349,6 +349,25 @@ func (c *Client) SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, err
|
||||
return c.router.NewIQResultRoute(ctx, iq.Attrs.Id), nil
|
||||
}
|
||||
|
||||
// SendIQ sends an IQ set or get stanza to the server. If a result is received
|
||||
// the provided handler function will automatically be called.
|
||||
//
|
||||
// The provided context should have a timeout to prevent the client from waiting
|
||||
// forever for an IQ result. For example:
|
||||
//
|
||||
// ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
|
||||
// result := <- client.SendIQ(ctx, iq)
|
||||
//
|
||||
func (c *Client) SendMamRequest(ctx context.Context, iq *stanza.IQ) (chan stanza.Packet, error) {
|
||||
if iq.Attrs.Type != stanza.IQTypeSet && iq.Attrs.Type != stanza.IQTypeGet {
|
||||
return nil, ErrCanOnlySendGetOrSetIq
|
||||
}
|
||||
if err := c.Send(iq); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.router.NewMamResultRoute(ctx, iq.Id), nil
|
||||
}
|
||||
|
||||
// SendRaw sends an XMPP stanza as a string to the server.
|
||||
// It can be invalid XML or XMPP content. In that case, the server will
|
||||
// disconnect the client. It is up to the user of this method to
|
||||
|
81
router.go
81
router.go
@ -30,6 +30,9 @@ type Router struct {
|
||||
|
||||
IQResultRoutes map[string]*IQResultRoute
|
||||
IQResultRouteLock sync.RWMutex
|
||||
|
||||
MamResultRoutes map[string]*MamResultRoute
|
||||
MamResultRoutesLock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewRouter returns a new router instance.
|
||||
@ -55,15 +58,43 @@ func (r *Router) route(s Sender, p stanza.Packet) {
|
||||
}
|
||||
iq, isIq := p.(*stanza.IQ)
|
||||
if isIq {
|
||||
r.IQResultRouteLock.RLock()
|
||||
route, ok := r.IQResultRoutes[iq.Id]
|
||||
r.IQResultRouteLock.RUnlock()
|
||||
// Mam IQs (See XEP-0313)
|
||||
if iq.Payload.Namespace() == stanza.NSMam {
|
||||
r.MamResultRoutesLock.RLock()
|
||||
route, ok := r.MamResultRoutes[iq.Id]
|
||||
r.MamResultRoutesLock.RUnlock()
|
||||
if ok {
|
||||
r.MamResultRoutesLock.Lock()
|
||||
delete(r.MamResultRoutes, iq.Id)
|
||||
r.MamResultRoutesLock.Unlock()
|
||||
route.results <- iq
|
||||
close(route.results)
|
||||
return
|
||||
}
|
||||
} else { // "Classic" IQs
|
||||
r.IQResultRouteLock.RLock()
|
||||
route, ok := r.IQResultRoutes[iq.Id]
|
||||
r.IQResultRouteLock.RUnlock()
|
||||
if ok {
|
||||
r.IQResultRouteLock.Lock()
|
||||
delete(r.IQResultRoutes, iq.Id)
|
||||
r.IQResultRouteLock.Unlock()
|
||||
route.result <- *iq
|
||||
close(route.result)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If message is part of a response to a Mam query, forward it through the dedicated channel (See XEP-0313)
|
||||
msg, ok := p.(stanza.Message)
|
||||
if ok {
|
||||
r.MamResultRoutesLock.RLock()
|
||||
route, ok := r.MamResultRoutes[iq.Id]
|
||||
r.MamResultRoutesLock.RUnlock()
|
||||
if ok {
|
||||
r.IQResultRouteLock.Lock()
|
||||
delete(r.IQResultRoutes, iq.Id)
|
||||
r.IQResultRouteLock.Unlock()
|
||||
route.result <- *iq
|
||||
close(route.result)
|
||||
route.results <- msg
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -147,6 +178,26 @@ func (r *Router) NewIQResultRoute(ctx context.Context, id string) chan stanza.IQ
|
||||
return route.result
|
||||
}
|
||||
|
||||
// NewIQResultRoute register a route that will catch message stanzas and the closing IQ result attached to the
|
||||
// the given queryId. The route will automatically be unregistered.
|
||||
func (r *Router) NewMamResultRoute(ctx context.Context, id string) chan stanza.Packet {
|
||||
route := NewMamResultRoute(ctx)
|
||||
r.MamResultRoutesLock.Lock()
|
||||
r.MamResultRoutes[id] = route
|
||||
r.MamResultRoutesLock.Unlock()
|
||||
|
||||
// Start a go function to make sure the route is unregistered when the context
|
||||
// is done.
|
||||
go func() {
|
||||
<-route.context.Done()
|
||||
r.MamResultRoutesLock.Lock()
|
||||
delete(r.IQResultRoutes, id)
|
||||
r.MamResultRoutesLock.Unlock()
|
||||
}()
|
||||
|
||||
return route.results
|
||||
}
|
||||
|
||||
func (r *Router) Match(p stanza.Packet, match *RouteMatch) bool {
|
||||
for _, route := range r.routes {
|
||||
if route.Match(p, match) {
|
||||
@ -187,6 +238,20 @@ func NewIQResultRoute(ctx context.Context) *IQResultRoute {
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
type MamResultRoute struct {
|
||||
context context.Context
|
||||
results chan stanza.Packet
|
||||
}
|
||||
|
||||
// NewIQResultRoute creates a new IQResultRoute instance
|
||||
func NewMamResultRoute(ctx context.Context) *MamResultRoute {
|
||||
return &MamResultRoute{
|
||||
context: ctx,
|
||||
results: make(chan stanza.Packet),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IQ result handler
|
||||
|
||||
|
@ -53,38 +53,6 @@ func (d *Delegation) GetSet() *ResultSet {
|
||||
return d.ResultSet
|
||||
}
|
||||
|
||||
// Forwarded is used to wrapped forwarded stanzas.
|
||||
// TODO: Move it in another file, as it is not limited to components.
|
||||
type Forwarded struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
|
||||
Stanza Packet
|
||||
}
|
||||
|
||||
// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
|
||||
// transform generic XML content into hierarchical Node structure.
|
||||
func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
// Check subelements to extract required field as boolean
|
||||
for {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tt := t.(type) {
|
||||
|
||||
case xml.StartElement:
|
||||
if packet, err := decodeClient(d, tt); err == nil {
|
||||
f.Stanza = packet
|
||||
}
|
||||
|
||||
case xml.EndElement:
|
||||
if tt == start.End() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Delegated struct {
|
||||
XMLName xml.Name `xml:"delegated"`
|
||||
Namespace string `xml:"namespace,attr,omitempty"`
|
||||
|
34
stanza/forwarded.go
Normal file
34
stanza/forwarded.go
Normal file
@ -0,0 +1,34 @@
|
||||
package stanza
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// Forwarded is used to wrapped forwarded stanzas.
|
||||
type Forwarded struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
|
||||
Stanza Packet
|
||||
}
|
||||
|
||||
// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
|
||||
// transform generic XML content into hierarchical Node structure.
|
||||
func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
// Check sub elements to extract required field as boolean
|
||||
for {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tt := t.(type) {
|
||||
|
||||
case xml.StartElement:
|
||||
if packet, err := decodeClient(d, tt); err == nil {
|
||||
f.Stanza = packet
|
||||
}
|
||||
|
||||
case xml.EndElement:
|
||||
if tt == start.End() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
stanza/iq_mam.go
Normal file
52
stanza/iq_mam.go
Normal file
@ -0,0 +1,52 @@
|
||||
package stanza
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ----------
|
||||
// Namespaces
|
||||
|
||||
const (
|
||||
// NSRoster is the Roster IQ namespace
|
||||
NSMam = "urn:xmpp:mam:2"
|
||||
)
|
||||
|
||||
// Roster struct represents Roster IQs
|
||||
type MamQuery struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:mam:2 query"`
|
||||
QueryId string `xml:"queryid,attr"`
|
||||
}
|
||||
|
||||
// Namespace defines the namespace for the RosterIQ
|
||||
func (mq *MamQuery) Namespace() string {
|
||||
return mq.XMLName.Space
|
||||
}
|
||||
func (mq *MamQuery) GetQueryId() string {
|
||||
return mq.QueryId
|
||||
}
|
||||
|
||||
// To implement IqPayload interface only
|
||||
func (mq *MamQuery) GetSet() *ResultSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Builder helpers
|
||||
|
||||
// RosterIQ builds a default Roster payload
|
||||
func (iq *IQ) NewMamIQ() *MamQuery {
|
||||
mq := MamQuery{
|
||||
XMLName: xml.Name{
|
||||
Space: NSMam,
|
||||
Local: "query",
|
||||
},
|
||||
}
|
||||
if id, err := uuid.NewRandom(); err == nil {
|
||||
mq.QueryId = id.String()
|
||||
}
|
||||
|
||||
iq.Payload = &mq
|
||||
return &mq
|
||||
}
|
@ -16,6 +16,7 @@ type Message struct {
|
||||
Subject string `xml:"subject,omitempty"`
|
||||
Body string `xml:"body,omitempty"`
|
||||
Thread string `xml:"thread,omitempty"`
|
||||
StanzaId *StanzaId `xml:"stanza-id"`
|
||||
Error Err `xml:"error,omitempty"`
|
||||
Extensions []MsgExtension `xml:",omitempty"`
|
||||
}
|
||||
|
16
stanza/msg_id.go
Normal file
16
stanza/msg_id.go
Normal file
@ -0,0 +1,16 @@
|
||||
package stanza
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
/*
|
||||
Support for:
|
||||
- XEP-0313 - Message Archive Management (MAM): https://xmpp.org/extensions/xep-0313.html
|
||||
This MUST NOT be interpreted as an archive ID unless the server has previously advertised support for 'urn:xmpp:mam:2'
|
||||
See : https://xmpp.org/extensions/xep-0313.html#archives_id
|
||||
*/
|
||||
|
||||
type StanzaId struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:sid:0 stanza-id"`
|
||||
By string `xml:"by,attr"`
|
||||
Id string `xml:"id,attr"`
|
||||
}
|
20
stanza/msg_id_test.go
Normal file
20
stanza/msg_id_test.go
Normal file
@ -0,0 +1,20 @@
|
||||
package stanza
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const expectedMarshal = `<stanza-id xmlns="urn:xmpp:sid:0" by="jid" id="unique-id"></stanza-id>`
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
d := StanzaId{
|
||||
By: "jid",
|
||||
Id: "unique-id",
|
||||
}
|
||||
data, e := xml.Marshal(d)
|
||||
if e != nil || !bytes.Equal(data, []byte(expectedMarshal)) {
|
||||
t.Fatalf("Marshal failed. Expected: %v, Actual: %v", expectedMarshal, string(data))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user