import { xml } from '@xmpp/client' ; import { Element } from 'ltx' ; import { BehaviorSubject , Subject } from 'rxjs' ; import { filter } from 'rxjs/operators' ; import { IqResponseStanza , Stanza } from '../../../../core/stanza' ; import { AbstractStanzaBuilder } from '../abstract-stanza-builder' ; import { IqResponseError } from '../iq-response.error' ; import { XmppChatAdapter } from '../xmpp-chat-adapter.service' ; import { AbstractXmppPlugin } from './abstract-xmpp-plugin' ; import { ServiceDiscoveryPlugin } from './service-discovery.plugin' ; export const PUBSUB_EVENT_XMLNS = 'http://jabber.org/protocol/pubsub#event' ; interface PublishOptions { node ? : string ; id ? : any ; data ? : Element ; persistItems ? : boolean ; } class PublishStanzaBuilder extends AbstractStanzaBuilder { private readonly publishOptions : PublishOptions = { persistItems : false , } ; constructor ( options ) { super ( ) ; if ( options ) { this . publishOptions = { ... this . publishOptions , ... options } ; } } toStanza ( ) { const { node , id , persistItems } = this . publishOptions ; const data = this . publishOptions . data || xml ( 'data' ) ; return xml ( 'iq' , { type : 'set' } , xml ( 'pubsub' , { xmlns : 'http://jabber.org/protocol/pubsub' } , xml ( 'publish' , { node } , xml ( 'item' , { id } , data ) ) , xml ( 'publish-options' , { } , xml ( 'x' , { xmlns : 'jabber:x:data' , type : 'submit' } , xml ( 'field' , { var : 'FORM_TYPE' , type : 'hidden' } , xml ( 'value' , { } , 'http://jabber.org/protocol/pubsub#publish-options' ) ) , xml ( 'field' , { var : 'pubsub#persist_items' } , xml ( 'value' , { } , persistItems ? 1 : 0 ) ) , xml ( 'field' , { var : 'pubsub#access_model' } , xml ( 'value' , { } , 'whitelist' ) ) ) ) ) ) ; } } class RetrieveDataStanzaBuilder extends AbstractStanzaBuilder { constructor ( private readonly node ) { super ( ) ; } toStanza ( ) { return xml ( 'iq' , { type : 'get' } , xml ( 'pubsub' , { xmlns : 'http://jabber.org/protocol/pubsub' } , xml ( 'items' , { node : this . node } ) ) ) ; } } /**
 * XEP-0060 Publish Subscribe (https://xmpp.org/extensions/xep-0060.html)
 * XEP-0223 Persistent Storage of Private Data via PubSub (https://xmpp.org/extensions/xep-0223.html)
 */ export class PublishSubscribePlugin extends AbstractXmppPlugin { readonly publish$ = new Subject < Stanza > ( ) ; private readonly supportsPrivatePublish = new BehaviorSubject < boolean | 'unknown' > ( 'unknown' ) ; constructor ( private readonly xmppChatAdapter , private readonly serviceDiscoveryPlugin ) { super ( ) ; } onBeforeOnline ( ) { return this . determineSupportForPrivatePublish ( ) ; } onOffline ( ) { this . supportsPrivatePublish . next ( 'unknown' ) ; } storePrivatePayloadPersistent ( node , id , data ) { return new Promise ( ( resolve , reject ) => { this . supportsPrivatePublish . pipe ( filter ( support => support !== 'unknown' ) ) . subscribe ( ( support ) => { if ( ! support ) { reject ( new Error ( 'does not support private publish subscribe' ) ) ; } else { resolve ( this . xmppChatAdapter . chatConnectionService . sendIq ( new PublishStanzaBuilder ( { node , id , data , persistItems : true } ) . toStanza ( ) ) ) ; } } ) ; } ) ; } privateNotify ( node , data ? , id ? ) { return new Promise ( ( resolve , reject ) => { this . supportsPrivatePublish . pipe ( filter ( support => support !== 'unknown' ) ) . subscribe ( ( support ) => { if ( ! support ) { reject ( new Error ( 'does not support private publish subscribe' ) ) ; } else { resolve ( this . xmppChatAdapter . chatConnectionService . sendIq ( new PublishStanzaBuilder ( { node , id , data , persistItems : false } ) . toStanza ( ) ) ) ; } } ) ; } ) ; } handleStanza ( stanza ) { const eventElement = stanza . getChild ( 'event' , PUBSUB_EVENT_XMLNS ) ; if ( stanza . is ( 'message' ) && eventElement ) { this . publish$ . next ( eventElement ) ; return true ; } return false ; } async retrieveNodeItems ( node ) { try { const iqResponseStanza = await this . xmppChatAdapter . chatConnectionService . sendIq ( new RetrieveDataStanzaBuilder ( node ) . toStanza ( ) ) ; return iqResponseStanza . getChild ( 'pubsub' ) . getChild ( 'items' ) . getChildren ( 'item' ) ; } catch ( e ) { if ( e instanceof IqResponseError && ( e . errorCondition === 'item-not-found' || e . errorCode === 404 ) ) { return [ ] ; } throw e ; } } private async determineSupportForPrivatePublish ( ) { let isSupported ; try { const service = await this . serviceDiscoveryPlugin . findService ( 'pubsub' , 'pep' ) ; isSupported = service . features . includes ( 'http://jabber.org/protocol/pubsub#publish-options' ) ; } catch ( e ) { isSupported = false ; } this . supportsPrivatePublish . next ( isSupported ) ; } }