@babblevoice/babble-drachtio-presence
Version:
A presence agent which will work with other modules in this family.
174 lines (149 loc) • 4.43 kB
JavaScript
const sipauth = require( "@babblevoice/babble-drachtio-auth" )
const uacstore = require( "./uacstore.js" )
const doc = require( "./presencedocument.js" )
const expiresmatch = /expires=(\d*)/
const activematch = /^(active|init)/
class subscription {
/**
* @param { object } reg - the register object from babble-drachtio-registrar
* @param { object } options - options
* @param { object } options - our options object
* @param { function } options.userlookup - async user lookup function - required
* @param { boolean } [ options.proxy = true ] - true = respond with 407 otherwise 401
* @param { number } [ options.authtimeout = 100000 ] - timeout in mS
*/
constructor( reg, options ) {
this.reg = reg
this.uuid = reg.uuid
this._options = options
try {
this._createdialog()
} catch( e ) {
console.error( `Error with creating client subscription for ${reg.auth.username}@${reg.auth.realm}` )
console.error( e )
}
}
/**
* @private
*/
async _createdialog() {
try {
this.dialog = await this._options.srf.createUAC( this.reg.contacts[ 0 ], {
"method": "SUBSCRIBE",
"headers": {
"To": `<sip:${this.reg.auth.username}@${this.reg.auth.realm}>`,
"From": `<sip:${this.reg.auth.username}@${this.reg.auth.realm}>`,
"Event": "presence",
"Expires": this.reg.expiresin,
"Accept": "application/pidf+xml"
}
} )
} catch( e ) {
console.error( "Failed SUBSCRIBE to client." )
return
}
this._auth = sipauth.create( this._options.proxy )
uacstore.set( this )
this.dialog.on( "destroy", this._ondestroy.bind( this ) )
this.dialog.on( "notify", this._onnotify.bind( this ) )
}
/**
*
* @param { object } reg
* @param { object } options
* @returns
*/
static create( reg, options ) {
if( uacstore.has( reg ) ) return
return new subscription( reg, options )
}
/**
*
* @param { object } reg
*/
static destroy( reg ) {
uacstore.delete( reg )
}
/**
* @private
*/
async _ondestroy() {
uacstore.delete( this )
}
/**
* @private
* @param { object } req
* @param { object } res
*/
// eslint-disable-next-line complexity
async _onnotify( req, res ) {
if( !this._auth.has( req ) ) {
this._auth.requestauth( req, res )
return
}
const authorization = this._auth.parseauthheaders( req )
if( !this._user ) {
this._user = await this._options.userlookup( authorization.username, authorization.realm )
if( !this._user ) {
this.dialog.destroy()
return false
}
this._user.username = authorization.username
this._user.realm = authorization.realm
this._user.entity = authorization.username + "@" + authorization.realm
}
if( !this._auth.equal( req ) ) {
const regauth = this.reg.getauth( req )
if( regauth ) {
this._auth = regauth
}
}
if( !this._auth.verifyauth( req, authorization, this._user.secret ) ) {
if( this._auth.stale ) {
this._auth.requestauth( req, res )
return
}
res.send( 403, "Bad auth stale" )
return
}
/*
Should be active and possibly contain ;expires= where 0 expires the subscription.
*/
const substate = req.get( "Subscription-State" )
if( null === substate.match( activematch ) ) {
res.send( 400, "Wrong subscription state" )
return
}
const expires = substate.match( expiresmatch )
if( null !== expires && 1 < expires.length ) {
if( "0" == expires[ 1 ] ) {
uacstore.delete( this )
res.send( 200 )
return
}
}
if( 0 === parseInt( req.get( "Content-Length" ) ) ) {
res.send( 200 )
return
}
/* We are now authed */
const s = doc.parsepidfxml( req.get( "Content-Type" ), req.body )
if( false === s ) {
res.send( 400, "Bad request - or at least we don't understand it" )
} else {
this._options.em.emit( "presence.status.in", {
...s,
...{
"entity": this._user.entity,
"source": {
"event": "NOTIFY",
"address": req.source_address,
"port": req.source_port,
"related": this.reg.contacts[ 0 ]
}
}
} )
}
}
}
module.exports = subscription