~samwhited/xmpp

5605d7cc29561b0e738b0c5ec2e6877600a84bf4 — Sam Whited 3 months ago 844b65f
all: implement disco identities and forms

Previously our service discovery implementation could only respond with
features. However, service discovery also supports "identities" and has
an extension mechanism that lets it respond with arbitrary data in the
form (pun intended) of a form result.

Adding implementations of these iterators to the mux and using them in
the disco handler brings us fully up to date on service discovery
features and should allow us to also implement entity capabilities in
the near future.

Signed-off-by: Sam Whited <sam@samwhited.com>
3 files changed, 111 insertions(+), 9 deletions(-)

M CHANGELOG.md
M disco/handler.go
M mux/mux.go
M CHANGELOG.md => CHANGELOG.md +4 -2
@@ 31,7 31,8 @@ All notable changes to this project will be documented in this file.
- history: implement [XEP-0313: Message Archive Management]
- form: add `Len` and `Raw` methods to form data
- muc: new package implementing [XEP-0045: Multi-User Chat] and [XEP-0249: Direct MUC Invitations]
- mux: `mux.ServeMux` now implements `info.FeatureIter` and `items.Iter`
- mux: `mux.ServeMux` now implements `info.FeatureIter`, `info.IdentityIter`,
  `form.Iter`, and `items.Iter`
- roster: the roster `Iter` now returns the roster version being iterated over
  from the `Version` method
- roster: if a `stanza.Error` is returned from the `Push` handler it is now sent


@@ 45,7 46,8 @@ All notable changes to this project will be documented in this file.
  config
- disco: new `disco.Handler` option to configure a `mux.ServeMux` to respond to
  disco#info requests
- disco/info: new package containing the former `disco.Feature` type
- disco/info: new package containing the former `disco.Feature` and
  `disco.Identity` types


### Fixed

M disco/handler.go => disco/handler.go +30 -7
@@ 10,13 10,14 @@ import (
	"mellium.im/xmlstream"
	"mellium.im/xmpp/disco/info"
	"mellium.im/xmpp/disco/items"
	"mellium.im/xmpp/form"
	"mellium.im/xmpp/mux"
	"mellium.im/xmpp/stanza"
)

// Handle returns an option that configures a multiplexer to handle service
// discovery requests by iterating over its own handlers and checking if they
// implement info.FeatureIter or items.Iter.
// implement info.FeatureIter, info.IdentityIter, form.Iter, or items.Iter.
func Handle() mux.Option {
	return func(m *mux.ServeMux) {
		h := &discoHandler{ServeMux: m}


@@ 26,7 27,7 @@ func Handle() mux.Option {
}

type discoHandler struct {
	*mux.ServeMux
	ServeMux *mux.ServeMux
}

func (h *discoHandler) HandleXMPP(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {


@@ 46,7 47,7 @@ func (h *discoHandler) HandleIQ(iq stanza.IQ, r xmlstream.TokenReadEncoder, star
		}
		switch start.Name.Space {
		case NSInfo:
			pw.CloseWithError(h.ServeMux.ForFeatures(node, func(f info.Feature) error {
			err := h.ServeMux.ForFeatures(node, func(f info.Feature) error {
				_, ok := seen[f.Var]
				if ok {
					return nil


@@ 54,6 55,32 @@ func (h *discoHandler) HandleIQ(iq stanza.IQ, r xmlstream.TokenReadEncoder, star
				seen[f.Var] = struct{}{}
				_, err := xmlstream.Copy(pw, f.TokenReader())
				return err
			})
			if err != nil {
				pw.CloseWithError(err)
				return
			}
			for k := range seen {
				delete(seen, k)
			}
			err = h.ServeMux.ForIdentities(node, func(f info.Identity) error {
				loopKey := f.Category + ":" + f.Type + ":" + f.Name + ":" + f.Lang
				_, ok := seen[loopKey]
				if ok {
					return nil
				}
				seen[loopKey] = struct{}{}
				_, err := xmlstream.Copy(pw, f.TokenReader())
				return err
			})
			if err != nil {
				pw.CloseWithError(err)
				return
			}
			pw.CloseWithError(h.ServeMux.ForForms(node, func(f *form.Data) error {
				result, _ := f.Submit()
				_, err := xmlstream.Copy(pw, result)
				return err
			}))
		case NSItems:
			pw.CloseWithError(h.ServeMux.ForItems(node, func(i items.Item) error {


@@ 74,7 101,3 @@ func (h *discoHandler) HandleIQ(iq stanza.IQ, r xmlstream.TokenReadEncoder, star
	)))
	return err
}

func (*discoHandler) ForItems(string, func(items.Item) error) error {
	return nil
}

M mux/mux.go => mux/mux.go +77 -0
@@ 19,6 19,7 @@ import (
	"mellium.im/xmpp"
	"mellium.im/xmpp/disco/info"
	"mellium.im/xmpp/disco/items"
	"mellium.im/xmpp/form"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/stanza"
)


@@ 286,6 287,82 @@ func (m *ServeMux) ForFeatures(node string, f func(info.Feature) error) error {
	return nil
}

// ForIdentities implements info.IdentityIter for the mux by iterating over
// all child handlers.
func (m *ServeMux) ForIdentities(node string, f func(info.Identity) error) error {
	for _, h := range m.patterns {
		if identIter, ok := h.(info.IdentityIter); ok {
			err := identIter.ForIdentities(node, f)
			if err != nil {
				return err
			}
		}
	}
	for _, h := range m.iqPatterns {
		if identIter, ok := h.(info.IdentityIter); ok {
			err := identIter.ForIdentities(node, f)
			if err != nil {
				return err
			}
		}
	}
	for _, h := range m.msgPatterns {
		if identIter, ok := h.(info.IdentityIter); ok {
			err := identIter.ForIdentities(node, f)
			if err != nil {
				return err
			}
		}
	}
	for _, h := range m.presencePatterns {
		if identIter, ok := h.(info.IdentityIter); ok {
			err := identIter.ForIdentities(node, f)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// ForIdentities implements form.Iter for the mux by iterating over all child
// handlers.
func (m *ServeMux) ForForms(node string, f func(*form.Data) error) error {
	for _, h := range m.patterns {
		if formIter, ok := h.(form.Iter); ok {
			err := formIter.ForForms(node, f)
			if err != nil {
				return err
			}
		}
	}
	for _, h := range m.iqPatterns {
		if formIter, ok := h.(form.Iter); ok {
			err := formIter.ForForms(node, f)
			if err != nil {
				return err
			}
		}
	}
	for _, h := range m.msgPatterns {
		if formIter, ok := h.(form.Iter); ok {
			err := formIter.ForForms(node, f)
			if err != nil {
				return err
			}
		}
	}
	for _, h := range m.presencePatterns {
		if formIter, ok := h.(form.Iter); ok {
			err := formIter.ForForms(node, f)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

type nopHandler struct{}

func (nopHandler) HandleXMPP(t xmlstream.TokenReadEncoder, start *xml.StartElement) error { return nil }