Skip to content

Instantly share code, notes, and snippets.

@SET001
Created August 20, 2015 08:31
Show Gist options
  • Save SET001/08ba42e4b29bf9180e7b to your computer and use it in GitHub Desktop.
Save SET001/08ba42e4b29bf9180e7b to your computer and use it in GitHub Desktop.
# specs:
# https://tools.ietf.org/html/rfc6121 - XMPP
# http://xmpp.org/extensions/xep-0045.html - Multi-User Chat
# Raised Events:
# xmpp_invite_accepted
# xmpp_add_invite
# xmpp_roster_update
app.service 'XMPP', ['$rootScope', 'Config', '$q', 'Account', 'User', 'Roster', ($rootScope, Config, $q, Account, User, Roster)->
xmpp = new JSJaCWebSocketConnection
# oDbg: new JSJaCConsoleLogger 4
oDbg: null
httpbase: Config.httpbase
timerval: 500
xmpp.isConnected = no
connected = $q.defer()
xmpp.__connected = connected.promise
xmpp.customHandlers = {}
xmpp.roster = new Roster()
xmpp.search = (username, success) ->
message = new JSJaCIQ()
message.setTo "vjud.#{Config.domain}"
message.setType 'set'
message.setFrom Account.user.jid.toString()
query = message.setQuery NS_SEARCH
x = message.buildNode 'x',
xmlns: 'jabber:x:data'
type: 'submit'
field = message.buildNode 'field',
type: 'hidden'
var: 'FORM_TYPE'
value = message.buildNode 'value'
$(value).text 'jabber:iq:search'
field.appendChild value
x.appendChild field
field = message.buildNode 'field', var: 'user'
value = message.buildNode 'value'
$(value).text username
field.appendChild value
x.appendChild field
query.appendChild x
xmpp.sendIQ message,
result_handler: (aIq) =>
users = []
items = $(aIq.getDoc()).find('item')
for item in items
user = {}
for field in $(item).children()
user[$(field).attr 'var'] = $(field).first().text()
users.push user
success users
error_handler: (response, asd) =>
console.log "error while searching user", response.doc
xmpp.send message, (responce) ->
console.log responce.getNode()
, (a, b, c) ->
console.log "2", a, b, c
xmpp.sendInvite = (user, muc) ->
message = new JSJaCMessage()
message.setTo user.jid.toString()
message.setFrom Account.user.jid.toString()
xnode = message.buildNode 'x', []
xnode.setAttribute 'xmlns', 'http://jabber.org/protocol/muc#user'
inode = message.buildNode 'invite', []
inode.setAttribute 'to', muc.split('/').shift()
reason = message.buildNode 'reason'
$(reason).text 'blah blah'
inode.appendChild reason
xnode.appendChild inode
message.appendNode xnode
@send message
xmpp.leave_muc = (room_jid) ->
console.log "leaving muc channel....#{room_jid}"
request = new JSJaCPresence()
request.setTo room_jid
request.setType 'unavailable'
@send request
xmpp.isNewMUC = (response) ->
# If response contain x node with status 201 this mean that new room was created.
# If this status not present in response - this mean that channel with such name alriedy exists
# http://xmpp.org/registrar/mucstatus.html
# http://xmpp.org/extensions/xep-0045.html#createroom-instant
$(response.getNode()).find('x status[code=201]').length
xmpp.checkMUC = (room_name) ->
d = $q.defer()
request = new JSJaCIQ()
request.setTo "#{room_name}@#{Config.muc_domain}"
request.setType 'get'
request.setQuery NS_MUC_OWNER
xmpp.send request, (response) =>
if response.isError()
switch $(response.getNode()).find('error').attr 'code'
when '403' then d.reject() # Owner privileges required
when '404' then d.resolve() # Conference room does not exist, ok for us
else d.reject() # this (response is not an error) mean channel with such name exists and that current user is the owner of it
d.promise
xmpp.createMUC = (room_name, owner_name) ->
room_jid = "#{room_name}@#{Config.muc_domain}"
request = new JSJaCPresence()
console.log "creating #{room_jid}"
request.setTo "#{room_jid}/#{owner_name}"
xnode = request.buildNode "x", []
xnode.setAttribute "xmlns", "http://jabber.org/protocol/muc"
request.appendNode xnode
request.setStatus 'available'
created = $q.defer()
@send request, (response) =>
if xmpp.isNewMUC response
request = new JSJaCIQ()
request.setTo room_jid
request.setType 'get'
request.setQuery NS_MUC_OWNER
@send request, (response) =>
request = new JSJaCIQ()
request.setTo room_jid
request.setFrom Account.room_jid # ????
request.setType 'set'
query = request.setQuery 'http://jabber.org/protocol/muc#owner'
query.setAttribute 'xmlns', 'http://jabber.org/protocol/muc#owner'
x = query.appendChild request.buildNode 'x'
x.setAttribute 'xmlns', 'jabber:x:data'
x.setAttribute 'type', 'submit'
addFormField = (form, field, value) =>
field = x.appendChild form.buildNode 'field', var: field
$(field.appendChild form.buildNode 'value').text value
for key, value of Config.xmpp.room_config
addFormField request, key, value
addFormField request, 'muc#roomconfig_roomname', room_name
@send request, (response) =>
created.resolve()
else
created.reject 'Room alriedy exists!'
created.promise
# to - string - jid
# type - string - 'chat'|'groupchat'
xmpp.sendMsg = (to, msg, type="", success) ->
oMsg = new JSJaCMessage()
oMsg.setTo to
oMsg.setBody encodeURIComponent msg
if type then oMsg.setType type
# console.log "XMPP: sending message", oMsg.getDoc()
@send oMsg, success
# to - JSJaCJID
xmpp._subscription = (user, state) ->
message = new JSJaCPresence()
message.setTo user.toString()
message.setType state
console.log "sending ", message.getNode()
@send message
# to - JSJaCJID
xmpp.request_subscription = (to) ->
user = xmpp.roster.findJID to
xmpp._subscription to, 'subscribe'
# from - JSJaCJID
xmpp.accept_subscription = (from) ->
xmpp._subscription from, 'subscribed'
xmpp.request_subscription from
xmpp.reject_subscription = (jid) ->
xmpp._subscription jid, 'unsubscribed'
user = @roster.findJID jid
if user then xmpp.remove_user user
# to - JSJaCJID
xmpp.remove_subscription = (to) ->
xmpp._subscription to, 'unsubscribe'
# This will only send request to server. Actuall removal from roster happens in IQ handler
# user - User
xmpp.remove_user = (user) ->
console.log "removing user"
message = new JSJaCIQ()
message.setType 'set'
message.setFrom Account.user.jid.toString()
query = message.setQuery NS_ROSTER
query.appendChild message.buildNode 'item',
jid: user.jid.toString()
subscription: 'remove'
@send message
no
# TODO: is it used anywhere?
xmpp.reject_invite = (from) ->
console.log "rejecting invite from #{from.toString()}"
message = new JSJaCPresence()
message.setTo from.toString()
message.setType 'unsubscribed'
@send message
xmpp.exit = ->
@isConnected = no
@roster.reset()
$rootScope.$broadcast 'xmpp_roster_update'
if xmpp.connected()
p = new JSJaCPresence()
p.setType "unavailable"
@send p
xmpp.registerCustomHandler = (event, handler) ->
if !@customHandlers[event]
@customHandlers[event] = []
@customHandlers[event].push handler
basicHandlers =
'onconnect': ->
xmpp.roster_loaded = $q.defer()
rosterRequest = new JSJaCIQ()
rosterRequest.setType 'get'
rosterRequest.setQuery NS_ROSTER
@send rosterRequest
@send new JSJaCPresence()
connected.resolve()
@isConnected = yes
'ondisconnect': -> @exit()
# seems like any presence message should be only handled after roster loaded
'presence': (response) ->
xmpp.roster_loaded.promise.then =>
# console.log "default presence handler", response.getNode()
from = response.getFromJID()
if from.getNode() != Account.user.name
username = from.getNode()
user = @roster.find username
type = response.getType()
unless type
if user and !user.is_muc
if show = response.getShow()
user.set_status show
else
user.set_status 'online'
else
console.log "can't find user"
else
switch type
when 'subscribe'
if user
if user.awaiting_subscription or user.subscription isnt 'none'
console.log "accepting subscription"
@accept_subscription user.jid
$rootScope.$broadcast 'xmpp_invite_accepted', user
else
@roster.add new User new JSJaCJID from.toString()
$rootScope.$broadcast 'xmpp_add_invite', from
when 'unsubscribed'
console.log 'unsubscribed:', user
if user
$rootScope.$broadcast 'xmpp_user_unsubscribed', user
@remove_user user
else console.log 'no user'
when 'unavailable'
if user and !user.is_muc then user.set_status 'offline'
'iq': (message) ->
if node = message.getQuery()
# console.log "IQ", message.getNode().outerHTML
contacts = $(node).children()
for contact in contacts
attr = $(contact).attr 'jid'
jid = null
if attr # otherwise it could be search results responce
try
jid = new JSJaCJID attr
catch e
console.error "IQ message with wrong JID: ", attr
if jid
subscription = $(contact).attr 'subscription'
unless subscription
console.log message.getNode()
throw "IQ message with unknown subscription!"
ask = $(contact).attr 'ask'
user = @roster.findJID jid
if user
if subscription is 'remove'
@roster.remove user
else
user.subscription = subscription
if !user.status and subscription in ['both', 'to']
user.status = 'offline'
$rootScope.$broadcast 'xmpp_roster_update'
else
# console.log "Adding new user to roster...", jid.toString()
user = new User jid
# No subscription and no `ask` tag mean that either subscription request was rejected or user deleted us from his roster.
# Seems like there are no accurate method to define which of this two events occured so I trigger `xmpp_user_unsubscribed`
# Me must send removal request to server and after responce user will be removed from roster.
# However, before server responce user should be listed in roster marked with `unknown` sign
if subscription is 'none' and !ask
@remove_user user
$rootScope.$broadcast 'xmpp_user_unsubscribed', user
if ask is 'subscribe'
user.awaiting_subscription = yes
user.subscription = subscription
if subscription in ['both', 'to']
user.status = 'offline'
xmpp.roster.add user
$rootScope.$broadcast 'xmpp_roster_update'
xmpp.roster_loaded.resolve()
'message': (response) -> angular.noop
'onerror': (e) ->
if parseInt(e.getAttribute('code')) is 503
xmpp.disconnect()
'packet_in': (packet) ->
# console.log "PACKET IN: ", packet.name, packet.getDoc()
'onStatusChanged': (status) ->
# http://sstrigler.github.io/JSJaC/JSJaCWebSocketConnection.html#status
console.log "XMPP status_changed to: ", status
if status is 'session-terminate-conflict' then xmpp.disconnect()
# each controller may register it's own handler so we should register
# final handler that will execute basic XMPP handler and then
# all customs (if any)
for name, handler of basicHandlers
xmpp.registerHandler name, do (name, handler) ->
->
handler.apply xmpp, arguments
if xmpp.customHandlers[name]
for custom_handler in xmpp.customHandlers[name]
custom_handler.apply xmpp, arguments
xmpp
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment