Anton Shestakov <av6@dwimlabs.net>, Fri, 12 Aug 2016 22:26:01 +0800
pipelines: re-enable idempotence testing
coffee/index.coffee
Permissions: -rw-r--r--
Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps') Strophe.addNamespace('CARBONS', 'urn:xmpp:carbons:2') Strophe.addNamespace('CHATSTATES', 'http://jabber.org/protocol/chatstates') Strophe.addNamespace('CSI', 'urn:xmpp:csi:0') Strophe.addNamespace('DELAY', 'urn:xmpp:delay') Strophe.addNamespace('FORWARD', 'urn:xmpp:forward:0') Strophe.addNamespace('LAST', 'jabber:iq:last') Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0') Strophe.addNamespace('PRIVATE', 'jabber:iq:private') Strophe.addNamespace('TIME', 'urn:xmpp:time') 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.settings = new Tram.ClientSettings() window.serverInfo = new Tram.ServerInfo() window.clientState = new Tram.ClientState() window.faviconApp = new Tram.FaviconApp(model: clientState) window.sidebarApp = new Tram.SidebarApp(el: $('[data-app="sidebar"]'), model: clientState) window.progressApp = new Tram.ProgressApp(el: $('[data-app="progress"]'), model: clientState) contacts.on 'change:show', (model) -> if model.get('type') is 'self' clientState.set('show', model.get('show')) contacts.on 'change:chatstate/self', (model, cs) -> if settings.get('chatstates') and Strophe.NS.CHATSTATES in (model?.get('features') ? []) msg = $msg(to: model.get('jid'), from: X.conn.jid, type: 'chat') .c(cs, xmlns: Strophe.NS.CHATSTATES) contacts.on 'change:presence', (model) -> dupes = contacts.where(bjid: model.get('bjid')) offline = (c for c in dupes when c.get('presence') is 'unavailable') if offline.length is dupes.length offline = (c for c in offline when c.get('jid') isnt model.get('jid')) clientState.on 'action/show', (show, status) -> X.sendPresence(show: show, status: status, priority: 1) clientState.on 'action/disconnect', -> X.disconnect('Logged out') clientState.on 'change:contact', -> previous = clientState.previous('contact') previous.set('chatstate/self', 'inactive') contact = clientState.get('contact') contact.set('chatstate/self', 'active') contact.set('active', true) $logs = $('[data-app="logs"]') $logs.children().detach() $logs.append(contact.chat.render().el) $('[data-form="send"]').toggleClass('uk-hidden', not contact?) clientState.on 'change:csi', -> state = clientState.get('csi') X.conn.send($build(state, xmlns: Strophe.NS.CSI).tree()) X.conn.addHandler(onPresence, null, 'presence') X.conn.addHandler(onChatMessage, null, 'message', 'chat') X.conn.addHandler(onChatState, Strophe.NS.CHATSTATES, 'message') X.conn.addHandler(onWebRTC, Tram.NS.WEBRTC, 'message', 'chat') X.conn.addHandler(onAnyError, null, null, 'error') 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.CAPS) X.conn.disco.addFeature(Strophe.NS.CARBONS) X.conn.disco.addFeature(Strophe.NS.CHATSTATES) X.conn.disco.addFeature(Strophe.NS.CSI) X.conn.disco.addFeature(Strophe.NS.DISCO_INFO) X.conn.disco.addFeature(Strophe.NS.LAST) X.conn.disco.addFeature(Strophe.NS.MESSAGE_CORRECT) X.conn.disco.addFeature(Strophe.NS.PING) X.conn.disco.addFeature(Strophe.NS.PRIVATE) X.conn.disco.addFeature(Strophe.NS.TIME) X.conn.disco.addFeature(Strophe.NS.VERSION) X.conn.disco.addFeature(Strophe.NS.XHTML_IM) X.conn.disco.addFeature(Tram.NS.WEBRTC) loadSettings(dSettings.resolve, dSettings.reject) loadCapsCache(dCaps.resolve, dCaps.reject) getServerVersion(dVersion.resolve, dVersion.reject) X.conn.roster.get(dRoster.resolve) getServerInfo(dInfo.resolve, dInfo.reject) if Strophe.NS.CARBONS in serverInfo.get('features') serverInfo.set('carbons', true) serverInfo.set('carbons', false) enableCarbons(okcb, failcb) hasCSI = X.conn.features?.getElementsByTagNameNS(Strophe.NS.CSI, 'csi')[0]? serverInfo.set('csi', hasCSI) $.when(dSettings, dVersion, dInfo, dRoster, dCarbons).always -> X.sendPresence(status: 'Online', priority: 1) delay = stanza.getElementsByTagNameNS(Strophe.NS.DELAY, 'delay')[0] return new Date(delay?.getAttribute('stamp') ? Date.now()) 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 getServerVersion = (doneCallback, failCallback) -> iq = $iq(type: 'get', id: X.conn.getUniqueId('version'), to: X.conn.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]) console.error("couldn't get version", stanza?.innerHTML) X.conn.sendIQ(iq.tree(), okcb, failcb, Tram.config.iqTimeout) enableCarbons = (doneCallback, failCallback) -> iq = $iq(type: 'set', id: X.conn.getUniqueId('enable')) .c('enable', xmlns: Strophe.NS.CARBONS) serverInfo.set('carbons', true) serverInfo.set('carbons', false) X.conn.sendIQ(iq.tree(), okcb, failcb, Tram.config.iqTimeout) $(window).on 'focus.tram', -> window.clearTimeout(csiTimer) clientState.set('csi', 'active') $(window).on 'blur.tram', -> window.clearTimeout(csiTimer) csiTimer = window.setTimeout -> clientState.set('csi', 'inactive') , Tram.config.inactiveTime loadSettings = (doneCallback, failCallback) -> iq = $iq(type: 'get', id: X.conn.getUniqueId('settings')) .c('query', xmlns: Strophe.NS.PRIVATE) .c('settings', xmlns: Tram.NS.PRIVATE) el = stanza.getElementsByTagName('settings')[0] data = JSON.parse(el.firstChild.nodeValue) console.error("couldn't load settings", stanza) X.conn.sendIQ(iq.tree(), okcb, failcb, Tram.config.iqTimeout) _saveSettings = (doneCallback, failCallback) -> payload = JSON.stringify(settings.toJSON()) iq = $iq(type: 'set', id: X.conn.getUniqueId('settings')) .c('query', xmlns: Strophe.NS.PRIVATE) .c('settings', xmlns: Tram.NS.PRIVATE).t(payload) console.warn("couldn't save settings", stanza) X.conn.sendIQ(iq.tree(), okcb, failcb, Tram.config.iqTimeout) saveSettings = _(_saveSettings).debounce(1000) loadCapsCache = (doneCallback, failCallback) -> iq = $iq(type: 'get', id: X.conn.getUniqueId('capscache')) .c('query', xmlns: Strophe.NS.PRIVATE) .c('capscache', xmlns: Tram.NS.PRIVATE) el = stanza.getElementsByTagName('capscache')[0] data = JSON.parse(el.firstChild.nodeValue) _(capscache).defaults(data) console.error("couldn't load caps cache", stanza) X.conn.sendIQ(iq.tree(), okcb, failcb, Tram.config.iqTimeout) _saveCapsCache = (doneCallback, failCallback) -> payload = JSON.stringify(capscache) iq = $iq(type: 'set', id: X.conn.getUniqueId('capscache')) .c('query', xmlns: Strophe.NS.PRIVATE) .c('capscache', xmlns: Tram.NS.PRIVATE).t(payload) console.warn("couldn't save caps cache", stanza) X.conn.sendIQ(iq.tree(), okcb, failcb, Tram.config.iqTimeout) saveCapsCache = _(_saveCapsCache).debounce(1000) console.warn('got an error:', stanza, stanza.outerHTML) id = stanza.getAttribute('id') from = stanza.getAttribute('from') iq = $iq(to: from, type: 'result', id: id) .c('query', xmlns: Strophe.NS.LAST, 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()) getContact = (from, node, ver) -> 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(bjid) contact.on 'action/unauthorize', -> X.conn.roster.unauthorize(bjid) contact.on 'action/remove', -> if X.conn.roster.findItem(bjid) X.conn.roster.remove bjid, -> contacts.remove(contacts.where(bjid: bjid)) if contacts.find(bjid: bjid, presence: 'subscribe')? X.conn.roster.unauthorize(bjid) contacts.remove(contacts.where(bjid: 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) if Strophe.getResourceFromJid(from)? getClientInfo(contact, node, ver) getContactProfile = (contact) -> bjid = contact.get('bjid') if bjid is Strophe.getBareJidFromJid(X.conn.jid) vcard = stanza.getElementsByTagNameNS(Strophe.NS.VCARD, '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')) if avatar.mime and avatar.data console.warn("couldn't get vcard", stanza) X.conn.vcard.get(okcb, bjid, failcb) getClientInfo = (contact, node, ver) -> clientId = "#{ node }##{ ver }" return contact.set('features', capscache[clientId]) features = (el.getAttribute('var') for el in stanza.getElementsByTagName('feature')) contact.set('features', features) capscache[clientId] = features console.warn("couldn't get client info", stanza) X.conn.disco.info(jid, clientId, okcb, failcb, Tram.config.iqTimeout) getServerInfo = (doneCallback, failCallback) -> caps = X.conn.features.getElementsByTagNameNS(Strophe.NS.CAPS, 'c')[0] node = caps?.getAttribute('node') ver = caps?.getAttribute('ver') serverId = "#{ node }##{ ver }" serverInfo.set('features', capscache[serverId]) features = (el.getAttribute('var') for el in stanza.getElementsByTagName('feature')) serverInfo.set('features', features) capscache[serverId] = features console.warn("couldn't get server info", stanza) X.conn.disco.info(X.conn.domain, serverId, okcb, failcb, Tram.config.iqTimeout) type = stanza.getAttribute('type') ? 'available' from = stanza.getAttribute('from') # Upon receiving the presence stanza of type "unsubscribe", the contact # SHOULD acknowledge receipt of that subscription state notification # through either "affirming" it by sending a presence stanza of type # "unsubscribed" to the user or ... X.conn.roster.unauthorize(from) # 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) caps = stanza.getElementsByTagNameNS(Strophe.NS.CAPS, 'c')[0] ver = caps?.getAttribute('ver') node = caps?.getAttribute('node') contact = getContact(from, node, ver) 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' contact.chat.collection.add id: stanza.getAttribute('id') text: status ? "is now #{ show }" onChatMessage = (stanza) -> sent = stanza.getElementsByTagNameNS(Strophe.NS.CARBONS, 'sent')[0] received = stanza.getElementsByTagNameNS(Strophe.NS.CARBONS, 'received')[0] carbon = sent? or received? forwarded = sent.getElementsByTagNameNS(Strophe.NS.FORWARD, 'forwarded')[0] forwarded = received.getElementsByTagNameNS(Strophe.NS.FORWARD, 'forwarded')[0] message = forwarded.getElementsByTagName('message')[0] text = getText(message.getElementsByTagName('body')[0]) xhtml = message.getElementsByTagNameNS(Strophe.NS.XHTML_IM, 'html')[0] id = message.getAttribute('id') to = message.getAttribute('to') from = message.getAttribute('from') type = message.getAttribute('type') ? 'normal' thread = getText(message.getElementsByTagName('thread')[0]) subject = getText(message.getElementsByTagName('subject')[0]) contact = getContact(from) fromSelf = contact.get('bjid') is Strophe.getBareJidFromJid(X.conn.jid) if xhtml? and (fromSelf or X.conn.roster.findItem(contact.get('bjid'))) body = xhtml.getElementsByTagNameNS(Strophe.NS.XHTML, 'body')[0] html = Strophe.createHtml(body).innerHTML if forwarded? and fromSelf chat = getContact(to).chat replace = stanza.getElementsByTagNameNS(Strophe.NS.MESSAGE_CORRECT, 'replace')[0] rid = replace?.getAttribute('id') msg = chat.collection.get(rid) if msg? and msg.get('from') is from onChatState = (stanza) -> delay = stanza.getElementsByTagNameNS(Strophe.NS.DELAY, 'delay')[0] from = stanza.getAttribute('from') contact = getContact(from) elements = stanza.getElementsByTagNameNS(Strophe.NS.CHATSTATES, '*') contact.set('chatstate', elements[0].tagName.toLowerCase()) from = stanza.getAttribute('from') contact = getContact(from) intent = stanza.getElementsByTagNameNS(Tram.NS.WEBRTC, 'intent')[0] payload = stanza.getElementsByTagNameNS(Tram.NS.WEBRTC, 'payload')[0] contact.set('callstate', 'incoming') contact.chat.collection.add id: stanza.getAttribute('id') contact.unset('callstate') contact.w.onPayload(stanza) window.X = new Tram.XMPPInterface() connForm.unset('auth-errors') connForm.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') X.on 'status', (status) -> when Strophe.Status.CONNECTING clientState.set('progress', 0) when Strophe.Status.CONNECTED clientState.set('progress', 100) clientState.unset('progress') $(window).on 'beforeunload unload', -> X.disconnect('Window closed') window.connForm = new Tram.ConnectionForm() X.connect(connForm.get('username').trim(), connForm.get('password')) $form = $('[data-form="connect"]') window.connRivet = rivets.bind($form, form: connForm, connect: connectfn) if text isnt '' and clientState.has('contact') contact = clientState.get('contact') msg = $msg(to: contact.get('jid'), from: X.conn.jid, type: 'chat').c('body').t(text).up() if settings.get('chatstates') and Strophe.NS.CHATSTATES in (contact.get('features') ? []) msg.c('active', xmlns: Strophe.NS.CHATSTATES) contact.set('chatstate/self', 'active', silent: true) contact.chat.collection.add contact: contacts.findWhere(type: 'self') $('body').on 'dragover dragenter', (event) -> $('body').on 'drop', (event) -> $('[data-send-button]').on('click', sendMessage) $('#msg').on 'keypress', (e) -> clientState.get('contact')?.set('chatstate/self', 'composing') if clientState.get('contact')?.get('chatstate/self') is 'composing' clientState.get('contact')?.set('chatstate/self', 'paused') $('[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') $('[data-unregister-button]').on 'click', -> $('#settings-modal').one 'show.uk.modal', -> window.settingsRivet = rivets.bind($('[data-app="settings"]'), settings: settings) window.serverRivet = rivets.bind($('[data-app="server-info"]'), server: serverInfo) settings.on('change', saveSettings) $('#profile-modal').one 'show.uk.modal', -> window.profileRivet = rivets.bind($('[data-app="profile"]'), contact: contacts.get(X.conn.jid)) $('time[datetime]').each -> $this.text(moment($this.attr('datetime')).fromNow())