diff --git a/lib/Subscribe.d.ts b/lib/Subscribe.d.ts new file mode 100644 index 000000000..d2a353ff3 --- /dev/null +++ b/lib/Subscribe.d.ts @@ -0,0 +1,23 @@ +/// +import {EventEmitter} from 'events' + +import {ExtraHeaders, Originator, OutgoingListener, SessionDirection, TerminateOptions} from './RTCSession' +import {IncomingResponse} from './SIPMessage' +import {NameAddrHeader} from './NameAddrHeader' +import {causes} from './Constants'; + +export interface AcceptOptions extends ExtraHeaders { + body?: string; +} + +export interface SendSubscribeOptions extends ExtraHeaders { + contentType?: string; + eventHandlers?: Partial; +} + +export class Subscribe extends EventEmitter { + + send(target: string, body: string, options?: SendSubscribeOptions): void; + + on(type: T, listener: MessageEventMap[T]): this; +} diff --git a/lib/Subscribe.js b/lib/Subscribe.js new file mode 100644 index 000000000..5dba1ff7b --- /dev/null +++ b/lib/Subscribe.js @@ -0,0 +1,156 @@ +const EventEmitter = require('events').EventEmitter; +const Logger = require('./Logger'); +const JsSIP_C = require('./Constants'); +const SIPMessage = require('./SIPMessage'); +const Utils = require('./Utils'); +const RequestSender = require('./RequestSender'); + +const logger = new Logger('Subscribe'); + +module.exports = class Subscribe extends EventEmitter +{ + constructor(ua) + { + super(); + + this._ua = ua; + this._request = null; + + } + + send(target, body, options = {}) + { + const originalTarget = target; + + if (target === undefined || body === undefined) + { + throw new TypeError('Not enough arguments'); + } + + // Check target validity. + target = this._ua.normalizeTarget(target); + if (!target) + { + throw new TypeError(`Invalid target: ${originalTarget}`); + } + + // Get call options. + const extraHeaders = Utils.cloneArray(options.extraHeaders); + const subscriptionDuration = options.subscriptionDuration || 1; + const contentType = options.contentType || 'text/plain'; + const eventType = options.eventType; + + extraHeaders.push(`Content-Type: ${contentType}`); + if (eventType === 'message-summary') + { + extraHeaders.push(`Contact: ${target}`); + extraHeaders.push(`Expires: ${subscriptionDuration}`); + extraHeaders.push(`Event: ${eventType}`); + } + + this._request = new SIPMessage.OutgoingRequest( + JsSIP_C.SUBSCRIBE, target, this._ua, null, extraHeaders); + + if (body) + { + this._request.body = body; + } + + const request_sender = new RequestSender(this._ua, this._request, { + onRequestTimeout : () => + { + this._onRequestTimeout(); + }, + onTransportError : () => + { + this._onTransportError(); + }, + onReceiveResponse : (response) => + { + this._receiveResponse(response); + } + }); + + request_sender.send(); + } + + _receiveResponse(response) + { + if (this._closed) + { + return; + } + switch (true) + { + case /^1[0-9]{2}$/.test(response.status_code): + // Ignore provisional responses. + break; + + case /^2[0-9]{2}$/.test(response.status_code): + this._succeeded('remote', response); + break; + + default: + { + const cause = Utils.sipErrorCause(response.status_code); + + this._failed('remote', response, cause); + break; + } + } + } + + _onRequestTimeout() + { + if (this._closed) + { + return; + } + this._failed('system', null, JsSIP_C.causes.REQUEST_TIMEOUT); + } + + _onTransportError() + { + if (this._closed) + { + return; + } + this._failed('system', null, JsSIP_C.causes.CONNECTION_ERROR); + } + + _close() + { + this._closed = true; + this._ua.destroyMessage(this); + } + + + _failed(originator, response, cause) + { + logger.debug('SUBSCRIBE failed'); + + this._close(); + + logger.debug('emit "failed"'); + + this.emit('failed', { + originator, + response : response || null, + cause + }); + } + + _succeeded(originator, response) + { + logger.debug('SUBSCRIBEsucceeded'); + + this._close(); + + logger.debug('emit "succeeded"'); + + this.emit('succeeded', { + originator, + response + }); + } +}; diff --git a/lib/UA.js b/lib/UA.js index fa26c2183..221dd98b5 100644 --- a/lib/UA.js +++ b/lib/UA.js @@ -4,6 +4,7 @@ const JsSIP_C = require('./Constants'); const Registrator = require('./Registrator'); const RTCSession = require('./RTCSession'); const Message = require('./Message'); +const Subscribe = require('./Subscribe'); const Options = require('./Options'); const Transactions = require('./Transactions'); const Transport = require('./Transport'); @@ -257,6 +258,27 @@ module.exports = class UA extends EventEmitter return message; } + /** + * Send a subscription. + * + * -param {String} target + * -param {String} body + * -param {Object} [options] + * + * -throws {TypeError} + * + */ + sendSubscribe(target, body, options) + { + logger.debug('sendSubscribe()'); + + const subscribe = new Subscribe(this); + + subscribe.send(target, body, options); + + return subscribe; + } + /** * Send a SIP OPTIONS. * @@ -742,6 +764,15 @@ module.exports = class UA extends EventEmitter { session.receiveRequest(request); } + // RFC3842 - check for message waiting information + else if (request.event['event'] === 'message-summary') + { + this.emit('sipEvent', { + event : request.event, + request + }); + request.reply(200); + } else { logger.debug('received NOTIFY request for a non existent subscription');