Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

An Object to XMPP mapper to make managing XMPP resources easy

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 script
Octocat-spinner-32 spec
Octocat-spinner-32 .gitignore
Octocat-spinner-32 .infinity_test
Octocat-spinner-32 .rspec
Octocat-spinner-32 Gemfile
Octocat-spinner-32 LICENSE
Octocat-spinner-32 README.mdown
Octocat-spinner-32 Rakefile
Octocat-spinner-32 jubjub.gemspec
README.mdown

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)
Something went wrong with that request. Please try again.