Created
August 20, 2015 08:31
-
-
Save SET001/08ba42e4b29bf9180e7b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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