The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

###############################################################################
#                          
# XMPPClient Example           
# (c) Nicholas Perez 2006, 2007. 
# Licensed under GPLv2     
#                          
# Please see the included  
# LICENSE file for details
#
# This example client script, instantiates a single PCJ object, connects to a 
# remote server, sends presence, and then begins sending messages to itself on 
# a small random interval
#                          
###############################################################################

use Filter::Template; 						#this is only a shortcut
const XNode POE::Filter::XML::Node

use warnings;
use strict;

use POE; 									#include POE constants
use POE::Component::Jabber; 				#include PCJ
use	POE::Component::Jabber::Error; 			#include error constants
use	POE::Component::Jabber::Status; 		#include status constants
use	POE::Component::Jabber::ProtocolFactory;#include connection type constants
use POE::Filter::XML::Node; 				#include to build nodes
use POE::Filter::XML::NS qw/ :JABBER :IQ /; #include namespace constants
use POE::Filter::XML::Utils; 				#include some general utilites
use Carp;

# First we create our own session within POE to interact with PCJ
POE::Session->create(
	options => { debug => 1, trace => 1},
	inline_states => {
		_start =>
			sub
			{
				my ($kernel, $heap) = @_[KERNEL, HEAP];
				$kernel->alias_set('Tester');
				
				# our PCJ instance is a fullblown object we should store
				# so we can access various bits of data during use
				
				$heap->{'component'} = 
					POE::Component::Jabber->new(
						IP => '10.0.0.1',
						Port => '5222',
						Hostname => 'laptop',
						Username => 'test01',
						Password => 'test01',
						Alias => 'COMPONENT',

				# Shown below are the various connection types included
				# from ProtocolFactory:
				#
				# 	LEGACY is for pre-XMPP/Jabber connections
				# 	XMPP is for XMPP1.0 compliant connections
				# 	JABBERD14_COMPONENT is for connecting as a service on the
				# 		backbone of a jabberd1.4.x server
				# 	JABBERD20_COMPONENT is for connecting as a service on the
				# 		backbone of a jabberd2.0.x server

						#ConnectionType => +LEGACY,
						ConnectionType => +XMPP,
						#ConnectionType => +JABBERD14_COMPONENT,
						#ConnectionType => +JABBERD20_COMPONENT,
						Debug => '1',

				# Here is where we define our states for PCJ to use when
				# sending us information from the server. It automatically
				# infers the instantiating session much like a Wheel does.
				# StateParent is optional unless you want another session
				# to receive events from PCJ

						#StateParent => 'Tester',
						States => {
							StatusEvent => 'status_event',
							InputEvent => 'input_event',
							ErrorEvent => 'error_event',
						}
					);
				
				# At this point, PCJ is instatiated and hooked up to POE. In
				# 1.x, upon instantiation connect was immedately called. This
				# is not the case anymore with 2.x. This allows for a pool of 
				# connections to be setup and executed when needed.

				$kernel->post('COMPONENT', 'connect');
				
			},

		_stop =>
			sub
			{
				my $kernel = $_[KERNEL];
				$kernel->alias_remove();
			},

		input_event => \&input_event,
		error_event => \&error_event,
		status_event => \&status_event,
		test_message => \&test_message,
		output_event => \&output_event,
				
	}
);

# The status event receives all of the various bits of status from PCJ. PCJ
# sends out numerous statuses to inform the consumer of events of what it is 
# currently doing (ie. connecting, negotiating TLS or SASL, etc). A list of 
# these events can be found in PCJ::Status.

sub status_event()
{
	my ($kernel, $sender, $heap, $state) = @_[KERNEL, SENDER, HEAP, ARG0];
	
	# In the example we only watch to see when PCJ is finished building the
	# connection. When PCJ_INIT_FINISHED occurs, the connection ready for use.
	# Until this status event is fired, any nodes sent out will be queued. It's
	# the responsibility of the end developer to purge the queue via the 
	# purge_queue event.

	if($state == +PCJ_INIT_FINISHED)
	{	
		# Notice how we are using the stored PCJ instance by calling the jid()
		# method? PCJ stores the jid that was negotiated during connecting and 
		# is retrievable through the jid() method

		my $jid = $heap->{'component'}->jid();
		print "INIT FINISHED! \n";
		print "JID: $jid \n";
		print "SID: $sender->ID() \n\n";
		
		$heap->{'jid'} = $jid;
		$heap->{'sid'} = $sender->ID();
	
		$kernel->post('COMPONENT', 'output_handler', XNode->new('presence'));
		
		# And here is the purge_queue. This is to make sure we haven't sent
		# nodes while something catastrophic has happened (like reconnecting).
		
		$kernel->post('COMPONENT', 'purge_queue');

		for(1..10)
		{
			$kernel->delay_add('test_message', int(rand(10)));
		}
	}

	print "Status received: $state \n";

}

# This is the input event. We receive all data from the server through this
# event. ARG0 will a POE::Filter::XML::Node object.

sub input_event()
{
	my ($kernel, $heap, $node) = @_[KERNEL, HEAP, ARG0];
	
	print "\n===PACKET RECEIVED===\n";
	print $node->to_str() . "\n";
	print "=====================\n\n";
	$kernel->delay_add('test_message', int(rand(10)));
		
}

sub test_message()
{
	my ($kernel, $heap) = @_[KERNEL, HEAP];
	
	my $node = XNode->new('message');
	
	# get_bare_jid is a helper method included from POE::Filter::XML::Utils.
	# It returns the user@domain part of the jid (ie. no resources)

	$node->attr('to', get_bare_jid($heap->{'jid'}));
	$node->insert_tag('body')->data('This is a test sent at: ' . time());
	
	$kernel->yield('output_event', $node, $heap->{'sid'});

}

# This is our own output_event that is a simple passthrough on the way to
# post()ing to PCJ's output_handler so it can then send the Node on to the
# server

sub output_event()
{
	my ($kernel, $heap, $node, $sid) = @_[KERNEL, HEAP, ARG0, ARG1];
	
	print "\n===PACKET SENT===\n";
	print $node->to_str() . "\n";
	print "=================\n\n";
	
	$kernel->post($sid, 'output_handler', $node);
}

# This is the error event. Any error conditions that arise from any point 
# during connection or negotiation to any time during normal operation will be
# send to this event from PCJ. For a list of possible error events and exported
# constants, please see PCJ::Error

sub error_event()
{
	my ($kernel, $sender, $heap, $error) = @_[KERNEL, SENDER, HEAP, ARG0];

	if($error == +PCJ_SOCKETFAIL)
	{
		my ($call, $code, $err) = @_[ARG1..ARG3];
		print "Socket error: $call, $code, $err\n";
		print "Reconnecting!\n";
		$kernel->post($sender, 'reconnect');
	
	} elsif($error == +PCJ_SOCKETDISCONNECT) {
		
		print "We got disconneted\n";
		print "Reconnecting!\n";
		$kernel->post($sender, 'reconnect');
	
	} elsif($error == +PCJ_CONNECTFAIL) {

		print "Connect failed\n";
		print "Retrying connection!\n";
		$kernel->post($sender, 'reconnect');
	
	} elsif ($error == +PCJ_SSLFAIL) {

		print "TLS/SSL negotiation failed\n";

	} elsif ($error == +PCJ_AUTHFAIL) {

		print "Failed to authenticate\n";

	} elsif ($error == +PCJ_BINDFAIL) {

		print "Failed to bind a resource\n";
	
	} elsif ($error == +PCJ_SESSIONFAIL) {

		print "Failed to establish a session\n";
	}
}
	
POE::Kernel->run();