Project

jubjub

0.0
No commit activity in last 3 years
No release in over 3 years
jubjub is designed to provie a simple API for controller XMPP servers and their resources. Currently it should be used in conjunction with xmpp_gateway, but the architecture has been left so that other backends could be implemented.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 0
~> 2.4
>= 0
~> 1.6

Runtime

 Project Readme

Description

Jubjub is an object to XMPP mapping library designed to be used inside of a web app to simplify the administration of XMPP resources. It aims to provide a very simple interface which hides the complexity of the XMPP protocol allowing your code to communicate intent rather than implementation details. This also makes the code significantly easier to mock and test.

Currently it uses xmpp_gateway to communicate to the XMPP server. This allows Jubjub to be used within non evented web servers like Phusion Passenger.

It is currently alpha software and merely a proof of concept.

Requirements

It is currently fully tested against:

  • Ruby 1.8.7
  • Ruby 1.9.2

Examples

require 'jubjub'
class User

  attr_accessor :xmpp_jid, :xmpp_password

  # :jid and :password are used to point to methods that contain
  # the credentials required to login as a user
  include Jubjub::User
  jubjub_client :jid => :xmpp_jid, :password => :xmpp_password

end

u = User.new
u.xmpp_jid = "theozaurus@jabber.org"
u.xmpp_password = "ruby"

