Anton Shestakov <av6@dwimlabs.net>, Thu, 07 Apr 2016 22:56:16 +0800
index: check if roster has the item before removing it
Sometimes we get events from contacts that are not in user's roster, trying to
remove such contacts used to fail before this patch.
coffee/index.coffee
Permissions: -rw-r--r--
Strophe.addNamespace('LAST', 'jabber:iq:last') Strophe.addNamespace('TIME', 'urn:xmpp:time') Strophe.addNamespace('VCARD_UPDATE', 'vcard-temp:x:update') class window.Tram.ClientState extends Backbone.Model window.contacts = new Tram.Contacts() window.contactsApp = new Tram.ContactsApp(el: $('[data-app="contacts"]'), collection: contacts) window.calls = new Tram.Calls() window.callsApp = new Tram.CallsApp(el: $('[data-app="calls"]'), collection: calls) window.clientState = new Tram.ClientState() window.faviconApp = new Tram.FaviconApp(model: clientState) window.sidebarApp = new Tram.SidebarApp(el: $('[data-app="sidebar"]'), model: clientState) contacts.on 'change:show', (model) -> if model.get('type') is 'self' clientState.set('show', model.get('show')) clientState.on 'action/show', (show, status) -> X.conn.send($pres().c('priority').t('1').up().c('show').t(show).up().c('status').t(status).tree()) clientState.on 'action/disconnect', -> X.disconnect('Logged out') clientState.on 'change:contact', -> contact = clientState.get('contact') $logs = $('[data-app="logs"]') $logs.children().detach() $logs.append(contact.chat.render().el) $('[data-form="send"]').toggleClass('uk-hidden', not clientState.has('contact')) X.conn.addHandler(onPresence, null, 'presence') X.conn.addHandler(onProfileUpdate, Strophe.NS.VCARD_UPDATE, 'presence') X.conn.addHandler(onChatMessage, null, 'message', 'chat') X.conn.addHandler(onWebRTC, Tram.NS.WEBRTC, 'message', 'chat') X.conn.addHandler(onGetLast, Strophe.NS.LAST, 'iq', 'get') X.conn.addHandler(onGetTime, Strophe.NS.TIME, 'iq', 'get') X.conn.addHandler(onGetVersion, Strophe.NS.VERSION, 'iq', 'get') X.conn.ping.addPingHandler(onPing) X.conn.disco.addIdentity('client', 'web', Tram.info.client) X.conn.disco.addFeature(Strophe.NS.DISCO_INFO) X.conn.disco.addFeature(Strophe.NS.LAST) X.conn.disco.addFeature(Strophe.NS.PING) X.conn.disco.addFeature(Strophe.NS.TIME) X.conn.disco.addFeature(Strophe.NS.VERSION) X.conn.disco.addFeature(Tram.NS.WEBRTC) X.conn.send($pres().c('priority').t('1').up().c('status').t('Online').tree()) if el.childNodes.length is 0 and el.nodeType is Strophe.ElementType.TEXT for node in el.childNodes if node.nodeType is Strophe.ElementType.TEXT iq = $iq(type: 'get', id: X.conn.getUniqueId('version'), to: Tram.config.domain).c('query', xmlns: Strophe.NS.VERSION) name = getText(stanza.getElementsByTagName('name')[0]) version = getText(stanza.getElementsByTagName('version')[0]) os = getText(stanza.getElementsByTagName('os')[0]) 'server/version': version console.error("couldn't get version", stanza?.innerHTML) X.conn.sendIQ(iq.tree(), okcb, failcb, 5000) id = stanza.getAttribute('id') from = stanza.getAttribute('from') iq = $iq(to: from, type: 'result', id: id) .c('query', xmlns: Strophe.NS.VERSION, seconds: '0') id = stanza.getAttribute('id') from = stanza.getAttribute('from') iq = $iq(to: from, type: 'result', id: id) .c('time', xmlns: Strophe.NS.TIME) .c('tzo').t(now.format('Z')).up() .c('utc').t(now.toISOString()) onGetVersion = (stanza) -> id = stanza.getAttribute('id') from = stanza.getAttribute('from') iq = $iq(to: from, type: 'result', id: id) .c('query', xmlns: Strophe.NS.VERSION) .c('name').t(Tram.info.client).up() .c('version').t(Tram.info.version) chats[jid] ?= new Tram.LogApp(collection: new Tram.Messages()) contact = contacts.get(from) self = from is X.conn.jid model.set('type', 'contact') bjid = Strophe.getBareJidFromJid(from) type: if self then 'self' else 'contact' contact.chat = getChat(bjid) contact.on 'action/chat', -> clientState.set('contact', contact) contact.on 'action/authorize', -> X.conn.roster.authorize(contact.get('bjid')) contact.on 'action/unauthorize', -> X.conn.roster.unauthorize(contact.get('bjid')) contact.on 'action/remove', -> if X.conn.roster.findItem(contact.get('bjid')) X.conn.roster.remove contact.get('bjid'), -> contact.w = new Tram.WebRTCInterface(contact) contact.on 'action/call', (media) -> contact.set('callstate', 'outgoing') contact.w.init(true, audio: 'a' in media, video: 'v' in media) contact.on 'action/accept', (media) -> contact.w.init(false, audio: 'a' in media, video: 'v' in media) contact.on 'action/decline action/hangup', -> contact.w.sendIntent('terminate') contact.unset('callstate') getContactProfile(contact) getContactProfile = (contact) -> vcard = stanza.getElementsByTagName('vCard')[0] console.warn("no vcard in response", stanza) nickname: getText(vcard.querySelector('NICKNAME')) fullname: getText(vcard.querySelector('FN')) firstname: getText(vcard.querySelector('N > GIVEN')) lastname: getText(vcard.querySelector('N > FAMILY')) mime = getText(vcard.querySelector('PHOTO > TYPE')) data = getText(vcard.querySelector('PHOTO > BINVAL')) contact.set('avatar', mime: mime, data: data) console.warn("couldn't get vcard", stanza) bjid = contact.get('bjid') if bjid is Strophe.getBareJidFromJid(X.conn.jid) X.conn.vcard.get(okcb, bjid, failcb) type = stanza.getAttribute('type') ? 'available' from = stanza.getAttribute('from') console.warn("""not handling <presence type="#{ type }">""", stanza) # Upon receiving the presence stanza of type "subscribed", the contact # SHOULD acknowledge receipt of that subscription state notification # through either "affirming" it by sending a presence stanza of type # "subscribe" to the user or ... X.conn.roster.subscribe(from) # Upon receiving the presence stanza of type "unsubscribed", the user # SHOULD acknowledge receipt of that subscription state notification # through either "affirming" it by sending a presence stanza of type # "unsubscribe" to the contact or ... X.conn.roster.unsubscribe(from) console.error('got <presence type="error">', stanza) contact = getContact(from) show = getText(stanza.getElementsByTagName('show')[0]) status = getText(stanza.getElementsByTagName('status')[0]) priority = getText(stanza.getElementsByTagName('priority')[0]) if type in ['available', 'unavailable'] and contact.get('type') isnt 'self' delay = stanza.getElementsByTagName('delay')[0] stamp = if delay? then new Date(delay.getAttribute('stamp')) else new Date() contact.chat.collection.add id: stanza.getAttribute('id') text: status ? "is now #{ show }" onProfileUpdate = (stanza) -> from = stanza.getAttribute('from') contact = getContact(from) getContactProfile(contact) onChatMessage = (stanza) -> from = stanza.getAttribute('from') type = stanza.getAttribute('type') text = getText(stanza.getElementsByTagName('body')[0]) thread = getText(stanza.getElementsByTagName('thread')[0]) delay = stanza.getElementsByTagName('delay')[0] stamp = if delay? then new Date(delay.getAttribute('stamp')) else new Date() contact = getContact(from) contact.chat.collection.add id: stanza.getAttribute('id') from = stanza.getAttribute('from') contact = getContact(from) intent = stanza.getElementsByTagName('intent')[0] payload = stanza.getElementsByTagName('payload')[0] delay = stanza.getElementsByTagName('delay')[0] stamp = if delay? then new Date(delay.getAttribute('stamp')) else new Date() contact.set('callstate', 'incoming') contact.chat.collection.add id: stanza.getAttribute('id') contact.unset('callstate') contact.w.onPayload(stanza) window.X = new Tram.XMPPInterface() connData.unset('auth-errors') connData.set('auth-errors', ['Invalid username or password.']) $('[data-step="login"]').removeClass('uk-hidden') $('[data-step="main"]').addClass('uk-hidden') X.on 'connected attached', -> $('[data-step="login"]').addClass('uk-hidden') $('[data-step="main"]').removeClass('uk-hidden') $(window).on 'beforeunload unload', -> X.disconnect('Window closed') class ConnectionData extends Backbone.Model validate: (attrs, options) -> @unset('username-errors') @unset('password-errors') if (attrs.username ? '').trim() is '' @set('username-errors', ['This field is required.']) if (attrs.password ? '') is '' @set('password-errors', ['This field is required.']) return @has('username-errors') or @has('password-errors') window.connData = new ConnectionData() X.connect(connData.get('username').trim(), connData.get('password')) $form = $('[data-form="connect"]') window.connRivet = rivets.bind($form.get(0), data: connData, connect: connectfn) $form.find('input').on 'keydown', (e) -> if (not @required or @value isnt '') and e.keyCode is 13 index = $form.find('input').index(@) $next = $form.find('input').eq(index + 1) $form.find('button').trigger('click') if text isnt '' and clientState.has('contact') contact = clientState.get('contact') X.conn.send($msg(to: contact.get('jid'), from: X.conn.jid, type: 'chat').c('body').t(text).tree()) contact.chat.collection.add contact: contacts.findWhere(type: 'self') $('[data-send-button]').on 'click', sendMessage $('#msg').on 'keypress', (e) -> $('[data-add-button]').on 'click', -> jid = $('#new-contact').val().trim() jid = "#{ jid }@#{ Tram.config.domain }" $('#new-contact').val('') X.conn.roster.add jid, null, [], -> X.conn.roster.subscribe(jid, 'I want to chat', null) $('#new-contact').on 'keypress', (e) -> $('[data-add-button]').trigger('click') $('time[datetime]').each -> $this.text(moment($this.attr('datetime')).fromNow())