# Sensibly defaults to conference.YOUR_CONNECTION_JID
u.mucs
=> [
  #<Jubjub::Muc:0x1027222b8 @jid="all@conference.jabber.org" @name="all">,
  #<Jubjub::Muc:0x102722038 @jid="all-linux-ru@conference.jabber.org" @name="all-linux-ru">,
  #<Jubjub::Muc:0x102721b60 @jid="allgorithmus@conference.jabber.org" @name="Allgorithmus Project">,
...

# Fancy a list from a different service?
u.mucs('conference.jabber.ru')
=> [
  #<Jubjub::Muc:0x10166e930 @jid="tota-room@conference.jabber.ru" @name="tota-room (5)">,
  #<Jubjub::Muc:0x10166e6b0 @jid="friends_room@conference.jabber.ru" @name="Friends (6)">,
  #<Jubjub::Muc:0x10166dcb0 @jid="think_linux@conference.jabber.ru" @name="think_linux (n/a)">
...

# Find a room
u.mucs('conference.jabber.ru')['tota-room']
=> #<Jubjub::Muc:0x10166e930 @jid="tota-room@conference.jabber.ru" @name="tota-room (5)">

# Create a room
room = u.mucs.create('jubjub')
=> #<Jubjub::Muc:0x101532f58 @jid="jubjub@conference.jabber.org" @name=nil>

# Create a room somewhere else
room = u.mucs('chat.test.com').create('jubjub')
=> #<Jubjub::Muc:0x10161c3b0 @jid="jubjub@chat.test.com" @name=nil>

# Create a room with a custom configuration
room = u.mucs.create('customjub'){|c|
  c['muc#roomconfig_allowinvites'] = false
}

# Retrieve current affiliations
room.affiliations
=> [#<Jubjub::Muc::Affiliation:0x1019a3c90 @muc_jid="jubjub@chat.test.com" @jid="theozaurus@jabber.org" @nick=nil @affiliation="owner"  @role=nil>]

# Search affiliations
room.affiliations['theozaurus@jabber.org']
=> #<Jubjub::Muc::Affiliation:0x1019a3c90 @muc_jid="jubjub@chat.test.com" @jid="theozaurus@jabber.org" @nick=nil @affiliation="owner"  @role=nil>

# Test affiliations
room.affiliations['theozaurus@jabber.org'].owner?
=> true
room.affiliations['theozaurus@jabber.org'].member?
=> false

# Create affiliation
room.affiliations['bob@test.com'].set_admin
=> true
# or
room.affiliations['bot@test.com'].set('admin')
=> true
# or
room.add_affiliations {"bot@test.com" => "admin", "theozaurus@jabber.org" => "owner"}
=> true

# Message a room
room.message "I am an invisible man."
=> #<Jubjub::Muc:0x10161c3b0 @jid="customjub@chat.test.com" @name=nil>

# Destroy a room
room.destroy
=> true

# Create a persistent room and exit it
u.mucs.create('persistent'){|c|
  c['muc#roomconfig_persistentroom'] = true
}.exit

# List pubsub nodes
u.pubsub
=> [
  #<Jubjub::Pubsub:0x101f23c88 @service="pubsub.jabber.org" @node="facts">,
  #<Jubjub::Pubsub:0x101f23a30 @service="pubsub.jabber.org" @node="news">
]

# Find a pubsub node
u.pubsub['facts']
=> #<Jubjub::Pubsub:0x101f23c88 @service="pubsub.jabber.org" @node="facts">

# List from a different service?
u.pubsub('pubsub.jabber.ru')
=> [
  ...
]

# Create a pubsub node
node = u.pubsub.create('node_1')
=> #<Jubjub::Pubsub:0x101f3bae0 @service="pubsub.jabber.org" @node="node_1">

# Create a pubsub node with a custom configuration
u.pubsub.create('node_2'){|c|
  c['pubsub#title'] = "Node 2" 
}

# Subscribe to node
subscription = node.subscribe
=> #<Jubjub::Pubsub::Subscription:0x101effd60 @service="pubsub.jabber.org" @node="node_1" @subscriber="theozaurus@jabber.org" @subid="5129CD7935528" @subscription="subscribed">

# Subscribe to something else
u.pubsub.subscribe('new_thing')
=> #<Jubjub::Pubsub::Subscription:0x101effd60 @service="pubsub.jabber.org" @node="new_thing" @subscriber="theozaurus@jabber.org" @subid="5129CD7935528" @subscription="subscribed">

# Subscribe someone else to the node
u.pubsub["new_thing"].subscriptions["foo@jabber.org"].subscribe
=> true

# Subscribe multiple jids
u.pubsub["new_thing"].add_subscriptions {"foo@jabber.org" => "subscribed", "bar@jabber.org" => "subscribed"}
=> true

# Show subscriptions
u.pubsub["new_thing"].subscriptions
=> [#<Jubjub::Pubsub::Subscription:0x7f99fda76c48 @jid="pubsub.jabber.org" @node="new_thing" @subscriber="theozaurus@jabber.org" @subid="5129CD7935528" @subscription="subscribed">, #<Jubjub::Pubsub::Subscription:0x7f99fda76770 @jid="pubsub.jabber.org" @node="new_thing" @subscriber="foo@jabber.org" @subid="53876943C09A7" @subscription="subscribed">] 

# Publish to a node
item = u.pubsub['node_1'].publish(Jubjub::DataForm.new({:foo => {:type => 'boolean', :value => 'bar'}}))
=> #<Jubjub::Pubsub::Item:0x101f2e9f8 @jid="pubsub.jabber.org" @node="node_1" @item_id="519DCAA72FFD6" @data="<x xmlns=\"jabber:x:data\" type=\"submit\">\n  <field var=\"foo\">\n    <value>false</value>\n  </field>\n</x>"> 

# Retract an item from a node
item.retract
=> true

# Or
u.pubsub['node_1'].retract('abc')
=> true

# List items on a node
u.pubsub['node_1'].items
=> [
  #<Jubjub::Pubsub::Item:0x101f7bd48 @jid="pubsub.jabber.org" @node="node_1" @item_id="519DCAA72FFD6" @data="...">,
  ...
]

# Retrieve an item from a node
u.pubsub['node_1'].items['519DCAA72FFD6']
=> #<Jubjub::Pubsub::Item:0x101f7bd48 @jid="pubsub.jabber.org" @node="node_1" @item_id="519DCAA72FFD6" @data="...">

# Retrieve affiliations from a node
u.pubsub['node_1'].affiliations
=> [
  #<Jubjub::Pubsub::Affiliation:0x101f52830 @pubsub_jid="pubsub.jabber.org" @pubsub_node="node_1" @jid="theozaurus@jabber.org" @affiliation="owner">
  ...
]

# Search affiliations
u.pubsub['node_1'].affiliations['theozaurus@jabber.org']
=> #<Jubjub::Pubsub::Affiliation:0x101f52830 @pubsub_jid="pubsub.jabber.org" @pubsub_node="node_1" @jid="theozaurus@jabber.org" @affiliation="owner">

# Test affiliations
u.pubsub['node_1'].affiliations['theozaurus@jabber.org'].owner?
=> true
u.pubsub['node_1'].affiliations['theozaurus@jabber.org'].publisher?
=> false

# Set affiliation
u.pubsub['node_1'].affiliations['theozaurus@jabber.org'].set_publisher
=> true
# or
u.pubsub['node_1'].affiliations['theozaurus@jabber.org'].set('publisher')
=> true
# or
u.pubsub['node_1'].add_affiliations {'theozaurus@jabber.org' => 'publisher, "foo@bar.com" => "owner"}
=> true
    
# Purge a node
node.purge
=> true

# Or
u.pubsub.purge('node_1')
=> true

# Unsubscribe
subscription.unsubscribe
=> true

# Destroy a node directly
node.destroy
=> true

# Destroy another node
u.pubsub.destroy('spare_node')
=> true

# Destroy another node with redirect
u.pubsub.destroy('old_node', 'pubsub.jabber.ru', 'new_new')
=> true

Dealing with errors

Every returned object from every action is really a Jubjub::Response::Proxy, which delegates to the Jubjub::Response and if the method doesn't exist there to the actual result. As Jubjub::Response only has a few methods it will usually just behaves like the actual result (say a Jubjub::Pubsub::Item). However, you can check what has really happened like so:

# Attempt to destroy a node that does not exist
u.pubsub.destroy('made up')
=> false

# False tells us destroy didn't succeed in this case, but it's not terribly helpful
# So we can use methods provided by the Jubjub::Response as well

response = u.pubsub.destroy('made up')
response.success?
=> false

response.stanza.to_s
=> "<?xml version=\"1.0\"?>\n<iq type=\"error\" id=\"blather0011\" from=\"pubsub.theo-template.local\" to=\"theozaurus@theo-template.local/42607076141308656900975361\" lang=\"en\">\n  <pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\">\n    <delete node=\"made up\"/>\n  </pubsub>\n<error code=\"404\" type=\"cancel\"><item-not-found xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/></error></iq>\n" 

# We can get the real result with no proxy sat in front
response.result
=> false
response.result.success?
NoMethodError: undefined method `success?' for false:FalseClass
	from (irb):14

# We can also get an enhanced error object, Jubjub::Response::Error
response.error
=> #<Jubjub::Response::Error:0x101ef51f8 @stanza=#<Nokogiri::XML::Element:0x80f7a7bc name="error" attributes=[#<Nokogiri::XML::Attr:0x80f7a654 name="code" value="404">, #<Nokogiri::XML::Attr:0x80f7a640 name="type" value="cancel">] children=[#<Nokogiri::XML::Element:0x80f7a03c name="item-not-found" namespace=#<Nokogiri::XML::Namespace:0x80f79fb0 href="urn:ietf:params:xml:ns:xmpp-stanzas">>]>> 

response.error.stanza.to_s
=> "<error code=\"404\" type=\"cancel\">\n  <item-not-found xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>\n</error>"

response.error.condition
=> 'item-not-found'

response.error.type
=> 'cancel'

# If there is any error text this can also be extracted
response.error.text
=> 'Item not found'

The error type and error condition are the important factors. The type is defined in the RFC 6121, and helps decide whether the action should be attempted again. The condition provides a bit more detail, and will always be part of the urn:ietf:params:xml:ns:xmpp-stanzas namespace.

TODO

  • MUC user role control
  • Better exception handling
  • Service discovery
  • Operations that are not IQ based, such as rosters and two way messaging
  • Other backends (for servers that are evented)