From 57e5bc61d5505d1612f59f63136f465700fac4c8 Mon Sep 17 00:00:00 2001 From: James Criscuolo Date: Tue, 4 Jun 2019 11:37:53 -0400 Subject: [PATCH] dist/ lib/ files for 0.14.2 --- dist/sip-0.14.2.js | 19888 ++++++++++++++++ dist/sip-0.14.2.min.js | 1 + dist/sip.js | 19888 ++++++++++++++++ dist/sip.min.js | 1 + lib/ClientContext.d.ts | 23 + lib/ClientContext.js | 101 + lib/Constants.d.ts | 62 + lib/Constants.js | 192 + lib/Enums.d.ts | 61 + lib/Enums.js | 71 + lib/Exceptions.d.ts | 67 + lib/Exceptions.js | 137 + lib/Parser.d.ts | 18 + lib/Parser.js | 241 + lib/PublishContext.d.ts | 38 + lib/PublishContext.js | 275 + lib/React/SessionDescriptionHandler.d.ts | 93 + lib/React/SessionDescriptionHandler.js | 636 + .../SessionDescriptionHandlerObserver.d.ts | 10 + .../SessionDescriptionHandlerObserver.js | 23 + lib/React/index.d.ts | 1 + lib/React/index.js | 4 + lib/ReferContext.d.ts | 56 + lib/ReferContext.js | 344 + lib/RegisterContext.d.ts | 39 + lib/RegisterContext.js | 413 + lib/ServerContext.d.ts | 28 + lib/ServerContext.js | 119 + lib/Session.d.ts | 352 + lib/Session.js | 2175 ++ lib/Session/DTMF.d.ts | 25 + lib/Session/DTMF.js | 152 + lib/Subscription.d.ts | 127 + lib/Subscription.js | 433 + lib/UA.d.ts | 228 + lib/UA.js | 1111 + lib/Utils.d.ts | 36 + lib/Utils.js | 238 + lib/Web/Modifiers.d.ts | 8 + lib/Web/Modifiers.js | 121 + lib/Web/SessionDescriptionHandler.d.ts | 122 + lib/Web/SessionDescriptionHandler.js | 626 + .../SessionDescriptionHandlerObserver.d.ts | 10 + lib/Web/SessionDescriptionHandlerObserver.js | 23 + lib/Web/Simple.d.ts | 45 + lib/Web/Simple.js | 411 + lib/Web/Transport.d.ts | 158 + lib/Web/Transport.js | 693 + lib/Web/index.d.ts | 5 + lib/Web/index.js | 11 + lib/core/dialogs/dialog-state.d.ts | 31 + lib/core/dialogs/dialog-state.js | 2 + lib/core/dialogs/dialog.d.ts | 151 + lib/core/dialogs/dialog.js | 559 + lib/core/dialogs/index.d.ts | 4 + lib/core/dialogs/index.js | 6 + lib/core/dialogs/session-dialog.d.ts | 168 + lib/core/dialogs/session-dialog.js | 797 + lib/core/dialogs/subscription-dialog.d.ts | 115 + lib/core/dialogs/subscription-dialog.js | 491 + lib/core/exceptions/exception.d.ts | 8 + lib/core/exceptions/exception.js | 19 + lib/core/exceptions/index.d.ts | 3 + lib/core/exceptions/index.js | 6 + .../exceptions/transaction-state-error.d.ts | 8 + .../exceptions/transaction-state-error.js | 16 + lib/core/exceptions/transport-error.d.ts | 8 + lib/core/exceptions/transport-error.js | 16 + lib/core/index.d.ts | 11 + lib/core/index.js | 16 + lib/core/log/index.d.ts | 3 + lib/core/log/index.js | 6 + lib/core/log/levels.d.ts | 10 + lib/core/log/levels.js | 13 + lib/core/log/logger-factory.d.ts | 19 + lib/core/log/logger-factory.js | 107 + lib/core/log/logger.d.ts | 16 + lib/core/log/logger.js | 23 + lib/core/messages/body.d.ts | 61 + lib/core/messages/body.js | 117 + lib/core/messages/digest-authentication.d.ts | 50 + lib/core/messages/digest-authentication.js | 146 + lib/core/messages/grammar.d.ts | 26 + lib/core/messages/grammar.js | 47 + lib/core/messages/incoming-message.d.ts | 79 + lib/core/messages/incoming-message.js | 132 + .../messages/incoming-request-message.d.ts | 9 + lib/core/messages/incoming-request-message.js | 15 + lib/core/messages/incoming-request.d.ts | 62 + lib/core/messages/incoming-request.js | 2 + .../messages/incoming-response-message.d.ts | 9 + .../messages/incoming-response-message.js | 17 + lib/core/messages/incoming-response.d.ts | 11 + lib/core/messages/incoming-response.js | 2 + lib/core/messages/index.d.ts | 15 + lib/core/messages/index.js | 17 + lib/core/messages/methods/ack.d.ts | 12 + lib/core/messages/methods/ack.js | 2 + lib/core/messages/methods/bye.d.ts | 9 + lib/core/messages/methods/bye.js | 2 + lib/core/messages/methods/cancel.d.ts | 9 + lib/core/messages/methods/cancel.js | 2 + lib/core/messages/methods/constants.d.ts | 20 + lib/core/messages/methods/constants.js | 23 + lib/core/messages/methods/index.d.ts | 13 + lib/core/messages/methods/index.js | 4 + lib/core/messages/methods/info.d.ts | 9 + lib/core/messages/methods/info.js | 2 + lib/core/messages/methods/invite.d.ts | 70 + lib/core/messages/methods/invite.js | 2 + lib/core/messages/methods/message.d.ts | 9 + lib/core/messages/methods/message.js | 2 + lib/core/messages/methods/notify.d.ts | 9 + lib/core/messages/methods/notify.js | 2 + lib/core/messages/methods/prack.d.ts | 9 + lib/core/messages/methods/prack.js | 2 + lib/core/messages/methods/publish.d.ts | 9 + lib/core/messages/methods/publish.js | 2 + lib/core/messages/methods/refer.d.ts | 9 + lib/core/messages/methods/refer.js | 2 + lib/core/messages/methods/register.d.ts | 9 + lib/core/messages/methods/register.js | 2 + lib/core/messages/methods/subscribe.d.ts | 36 + lib/core/messages/methods/subscribe.js | 2 + lib/core/messages/name-addr-header.d.ts | 23 + lib/core/messages/name-addr-header.js | 56 + .../messages/outgoing-request-message.d.ts | 95 + lib/core/messages/outgoing-request-message.js | 240 + lib/core/messages/outgoing-request.d.ts | 59 + lib/core/messages/outgoing-request.js | 2 + lib/core/messages/outgoing-response.d.ts | 37 + lib/core/messages/outgoing-response.js | 128 + lib/core/messages/parameters.d.ts | 16 + lib/core/messages/parameters.js | 44 + lib/core/messages/uri.d.ts | 38 + lib/core/messages/uri.js | 205 + lib/core/messages/utils.d.ts | 24 + lib/core/messages/utils.js | 145 + lib/core/session/index.d.ts | 2 + lib/core/session/index.js | 4 + lib/core/session/session-delegate.d.ts | 49 + lib/core/session/session-delegate.js | 2 + lib/core/session/session.d.ts | 131 + lib/core/session/session.js | 37 + lib/core/subscription/index.d.ts | 2 + lib/core/subscription/index.js | 4 + .../subscription/subscription-delegate.d.ts | 23 + .../subscription/subscription-delegate.js | 2 + lib/core/subscription/subscription.d.ts | 50 + lib/core/subscription/subscription.js | 14 + lib/core/timers.d.ts | 16 + lib/core/timers.js | 21 + lib/core/transactions/client-transaction.d.ts | 44 + lib/core/transactions/client-transaction.js | 72 + lib/core/transactions/index.d.ts | 10 + lib/core/transactions/index.js | 12 + .../invite-client-transaction.d.ts | 115 + .../transactions/invite-client-transaction.js | 485 + .../invite-server-transaction.d.ts | 125 + .../transactions/invite-server-transaction.js | 389 + .../non-invite-client-transaction.d.ts | 68 + .../non-invite-client-transaction.js | 245 + .../non-invite-server-transaction.d.ts | 55 + .../non-invite-server-transaction.js | 229 + lib/core/transactions/server-transaction.d.ts | 33 + lib/core/transactions/server-transaction.js | 32 + lib/core/transactions/transaction-state.d.ts | 10 + lib/core/transactions/transaction-state.js | 13 + lib/core/transactions/transaction-user.d.ts | 68 + lib/core/transactions/transaction-user.js | 2 + lib/core/transactions/transaction.d.ts | 60 + lib/core/transactions/transaction.js | 122 + lib/core/transport.d.ts | 73 + lib/core/transport.js | 88 + lib/core/user-agent-core/allowed-methods.d.ts | 4 + lib/core/user-agent-core/allowed-methods.js | 19 + lib/core/user-agent-core/index.d.ts | 3 + lib/core/user-agent-core/index.js | 4 + .../user-agent-core-configuration.d.ts | 94 + .../user-agent-core-configuration.js | 2 + .../user-agent-core-delegate.d.ts | 31 + .../user-agent-core-delegate.js | 2 + lib/core/user-agent-core/user-agent-core.d.ts | 176 + lib/core/user-agent-core/user-agent-core.js | 791 + .../user-agents/bye-user-agent-client.d.ts | 6 + lib/core/user-agents/bye-user-agent-client.js | 18 + .../user-agents/bye-user-agent-server.d.ts | 6 + lib/core/user-agents/bye-user-agent-server.js | 13 + .../user-agents/cancel-user-agent-client.d.ts | 6 + .../user-agents/cancel-user-agent-client.js | 13 + lib/core/user-agents/index.d.ts | 24 + lib/core/user-agents/index.js | 27 + .../user-agents/info-user-agent-client.d.ts | 6 + .../user-agents/info-user-agent-client.js | 17 + .../user-agents/info-user-agent-server.d.ts | 6 + .../user-agents/info-user-agent-server.js | 13 + .../user-agents/invite-user-agent-client.d.ts | 26 + .../user-agents/invite-user-agent-client.js | 283 + .../user-agents/invite-user-agent-server.d.ts | 73 + .../user-agents/invite-user-agent-server.js | 225 + .../message-user-agent-client.d.ts | 6 + .../user-agents/message-user-agent-client.js | 13 + .../message-user-agent-server.d.ts | 7 + .../user-agents/message-user-agent-server.js | 15 + .../user-agents/notify-user-agent-client.d.ts | 6 + .../user-agents/notify-user-agent-client.js | 17 + .../user-agents/notify-user-agent-server.d.ts | 12 + .../user-agents/notify-user-agent-server.js | 26 + .../user-agents/prack-user-agent-client.d.ts | 6 + .../user-agents/prack-user-agent-client.js | 18 + .../user-agents/prack-user-agent-server.d.ts | 12 + .../user-agents/prack-user-agent-server.js | 29 + .../publish-user-agent-client.d.ts | 6 + .../user-agents/publish-user-agent-client.js | 13 + .../re-invite-user-agent-client.d.ts | 15 + .../re-invite-user-agent-client.js | 106 + .../re-invite-user-agent-server.d.ts | 23 + .../re-invite-user-agent-server.js | 68 + .../re-subscribe-user-agent-client.d.ts | 13 + .../re-subscribe-user-agent-client.js | 69 + .../re-subscribe-user-agent-server.d.ts | 6 + .../re-subscribe-user-agent-server.js | 13 + .../user-agents/refer-user-agent-client.d.ts | 6 + .../user-agents/refer-user-agent-client.js | 17 + .../user-agents/refer-user-agent-server.d.ts | 12 + .../user-agents/refer-user-agent-server.js | 26 + .../register-user-agent-client.d.ts | 6 + .../user-agents/register-user-agent-client.js | 13 + .../subscribe-user-agent-client.d.ts | 62 + .../subscribe-user-agent-client.js | 294 + .../subscribe-user-agent-server.d.ts | 7 + .../subscribe-user-agent-server.js | 15 + lib/core/user-agents/user-agent-client.d.ts | 85 + lib/core/user-agents/user-agent-client.js | 310 + lib/core/user-agents/user-agent-server.d.ts | 76 + lib/core/user-agents/user-agent-server.js | 258 + lib/grammar/dist/grammar.d.ts | 50 + lib/grammar/dist/grammar.js | 1508 ++ lib/index.d.ts | 31 + lib/index.js | 65 + lib/session-description-handler-factory.d.ts | 14 + lib/session-description-handler-factory.js | 2 + lib/session-description-handler.d.ts | 73 + lib/session-description-handler.js | 3 + 244 files changed, 62811 insertions(+) create mode 100644 dist/sip-0.14.2.js create mode 100644 dist/sip-0.14.2.min.js create mode 100644 dist/sip.js create mode 100644 dist/sip.min.js create mode 100644 lib/ClientContext.d.ts create mode 100644 lib/ClientContext.js create mode 100644 lib/Constants.d.ts create mode 100644 lib/Constants.js create mode 100644 lib/Enums.d.ts create mode 100644 lib/Enums.js create mode 100644 lib/Exceptions.d.ts create mode 100644 lib/Exceptions.js create mode 100644 lib/Parser.d.ts create mode 100644 lib/Parser.js create mode 100644 lib/PublishContext.d.ts create mode 100644 lib/PublishContext.js create mode 100644 lib/React/SessionDescriptionHandler.d.ts create mode 100644 lib/React/SessionDescriptionHandler.js create mode 100644 lib/React/SessionDescriptionHandlerObserver.d.ts create mode 100644 lib/React/SessionDescriptionHandlerObserver.js create mode 100644 lib/React/index.d.ts create mode 100644 lib/React/index.js create mode 100644 lib/ReferContext.d.ts create mode 100644 lib/ReferContext.js create mode 100644 lib/RegisterContext.d.ts create mode 100644 lib/RegisterContext.js create mode 100644 lib/ServerContext.d.ts create mode 100644 lib/ServerContext.js create mode 100644 lib/Session.d.ts create mode 100644 lib/Session.js create mode 100644 lib/Session/DTMF.d.ts create mode 100644 lib/Session/DTMF.js create mode 100644 lib/Subscription.d.ts create mode 100644 lib/Subscription.js create mode 100644 lib/UA.d.ts create mode 100644 lib/UA.js create mode 100644 lib/Utils.d.ts create mode 100644 lib/Utils.js create mode 100644 lib/Web/Modifiers.d.ts create mode 100644 lib/Web/Modifiers.js create mode 100644 lib/Web/SessionDescriptionHandler.d.ts create mode 100644 lib/Web/SessionDescriptionHandler.js create mode 100644 lib/Web/SessionDescriptionHandlerObserver.d.ts create mode 100644 lib/Web/SessionDescriptionHandlerObserver.js create mode 100644 lib/Web/Simple.d.ts create mode 100644 lib/Web/Simple.js create mode 100644 lib/Web/Transport.d.ts create mode 100644 lib/Web/Transport.js create mode 100644 lib/Web/index.d.ts create mode 100644 lib/Web/index.js create mode 100644 lib/core/dialogs/dialog-state.d.ts create mode 100644 lib/core/dialogs/dialog-state.js create mode 100644 lib/core/dialogs/dialog.d.ts create mode 100644 lib/core/dialogs/dialog.js create mode 100644 lib/core/dialogs/index.d.ts create mode 100644 lib/core/dialogs/index.js create mode 100644 lib/core/dialogs/session-dialog.d.ts create mode 100644 lib/core/dialogs/session-dialog.js create mode 100644 lib/core/dialogs/subscription-dialog.d.ts create mode 100644 lib/core/dialogs/subscription-dialog.js create mode 100644 lib/core/exceptions/exception.d.ts create mode 100644 lib/core/exceptions/exception.js create mode 100644 lib/core/exceptions/index.d.ts create mode 100644 lib/core/exceptions/index.js create mode 100644 lib/core/exceptions/transaction-state-error.d.ts create mode 100644 lib/core/exceptions/transaction-state-error.js create mode 100644 lib/core/exceptions/transport-error.d.ts create mode 100644 lib/core/exceptions/transport-error.js create mode 100644 lib/core/index.d.ts create mode 100644 lib/core/index.js create mode 100644 lib/core/log/index.d.ts create mode 100644 lib/core/log/index.js create mode 100644 lib/core/log/levels.d.ts create mode 100644 lib/core/log/levels.js create mode 100644 lib/core/log/logger-factory.d.ts create mode 100644 lib/core/log/logger-factory.js create mode 100644 lib/core/log/logger.d.ts create mode 100644 lib/core/log/logger.js create mode 100644 lib/core/messages/body.d.ts create mode 100644 lib/core/messages/body.js create mode 100644 lib/core/messages/digest-authentication.d.ts create mode 100644 lib/core/messages/digest-authentication.js create mode 100644 lib/core/messages/grammar.d.ts create mode 100644 lib/core/messages/grammar.js create mode 100644 lib/core/messages/incoming-message.d.ts create mode 100644 lib/core/messages/incoming-message.js create mode 100644 lib/core/messages/incoming-request-message.d.ts create mode 100644 lib/core/messages/incoming-request-message.js create mode 100644 lib/core/messages/incoming-request.d.ts create mode 100644 lib/core/messages/incoming-request.js create mode 100644 lib/core/messages/incoming-response-message.d.ts create mode 100644 lib/core/messages/incoming-response-message.js create mode 100644 lib/core/messages/incoming-response.d.ts create mode 100644 lib/core/messages/incoming-response.js create mode 100644 lib/core/messages/index.d.ts create mode 100644 lib/core/messages/index.js create mode 100644 lib/core/messages/methods/ack.d.ts create mode 100644 lib/core/messages/methods/ack.js create mode 100644 lib/core/messages/methods/bye.d.ts create mode 100644 lib/core/messages/methods/bye.js create mode 100644 lib/core/messages/methods/cancel.d.ts create mode 100644 lib/core/messages/methods/cancel.js create mode 100644 lib/core/messages/methods/constants.d.ts create mode 100644 lib/core/messages/methods/constants.js create mode 100644 lib/core/messages/methods/index.d.ts create mode 100644 lib/core/messages/methods/index.js create mode 100644 lib/core/messages/methods/info.d.ts create mode 100644 lib/core/messages/methods/info.js create mode 100644 lib/core/messages/methods/invite.d.ts create mode 100644 lib/core/messages/methods/invite.js create mode 100644 lib/core/messages/methods/message.d.ts create mode 100644 lib/core/messages/methods/message.js create mode 100644 lib/core/messages/methods/notify.d.ts create mode 100644 lib/core/messages/methods/notify.js create mode 100644 lib/core/messages/methods/prack.d.ts create mode 100644 lib/core/messages/methods/prack.js create mode 100644 lib/core/messages/methods/publish.d.ts create mode 100644 lib/core/messages/methods/publish.js create mode 100644 lib/core/messages/methods/refer.d.ts create mode 100644 lib/core/messages/methods/refer.js create mode 100644 lib/core/messages/methods/register.d.ts create mode 100644 lib/core/messages/methods/register.js create mode 100644 lib/core/messages/methods/subscribe.d.ts create mode 100644 lib/core/messages/methods/subscribe.js create mode 100644 lib/core/messages/name-addr-header.d.ts create mode 100644 lib/core/messages/name-addr-header.js create mode 100644 lib/core/messages/outgoing-request-message.d.ts create mode 100644 lib/core/messages/outgoing-request-message.js create mode 100644 lib/core/messages/outgoing-request.d.ts create mode 100644 lib/core/messages/outgoing-request.js create mode 100644 lib/core/messages/outgoing-response.d.ts create mode 100644 lib/core/messages/outgoing-response.js create mode 100644 lib/core/messages/parameters.d.ts create mode 100644 lib/core/messages/parameters.js create mode 100644 lib/core/messages/uri.d.ts create mode 100644 lib/core/messages/uri.js create mode 100644 lib/core/messages/utils.d.ts create mode 100644 lib/core/messages/utils.js create mode 100644 lib/core/session/index.d.ts create mode 100644 lib/core/session/index.js create mode 100644 lib/core/session/session-delegate.d.ts create mode 100644 lib/core/session/session-delegate.js create mode 100644 lib/core/session/session.d.ts create mode 100644 lib/core/session/session.js create mode 100644 lib/core/subscription/index.d.ts create mode 100644 lib/core/subscription/index.js create mode 100644 lib/core/subscription/subscription-delegate.d.ts create mode 100644 lib/core/subscription/subscription-delegate.js create mode 100644 lib/core/subscription/subscription.d.ts create mode 100644 lib/core/subscription/subscription.js create mode 100644 lib/core/timers.d.ts create mode 100644 lib/core/timers.js create mode 100644 lib/core/transactions/client-transaction.d.ts create mode 100644 lib/core/transactions/client-transaction.js create mode 100644 lib/core/transactions/index.d.ts create mode 100644 lib/core/transactions/index.js create mode 100644 lib/core/transactions/invite-client-transaction.d.ts create mode 100644 lib/core/transactions/invite-client-transaction.js create mode 100644 lib/core/transactions/invite-server-transaction.d.ts create mode 100644 lib/core/transactions/invite-server-transaction.js create mode 100644 lib/core/transactions/non-invite-client-transaction.d.ts create mode 100644 lib/core/transactions/non-invite-client-transaction.js create mode 100644 lib/core/transactions/non-invite-server-transaction.d.ts create mode 100644 lib/core/transactions/non-invite-server-transaction.js create mode 100644 lib/core/transactions/server-transaction.d.ts create mode 100644 lib/core/transactions/server-transaction.js create mode 100644 lib/core/transactions/transaction-state.d.ts create mode 100644 lib/core/transactions/transaction-state.js create mode 100644 lib/core/transactions/transaction-user.d.ts create mode 100644 lib/core/transactions/transaction-user.js create mode 100644 lib/core/transactions/transaction.d.ts create mode 100644 lib/core/transactions/transaction.js create mode 100644 lib/core/transport.d.ts create mode 100644 lib/core/transport.js create mode 100644 lib/core/user-agent-core/allowed-methods.d.ts create mode 100644 lib/core/user-agent-core/allowed-methods.js create mode 100644 lib/core/user-agent-core/index.d.ts create mode 100644 lib/core/user-agent-core/index.js create mode 100644 lib/core/user-agent-core/user-agent-core-configuration.d.ts create mode 100644 lib/core/user-agent-core/user-agent-core-configuration.js create mode 100644 lib/core/user-agent-core/user-agent-core-delegate.d.ts create mode 100644 lib/core/user-agent-core/user-agent-core-delegate.js create mode 100644 lib/core/user-agent-core/user-agent-core.d.ts create mode 100644 lib/core/user-agent-core/user-agent-core.js create mode 100644 lib/core/user-agents/bye-user-agent-client.d.ts create mode 100644 lib/core/user-agents/bye-user-agent-client.js create mode 100644 lib/core/user-agents/bye-user-agent-server.d.ts create mode 100644 lib/core/user-agents/bye-user-agent-server.js create mode 100644 lib/core/user-agents/cancel-user-agent-client.d.ts create mode 100644 lib/core/user-agents/cancel-user-agent-client.js create mode 100644 lib/core/user-agents/index.d.ts create mode 100644 lib/core/user-agents/index.js create mode 100644 lib/core/user-agents/info-user-agent-client.d.ts create mode 100644 lib/core/user-agents/info-user-agent-client.js create mode 100644 lib/core/user-agents/info-user-agent-server.d.ts create mode 100644 lib/core/user-agents/info-user-agent-server.js create mode 100644 lib/core/user-agents/invite-user-agent-client.d.ts create mode 100644 lib/core/user-agents/invite-user-agent-client.js create mode 100644 lib/core/user-agents/invite-user-agent-server.d.ts create mode 100644 lib/core/user-agents/invite-user-agent-server.js create mode 100644 lib/core/user-agents/message-user-agent-client.d.ts create mode 100644 lib/core/user-agents/message-user-agent-client.js create mode 100644 lib/core/user-agents/message-user-agent-server.d.ts create mode 100644 lib/core/user-agents/message-user-agent-server.js create mode 100644 lib/core/user-agents/notify-user-agent-client.d.ts create mode 100644 lib/core/user-agents/notify-user-agent-client.js create mode 100644 lib/core/user-agents/notify-user-agent-server.d.ts create mode 100644 lib/core/user-agents/notify-user-agent-server.js create mode 100644 lib/core/user-agents/prack-user-agent-client.d.ts create mode 100644 lib/core/user-agents/prack-user-agent-client.js create mode 100644 lib/core/user-agents/prack-user-agent-server.d.ts create mode 100644 lib/core/user-agents/prack-user-agent-server.js create mode 100644 lib/core/user-agents/publish-user-agent-client.d.ts create mode 100644 lib/core/user-agents/publish-user-agent-client.js create mode 100644 lib/core/user-agents/re-invite-user-agent-client.d.ts create mode 100644 lib/core/user-agents/re-invite-user-agent-client.js create mode 100644 lib/core/user-agents/re-invite-user-agent-server.d.ts create mode 100644 lib/core/user-agents/re-invite-user-agent-server.js create mode 100644 lib/core/user-agents/re-subscribe-user-agent-client.d.ts create mode 100644 lib/core/user-agents/re-subscribe-user-agent-client.js create mode 100644 lib/core/user-agents/re-subscribe-user-agent-server.d.ts create mode 100644 lib/core/user-agents/re-subscribe-user-agent-server.js create mode 100644 lib/core/user-agents/refer-user-agent-client.d.ts create mode 100644 lib/core/user-agents/refer-user-agent-client.js create mode 100644 lib/core/user-agents/refer-user-agent-server.d.ts create mode 100644 lib/core/user-agents/refer-user-agent-server.js create mode 100644 lib/core/user-agents/register-user-agent-client.d.ts create mode 100644 lib/core/user-agents/register-user-agent-client.js create mode 100644 lib/core/user-agents/subscribe-user-agent-client.d.ts create mode 100644 lib/core/user-agents/subscribe-user-agent-client.js create mode 100644 lib/core/user-agents/subscribe-user-agent-server.d.ts create mode 100644 lib/core/user-agents/subscribe-user-agent-server.js create mode 100644 lib/core/user-agents/user-agent-client.d.ts create mode 100644 lib/core/user-agents/user-agent-client.js create mode 100644 lib/core/user-agents/user-agent-server.d.ts create mode 100644 lib/core/user-agents/user-agent-server.js create mode 100644 lib/grammar/dist/grammar.d.ts create mode 100644 lib/grammar/dist/grammar.js create mode 100644 lib/index.d.ts create mode 100644 lib/index.js create mode 100644 lib/session-description-handler-factory.d.ts create mode 100644 lib/session-description-handler-factory.js create mode 100644 lib/session-description-handler.d.ts create mode 100644 lib/session-description-handler.js diff --git a/dist/sip-0.14.2.js b/dist/sip-0.14.2.js new file mode 100644 index 000000000..b8a544976 --- /dev/null +++ b/dist/sip-0.14.2.js @@ -0,0 +1,19888 @@ +/*! + * + * SIP version 0.14.2 + * Copyright (c) 2014-2019 Junction Networks, Inc + * Homepage: https://sipjs.com + * License: https://sipjs.com/license/ + * + * + * ~~~SIP.js contains substantial portions of JsSIP under the following license~~~ + * Homepage: http://jssip.net + * Copyright (c) 2012-2013 José Luis Millán - Versatica + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * ~~~ end JsSIP license ~~~ + * + * + * + * + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["SIP"] = factory(); + else + root["SIP"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var core_1 = __webpack_require__(2); +exports.DigestAuthentication = core_1.DigestAuthentication; +exports.Grammar = core_1.Grammar; +exports.IncomingRequest = core_1.IncomingRequestMessage; +exports.IncomingResponse = core_1.IncomingResponseMessage; +exports.LoggerFactory = core_1.LoggerFactory; +exports.NameAddrHeader = core_1.NameAddrHeader; +exports.OutgoingRequest = core_1.OutgoingRequestMessage; +exports.Timers = core_1.Timers; +exports.Transport = core_1.Transport; +exports.URI = core_1.URI; +var ClientContext_1 = __webpack_require__(78); +exports.ClientContext = ClientContext_1.ClientContext; +var Constants_1 = __webpack_require__(79); +exports.C = Constants_1.C; +var Enums_1 = __webpack_require__(81); +exports.DialogStatus = Enums_1.DialogStatus; +exports.SessionStatus = Enums_1.SessionStatus; +exports.TypeStrings = Enums_1.TypeStrings; +exports.UAStatus = Enums_1.UAStatus; +var Exceptions_1 = __webpack_require__(83); +exports.Exceptions = Exceptions_1.Exceptions; +var Parser_1 = __webpack_require__(84); +exports.Parser = Parser_1.Parser; +var PublishContext_1 = __webpack_require__(85); +exports.PublishContext = PublishContext_1.PublishContext; +var ReferContext_1 = __webpack_require__(86); +exports.ReferClientContext = ReferContext_1.ReferClientContext; +exports.ReferServerContext = ReferContext_1.ReferServerContext; +var RegisterContext_1 = __webpack_require__(88); +exports.RegisterContext = RegisterContext_1.RegisterContext; +var ServerContext_1 = __webpack_require__(87); +exports.ServerContext = ServerContext_1.ServerContext; +var Session_1 = __webpack_require__(89); +exports.InviteClientContext = Session_1.InviteClientContext; +exports.InviteServerContext = Session_1.InviteServerContext; +exports.Session = Session_1.Session; +var Subscription_1 = __webpack_require__(91); +exports.Subscription = Subscription_1.Subscription; +var transactions_1 = __webpack_require__(27); +var Transactions = { + InviteClientTransaction: transactions_1.InviteClientTransaction, + InviteServerTransaction: transactions_1.InviteServerTransaction, + NonInviteClientTransaction: transactions_1.NonInviteClientTransaction, + NonInviteServerTransaction: transactions_1.NonInviteServerTransaction +}; +exports.Transactions = Transactions; +var UA_1 = __webpack_require__(92); +exports.makeUserAgentCoreConfigurationFromUA = UA_1.makeUserAgentCoreConfigurationFromUA; +exports.UA = UA_1.UA; +var Utils_1 = __webpack_require__(82); +exports.Utils = Utils_1.Utils; +var Web = tslib_1.__importStar(__webpack_require__(98)); +exports.Web = Web; +// tslint:disable-next-line:no-var-requires +var pkg = __webpack_require__(80); +var name = pkg.title; +exports.name = name; +var version = pkg.version; +exports.version = version; +var Core = tslib_1.__importStar(__webpack_require__(2)); +exports.Core = Core; + + +/***/ }), +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__extends", function() { return __extends; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__assign", function() { return __assign; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__rest", function() { return __rest; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__decorate", function() { return __decorate; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__param", function() { return __param; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__metadata", function() { return __metadata; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__awaiter", function() { return __awaiter; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__generator", function() { return __generator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__exportStar", function() { return __exportStar; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__values", function() { return __values; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__read", function() { return __read; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spread", function() { return __spread; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__await", function() { return __await; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncGenerator", function() { return __asyncGenerator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncDelegator", function() { return __asyncDelegator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncValues", function() { return __asyncValues; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__makeTemplateObject", function() { return __makeTemplateObject; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importStar", function() { return __importStar; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importDefault", function() { return __importDefault; }); +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + } + return __assign.apply(this, arguments); +} + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __param(paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +} + +function __metadata(metadataKey, metadataValue) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); +} + +function __awaiter(thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __exportStar(m, exports) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} + +function __values(o) { + var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; + if (m) return m.call(o); + return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; +} + +function __read(o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +} + +function __spread() { + for (var ar = [], i = 0; i < arguments.length; i++) + ar = ar.concat(__read(arguments[i])); + return ar; +} + +function __await(v) { + return this instanceof __await ? (this.v = v, this) : new __await(v); +} + +function __asyncGenerator(thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +} + +function __asyncDelegator(o) { + var i, p; + return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; + function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } +} + +function __asyncValues(o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +} + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; + +function __importStar(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result.default = mod; + return result; +} + +function __importDefault(mod) { + return (mod && mod.__esModule) ? mod : { default: mod }; +} + + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +// Directories +tslib_1.__exportStar(__webpack_require__(3), exports); +tslib_1.__exportStar(__webpack_require__(31), exports); +tslib_1.__exportStar(__webpack_require__(60), exports); +tslib_1.__exportStar(__webpack_require__(5), exports); +tslib_1.__exportStar(__webpack_require__(24), exports); +tslib_1.__exportStar(__webpack_require__(56), exports); +tslib_1.__exportStar(__webpack_require__(27), exports); +tslib_1.__exportStar(__webpack_require__(64), exports); +tslib_1.__exportStar(__webpack_require__(66), exports); +// Files +tslib_1.__exportStar(__webpack_require__(26), exports); +tslib_1.__exportStar(__webpack_require__(77), exports); + + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(4), exports); +tslib_1.__exportStar(__webpack_require__(23), exports); +tslib_1.__exportStar(__webpack_require__(55), exports); + + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = __webpack_require__(5); +/** + * A key concept for a user agent is that of a dialog. A dialog + * represents a peer-to-peer SIP relationship between two user agents + * that persists for some time. The dialog facilitates sequencing of + * messages between the user agents and proper routing of requests + * between both of them. The dialog represents a context in which to + * interpret SIP messages. + * https://tools.ietf.org/html/rfc3261#section-12 + */ +var Dialog = /** @class */ (function () { + /** + * Dialog constructor. + * @param core User agent core. + * @param dialogState Initial dialog state. + */ + function Dialog(core, dialogState) { + this.core = core; + this.dialogState = dialogState; + this.core.dialogs.set(this.id, this); + } + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + Dialog.initialDialogStateForUserAgentClient = function (outgoingRequestMessage, incomingResponseMessage) { + // If the request was sent over TLS, and the Request-URI contained a + // SIPS URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the response, taken in reverse order and preserving + // all URI parameters. If no Record-Route header field is present in + // the response, the route set MUST be set to the empty set. This route + // set, even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the response. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var routeSet = incomingResponseMessage.getHeaders("record-route").reverse(); + var contact = incomingResponseMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The local sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The remote sequence + // number MUST be empty (it is established when the remote UA sends a + // request within the dialog). The call identifier component of the + // dialog ID MUST be set to the value of the Call-ID in the request. + // The local tag component of the dialog ID MUST be set to the tag in + // the From field in the request, and the remote tag component of the + // dialog ID MUST be set to the tag in the To field of the response. A + // UAC MUST be prepared to receive a response without a tag in the To + // field, in which case the tag is considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate To tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var localSequenceNumber = outgoingRequestMessage.cseq; + var remoteSequenceNumber = undefined; + var callId = outgoingRequestMessage.callId; + var localTag = outgoingRequestMessage.fromTag; + var remoteTag = incomingResponseMessage.toTag; + if (!callId) { // TODO: Review to make sure this will never happen + throw new Error("Call id undefined."); + } + if (!localTag) { // TODO: Review to make sure this will never happen + throw new Error("From tag undefined."); + } + if (!remoteTag) { // TODO: Review to make sure this will never happen + throw new Error("To tag undefined."); // FIXME: No backwards compatibility with RFC 2543 + } + // The remote URI MUST be set to the URI in the To field, and the local + // URI MUST be set to the URI in the From field. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + if (!outgoingRequestMessage.from) { // TODO: Review to make sure this will never happen + throw new Error("From undefined."); + } + if (!outgoingRequestMessage.to) { // TODO: Review to make sure this will never happen + throw new Error("To undefined."); + } + var localURI = outgoingRequestMessage.from.uri; + var remoteURI = outgoingRequestMessage.to.uri; + // A dialog can also be in the "early" state, which occurs when it is + // created with a provisional response, and then transition to the + // "confirmed" state when a 2xx final response arrives. + // https://tools.ietf.org/html/rfc3261#section-12 + if (!incomingResponseMessage.statusCode) { + throw new Error("Incoming response status code undefined."); + } + var early = incomingResponseMessage.statusCode < 200 ? true : false; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + /** + * The UAS then constructs the state of the dialog. This state MUST be + * maintained for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.1 + * @param incomingRequestMessage Incoming request message creating dialog. + * @param toTag Tag in the To field in the response to the incoming request. + */ + Dialog.initialDialogStateForUserAgentServer = function (incomingRequestMessage, toTag, early) { + if (early === void 0) { early = false; } + // If the request arrived over TLS, and the Request-URI contained a SIPS + // URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the request, taken in order and preserving all URI + // parameters. If no Record-Route header field is present in the + // request, the route set MUST be set to the empty set. This route set, + // even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the request. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var routeSet = incomingRequestMessage.getHeaders("record-route"); + var contact = incomingRequestMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The remote sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The local sequence + // number MUST be empty. The call identifier component of the dialog ID + // MUST be set to the value of the Call-ID in the request. The local + // tag component of the dialog ID MUST be set to the tag in the To field + // in the response to the request (which always includes a tag), and the + // remote tag component of the dialog ID MUST be set to the tag from the + // From field in the request. A UAS MUST be prepared to receive a + // request without a tag in the From field, in which case the tag is + // considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate From tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var remoteSequenceNumber = incomingRequestMessage.cseq; + var localSequenceNumber = undefined; + var callId = incomingRequestMessage.callId; + var localTag = toTag; + var remoteTag = incomingRequestMessage.fromTag; + // The remote URI MUST be set to the URI in the From field, and the + // local URI MUST be set to the URI in the To field. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var remoteURI = incomingRequestMessage.from.uri; + var localURI = incomingRequestMessage.to.uri; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + /** Destructor. */ + Dialog.prototype.dispose = function () { + this.core.dialogs.delete(this.id); + }; + Object.defineProperty(Dialog.prototype, "id", { + /** + * A dialog is identified at each UA with a dialog ID, which consists of + * a Call-ID value, a local tag and a remote tag. The dialog ID at each + * UA involved in the dialog is not the same. Specifically, the local + * tag at one UA is identical to the remote tag at the peer UA. The + * tags are opaque tokens that facilitate the generation of unique + * dialog IDs. + * https://tools.ietf.org/html/rfc3261#section-12 + */ + get: function () { + return this.dialogState.id; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "early", { + /** + * A dialog can also be in the "early" state, which occurs when it is + * created with a provisional response, and then it transition to the + * "confirmed" state when a 2xx final response received or is sent. + * + * Note: RFC 3261 is concise on when a dialog is "confirmed", but it + * can be a point of confusion if an INVITE dialog is "confirmed" after + * a 2xx is sent or after receiving the ACK for the 2xx response. + * With careful reading it can be inferred a dialog is always is + * "confirmed" when the 2xx is sent (regardless of type of dialog). + * However a INVITE dialog does have additional considerations + * when it is confirmed but an ACK has not yet been received (in + * particular with regard to a callee sending BYE requests). + */ + get: function () { + return this.dialogState.early; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "callId", { + /** Call identifier component of the dialog id. */ + get: function () { + return this.dialogState.callId; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localTag", { + /** Local tag component of the dialog id. */ + get: function () { + return this.dialogState.localTag; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteTag", { + /** Remote tag component of the dialog id. */ + get: function () { + return this.dialogState.remoteTag; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localSequenceNumber", { + /** Local sequence number (used to order requests from the UA to its peer). */ + get: function () { + return this.dialogState.localSequenceNumber; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteSequenceNumber", { + /** Remote sequence number (used to order requests from its peer to the UA). */ + get: function () { + return this.dialogState.remoteSequenceNumber; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localURI", { + /** Local URI. */ + get: function () { + return this.dialogState.localURI; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteURI", { + /** Remote URI. */ + get: function () { + return this.dialogState.remoteURI; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteTarget", { + /** Remote target. */ + get: function () { + return this.dialogState.remoteTarget; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "routeSet", { + /** + * Route set, which is an ordered list of URIs. The route set is the + * list of servers that need to be traversed to send a request to the peer. + */ + get: function () { + return this.dialogState.routeSet; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "secure", { + /** + * If the request was sent over TLS, and the Request-URI contained + * a SIPS URI, the "secure" flag is set to true. *NOT IMPLEMENTED* + */ + get: function () { + return this.dialogState.secure; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "userAgentCore", { + /** The user agent core servicing this dialog. */ + get: function () { + return this.core; + }, + enumerable: true, + configurable: true + }); + /** Confirm the dialog. Only matters if dialog is currently early. */ + Dialog.prototype.confirm = function () { + this.dialogState.early = false; + }; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * + * Note that some requests, such as INVITEs, affect several pieces of + * state. + * + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + Dialog.prototype.receiveRequest = function (message) { + // ACK guard. + // By convention, the handling of ACKs is the responsibility + // the particular dialog implementation. For example, see SessionDialog. + // Furthermore, ACKs have same sequence number as the associated INVITE. + if (message.method === messages_1.C.ACK) { + return; + } + // If the remote sequence number was not empty, but the sequence number + // of the request is lower than the remote sequence number, the request + // is out of order and MUST be rejected with a 500 (Server Internal + // Error) response. If the remote sequence number was not empty, and + // the sequence number of the request is greater than the remote + // sequence number, the request is in order. It is possible for the + // CSeq sequence number to be higher than the remote sequence number by + // more than one. This is not an error condition, and a UAS SHOULD be + // prepared to receive and process requests with CSeq values more than + // one higher than the previous received request. The UAS MUST then set + // the remote sequence number to the value of the sequence number in the + // CSeq header field value in the request. + // + // If a proxy challenges a request generated by the UAC, the UAC has + // to resubmit the request with credentials. The resubmitted request + // will have a new CSeq number. The UAS will never see the first + // request, and thus, it will notice a gap in the CSeq number space. + // Such a gap does not represent any error condition. + // + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (this.remoteSequenceNumber) { + if (message.cseq <= this.remoteSequenceNumber) { + throw new Error("Out of sequence in dialog request. Did you forget to call sequenceGuard()?"); + } + this.dialogState.remoteSequenceNumber = message.cseq; + } + // If the remote sequence number is empty, it MUST be set to the value + // of the sequence number in the CSeq header field value in the request. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.remoteSequenceNumber) { + this.dialogState.remoteSequenceNumber = message.cseq; + } + // When a UAS receives a target refresh request, it MUST replace the + // dialog's remote target URI with the URI from the Contact header field + // in that request, if present. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + // Note: "target refresh request" processing delegated to sub-class. + }; + /** + * If the dialog identifier in the 2xx response matches the dialog + * identifier of an existing dialog, the dialog MUST be transitioned to + * the "confirmed" state, and the route set for the dialog MUST be + * recomputed based on the 2xx response using the procedures of Section + * 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + * constructed using the procedures of Section 12.1.2. + * + * Note that the only piece of state that is recomputed is the route + * set. Other pieces of state such as the highest sequence numbers + * (remote and local) sent within the dialog are not recomputed. The + * route set only is recomputed for backwards compatibility. RFC + * 2543 did not mandate mirroring of the Record-Route header field in + * a 1xx, only 2xx. However, we cannot update the entire state of + * the dialog, since mid-dialog requests may have been sent within + * the early dialog, modifying the sequence numbers, for example. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + */ + Dialog.prototype.recomputeRouteSet = function (message) { + this.dialogState.routeSet = message.getHeaders("record-route").reverse(); + }; + /** + * A request within a dialog is constructed by using many of the + * components of the state stored as part of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + * @param method Outgoing request method. + */ + Dialog.prototype.createOutgoingRequestMessage = function (method, options) { + // The URI in the To field of the request MUST be set to the remote URI + // from the dialog state. The tag in the To header field of the request + // MUST be set to the remote tag of the dialog ID. The From URI of the + // request MUST be set to the local URI from the dialog state. The tag + // in the From header field of the request MUST be set to the local tag + // of the dialog ID. If the value of the remote or local tags is null, + // the tag parameter MUST be omitted from the To or From header fields, + // respectively. + // + // Usage of the URI from the To and From fields in the original + // request within subsequent requests is done for backwards + // compatibility with RFC 2543, which used the URI for dialog + // identification. In this specification, only the tags are used for + // dialog identification. It is expected that mandatory reflection + // of the original To and From URI in mid-dialog requests will be + // deprecated in a subsequent revision of this specification. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + var toUri = this.remoteURI; + var toTag = this.remoteTag; + var fromUri = this.localURI; + var fromTag = this.localTag; + // The Call-ID of the request MUST be set to the Call-ID of the dialog. + // Requests within a dialog MUST contain strictly monotonically + // increasing and contiguous CSeq sequence numbers (increasing-by-one) + // in each direction (excepting ACK and CANCEL of course, whose numbers + // equal the requests being acknowledged or cancelled). Therefore, if + // the local sequence number is not empty, the value of the local + // sequence number MUST be incremented by one, and this value MUST be + // placed into the CSeq header field. If the local sequence number is + // empty, an initial value MUST be chosen using the guidelines of + // Section 8.1.1.5. The method field in the CSeq header field value + // MUST match the method of the request. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + var callId = this.callId; + var cseq; + if (options && options.cseq) { + cseq = options.cseq; + } + else if (!this.dialogState.localSequenceNumber) { + cseq = this.dialogState.localSequenceNumber = 1; // https://tools.ietf.org/html/rfc3261#section-8.1.1.5 + } + else { + cseq = this.dialogState.localSequenceNumber += 1; + } + // The UAC uses the remote target and route set to build the Request-URI + // and Route header field of the request. + // + // If the route set is empty, the UAC MUST place the remote target URI + // into the Request-URI. The UAC MUST NOT add a Route header field to + // the request. + // + // If the route set is not empty, and the first URI in the route set + // contains the lr parameter (see Section 19.1.1), the UAC MUST place + // the remote target URI into the Request-URI and MUST include a Route + // header field containing the route set values in order, including all + // parameters. + // + // If the route set is not empty, and its first URI does not contain the + // lr parameter, the UAC MUST place the first URI from the route set + // into the Request-URI, stripping any parameters that are not allowed + // in a Request-URI. The UAC MUST add a Route header field containing + // the remainder of the route set values in order, including all + // parameters. The UAC MUST then place the remote target URI into the + // Route header field as the last value. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + // The lr parameter, when present, indicates that the element + // responsible for this resource implements the routing mechanisms + // specified in this document. This parameter will be used in the + // URIs proxies place into Record-Route header field values, and + // may appear in the URIs in a pre-existing route set. + // + // This parameter is used to achieve backwards compatibility with + // systems implementing the strict-routing mechanisms of RFC 2543 + // and the rfc2543bis drafts up to bis-05. An element preparing + // to send a request based on a URI not containing this parameter + // can assume the receiving element implements strict-routing and + // reformat the message to preserve the information in the + // Request-URI. + // https://tools.ietf.org/html/rfc3261#section-19.1.1 + // NOTE: Not backwards compatible with RFC 2543 (no support for strict-routing). + var ruri = this.remoteTarget; + var routeSet = this.routeSet; + var extraHeaders = options && options.extraHeaders; + var body = options && options.body; + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + var message = this.userAgentCore.makeOutgoingRequestMessage(method, ruri, fromUri, toUri, { + callId: callId, + cseq: cseq, + fromTag: fromTag, + toTag: toTag, + routeSet: routeSet + }, extraHeaders, body); + return message; + }; + /** + * If the remote sequence number was not empty, but the sequence number + * of the request is lower than the remote sequence number, the request + * is out of order and MUST be rejected with a 500 (Server Internal + * Error) response. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param request Incoming request to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise a 500 Server Internal Error was stateless sent and request processing must stop. + */ + Dialog.prototype.sequenceGuard = function (message) { + // ACK guard. + // By convention, handling of unexpected ACKs is responsibility + // the particular dialog implementation. For example, see SessionDialog. + // Furthermore, we cannot reply to an "out of sequence" ACK. + if (message.method === messages_1.C.ACK) { + return true; + } + // Note: We are rejecting on "less than or equal to" the remote + // sequence number (excepting ACK whose numbers equal the requests + // being acknowledged or cancelled), which is the correct thing to + // do in our case. The only time a request with the same sequence number + // will show up here if is a) it is a very late retransmission of a + // request we already handled or b) it is a different request with the + // same sequence number which would be violation of the standard. + // Request retransmissions are absorbed by the transaction layer, + // so any request with a duplicate sequence number getting here + // would have to be a retransmission after the transaction terminated + // or a broken request (with unique via branch value). + // Requests within a dialog MUST contain strictly monotonically + // increasing and contiguous CSeq sequence numbers (increasing-by-one) + // in each direction (excepting ACK and CANCEL of course, whose numbers + // equal the requests being acknowledged or cancelled). Therefore, if + // the local sequence number is not empty, the value of the local + // sequence number MUST be incremented by one, and this value MUST be + // placed into the CSeq header field. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + if (this.remoteSequenceNumber && message.cseq <= this.remoteSequenceNumber) { + this.core.replyStateless(message, { statusCode: 500 }); + return false; + } + return true; + }; + return Dialog; +}()); +exports.Dialog = Dialog; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +// Directories +tslib_1.__exportStar(__webpack_require__(6), exports); +// Files +tslib_1.__exportStar(__webpack_require__(8), exports); +tslib_1.__exportStar(__webpack_require__(19), exports); +tslib_1.__exportStar(__webpack_require__(11), exports); +tslib_1.__exportStar(__webpack_require__(10), exports); +tslib_1.__exportStar(__webpack_require__(9), exports); +tslib_1.__exportStar(__webpack_require__(17), exports); +tslib_1.__exportStar(__webpack_require__(13), exports); +tslib_1.__exportStar(__webpack_require__(18), exports); +tslib_1.__exportStar(__webpack_require__(22), exports); +tslib_1.__exportStar(__webpack_require__(14), exports); +tslib_1.__exportStar(__webpack_require__(15), exports); + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(7), exports); + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * SIP Methods + * @internal + */ +var C; +(function (C) { + C.ACK = "ACK"; + C.BYE = "BYE"; + C.CANCEL = "CANCEL"; + C.INFO = "INFO"; + C.INVITE = "INVITE"; + C.MESSAGE = "MESSAGE"; + C.NOTIFY = "NOTIFY"; + C.OPTIONS = "OPTIONS"; + C.REGISTER = "REGISTER"; + C.UPDATE = "UPDATE"; + C.SUBSCRIBE = "SUBSCRIBE"; + C.PUBLISH = "PUBLISH"; + C.REFER = "REFER"; + C.PRACK = "PRACK"; +})(C = exports.C || (exports.C = {})); + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var incoming_request_message_1 = __webpack_require__(9); +var incoming_response_message_1 = __webpack_require__(17); +var outgoing_request_message_1 = __webpack_require__(18); +/** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ +function fromBodyLegacy(bodyLegacy) { + var content = (typeof bodyLegacy === "string") ? bodyLegacy : bodyLegacy.body; + var contentType = (typeof bodyLegacy === "string") ? "application/sdp" : bodyLegacy.contentType; + var contentDisposition = contentTypeToContentDisposition(contentType); + var body = { contentDisposition: contentDisposition, contentType: contentType, content: content }; + return body; +} +exports.fromBodyLegacy = fromBodyLegacy; +/** + * Given a message, get a normalized body. + * The content disposition is inferred if not set. + * @param message The message. + */ +function getBody(message) { + var contentDisposition; + var contentType; + var content; + // We're in UAS role, receiving incoming request + if (message instanceof incoming_request_message_1.IncomingRequestMessage) { + if (message.body) { + // FIXME: Parsing needs typing + var parse = message.parseHeader("Content-Disposition"); + contentDisposition = parse ? parse.type : undefined; + contentType = message.parseHeader("Content-Type"); + content = message.body; + } + } + // We're in UAC role, receiving incoming response + if (message instanceof incoming_response_message_1.IncomingResponseMessage) { + if (message.body) { + // FIXME: Parsing needs typing + var parse = message.parseHeader("Content-Disposition"); + contentDisposition = parse ? parse.type : undefined; + contentType = message.parseHeader("Content-Type"); + content = message.body; + } + } + // We're in UAC role, sending outgoing request + if (message instanceof outgoing_request_message_1.OutgoingRequestMessage) { + if (message.body) { + contentDisposition = message.getHeader("Content-Disposition"); + contentType = message.getHeader("Content-Type"); + if (typeof message.body === "string") { + // FIXME: OutgoingRequest should not allow a "string" body without a "Content-Type" header. + if (!contentType) { + throw new Error("Header content type header does not equal body content type."); + } + content = message.body; + } + else { + // FIXME: OutgoingRequest should not allow the "Content-Type" header not to match th body content type + if (contentType && contentType !== message.body.contentType) { + throw new Error("Header content type header does not equal body content type."); + } + contentType = message.body.contentType; + content = message.body.body; + } + } + } + // We're in UAS role, sending outgoing response + if (isBody(message)) { + contentDisposition = message.contentDisposition; + contentType = message.contentType; + content = message.content; + } + // No content, no body. + if (!content) { + return undefined; + } + if (contentType && !contentDisposition) { + contentDisposition = contentTypeToContentDisposition(contentType); + } + if (!contentDisposition) { + throw new Error("Content disposition undefined."); + } + if (!contentType) { + throw new Error("Content type undefined."); + } + return { + contentDisposition: contentDisposition, + contentType: contentType, + content: content + }; +} +exports.getBody = getBody; +/** + * User-Defined Type Guard for Body. + * @param body Body to check. + */ +function isBody(body) { + return body && + typeof body.content === "string" && + typeof body.contentType === "string" && + body.contentDisposition === undefined ? true : typeof body.contentDisposition === "string"; +} +exports.isBody = isBody; +// If the Content-Disposition header field is missing, bodies of +// Content-Type application/sdp imply the disposition "session", while +// other content types imply "render". +// https://tools.ietf.org/html/rfc3261#section-13.2.1 +function contentTypeToContentDisposition(contentType) { + if (contentType === "application/sdp") { + return "session"; + } + else { + return "render"; + } +} + + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var incoming_message_1 = __webpack_require__(10); +/** + * Incoming SIP request message. + */ +var IncomingRequestMessage = /** @class */ (function (_super) { + tslib_1.__extends(IncomingRequestMessage, _super); + function IncomingRequestMessage() { + return _super.call(this) || this; + } + return IncomingRequestMessage; +}(incoming_message_1.IncomingMessage)); +exports.IncomingRequestMessage = IncomingRequestMessage; + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var grammar_1 = __webpack_require__(11); +var utils_1 = __webpack_require__(16); +/** + * Incoming SIP message. + * @public + */ +var IncomingMessage = /** @class */ (function () { + function IncomingMessage() { + this.headers = {}; + } + /** + * Insert a header of the given name and value into the last position of the + * header array. + * @param name - header name + * @param value - header value + */ + IncomingMessage.prototype.addHeader = function (name, value) { + var header = { raw: value }; + name = utils_1.headerize(name); + if (this.headers[name]) { + this.headers[name].push(header); + } + else { + this.headers[name] = [header]; + } + }; + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + IncomingMessage.prototype.getHeader = function (name) { + var header = this.headers[utils_1.headerize(name)]; + if (header) { + if (header[0]) { + return header[0].raw; + } + } + else { + return; + } + }; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array - with all the headers of the specified name. + */ + IncomingMessage.prototype.getHeaders = function (name) { + var header = this.headers[utils_1.headerize(name)]; + var result = []; + if (!header) { + return []; + } + for (var _i = 0, header_1 = header; _i < header_1.length; _i++) { + var headerPart = header_1[_i]; + result.push(headerPart.raw); + } + return result; + }; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + IncomingMessage.prototype.hasHeader = function (name) { + return !!this.headers[utils_1.headerize(name)]; + }; + /** + * Parse the given header on the given index. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + */ + IncomingMessage.prototype.parseHeader = function (name, idx) { + if (idx === void 0) { idx = 0; } + name = utils_1.headerize(name); + if (!this.headers[name]) { + // this.logger.log("header '" + name + "' not present"); + return; + } + else if (idx >= this.headers[name].length) { + // this.logger.log("not so many '" + name + "' headers present"); + return; + } + var header = this.headers[name][idx]; + var value = header.raw; + if (header.parsed) { + return header.parsed; + } + // substitute '-' by '_' for grammar rule matching. + var parsed = grammar_1.Grammar.parse(value, name.replace(/-/g, "_")); + if (parsed === -1) { + this.headers[name].splice(idx, 1); // delete from headers + // this.logger.warn('error parsing "' + name + '" header field with value "' + value + '"'); + return; + } + else { + header.parsed = parsed; + return parsed; + } + }; + /** + * Message Header attribute selector. Alias of parseHeader. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + * + * @example + * message.s('via',3).port + */ + IncomingMessage.prototype.s = function (name, idx) { + if (idx === void 0) { idx = 0; } + return this.parseHeader(name, idx); + }; + /** + * Replace the value of the given header by the value. + * @param name - header name + * @param value - header value + */ + IncomingMessage.prototype.setHeader = function (name, value) { + this.headers[utils_1.headerize(name)] = [{ raw: value }]; + }; + IncomingMessage.prototype.toString = function () { + return this.data; + }; + return IncomingMessage; +}()); +exports.IncomingMessage = IncomingMessage; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var pegGrammar = tslib_1.__importStar(__webpack_require__(12)); +/** + * Grammar. + * @internal + */ +var Grammar; +(function (Grammar) { + /** + * Parse. + * @param input - + * @param startRule - + */ + function parse(input, startRule) { + var options = { startRule: startRule }; + try { + pegGrammar.parse(input, options); + } + catch (e) { + options.data = -1; + } + return options.data; + } + Grammar.parse = parse; + /** + * Parse the given string and returns a SIP.NameAddrHeader instance or undefined if + * it is an invalid NameAddrHeader. + * @param name_addr_header - + */ + function nameAddrHeaderParse(nameAddrHeader) { + var parsedNameAddrHeader = Grammar.parse(nameAddrHeader, "Name_Addr_Header"); + return parsedNameAddrHeader !== -1 ? parsedNameAddrHeader : undefined; + } + Grammar.nameAddrHeaderParse = nameAddrHeaderParse; + /** + * Parse the given string and returns a SIP.URI instance or undefined if + * it is an invalid URI. + * @param uri - + */ + function URIParse(uri) { + var parsedUri = Grammar.parse(uri, "SIP_URI"); + return parsedUri !== -1 ? parsedUri : undefined; + } + Grammar.URIParse = URIParse; +})(Grammar = exports.Grammar || (exports.Grammar = {})); + + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +// tslint:disable:interface-name +// tslint:disable: trailing-comma +// tslint:disable: object-literal-sort-keys +// tslint:disable: max-line-length +// tslint:disable: only-arrow-functions +// tslint:disable: one-variable-per-declaration +// tslint:disable: no-consecutive-blank-lines +// tslint:disable: align +// tslint:disable: radix +// tslint:disable: quotemark +// tslint:disable: semicolon +// tslint:disable: object-literal-shorthand +// tslint:disable: variable-name +// tslint:disable: no-var-keyword +// tslint:disable: whitespace +// tslint:disable: curly +// tslint:disable: prefer-const +// tslint:disable: object-literal-key-quotes +// tslint:disable: no-string-literal +// tslint:disable: one-line +// tslint:disable: no-unused-expression +// tslint:disable: space-before-function-paren +// tslint:disable: arrow-return-shorthand +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +// Generated by PEG.js v. 0.10.0 (ts-pegjs plugin v. 0.2.5 ) +// +// https://pegjs.org/ https://github.com/metadevpro/ts-pegjs +var name_addr_header_1 = __webpack_require__(13); +var uri_1 = __webpack_require__(15); +var SyntaxError = /** @class */ (function (_super) { + tslib_1.__extends(SyntaxError, _super); + function SyntaxError(message, expected, found, location) { + var _this = _super.call(this) || this; + _this.message = message; + _this.expected = expected; + _this.found = found; + _this.location = location; + _this.name = "SyntaxError"; + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(_this, SyntaxError); + } + return _this; + } + SyntaxError.buildMessage = function (expected, found) { + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); + } + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); + } + function describeExpectation(expectation) { + switch (expectation.type) { + case "literal": + return "\"" + literalEscape(expectation.text) + "\""; + case "class": + var escapedParts = expectation.parts.map(function (part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + case "any": + return "any character"; + case "end": + return "end of input"; + case "other": + return expectation.description; + } + } + function describeExpected(expected1) { + var descriptions = expected1.map(describeExpectation); + var i; + var j; + descriptions.sort(); + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + switch (descriptions.length) { + case 1: + return descriptions[0]; + case 2: + return descriptions[0] + " or " + descriptions[1]; + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + function describeFound(found1) { + return found1 ? "\"" + literalEscape(found1) + "\"" : "end of input"; + } + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; + }; + return SyntaxError; +}(Error)); +exports.SyntaxError = SyntaxError; +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + var peg$FAILED = {}; + var peg$startRuleIndices = { Contact: 119, Name_Addr_Header: 156, Record_Route: 176, Request_Response: 81, SIP_URI: 45, Subscription_State: 186, Supported: 191, Require: 182, Via: 194, absoluteURI: 84, Call_ID: 118, Content_Disposition: 130, Content_Length: 135, Content_Type: 136, CSeq: 146, displayName: 122, Event: 149, From: 151, host: 52, Max_Forwards: 154, Min_SE: 213, Proxy_Authenticate: 157, quoted_string: 40, Refer_To: 178, Replaces: 179, Session_Expires: 210, stun_URI: 217, To: 192, turn_URI: 223, uuid: 226, WWW_Authenticate: 209, challenge: 158, sipfrag: 230, Referred_By: 231 }; + var peg$startRuleIndex = 119; + var peg$consts = [ + "\r\n", + peg$literalExpectation("\r\n", false), + /^[0-9]/, + peg$classExpectation([["0", "9"]], false, false), + /^[a-zA-Z]/, + peg$classExpectation([["a", "z"], ["A", "Z"]], false, false), + /^[0-9a-fA-F]/, + peg$classExpectation([["0", "9"], ["a", "f"], ["A", "F"]], false, false), + /^[\0-\xFF]/, + peg$classExpectation([["\0", "\xFF"]], false, false), + /^["]/, + peg$classExpectation(["\""], false, false), + " ", + peg$literalExpectation(" ", false), + "\t", + peg$literalExpectation("\t", false), + /^[a-zA-Z0-9]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false), + ";", + peg$literalExpectation(";", false), + "/", + peg$literalExpectation("/", false), + "?", + peg$literalExpectation("?", false), + ":", + peg$literalExpectation(":", false), + "@", + peg$literalExpectation("@", false), + "&", + peg$literalExpectation("&", false), + "=", + peg$literalExpectation("=", false), + "+", + peg$literalExpectation("+", false), + "$", + peg$literalExpectation("$", false), + ",", + peg$literalExpectation(",", false), + "-", + peg$literalExpectation("-", false), + "_", + peg$literalExpectation("_", false), + ".", + peg$literalExpectation(".", false), + "!", + peg$literalExpectation("!", false), + "~", + peg$literalExpectation("~", false), + "*", + peg$literalExpectation("*", false), + "'", + peg$literalExpectation("'", false), + "(", + peg$literalExpectation("(", false), + ")", + peg$literalExpectation(")", false), + "%", + peg$literalExpectation("%", false), + function () { return " "; }, + function () { return ':'; }, + /^[!-~]/, + peg$classExpectation([["!", "~"]], false, false), + /^[\x80-\uFFFF]/, + peg$classExpectation([["\x80", "\uFFFF"]], false, false), + /^[\x80-\xBF]/, + peg$classExpectation([["\x80", "\xBF"]], false, false), + /^[a-f]/, + peg$classExpectation([["a", "f"]], false, false), + "`", + peg$literalExpectation("`", false), + "<", + peg$literalExpectation("<", false), + ">", + peg$literalExpectation(">", false), + "\\", + peg$literalExpectation("\\", false), + "[", + peg$literalExpectation("[", false), + "]", + peg$literalExpectation("]", false), + "{", + peg$literalExpectation("{", false), + "}", + peg$literalExpectation("}", false), + function () { return "*"; }, + function () { return "/"; }, + function () { return "="; }, + function () { return "("; }, + function () { return ")"; }, + function () { return ">"; }, + function () { return "<"; }, + function () { return ","; }, + function () { return ";"; }, + function () { return ":"; }, + function () { return "\""; }, + /^[!-']/, + peg$classExpectation([["!", "'"]], false, false), + /^[*-[]/, + peg$classExpectation([["*", "["]], false, false), + /^[\]-~]/, + peg$classExpectation([["]", "~"]], false, false), + function (contents) { + return contents; + }, + /^[#-[]/, + peg$classExpectation([["#", "["]], false, false), + /^[\0-\t]/, + peg$classExpectation([["\0", "\t"]], false, false), + /^[\x0B-\f]/, + peg$classExpectation([["\x0B", "\f"]], false, false), + /^[\x0E-\x7F]/, + peg$classExpectation([["\x0E", "\x7F"]], false, false), + function () { + options = options || { data: {} }; + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + }, + function () { + options = options || { data: {} }; + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + delete options.data.uri_params; + if (options.startRule === 'SIP_URI') { + options.data = options.data.uri; + } + }, + "sips", + peg$literalExpectation("sips", true), + "sip", + peg$literalExpectation("sip", true), + function (uri_scheme) { + options = options || { data: {} }; + options.data.scheme = uri_scheme; + }, + function () { + options = options || { data: {} }; + options.data.user = decodeURIComponent(text().slice(0, -1)); + }, + function () { + options = options || { data: {} }; + options.data.password = text(); + }, + function () { + options = options || { data: {} }; + options.data.host = text(); + return options.data.host; + }, + function () { + options = options || { data: {} }; + options.data.host_type = 'domain'; + return text(); + }, + /^[a-zA-Z0-9_\-]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false), + /^[a-zA-Z0-9\-]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "-"], false, false), + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv6'; + return text(); + }, + "::", + peg$literalExpectation("::", false), + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv6'; + return text(); + }, + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv4'; + return text(); + }, + "25", + peg$literalExpectation("25", false), + /^[0-5]/, + peg$classExpectation([["0", "5"]], false, false), + "2", + peg$literalExpectation("2", false), + /^[0-4]/, + peg$classExpectation([["0", "4"]], false, false), + "1", + peg$literalExpectation("1", false), + /^[1-9]/, + peg$classExpectation([["1", "9"]], false, false), + function (port) { + options = options || { data: {} }; + port = parseInt(port.join('')); + options.data.port = port; + return port; + }, + "transport=", + peg$literalExpectation("transport=", true), + "udp", + peg$literalExpectation("udp", true), + "tcp", + peg$literalExpectation("tcp", true), + "sctp", + peg$literalExpectation("sctp", true), + "tls", + peg$literalExpectation("tls", true), + function (transport) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['transport'] = transport.toLowerCase(); + }, + "user=", + peg$literalExpectation("user=", true), + "phone", + peg$literalExpectation("phone", true), + "ip", + peg$literalExpectation("ip", true), + function (user) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['user'] = user.toLowerCase(); + }, + "method=", + peg$literalExpectation("method=", true), + function (method) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['method'] = method; + }, + "ttl=", + peg$literalExpectation("ttl=", true), + function (ttl) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['ttl'] = ttl; + }, + "maddr=", + peg$literalExpectation("maddr=", true), + function (maddr) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['maddr'] = maddr; + }, + "lr", + peg$literalExpectation("lr", true), + function () { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['lr'] = undefined; + }, + function (param, value) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + if (value === null) { + value = undefined; + } + else { + value = value[1]; + } + options.data.uri_params[param.toLowerCase()] = value; + }, + function (hname, hvalue) { + hname = hname.join('').toLowerCase(); + hvalue = hvalue.join(''); + options = options || { data: {} }; + if (!options.data.uri_headers) + options.data.uri_headers = {}; + if (!options.data.uri_headers[hname]) { + options.data.uri_headers[hname] = [hvalue]; + } + else { + options.data.uri_headers[hname].push(hvalue); + } + }, + function () { + options = options || { data: {} }; + // lots of tests fail if this isn't guarded... + if (options.startRule === 'Refer_To') { + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + delete options.data.uri_params; + } + }, + "//", + peg$literalExpectation("//", false), + function () { + options = options || { data: {} }; + options.data.scheme = text(); + }, + peg$literalExpectation("SIP", true), + function () { + options = options || { data: {} }; + options.data.sip_version = text(); + }, + "INVITE", + peg$literalExpectation("INVITE", false), + "ACK", + peg$literalExpectation("ACK", false), + "VXACH", + peg$literalExpectation("VXACH", false), + "OPTIONS", + peg$literalExpectation("OPTIONS", false), + "BYE", + peg$literalExpectation("BYE", false), + "CANCEL", + peg$literalExpectation("CANCEL", false), + "REGISTER", + peg$literalExpectation("REGISTER", false), + "SUBSCRIBE", + peg$literalExpectation("SUBSCRIBE", false), + "NOTIFY", + peg$literalExpectation("NOTIFY", false), + "REFER", + peg$literalExpectation("REFER", false), + "PUBLISH", + peg$literalExpectation("PUBLISH", false), + function () { + options = options || { data: {} }; + options.data.method = text(); + return options.data.method; + }, + function (status_code) { + options = options || { data: {} }; + options.data.status_code = parseInt(status_code.join('')); + }, + function () { + options = options || { data: {} }; + options.data.reason_phrase = text(); + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + function () { + var idx, length; + options = options || { data: {} }; + length = options.data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (options.data.multi_header[idx].parsed === null) { + options.data = null; + break; + } + } + if (options.data !== null) { + options.data = options.data.multi_header; + } + else { + options.data = -1; + } + }, + function () { + var header; + options = options || { data: {} }; + if (!options.data.multi_header) + options.data.multi_header = []; + try { + header = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + delete options.data.uri; + delete options.data.displayName; + delete options.data.params; + } + catch (e) { + header = null; + } + options.data.multi_header.push({ 'position': peg$currPos, + 'offset': location().start.offset, + 'parsed': header + }); + }, + function (displayName) { + displayName = text().trim(); + if (displayName[0] === '\"') { + displayName = displayName.substring(1, displayName.length - 1); + } + options = options || { data: {} }; + options.data.displayName = displayName; + }, + "q", + peg$literalExpectation("q", true), + function (q) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['q'] = q; + }, + "expires", + peg$literalExpectation("expires", true), + function (expires) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['expires'] = expires; + }, + function (delta_seconds) { + return parseInt(delta_seconds.join('')); + }, + "0", + peg$literalExpectation("0", false), + function () { + return parseFloat(text()); + }, + function (param, value) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + if (value === null) { + value = undefined; + } + else { + value = value[1]; + } + options.data.params[param.toLowerCase()] = value; + }, + "render", + peg$literalExpectation("render", true), + "session", + peg$literalExpectation("session", true), + "icon", + peg$literalExpectation("icon", true), + "alert", + peg$literalExpectation("alert", true), + function () { + options = options || { data: {} }; + if (options.startRule === 'Content_Disposition') { + options.data.type = text().toLowerCase(); + } + }, + "handling", + peg$literalExpectation("handling", true), + "optional", + peg$literalExpectation("optional", true), + "required", + peg$literalExpectation("required", true), + function (length) { + options = options || { data: {} }; + options.data = parseInt(length.join('')); + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + "text", + peg$literalExpectation("text", true), + "image", + peg$literalExpectation("image", true), + "audio", + peg$literalExpectation("audio", true), + "video", + peg$literalExpectation("video", true), + "application", + peg$literalExpectation("application", true), + "message", + peg$literalExpectation("message", true), + "multipart", + peg$literalExpectation("multipart", true), + "x-", + peg$literalExpectation("x-", true), + function (cseq_value) { + options = options || { data: {} }; + options.data.value = parseInt(cseq_value.join('')); + }, + function (expires) { options = options || { data: {} }; options.data = expires; }, + function (event_type) { + options = options || { data: {} }; + options.data.event = event_type.toLowerCase(); + }, + function () { + options = options || { data: {} }; + var tag = options.data.tag; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + if (tag) { + options.data.setParam('tag', tag); + } + }, + "tag", + peg$literalExpectation("tag", true), + function (tag) { options = options || { data: {} }; options.data.tag = tag; }, + function (forwards) { + options = options || { data: {} }; + options.data = parseInt(forwards.join('')); + }, + function (min_expires) { options = options || { data: {} }; options.data = min_expires; }, + function () { + options = options || { data: {} }; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + }, + "digest", + peg$literalExpectation("Digest", true), + "realm", + peg$literalExpectation("realm", true), + function (realm) { options = options || { data: {} }; options.data.realm = realm; }, + "domain", + peg$literalExpectation("domain", true), + "nonce", + peg$literalExpectation("nonce", true), + function (nonce) { options = options || { data: {} }; options.data.nonce = nonce; }, + "opaque", + peg$literalExpectation("opaque", true), + function (opaque) { options = options || { data: {} }; options.data.opaque = opaque; }, + "stale", + peg$literalExpectation("stale", true), + "true", + peg$literalExpectation("true", true), + function () { options = options || { data: {} }; options.data.stale = true; }, + "false", + peg$literalExpectation("false", true), + function () { options = options || { data: {} }; options.data.stale = false; }, + "algorithm", + peg$literalExpectation("algorithm", true), + "md5", + peg$literalExpectation("MD5", true), + "md5-sess", + peg$literalExpectation("MD5-sess", true), + function (algorithm) { + options = options || { data: {} }; + options.data.algorithm = algorithm.toUpperCase(); + }, + "qop", + peg$literalExpectation("qop", true), + "auth-int", + peg$literalExpectation("auth-int", true), + "auth", + peg$literalExpectation("auth", true), + function (qop_value) { + options = options || { data: {} }; + options.data.qop || (options.data.qop = []); + options.data.qop.push(qop_value.toLowerCase()); + }, + function (rack_value) { + options = options || { data: {} }; + options.data.value = parseInt(rack_value.join('')); + }, + function () { + var idx, length; + options = options || { data: {} }; + length = options.data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (options.data.multi_header[idx].parsed === null) { + options.data = null; + break; + } + } + if (options.data !== null) { + options.data = options.data.multi_header; + } + else { + options.data = -1; + } + }, + function () { + var header; + options = options || { data: {} }; + if (!options.data.multi_header) + options.data.multi_header = []; + try { + header = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + delete options.data.uri; + delete options.data.displayName; + delete options.data.params; + } + catch (e) { + header = null; + } + options.data.multi_header.push({ 'position': peg$currPos, + 'offset': location().start.offset, + 'parsed': header + }); + }, + function () { + options = options || { data: {} }; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + }, + function () { + options = options || { data: {} }; + if (!(options.data.replaces_from_tag && options.data.replaces_to_tag)) { + options.data = -1; + } + }, + function () { + options = options || { data: {} }; + options.data = { + call_id: options.data + }; + }, + "from-tag", + peg$literalExpectation("from-tag", true), + function (from_tag) { + options = options || { data: {} }; + options.data.replaces_from_tag = from_tag; + }, + "to-tag", + peg$literalExpectation("to-tag", true), + function (to_tag) { + options = options || { data: {} }; + options.data.replaces_to_tag = to_tag; + }, + "early-only", + peg$literalExpectation("early-only", true), + function () { + options = options || { data: {} }; + options.data.early_only = true; + }, + function (head, r) { return r; }, + function (head, tail) { return list(head, tail); }, + function (value) { + options = options || { data: {} }; + if (options.startRule === 'Require') { + options.data = value || []; + } + }, + function (rseq_value) { + options = options || { data: {} }; + options.data.value = parseInt(rseq_value.join('')); + }, + "active", + peg$literalExpectation("active", true), + "pending", + peg$literalExpectation("pending", true), + "terminated", + peg$literalExpectation("terminated", true), + function () { + options = options || { data: {} }; + options.data.state = text(); + }, + "reason", + peg$literalExpectation("reason", true), + function (reason) { + options = options || { data: {} }; + if (typeof reason !== 'undefined') + options.data.reason = reason; + }, + function (expires) { + options = options || { data: {} }; + if (typeof expires !== 'undefined') + options.data.expires = expires; + }, + "retry_after", + peg$literalExpectation("retry_after", true), + function (retry_after) { + options = options || { data: {} }; + if (typeof retry_after !== 'undefined') + options.data.retry_after = retry_after; + }, + "deactivated", + peg$literalExpectation("deactivated", true), + "probation", + peg$literalExpectation("probation", true), + "rejected", + peg$literalExpectation("rejected", true), + "timeout", + peg$literalExpectation("timeout", true), + "giveup", + peg$literalExpectation("giveup", true), + "noresource", + peg$literalExpectation("noresource", true), + "invariant", + peg$literalExpectation("invariant", true), + function (value) { + options = options || { data: {} }; + if (options.startRule === 'Supported') { + options.data = value || []; + } + }, + function () { + options = options || { data: {} }; + var tag = options.data.tag; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + if (tag) { + options.data.setParam('tag', tag); + } + }, + "ttl", + peg$literalExpectation("ttl", true), + function (via_ttl_value) { + options = options || { data: {} }; + options.data.ttl = via_ttl_value; + }, + "maddr", + peg$literalExpectation("maddr", true), + function (via_maddr) { + options = options || { data: {} }; + options.data.maddr = via_maddr; + }, + "received", + peg$literalExpectation("received", true), + function (via_received) { + options = options || { data: {} }; + options.data.received = via_received; + }, + "branch", + peg$literalExpectation("branch", true), + function (via_branch) { + options = options || { data: {} }; + options.data.branch = via_branch; + }, + "rport", + peg$literalExpectation("rport", true), + function (response_port) { + options = options || { data: {} }; + if (typeof response_port !== 'undefined') + options.data.rport = response_port.join(''); + }, + function (via_protocol) { + options = options || { data: {} }; + options.data.protocol = via_protocol; + }, + peg$literalExpectation("UDP", true), + peg$literalExpectation("TCP", true), + peg$literalExpectation("TLS", true), + peg$literalExpectation("SCTP", true), + function (via_transport) { + options = options || { data: {} }; + options.data.transport = via_transport; + }, + function () { + options = options || { data: {} }; + options.data.host = text(); + }, + function (via_sent_by_port) { + options = options || { data: {} }; + options.data.port = parseInt(via_sent_by_port.join('')); + }, + function (ttl) { + return parseInt(ttl.join('')); + }, + function (deltaSeconds) { + options = options || { data: {} }; + if (options.startRule === 'Session_Expires') { + options.data.deltaSeconds = deltaSeconds; + } + }, + "refresher", + peg$literalExpectation("refresher", false), + "uas", + peg$literalExpectation("uas", false), + "uac", + peg$literalExpectation("uac", false), + function (endpoint) { + options = options || { data: {} }; + if (options.startRule === 'Session_Expires') { + options.data.refresher = endpoint; + } + }, + function (deltaSeconds) { + options = options || { data: {} }; + if (options.startRule === 'Min_SE') { + options.data = deltaSeconds; + } + }, + "stuns", + peg$literalExpectation("stuns", true), + "stun", + peg$literalExpectation("stun", true), + function (scheme) { + options = options || { data: {} }; + options.data.scheme = scheme; + }, + function (host) { + options = options || { data: {} }; + options.data.host = host; + }, + "?transport=", + peg$literalExpectation("?transport=", false), + "turns", + peg$literalExpectation("turns", true), + "turn", + peg$literalExpectation("turn", true), + function (transport) { + options = options || { data: {} }; + options.data.transport = transport; + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + "Referred-By", + peg$literalExpectation("Referred-By", false), + "b", + peg$literalExpectation("b", false), + "cid", + peg$literalExpectation("cid", false) + ]; + var peg$bytecode = [ + peg$decode("2 \"\"6 7!"), + peg$decode("4\"\"\"5!7#"), + peg$decode("4$\"\"5!7%"), + peg$decode("4&\"\"5!7'"), + peg$decode(";'.# &;("), + peg$decode("4(\"\"5!7)"), + peg$decode("4*\"\"5!7+"), + peg$decode("2,\"\"6,7-"), + peg$decode("2.\"\"6.7/"), + peg$decode("40\"\"5!71"), + peg$decode("22\"\"6273.\x89 &24\"\"6475.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode(";).# &;,"), + peg$decode("2F\"\"6F7G.} &2H\"\"6H7I.q &2J\"\"6J7K.e &2L\"\"6L7M.Y &2N\"\"6N7O.M &2P\"\"6P7Q.A &2R\"\"6R7S.5 &2T\"\"6T7U.) &2V\"\"6V7W"), + peg$decode("%%2X\"\"6X7Y/5#;#/,$;#/#$+#)(#'#(\"'#&'#/\"!&,)"), + peg$decode("%%$;$0#*;$&/,#; /#$+\")(\"'#&'#.\" &\"/=#$;$/�#*;$&&&#/'$8\":Z\" )(\"'#&'#"), + peg$decode(";..\" &\""), + peg$decode("%$;'.# &;(0)*;'.# &;(&/?#28\"\"6879/0$;//'$8#:[# )(#'#(\"'#&'#"), + peg$decode("%%$;2/�#*;2&&&#/g#$%$;.0#*;.&/,#;2/#$+\")(\"'#&'#0=*%$;.0#*;.&/,#;2/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/\"!&,)"), + peg$decode("4\\\"\"5!7].# &;3"), + peg$decode("4^\"\"5!7_"), + peg$decode("4`\"\"5!7a"), + peg$decode(";!.) &4b\"\"5!7c"), + peg$decode("%$;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x9E#0\x9B*;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"), + peg$decode("%$;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x92#0\x8F*;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"), + peg$decode("2T\"\"6T7U.\xE3 &2V\"\"6V7W.\xD7 &2f\"\"6f7g.\xCB &2h\"\"6h7i.\xBF &2:\"\"6:7;.\xB3 &2D\"\"6D7E.\xA7 &22\"\"6273.\x9B &28\"\"6879.\x8F &2j\"\"6j7k.\x83 &;&.} &24\"\"6475.q &2l\"\"6l7m.e &2n\"\"6n7o.Y &26\"\"6677.M &2>\"\"6>7?.A &2p\"\"6p7q.5 &2r\"\"6r7s.) &;'.# &;("), + peg$decode("%$;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s/\u0134#0\u0131*;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s&&&#/\"!&,)"), + peg$decode("%;//?#2P\"\"6P7Q/0$;//'$8#:t# )(#'#(\"'#&'#"), + peg$decode("%;//?#24\"\"6475/0$;//'$8#:u# )(#'#(\"'#&'#"), + peg$decode("%;//?#2>\"\"6>7?/0$;//'$8#:v# )(#'#(\"'#&'#"), + peg$decode("%;//?#2T\"\"6T7U/0$;//'$8#:w# )(#'#(\"'#&'#"), + peg$decode("%;//?#2V\"\"6V7W/0$;//'$8#:x# )(#'#(\"'#&'#"), + peg$decode("%2h\"\"6h7i/0#;//'$8\":y\" )(\"'#&'#"), + peg$decode("%;//6#2f\"\"6f7g/'$8\":z\" )(\"'#&'#"), + peg$decode("%;//?#2D\"\"6D7E/0$;//'$8#:{# )(#'#(\"'#&'#"), + peg$decode("%;//?#22\"\"6273/0$;//'$8#:|# )(#'#(\"'#&'#"), + peg$decode("%;//?#28\"\"6879/0$;//'$8#:}# )(#'#(\"'#&'#"), + peg$decode("%;//0#;&/'$8\":~\" )(\"'#&'#"), + peg$decode("%;&/0#;//'$8\":~\" )(\"'#&'#"), + peg$decode("%;=/T#$;G.) &;K.# &;F0/*;G.) &;K.# &;F&/,$;>/#$+#)(#'#(\"'#&'#"), + peg$decode("4\x7F\"\"5!7\x80.A &4\x81\"\"5!7\x82.5 &4\x83\"\"5!7\x84.) &;3.# &;."), + peg$decode("%%;//Q#;&/H$$;J.# &;K0)*;J.# &;K&/,$;&/#$+$)($'#(#'#(\"'#&'#/\"!&,)"), + peg$decode("%;//]#;&/T$%$;J.# &;K0)*;J.# &;K&/\"!&,)/1$;&/($8$:\x85$!!)($'#(#'#(\"'#&'#"), + peg$decode(";..G &2L\"\"6L7M.; &4\x86\"\"5!7\x87./ &4\x83\"\"5!7\x84.# &;3"), + peg$decode("%2j\"\"6j7k/J#4\x88\"\"5!7\x89.5 &4\x8A\"\"5!7\x8B.) &4\x8C\"\"5!7\x8D/#$+\")(\"'#&'#"), + peg$decode("%;N/M#28\"\"6879/>$;O.\" &\"/0$;S/'$8$:\x8E$ )($'#(#'#(\"'#&'#"), + peg$decode("%;N/d#28\"\"6879/U$;O.\" &\"/G$;S/>$;_/5$;l.\" &\"/'$8&:\x8F& )(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%3\x90\"\"5$7\x91.) &3\x92\"\"5#7\x93/' 8!:\x94!! )"), + peg$decode("%;P/]#%28\"\"6879/,#;R/#$+\")(\"'#&'#.\" &\"/6$2:\"\"6:7;/'$8#:\x95# )(#'#(\"'#&'#"), + peg$decode("$;+.) &;-.# &;Q/2#0/*;+.) &;-.# &;Q&&&#"), + peg$decode("2<\"\"6<7=.q &2>\"\"6>7?.e &2@\"\"6@7A.Y &2B\"\"6B7C.M &2D\"\"6D7E.A &22\"\"6273.5 &26\"\"6677.) &24\"\"6475"), + peg$decode("%$;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E0e*;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E&/& 8!:\x96! )"), + peg$decode("%;T/J#%28\"\"6879/,#;^/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%;U.) &;\\.# &;X/& 8!:\x97! )"), + peg$decode("%$%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#0<*%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#&/D#;W/;$2J\"\"6J7K.\" &\"/'$8#:\x98# )(#'#(\"'#&'#"), + peg$decode("$4\x99\"\"5!7\x9A/,#0)*4\x99\"\"5!7\x9A&&&#"), + peg$decode("%4$\"\"5!7%/?#$4\x9B\"\"5!7\x9C0)*4\x9B\"\"5!7\x9C&/#$+\")(\"'#&'#"), + peg$decode("%2l\"\"6l7m/?#;Y/6$2n\"\"6n7o/'$8#:\x9D# )(#'#(\"'#&'#"), + peg$decode("%%;Z/\xB3#28\"\"6879/\xA4$;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+-)(-'#(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0790 &%2\x9E\"\"6\x9E7\x9F/\xA4#;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+,)(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u06F9 &%2\x9E\"\"6\x9E7\x9F/\x8C#;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u067A &%2\x9E\"\"6\x9E7\x9F/t#;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0613 &%2\x9E\"\"6\x9E7\x9F/\\#;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+&)(&'#(%'#($'#(#'#(\"'#&'#.\u05C4 &%2\x9E\"\"6\x9E7\x9F/D#;Z/;$28\"\"6879/,$;[/#$+$)($'#(#'#(\"'#&'#.\u058D &%2\x9E\"\"6\x9E7\x9F/,#;[/#$+\")(\"'#&'#.\u056E &%2\x9E\"\"6\x9E7\x9F/,#;Z/#$+\")(\"'#&'#.\u054F &%;Z/\x9B#2\x9E\"\"6\x9E7\x9F/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$++)(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u04C7 &%;Z/\xAA#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x83$2\x9E\"\"6\x9E7\x9F/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0430 &%;Z/\xB9#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x92$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/k$2\x9E\"\"6\x9E7\x9F/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+))()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u038A &%;Z/\xC8#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA1$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/z$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/S$2\x9E\"\"6\x9E7\x9F/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u02D5 &%;Z/\xD7#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;[/#$+')(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0211 &%;Z/\xFE#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xD7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;Z/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0126 &%;Z/\u011C#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xF5$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xCE$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x80$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/Y$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/2$2\x9E\"\"6\x9E7\x9F/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#/& 8!:\xA0! )"), + peg$decode("%;#/M#;#.\" &\"/?$;#.\" &\"/1$;#.\" &\"/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%;Z/;#28\"\"6879/,$;Z/#$+#)(#'#(\"'#&'#.# &;\\"), + peg$decode("%;]/o#2J\"\"6J7K/`$;]/W$2J\"\"6J7K/H$;]/?$2J\"\"6J7K/0$;]/'$8':\xA1' )(''#(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%2\xA2\"\"6\xA27\xA3/2#4\xA4\"\"5!7\xA5/#$+\")(\"'#&'#.\x98 &%2\xA6\"\"6\xA67\xA7/;#4\xA8\"\"5!7\xA9/,$;!/#$+#)(#'#(\"'#&'#.j &%2\xAA\"\"6\xAA7\xAB/5#;!/,$;!/#$+#)(#'#(\"'#&'#.B &%4\xAC\"\"5!7\xAD/,#;!/#$+\")(\"'#&'#.# &;!"), + peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\xAE!! )"), + peg$decode("$%22\"\"6273/,#;`/#$+\")(\"'#&'#0<*%22\"\"6273/,#;`/#$+\")(\"'#&'#&"), + peg$decode(";a.A &;b.; &;c.5 &;d./ &;e.) &;f.# &;g"), + peg$decode("%3\xAF\"\"5*7\xB0/a#3\xB1\"\"5#7\xB2.G &3\xB3\"\"5#7\xB4.; &3\xB5\"\"5$7\xB6./ &3\xB7\"\"5#7\xB8.# &;6/($8\":\xB9\"! )(\"'#&'#"), + peg$decode("%3\xBA\"\"5%7\xBB/I#3\xBC\"\"5%7\xBD./ &3\xBE\"\"5\"7\xBF.# &;6/($8\":\xC0\"! )(\"'#&'#"), + peg$decode("%3\xC1\"\"5'7\xC2/1#;\x90/($8\":\xC3\"! )(\"'#&'#"), + peg$decode("%3\xC4\"\"5$7\xC5/1#;\xF0/($8\":\xC6\"! )(\"'#&'#"), + peg$decode("%3\xC7\"\"5&7\xC8/1#;T/($8\":\xC9\"! )(\"'#&'#"), + peg$decode("%3\xCA\"\"5\"7\xCB/N#%2>\"\"6>7?/,#;6/#$+\")(\"'#&'#.\" &\"/'$8\":\xCC\" )(\"'#&'#"), + peg$decode("%;h/P#%2>\"\"6>7?/,#;i/#$+\")(\"'#&'#.\" &\"/)$8\":\xCD\"\"! )(\"'#&'#"), + peg$decode("%$;j/�#*;j&&&#/\"!&,)"), + peg$decode("%$;j/�#*;j&&&#/\"!&,)"), + peg$decode(";k.) &;+.# &;-"), + peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &28\"\"6879.A &2<\"\"6<7=.5 &2@\"\"6@7A.) &2B\"\"6B7C"), + peg$decode("%26\"\"6677/n#;m/e$$%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#0<*%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#&/#$+#)(#'#(\"'#&'#"), + peg$decode("%;n/A#2>\"\"6>7?/2$;o/)$8#:\xCE#\"\" )(#'#(\"'#&'#"), + peg$decode("$;p.) &;+.# &;-/2#0/*;p.) &;+.# &;-&&&#"), + peg$decode("$;p.) &;+.# &;-0/*;p.) &;+.# &;-&"), + peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &26\"\"6677.A &28\"\"6879.5 &2@\"\"6@7A.) &2B\"\"6B7C"), + peg$decode(";\x91.# &;r"), + peg$decode("%;\x90/G#;'/>$;s/5$;'/,$;\x84/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode(";M.# &;t"), + peg$decode("%;\x7F/E#28\"\"6879/6$;u.# &;x/'$8#:\xCF# )(#'#(\"'#&'#"), + peg$decode("%;v.# &;w/J#%26\"\"6677/,#;\x83/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%2\xD0\"\"6\xD07\xD1/:#;\x80/1$;w.\" &\"/#$+#)(#'#(\"'#&'#"), + peg$decode("%24\"\"6475/,#;{/#$+\")(\"'#&'#"), + peg$decode("%;z/3#$;y0#*;y&/#$+\")(\"'#&'#"), + peg$decode(";*.) &;+.# &;-"), + peg$decode(";+.\x8F &;-.\x89 &22\"\"6273.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode("%;|/e#$%24\"\"6475/,#;|/#$+\")(\"'#&'#0<*%24\"\"6475/,#;|/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("%$;~0#*;~&/e#$%22\"\"6273/,#;}/#$+\")(\"'#&'#0<*%22\"\"6273/,#;}/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("$;~0#*;~&"), + peg$decode(";+.w &;-.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode("%%;\"/\x87#$;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K0M*;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K&/#$+\")(\"'#&'#/& 8!:\xD2! )"), + peg$decode(";\x81.# &;\x82"), + peg$decode("%%;O/2#2:\"\"6:7;/#$+\")(\"'#&'#.\" &\"/,#;S/#$+\")(\"'#&'#.\" &\""), + peg$decode("$;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A/\x8C#0\x89*;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A&&&#"), + peg$decode("$;y0#*;y&"), + peg$decode("%3\x92\"\"5#7\xD3/q#24\"\"6475/b$$;!/�#*;!&&&#/L$2J\"\"6J7K/=$$;!/�#*;!&&&#/'$8%:\xD4% )(%'#($'#(#'#(\"'#&'#"), + peg$decode("2\xD5\"\"6\xD57\xD6"), + peg$decode("2\xD7\"\"6\xD77\xD8"), + peg$decode("2\xD9\"\"6\xD97\xDA"), + peg$decode("2\xDB\"\"6\xDB7\xDC"), + peg$decode("2\xDD\"\"6\xDD7\xDE"), + peg$decode("2\xDF\"\"6\xDF7\xE0"), + peg$decode("2\xE1\"\"6\xE17\xE2"), + peg$decode("2\xE3\"\"6\xE37\xE4"), + peg$decode("2\xE5\"\"6\xE57\xE6"), + peg$decode("2\xE7\"\"6\xE77\xE8"), + peg$decode("2\xE9\"\"6\xE97\xEA"), + peg$decode("%;\x85.Y &;\x86.S &;\x88.M &;\x89.G &;\x8A.A &;\x8B.; &;\x8C.5 &;\x8F./ &;\x8D.) &;\x8E.# &;6/& 8!:\xEB! )"), + peg$decode("%;\x84/G#;'/>$;\x92/5$;'/,$;\x94/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%;\x93/' 8!:\xEC!! )"), + peg$decode("%;!/5#;!/,$;!/#$+#)(#'#(\"'#&'#"), + peg$decode("%$;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(0G*;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(&/& 8!:\xED! )"), + peg$decode("%;\xB6/Y#$%;A/,#;\xB6/#$+\")(\"'#&'#06*%;A/,#;\xB6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("%;9/N#%2:\"\"6:7;/,#;9/#$+\")(\"'#&'#.\" &\"/'$8\":\xEE\" )(\"'#&'#"), + peg$decode("%;:.c &%;\x98/Y#$%;A/,#;\x98/#$+\")(\"'#&'#06*%;A/,#;\x98/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/& 8!:\xEF! )"), + peg$decode("%;L.# &;\x99/]#$%;B/,#;\x9B/#$+\")(\"'#&'#06*%;B/,#;\x9B/#$+\")(\"'#&'#&/'$8\":\xF0\" )(\"'#&'#"), + peg$decode("%;\x9A.\" &\"/>#;@/5$;M/,$;?/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%%;6/Y#$%;./,#;6/#$+\")(\"'#&'#06*%;./,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#.# &;H/' 8!:\xF1!! )"), + peg$decode(";\x9C.) &;\x9D.# &;\xA0"), + peg$decode("%3\xF2\"\"5!7\xF3/:#;$;\xCF/5$;./,$;\x90/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%$;!/�#*;!&&&#/' 8!:\u014B!! )"), + peg$decode("%;\xD1/]#$%;A/,#;\xD1/#$+\")(\"'#&'#06*%;A/,#;\xD1/#$+\")(\"'#&'#&/'$8\":\u014C\" )(\"'#&'#"), + peg$decode("%;\x99/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014D\" )(\"'#&'#"), + peg$decode("%;L.O &;\x99.I &%;@.\" &\"/:#;t/1$;?.\" &\"/#$+#)(#'#(\"'#&'#/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014E\" )(\"'#&'#"), + peg$decode("%;\xD4/]#$%;B/,#;\xD5/#$+\")(\"'#&'#06*%;B/,#;\xD5/#$+\")(\"'#&'#&/'$8\":\u014F\" )(\"'#&'#"), + peg$decode("%;\x96/& 8!:\u0150! )"), + peg$decode("%3\u0151\"\"5(7\u0152/:#;$;6/5$;;/,$;\xEC/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%3\x92\"\"5#7\xD3.# &;6/' 8!:\u018B!! )"), + peg$decode("%3\xB1\"\"5#7\u018C.G &3\xB3\"\"5#7\u018D.; &3\xB7\"\"5#7\u018E./ &3\xB5\"\"5$7\u018F.# &;6/' 8!:\u0190!! )"), + peg$decode("%;\xEE/D#%;C/,#;\xEF/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%;U.) &;\\.# &;X/& 8!:\u0191! )"), + peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\u0192!! )"), + peg$decode("%%;!/?#;!.\" &\"/1$;!.\" &\"/#$+#)(#'#(\"'#&'#/' 8!:\u0193!! )"), + peg$decode(";\xBE"), + peg$decode("%;\x9E/^#$%;B/,#;\xF3/#$+\")(\"'#&'#06*%;B/,#;\xF3/#$+\")(\"'#&'#&/($8\":\u0194\"!!)(\"'#&'#"), + peg$decode(";\xF4.# &;\xA0"), + peg$decode("%2\u0195\"\"6\u01957\u0196/L#;\"\"6>7?"), + peg$decode("%;\u0100/b#28\"\"6879/S$;\xFB/J$%2\u01A3\"\"6\u01A37\u01A4/,#;\xEC/#$+\")(\"'#&'#.\" &\"/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%3\u01A5\"\"5%7\u01A6.) &3\u01A7\"\"5$7\u01A8/' 8!:\u01A1!! )"), + peg$decode("%3\xB1\"\"5#7\xB2.6 &3\xB3\"\"5#7\xB4.* &$;+0#*;+&/' 8!:\u01A9!! )"), + peg$decode("%;\u0104/\x87#2F\"\"6F7G/x$;\u0103/o$2F\"\"6F7G/`$;\u0103/W$2F\"\"6F7G/H$;\u0103/?$2F\"\"6F7G/0$;\u0105/'$8):\u01AA) )()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%;#/>#;#/5$;#/,$;#/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%;\u0103/,#;\u0103/#$+\")(\"'#&'#"), + peg$decode("%;\u0103/5#;\u0103/,$;\u0103/#$+#)(#'#(\"'#&'#"), + peg$decode("%;q/T#$;m0#*;m&/D$%; /,#;\xF8/#$+\")(\"'#&'#.\" &\"/#$+#)(#'#(\"'#&'#"), + peg$decode("%2\u01AB\"\"6\u01AB7\u01AC.) &2\u01AD\"\"6\u01AD7\u01AE/w#;0/n$;\u0108/e$$%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#0<*%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#"), + peg$decode(";\x99.# &;L"), + peg$decode("%2\u01AF\"\"6\u01AF7\u01B0/5#; peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + peg$maxFailExpected.push(expected1); + } + function peg$buildSimpleError(message, location1) { + return new SyntaxError(message, [], "", location1); + } + function peg$buildStructuredError(expected1, found, location1) { + return new SyntaxError(SyntaxError.buildMessage(expected1, found), expected1, found, location1); + } + function peg$decode(s) { + return s.split("").map(function (ch) { return ch.charCodeAt(0) - 32; }); + } + function peg$parseRule(index) { + var bc = peg$bytecode[index]; + var ip = 0; + var ips = []; + var end = bc.length; + var ends = []; + var stack = []; + var params; + while (true) { + while (ip < end) { + switch (bc[ip]) { + case 0: + stack.push(peg$consts[bc[ip + 1]]); + ip += 2; + break; + case 1: + stack.push(undefined); + ip++; + break; + case 2: + stack.push(null); + ip++; + break; + case 3: + stack.push(peg$FAILED); + ip++; + break; + case 4: + stack.push([]); + ip++; + break; + case 5: + stack.push(peg$currPos); + ip++; + break; + case 6: + stack.pop(); + ip++; + break; + case 7: + peg$currPos = stack.pop(); + ip++; + break; + case 8: + stack.length -= bc[ip + 1]; + ip += 2; + break; + case 9: + stack.splice(-2, 1); + ip++; + break; + case 10: + stack[stack.length - 2].push(stack.pop()); + ip++; + break; + case 11: + stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1])); + ip += 2; + break; + case 12: + stack.push(input.substring(stack.pop(), peg$currPos)); + ip++; + break; + case 13: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1]) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 14: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1] === peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 15: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1] !== peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 16: + if (stack[stack.length - 1] !== peg$FAILED) { + ends.push(end); + ips.push(ip); + end = ip + 2 + bc[ip + 1]; + ip += 2; + } + else { + ip += 2 + bc[ip + 1]; + } + break; + case 17: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (input.length > peg$currPos) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 18: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 19: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 20: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 21: + stack.push(input.substr(peg$currPos, bc[ip + 1])); + peg$currPos += bc[ip + 1]; + ip += 2; + break; + case 22: + stack.push(peg$consts[bc[ip + 1]]); + peg$currPos += peg$consts[bc[ip + 1]].length; + ip += 2; + break; + case 23: + stack.push(peg$FAILED); + if (peg$silentFails === 0) { + peg$fail(peg$consts[bc[ip + 1]]); + } + ip += 2; + break; + case 24: + peg$savedPos = stack[stack.length - 1 - bc[ip + 1]]; + ip += 2; + break; + case 25: + peg$savedPos = peg$currPos; + ip++; + break; + case 26: + params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]) + .map(function (p) { return stack[stack.length - 1 - p]; }); + stack.splice(stack.length - bc[ip + 2], bc[ip + 2], peg$consts[bc[ip + 1]].apply(null, params)); + ip += 4 + bc[ip + 3]; + break; + case 27: + stack.push(peg$parseRule(bc[ip + 1])); + ip += 2; + break; + case 28: + peg$silentFails++; + ip++; + break; + case 29: + peg$silentFails--; + ip++; + break; + default: + throw new Error("Invalid opcode: " + bc[ip] + "."); + } + } + if (ends.length > 0) { + end = ends.pop(); + ip = ips.pop(); + } + else { + break; + } + } + return stack[0]; + } + options.data = {}; // Object to which header attributes will be assigned during parsing + function list(head, tail) { + return [head].concat(tail); + } + peg$result = peg$parseRule(peg$startRuleIndex); + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } + else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + throw peg$buildStructuredError(peg$maxFailExpected, peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)); + } +} +exports.parse = peg$parse; + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var parameters_1 = __webpack_require__(14); +/** + * Name Address SIP header. + * @public + */ +var NameAddrHeader = /** @class */ (function (_super) { + tslib_1.__extends(NameAddrHeader, _super); + /** + * Constructor + * @param uri + * @param displayName + * @param parameters + */ + function NameAddrHeader(uri, displayName, parameters) { + var _this = _super.call(this, parameters) || this; + _this.uri = uri; + _this._displayName = displayName; + return _this; + } + Object.defineProperty(NameAddrHeader.prototype, "friendlyName", { + get: function () { + return this.displayName || this.uri.aor; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(NameAddrHeader.prototype, "displayName", { + get: function () { return this._displayName; }, + set: function (value) { + this._displayName = value; + }, + enumerable: true, + configurable: true + }); + NameAddrHeader.prototype.clone = function () { + return new NameAddrHeader(this.uri.clone(), this._displayName, JSON.parse(JSON.stringify(this.parameters))); + }; + NameAddrHeader.prototype.toString = function () { + var body = (this.displayName || this.displayName === "0") ? '"' + this.displayName + '" ' : ""; + body += "<" + this.uri.toString() + ">"; + for (var parameter in this.parameters) { + if (this.parameters.hasOwnProperty(parameter)) { + body += ";" + parameter; + if (this.parameters[parameter] !== null) { + body += "=" + this.parameters[parameter]; + } + } + } + return body; + }; + return NameAddrHeader; +}(parameters_1.Parameters)); +exports.NameAddrHeader = NameAddrHeader; + + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * @internal + */ +var Parameters = /** @class */ (function () { + function Parameters(parameters) { + this.parameters = {}; + for (var param in parameters) { + if (parameters.hasOwnProperty(param)) { + this.setParam(param, parameters[param]); + } + } + } + Parameters.prototype.setParam = function (key, value) { + if (key) { + this.parameters[key.toLowerCase()] = (typeof value === "undefined" || value === null) ? null : value.toString(); + } + }; + Parameters.prototype.getParam = function (key) { + if (key) { + return this.parameters[key.toLowerCase()]; + } + }; + Parameters.prototype.hasParam = function (key) { + if (key) { + return !!this.parameters.hasOwnProperty(key.toLowerCase()); + } + return false; + }; + Parameters.prototype.deleteParam = function (parameter) { + parameter = parameter.toLowerCase(); + if (this.parameters.hasOwnProperty(parameter)) { + var value = this.parameters[parameter]; + delete this.parameters[parameter]; + return value; + } + }; + Parameters.prototype.clearParams = function () { + this.parameters = {}; + }; + return Parameters; +}()); +exports.Parameters = Parameters; + + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var parameters_1 = __webpack_require__(14); +/** + * URI. + * @public + */ +var URI = /** @class */ (function (_super) { + tslib_1.__extends(URI, _super); + /** + * Constructor + * @param scheme + * @param user + * @param host + * @param port + * @param parameters + * @param headers + */ + function URI(scheme, user, host, port, parameters, headers) { + var _this = _super.call(this, parameters) || this; + _this.headers = {}; + // Checks + if (!host) { + throw new TypeError('missing or invalid "host" parameter'); + } + // Initialize parameters + scheme = scheme || "sip"; + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + _this.setHeader(header, headers[header]); + } + } + // Raw URI + _this.raw = { + scheme: scheme, + user: user, + host: host, + port: port + }; + // Normalized URI + _this.normal = { + scheme: scheme.toLowerCase(), + user: user, + host: host.toLowerCase(), + port: port + }; + return _this; + } + Object.defineProperty(URI.prototype, "scheme", { + get: function () { return this.normal.scheme; }, + set: function (value) { + this.raw.scheme = value; + this.normal.scheme = value.toLowerCase(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "user", { + get: function () { return this.normal.user; }, + set: function (value) { + this.normal.user = this.raw.user = value; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "host", { + get: function () { return this.normal.host; }, + set: function (value) { + this.raw.host = value; + this.normal.host = value.toLowerCase(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "aor", { + get: function () { return this.normal.user + "@" + this.normal.host; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "port", { + get: function () { return this.normal.port; }, + set: function (value) { + this.normal.port = this.raw.port = value === 0 ? value : value; + }, + enumerable: true, + configurable: true + }); + URI.prototype.setHeader = function (name, value) { + this.headers[this.headerize(name)] = (value instanceof Array) ? value : [value]; + }; + URI.prototype.getHeader = function (name) { + if (name) { + return this.headers[this.headerize(name)]; + } + }; + URI.prototype.hasHeader = function (name) { + return !!name && !!this.headers.hasOwnProperty(this.headerize(name)); + }; + URI.prototype.deleteHeader = function (header) { + header = this.headerize(header); + if (this.headers.hasOwnProperty(header)) { + var value = this.headers[header]; + delete this.headers[header]; + return value; + } + }; + URI.prototype.clearHeaders = function () { + this.headers = {}; + }; + URI.prototype.clone = function () { + return new URI(this._raw.scheme, this._raw.user || "", this._raw.host, this._raw.port, JSON.parse(JSON.stringify(this.parameters)), JSON.parse(JSON.stringify(this.headers))); + }; + URI.prototype.toRaw = function () { + return this._toString(this._raw); + }; + URI.prototype.toString = function () { + return this._toString(this._normal); + }; + Object.defineProperty(URI.prototype, "_normal", { + get: function () { return this.normal; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "_raw", { + get: function () { return this.raw; }, + enumerable: true, + configurable: true + }); + URI.prototype._toString = function (uri) { + var uriString = uri.scheme + ":"; + // add slashes if it's not a sip(s) URI + if (!uri.scheme.toLowerCase().match("^sips?$")) { + uriString += "//"; + } + if (uri.user) { + uriString += this.escapeUser(uri.user) + "@"; + } + uriString += uri.host; + if (uri.port || uri.port === 0) { + uriString += ":" + uri.port; + } + for (var parameter in this.parameters) { + if (this.parameters.hasOwnProperty(parameter)) { + uriString += ";" + parameter; + if (this.parameters[parameter] !== null) { + uriString += "=" + this.parameters[parameter]; + } + } + } + var headers = []; + for (var header in this.headers) { + if (this.headers.hasOwnProperty(header)) { + for (var idx in this.headers[header]) { + if (this.headers[header].hasOwnProperty(idx)) { + headers.push(header + "=" + this.headers[header][idx]); + } + } + } + } + if (headers.length > 0) { + uriString += "?" + headers.join("&"); + } + return uriString; + }; + // The following two functions were copied from Utils to break a circular dependency + /* + * Hex-escape a SIP URI user. + * @private + * @param {String} user + */ + URI.prototype.escapeUser = function (user) { + // Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F). + return encodeURIComponent(decodeURIComponent(user)) + .replace(/%3A/ig, ":") + .replace(/%2B/ig, "+") + .replace(/%3F/ig, "?") + .replace(/%2F/ig, "/"); + }; + URI.prototype.headerize = function (str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; + }; + return URI; +}(parameters_1.Parameters)); +exports.URI = URI; + + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * @param size - + * @param base - + * @internal + */ +function createRandomToken(size, base) { + if (base === void 0) { base = 32; } + var token = ""; + for (var i = 0; i < size; i++) { + var r = Math.floor(Math.random() * base); + token += r.toString(base); + } + return token; +} +exports.createRandomToken = createRandomToken; +/** + * @internal + */ +function getReasonPhrase(code) { + return REASON_PHRASE[code] || ""; +} +exports.getReasonPhrase = getReasonPhrase; +/** + * @internal + */ +function newTag() { + return createRandomToken(10); +} +exports.newTag = newTag; +/** + * @param str - + * @internal + */ +function headerize(str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; +} +exports.headerize = headerize; +/** + * @param str - + * @internal + */ +function str_utf8_length(str) { + return encodeURIComponent(str).replace(/%[A-F\d]{2}/g, "U").length; +} +exports.str_utf8_length = str_utf8_length; +/** + * SIP Response Reasons + * DOC: http://www.iana.org/assignments/sip-parameters + * @internal + */ +var REASON_PHRASE = { + 100: "Trying", + 180: "Ringing", + 181: "Call Is Being Forwarded", + 182: "Queued", + 183: "Session Progress", + 199: "Early Dialog Terminated", + 200: "OK", + 202: "Accepted", + 204: "No Notification", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Moved Temporarily", + 305: "Use Proxy", + 380: "Alternative Service", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 410: "Gone", + 412: "Conditional Request Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Unsupported URI Scheme", + 417: "Unknown Resource-Priority", + 420: "Bad Extension", + 421: "Extension Required", + 422: "Session Interval Too Small", + 423: "Interval Too Brief", + 428: "Use Identity Header", + 429: "Provide Referrer Identity", + 430: "Flow Failed", + 433: "Anonymity Disallowed", + 436: "Bad Identity-Info", + 437: "Unsupported Certificate", + 438: "Invalid Identity Header", + 439: "First Hop Lacks Outbound Support", + 440: "Max-Breadth Exceeded", + 469: "Bad Info Package", + 470: "Consent Needed", + 478: "Unresolvable Destination", + 480: "Temporarily Unavailable", + 481: "Call/Transaction Does Not Exist", + 482: "Loop Detected", + 483: "Too Many Hops", + 484: "Address Incomplete", + 485: "Ambiguous", + 486: "Busy Here", + 487: "Request Terminated", + 488: "Not Acceptable Here", + 489: "Bad Event", + 491: "Request Pending", + 493: "Undecipherable", + 494: "Security Agreement Required", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Server Time-out", + 505: "Version Not Supported", + 513: "Message Too Large", + 580: "Precondition Failure", + 600: "Busy Everywhere", + 603: "Decline", + 604: "Does Not Exist Anywhere", + 606: "Not Acceptable" +}; + + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var incoming_message_1 = __webpack_require__(10); +/** + * Incoming SIP response message. + */ +var IncomingResponseMessage = /** @class */ (function (_super) { + tslib_1.__extends(IncomingResponseMessage, _super); + function IncomingResponseMessage() { + var _this = _super.call(this) || this; + _this.headers = {}; + return _this; + } + return IncomingResponseMessage; +}(incoming_message_1.IncomingMessage)); +exports.IncomingResponseMessage = IncomingResponseMessage; + + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var name_addr_header_1 = __webpack_require__(13); +var utils_1 = __webpack_require__(16); +/** + * Outgoing SIP request message. + * @public + */ +var OutgoingRequestMessage = /** @class */ (function () { + function OutgoingRequestMessage(method, ruri, fromURI, toURI, options, extraHeaders, body) { + this.headers = {}; + this.extraHeaders = []; + this.options = OutgoingRequestMessage.getDefaultOptions(); + // Options - merge a deep copy + if (options) { + this.options = tslib_1.__assign({}, this.options, options); + if (this.options.optionTags && this.options.optionTags.length) { + this.options.optionTags = this.options.optionTags.slice(); + } + if (this.options.routeSet && this.options.routeSet.length) { + this.options.routeSet = this.options.routeSet.slice(); + } + } + // Extra headers - deep copy + if (extraHeaders && extraHeaders.length) { + this.extraHeaders = extraHeaders.slice(); + } + // Body - deep copy + if (body) { + // TODO: internal representation should be Body + // this.body = { ...body }; + this.body = { + body: body.content, + contentType: body.contentType + }; + } + // Method + this.method = method; + // RURI + this.ruri = ruri.clone(); + // From + this.fromURI = fromURI.clone(); + this.fromTag = this.options.fromTag ? this.options.fromTag : utils_1.newTag(); + this.from = OutgoingRequestMessage.makeNameAddrHeader(this.fromURI, this.options.fromDisplayName, this.fromTag); + // To + this.toURI = toURI.clone(); + this.toTag = this.options.toTag; + this.to = OutgoingRequestMessage.makeNameAddrHeader(this.toURI, this.options.toDisplayName, this.toTag); + // Call-ID + this.callId = this.options.callId ? this.options.callId : this.options.callIdPrefix + utils_1.createRandomToken(15); + // CSeq + this.cseq = this.options.cseq; + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + this.setHeader("route", this.options.routeSet); + this.setHeader("via", ""); + this.setHeader("to", this.to.toString()); + this.setHeader("from", this.from.toString()); + this.setHeader("cseq", this.cseq + " " + this.method); + this.setHeader("call-id", this.callId); + this.setHeader("max-forwards", "70"); + } + /** Get a copy of the default options. */ + OutgoingRequestMessage.getDefaultOptions = function () { + return { + callId: "", + callIdPrefix: "", + cseq: 1, + toDisplayName: "", + toTag: "", + fromDisplayName: "", + fromTag: "", + forceRport: false, + hackViaTcp: false, + optionTags: ["outbound"], + routeSet: [], + userAgentString: "sip.js", + viaHost: "" + }; + }; + OutgoingRequestMessage.makeNameAddrHeader = function (uri, displayName, tag) { + var parameters = {}; + if (tag) { + parameters.tag = tag; + } + return new name_addr_header_1.NameAddrHeader(uri, displayName, parameters); + }; + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + OutgoingRequestMessage.prototype.getHeader = function (name) { + var header = this.headers[utils_1.headerize(name)]; + if (header) { + if (header[0]) { + return header[0]; + } + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _i = 0, _a = this.extraHeaders; _i < _a.length; _i++) { + var exHeader = _a[_i]; + if (regexp.test(exHeader)) { + return exHeader.substring(exHeader.indexOf(":") + 1).trim(); + } + } + } + return; + }; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array with all the headers of the specified name. + */ + OutgoingRequestMessage.prototype.getHeaders = function (name) { + var result = []; + var headerArray = this.headers[utils_1.headerize(name)]; + if (headerArray) { + for (var _i = 0, headerArray_1 = headerArray; _i < headerArray_1.length; _i++) { + var headerPart = headerArray_1[_i]; + result.push(headerPart); + } + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _a = 0, _b = this.extraHeaders; _a < _b.length; _a++) { + var exHeader = _b[_a]; + if (regexp.test(exHeader)) { + result.push(exHeader.substring(exHeader.indexOf(":") + 1).trim()); + } + } + } + return result; + }; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + OutgoingRequestMessage.prototype.hasHeader = function (name) { + if (this.headers[utils_1.headerize(name)]) { + return true; + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _i = 0, _a = this.extraHeaders; _i < _a.length; _i++) { + var extraHeader = _a[_i]; + if (regexp.test(extraHeader)) { + return true; + } + } + } + return false; + }; + /** + * Replace the the given header by the given value. + * @param name - header name + * @param value - header value + */ + OutgoingRequestMessage.prototype.setHeader = function (name, value) { + this.headers[utils_1.headerize(name)] = (value instanceof Array) ? value : [value]; + }; + /** + * The Via header field indicates the transport used for the transaction + * and identifies the location where the response is to be sent. A Via + * header field value is added only after the transport that will be + * used to reach the next hop has been selected (which may involve the + * usage of the procedures in [4]). + * + * When the UAC creates a request, it MUST insert a Via into that + * request. The protocol name and protocol version in the header field + * MUST be SIP and 2.0, respectively. The Via header field value MUST + * contain a branch parameter. This parameter is used to identify the + * transaction created by that request. This parameter is used by both + * the client and the server. + * https://tools.ietf.org/html/rfc3261#section-8.1.1.7 + * @param branchParameter - The branch parameter. + * @param scheme - The scheme. + */ + OutgoingRequestMessage.prototype.setViaHeader = function (branch, scheme) { + if (scheme === void 0) { scheme = "WSS"; } + // FIXME: Hack + if (this.options.hackViaTcp) { + scheme = "TCP"; + } + var via = "SIP/2.0/" + scheme; + via += " " + this.options.viaHost + ";branch=" + branch; + if (this.options.forceRport) { + via += ";rport"; + } + this.setHeader("via", via); + this.branch = branch; + }; + OutgoingRequestMessage.prototype.toString = function () { + var msg = ""; + msg += this.method + " " + this.ruri.toRaw() + " SIP/2.0\r\n"; + for (var header in this.headers) { + if (this.headers[header]) { + for (var _i = 0, _a = this.headers[header]; _i < _a.length; _i++) { + var headerPart = _a[_i]; + msg += header + ": " + headerPart + "\r\n"; + } + } + } + for (var _b = 0, _c = this.extraHeaders; _b < _c.length; _b++) { + var header = _c[_b]; + msg += header.trim() + "\r\n"; + } + msg += "Supported: " + this.options.optionTags.join(", ") + "\r\n"; + msg += "User-Agent: " + this.options.userAgentString + "\r\n"; + if (this.body) { + if (typeof this.body === "string") { + msg += "Content-Length: " + utils_1.str_utf8_length(this.body) + "\r\n\r\n"; + msg += this.body; + } + else { + if (this.body.body && this.body.contentType) { + msg += "Content-Type: " + this.body.contentType + "\r\n"; + msg += "Content-Length: " + utils_1.str_utf8_length(this.body.body) + "\r\n\r\n"; + msg += this.body.body; + } + else { + msg += "Content-Length: " + 0 + "\r\n\r\n"; + } + } + } + else { + msg += "Content-Length: " + 0 + "\r\n\r\n"; + } + return msg; + }; + return OutgoingRequestMessage; +}()); +exports.OutgoingRequestMessage = OutgoingRequestMessage; + + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var md5_1 = tslib_1.__importDefault(__webpack_require__(20)); +var utils_1 = __webpack_require__(16); +/** + * Digest Authentication. + * @internal + */ +var DigestAuthentication = /** @class */ (function () { + /** + * Constructor. + * @param loggerFactory - LoggerFactory. + * @param username - Username. + * @param password - Password. + */ + function DigestAuthentication(loggerFactory, username, password) { + this.logger = loggerFactory.getLogger("sipjs.digestauthentication"); + this.username = username; + this.password = password; + this.nc = 0; + this.ncHex = "00000000"; + } + /** + * Performs Digest authentication given a SIP request and the challenge + * received in a response to that request. + * @param request - + * @param challenge - + * @returns true if credentials were successfully generated, false otherwise. + */ + DigestAuthentication.prototype.authenticate = function (request, challenge, body) { + // Inspect and validate the challenge. + this.algorithm = challenge.algorithm; + this.realm = challenge.realm; + this.nonce = challenge.nonce; + this.opaque = challenge.opaque; + this.stale = challenge.stale; + if (this.algorithm) { + if (this.algorithm !== "MD5") { + this.logger.warn("challenge with Digest algorithm different than 'MD5', authentication aborted"); + return false; + } + } + else { + this.algorithm = "MD5"; + } + if (!this.realm) { + this.logger.warn("challenge without Digest realm, authentication aborted"); + return false; + } + if (!this.nonce) { + this.logger.warn("challenge without Digest nonce, authentication aborted"); + return false; + } + // 'qop' can contain a list of values (Array). Let's choose just one. + if (challenge.qop) { + if (challenge.qop.indexOf("auth") > -1) { + this.qop = "auth"; + } + else if (challenge.qop.indexOf("auth-int") > -1) { + this.qop = "auth-int"; + } + else { + // Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here. + this.logger.warn("challenge without Digest qop different than 'auth' or 'auth-int', authentication aborted"); + return false; + } + } + else { + this.qop = undefined; + } + // Fill other attributes. + this.method = request.method; + this.uri = request.ruri; + this.cnonce = utils_1.createRandomToken(12); + this.nc += 1; + this.updateNcHex(); + // nc-value = 8LHEX. Max value = 'FFFFFFFF'. + if (this.nc === 4294967296) { + this.nc = 1; + this.ncHex = "00000001"; + } + // Calculate the Digest "response" value. + this.calculateResponse(body); + return true; + }; + /** + * Return the Proxy-Authorization or WWW-Authorization header value. + */ + DigestAuthentication.prototype.toString = function () { + var authParams = []; + if (!this.response) { + throw new Error("response field does not exist, cannot generate Authorization header"); + } + authParams.push("algorithm=" + this.algorithm); + authParams.push('username="' + this.username + '"'); + authParams.push('realm="' + this.realm + '"'); + authParams.push('nonce="' + this.nonce + '"'); + authParams.push('uri="' + this.uri + '"'); + authParams.push('response="' + this.response + '"'); + if (this.opaque) { + authParams.push('opaque="' + this.opaque + '"'); + } + if (this.qop) { + authParams.push("qop=" + this.qop); + authParams.push('cnonce="' + this.cnonce + '"'); + authParams.push("nc=" + this.ncHex); + } + return "Digest " + authParams.join(", "); + }; + /** + * Generate the 'nc' value as required by Digest in this.ncHex by reading this.nc. + */ + DigestAuthentication.prototype.updateNcHex = function () { + var hex = Number(this.nc).toString(16); + this.ncHex = "00000000".substr(0, 8 - hex.length) + hex; + }; + /** + * Generate Digest 'response' value. + */ + DigestAuthentication.prototype.calculateResponse = function (body) { + var ha2; + // HA1 = MD5(A1) = MD5(username:realm:password) + var ha1 = md5_1.default(this.username + ":" + this.realm + ":" + this.password); + if (this.qop === "auth") { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = md5_1.default(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth:" + ha2); + } + else if (this.qop === "auth-int") { + // HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody)) + ha2 = md5_1.default(this.method + ":" + this.uri + ":" + md5_1.default(body ? body : "")); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth-int:" + ha2); + } + else if (this.qop === undefined) { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = md5_1.default(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + ha2); + } + }; + return DigestAuthentication; +}()); +exports.DigestAuthentication = DigestAuthentication; + + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +;(function (root, factory) { + if (true) { + // CommonJS + module.exports = exports = factory(__webpack_require__(21)); + } + else {} +}(this, function (CryptoJS) { + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + return CryptoJS.MD5; + +})); + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +;(function (root, factory) { + if (true) { + // CommonJS + module.exports = exports = factory(); + } + else {} +}(this, function () { + + /** + * CryptoJS core components. + */ + var CryptoJS = CryptoJS || (function (Math, undefined) { + /* + * Local polyfil of Object.create + */ + var create = Object.create || (function () { + function F() {}; + + return function (obj) { + var subtype; + + F.prototype = obj; + + subtype = new F(); + + F.prototype = null; + + return subtype; + }; + }()) + + /** + * CryptoJS namespace. + */ + var C = {}; + + /** + * Library namespace. + */ + var C_lib = C.lib = {}; + + /** + * Base object for prototypal inheritance. + */ + var Base = C_lib.Base = (function () { + + + return { + /** + * Creates a new object that inherits from this object. + * + * @param {Object} overrides Properties to copy into the new object. + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * field: 'value', + * + * method: function () { + * } + * }); + */ + extend: function (overrides) { + // Spawn + var subtype = create(this); + + // Augment + if (overrides) { + subtype.mixIn(overrides); + } + + // Create default initializer + if (!subtype.hasOwnProperty('init') || this.init === subtype.init) { + subtype.init = function () { + subtype.$super.init.apply(this, arguments); + }; + } + + // Initializer's prototype is the subtype object + subtype.init.prototype = subtype; + + // Reference supertype + subtype.$super = this; + + return subtype; + }, + + /** + * Extends this object and runs the init method. + * Arguments to create() will be passed to init(). + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var instance = MyType.create(); + */ + create: function () { + var instance = this.extend(); + instance.init.apply(instance, arguments); + + return instance; + }, + + /** + * Initializes a newly created object. + * Override this method to add some logic when your objects are created. + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * init: function () { + * // ... + * } + * }); + */ + init: function () { + }, + + /** + * Copies properties into this object. + * + * @param {Object} properties The properties to mix in. + * + * @example + * + * MyType.mixIn({ + * field: 'value' + * }); + */ + mixIn: function (properties) { + for (var propertyName in properties) { + if (properties.hasOwnProperty(propertyName)) { + this[propertyName] = properties[propertyName]; + } + } + + // IE won't copy toString using the loop above + if (properties.hasOwnProperty('toString')) { + this.toString = properties.toString; + } + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = instance.clone(); + */ + clone: function () { + return this.init.prototype.extend(this); + } + }; + }()); + + /** + * An array of 32-bit words. + * + * @property {Array} words The array of 32-bit words. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var WordArray = C_lib.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of 32-bit words. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.create(); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 4; + } + }, + + /** + * Converts this word array to a string. + * + * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex + * + * @return {string} The stringified word array. + * + * @example + * + * var string = wordArray + ''; + * var string = wordArray.toString(); + * var string = wordArray.toString(CryptoJS.enc.Utf8); + */ + toString: function (encoder) { + return (encoder || Hex).stringify(this); + }, + + /** + * Concatenates a word array to this word array. + * + * @param {WordArray} wordArray The word array to append. + * + * @return {WordArray} This word array. + * + * @example + * + * wordArray1.concat(wordArray2); + */ + concat: function (wordArray) { + // Shortcuts + var thisWords = this.words; + var thatWords = wordArray.words; + var thisSigBytes = this.sigBytes; + var thatSigBytes = wordArray.sigBytes; + + // Clamp excess bits + this.clamp(); + + // Concat + if (thisSigBytes % 4) { + // Copy one byte at a time + for (var i = 0; i < thatSigBytes; i++) { + var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (var i = 0; i < thatSigBytes; i += 4) { + thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + }, + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp: function () { + // Shortcuts + var words = this.words; + var sigBytes = this.sigBytes; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + }, + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + }, + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random: function (nBytes) { + var words = []; + + var r = (function (m_w) { + var m_w = m_w; + var m_z = 0x3ade68b1; + var mask = 0xffffffff; + + return function () { + m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask; + m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask; + var result = ((m_z << 0x10) + m_w) & mask; + result /= 0x100000000; + result += 0.5; + return result * (Math.random() > .5 ? 1 : -1); + } + }); + + for (var i = 0, rcache; i < nBytes; i += 4) { + var _r = r((rcache || Math.random()) * 0x100000000); + + rcache = _r() * 0x3ade67b7; + words.push((_r() * 0x100000000) | 0); + } + + return new WordArray.init(words, nBytes); + } + }); + + /** + * Encoder namespace. + */ + var C_enc = C.enc = {}; + + /** + * Hex encoding strategy. + */ + var Hex = C_enc.Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var hexChars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse: function (hexStr) { + // Shortcut + var hexStrLength = hexStr.length; + + // Convert + var words = []; + for (var i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray.init(words, hexStrLength / 2); + } + }; + + /** + * Latin1 encoding strategy. + */ + var Latin1 = C_enc.Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var latin1Chars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse: function (latin1Str) { + // Shortcut + var latin1StrLength = latin1Str.length; + + // Convert + var words = []; + for (var i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray.init(words, latin1StrLength); + } + }; + + /** + * UTF-8 encoding strategy. + */ + var Utf8 = C_enc.Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify: function (wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse: function (utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } + }; + + /** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset: function () { + // Initial values + this._data = new WordArray.init(); + this._nDataBytes = 0; + }, + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append: function (data) { + // Convert string to WordArray, else assume WordArray already + if (typeof data == 'string') { + data = Utf8.parse(data); + } + + // Append + this._data.concat(data); + this._nDataBytes += data.sigBytes; + }, + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process: function (doFlush) { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var dataSigBytes = data.sigBytes; + var blockSize = this.blockSize; + var blockSizeBytes = blockSize * 4; + + // Count blocks ready + var nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + var nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (var offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + var processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray.init(processedWords, nBytesReady); + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone._data = this._data.clone(); + + return clone; + }, + + _minBufferSize: 0 + }); + + /** + * Abstract hasher template. + * + * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + */ + cfg: Base.extend(), + + /** + * Initializes a newly created hasher. + * + * @param {Object} cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + init: function (cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Set initial values + this.reset(); + }, + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + }, + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update: function (messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + }, + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + var hash = this._doFinalize(); + + return hash; + }, + + blockSize: 512/32, + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} hasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper: function (hasher) { + return function (message, cfg) { + return new hasher.init(cfg).finalize(message); + }; + }, + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} hasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper: function (hasher) { + return function (message, key) { + return new C_algo.HMAC.init(hasher, key).finalize(message); + }; + } + }); + + /** + * Algorithm namespace. + */ + var C_algo = C.algo = {}; + + return C; + }(Math)); + + + return CryptoJS; + +})); + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var utils_1 = __webpack_require__(16); +/** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + */ +function constructOutgoingResponse(message, options) { + var CRLF = "\r\n"; + if (options.statusCode < 100 || options.statusCode > 699) { + throw new TypeError("Invalid statusCode: " + options.statusCode); + } + var reasonPhrase = options.reasonPhrase ? options.reasonPhrase : utils_1.getReasonPhrase(options.statusCode); + // SIP responses are distinguished from requests by having a Status-Line + // as their start-line. A Status-Line consists of the protocol version + // followed by a numeric Status-Code and its associated textual phrase, + // with each element separated by a single SP character. + // https://tools.ietf.org/html/rfc3261#section-7.2 + var response = "SIP/2.0 " + options.statusCode + " " + reasonPhrase + CRLF; + // One largely non-method-specific guideline for the generation of + // responses is that UASs SHOULD NOT issue a provisional response for a + // non-INVITE request. Rather, UASs SHOULD generate a final response to + // a non-INVITE request as soon as possible. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.1 + if (options.statusCode >= 100 && options.statusCode < 200) { + // TODO + } + // When a 100 (Trying) response is generated, any Timestamp header field + // present in the request MUST be copied into this 100 (Trying) + // response. If there is a delay in generating the response, the UAS + // SHOULD add a delay value into the Timestamp value in the response. + // This value MUST contain the difference between the time of sending of + // the response and receipt of the request, measured in seconds. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.1 + if (options.statusCode === 100) { + // TODO + } + // The From field of the response MUST equal the From header field of + // the request. The Call-ID header field of the response MUST equal the + // Call-ID header field of the request. The CSeq header field of the + // response MUST equal the CSeq field of the request. The Via header + // field values in the response MUST equal the Via header field values + // in the request and MUST maintain the same ordering. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.2 + var fromHeader = "From: " + message.getHeader("From") + CRLF; + var callIdHeader = "Call-ID: " + message.callId + CRLF; + var cSeqHeader = "CSeq: " + message.cseq + " " + message.method + CRLF; + var viaHeaders = message.getHeaders("via").reduce(function (previous, current) { + return previous + "Via: " + current + CRLF; + }, ""); + // If a request contained a To tag in the request, the To header field + // in the response MUST equal that of the request. However, if the To + // header field in the request did not contain a tag, the URI in the To + // header field in the response MUST equal the URI in the To header + // field; additionally, the UAS MUST add a tag to the To header field in + // the response (with the exception of the 100 (Trying) response, in + // which a tag MAY be present). This serves to identify the UAS that is + // responding, possibly resulting in a component of a dialog ID. The + // same tag MUST be used for all responses to that request, both final + // and provisional (again excepting the 100 (Trying)). + // https://tools.ietf.org/html/rfc3261#section-8.2.6.2 + var toHeader = "To: " + message.getHeader("to"); + if (options.statusCode > 100 && !message.parseHeader("to").hasParam("tag")) { + var toTag = options.toTag; + if (!toTag) { + // Stateless UAS Behavior... + // o To header tags MUST be generated for responses in a stateless + // manner - in a manner that will generate the same tag for the + // same request consistently. For information on tag construction + // see Section 19.3. + // https://tools.ietf.org/html/rfc3261#section-8.2.7 + toTag = utils_1.newTag(); // FIXME: newTag() currently generates random tags + } + toHeader += ";tag=" + toTag; + } + toHeader += CRLF; + // FIXME: TODO: needs review... moved to InviteUserAgentServer (as it is specific to that) + // let recordRouteHeaders = ""; + // if (request.method === C.INVITE && statusCode > 100 && statusCode <= 200) { + // recordRouteHeaders = request.getHeaders("record-route").reduce((previous, current) => { + // return previous + "Record-Route: " + current + CRLF; + // }, ""); + // } + // FIXME: TODO: needs review... + var supportedHeader = ""; + if (options.supported) { + supportedHeader = "Supported: " + options.supported.join(", ") + CRLF; + } + // FIXME: TODO: needs review... + var userAgentHeader = ""; + if (options.userAgent) { + userAgentHeader = "User-Agent: " + options.userAgent + CRLF; + } + var extensionHeaders = ""; + if (options.extraHeaders) { + extensionHeaders = options.extraHeaders.reduce(function (previous, current) { + return previous + current.trim() + CRLF; + }, ""); + } + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + // response += recordRouteHeaders; + response += viaHeaders; + response += fromHeader; + response += toHeader; + response += cSeqHeader; + response += callIdHeader; + response += supportedHeader; + response += userAgentHeader; + response += extensionHeaders; + if (options.body) { + response += "Content-Type: " + options.body.contentType + CRLF; + response += "Content-Length: " + utils_1.str_utf8_length(options.body.content) + CRLF + CRLF; + response += options.body.content; + } + else { + response += "Content-Length: " + 0 + CRLF + CRLF; + } + return { message: response }; +} +exports.constructOutgoingResponse = constructOutgoingResponse; + + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var session_1 = __webpack_require__(24); +var timers_1 = __webpack_require__(26); +var transactions_1 = __webpack_require__(27); +var bye_user_agent_client_1 = __webpack_require__(41); +var bye_user_agent_server_1 = __webpack_require__(43); +var info_user_agent_client_1 = __webpack_require__(45); +var info_user_agent_server_1 = __webpack_require__(46); +var notify_user_agent_client_1 = __webpack_require__(47); +var notify_user_agent_server_1 = __webpack_require__(48); +var prack_user_agent_client_1 = __webpack_require__(49); +var prack_user_agent_server_1 = __webpack_require__(50); +var re_invite_user_agent_client_1 = __webpack_require__(51); +var re_invite_user_agent_server_1 = __webpack_require__(52); +var refer_user_agent_client_1 = __webpack_require__(53); +var refer_user_agent_server_1 = __webpack_require__(54); +var dialog_1 = __webpack_require__(4); +var SessionDialog = /** @class */ (function (_super) { + tslib_1.__extends(SessionDialog, _super); + function SessionDialog(initialTransaction, core, state, delegate) { + var _this = _super.call(this, core, state) || this; + _this.initialTransaction = initialTransaction; + /** The state of the offer/answer exchange. */ + _this._signalingState = session_1.SignalingState.Initial; + /** True if waiting for an ACK to the initial transaction 2xx (UAS only). */ + _this.ackWait = false; + _this.delegate = delegate; + if (initialTransaction instanceof transactions_1.InviteServerTransaction) { + // If we're created by an invite server transaction, we're + // going to be waiting for an ACK if are to be confirmed. + _this.ackWait = true; + } + // If we're confirmed upon creation start the retransmitting whatever + // the 2xx final response was that confirmed us into existence. + if (!_this.early) { + _this.start2xxRetransmissionTimer(); + } + _this.signalingStateTransition(initialTransaction.request); + _this.logger = core.loggerFactory.getLogger("sip.invite-dialog"); + _this.logger.log("INVITE dialog " + _this.id + " constructed"); + return _this; + } + SessionDialog.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._signalingState = session_1.SignalingState.Closed; + this._offer = undefined; + this._answer = undefined; + if (this.invite2xxTimer) { + clearTimeout(this.invite2xxTimer); + this.invite2xxTimer = undefined; + } + // The UAS MUST still respond to any pending requests received for that + // dialog. It is RECOMMENDED that a 487 (Request Terminated) response + // be generated to those pending requests. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + // TODO: + // this.userAgentServers.forEach((uas) => uas.reply(487)); + this.logger.log("INVITE dialog " + this.id + " destroyed"); + }; + Object.defineProperty(SessionDialog.prototype, "sessionState", { + // FIXME: Need real state machine + get: function () { + if (this.early) { + return session_1.SessionState.Early; + } + else if (this.ackWait) { + return session_1.SessionState.AckWait; + } + else if (this._signalingState === session_1.SignalingState.Closed) { + return session_1.SessionState.Terminated; + } + else { + return session_1.SessionState.Confirmed; + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "signalingState", { + /** The state of the offer/answer exchange. */ + get: function () { + return this._signalingState; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "offer", { + /** The current offer. Undefined unless signaling state HaveLocalOffer, HaveRemoteOffer, of Stable. */ + get: function () { + return this._offer; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "answer", { + /** The current answer. Undefined unless signaling state Stable. */ + get: function () { + return this._answer; + }, + enumerable: true, + configurable: true + }); + /** Confirm the dialog. Only matters if dialog is currently early. */ + SessionDialog.prototype.confirm = function () { + // When we're confirmed start the retransmitting whatever + // the 2xx final response that may have confirmed us. + if (this.early) { + this.start2xxRetransmissionTimer(); + } + _super.prototype.confirm.call(this); + }; + /** Re-confirm the dialog. Only matters if handling re-INVITE request. */ + SessionDialog.prototype.reConfirm = function () { + // When we're confirmed start the retransmitting whatever + // the 2xx final response that may have confirmed us. + if (this.reinviteUserAgentServer) { + this.startReInvite2xxRetransmissionTimer(); + } + }; + /** + * The UAC core MUST generate an ACK request for each 2xx received from + * the transaction layer. The header fields of the ACK are constructed + * in the same way as for any request sent within a dialog (see Section + * 12) with the exception of the CSeq and the header fields related to + * authentication. The sequence number of the CSeq header field MUST be + * the same as the INVITE being acknowledged, but the CSeq method MUST + * be ACK. The ACK MUST contain the same credentials as the INVITE. If + * the 2xx contains an offer (based on the rules above), the ACK MUST + * carry an answer in its body. If the offer in the 2xx response is not + * acceptable, the UAC core MUST generate a valid answer in the ACK and + * then send a BYE immediately. + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + * @param options ACK options bucket. + */ + SessionDialog.prototype.ack = function (options) { + if (options === void 0) { options = {}; } + this.logger.log("INVITE dialog " + this.id + " sending ACK request"); + var transaction; + if (this.reinviteUserAgentClient) { + // We're sending ACK for a re-INVITE + if (!(this.reinviteUserAgentClient.transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + transaction = this.reinviteUserAgentClient.transaction; + this.reinviteUserAgentClient = undefined; + } + else { + // We're sending ACK for the initial INVITE + if (!(this.initialTransaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Initial transaction not instance of InviteClientTransaction."); + } + transaction = this.initialTransaction; + } + options.cseq = transaction.request.cseq; // ACK cseq is INVITE cseq + var message = this.createOutgoingRequestMessage(messages_1.C.ACK, options); + transaction.ackResponse(message); // See InviteClientTransaction for details. + this.signalingStateTransition(message); + return { message: message }; + }; + /** + * Terminating a Session + * + * This section describes the procedures for terminating a session + * established by SIP. The state of the session and the state of the + * dialog are very closely related. When a session is initiated with an + * INVITE, each 1xx or 2xx response from a distinct UAS creates a + * dialog, and if that response completes the offer/answer exchange, it + * also creates a session. As a result, each session is "associated" + * with a single dialog - the one which resulted in its creation. If an + * initial INVITE generates a non-2xx final response, that terminates + * all sessions (if any) and all dialogs (if any) that were created + * through responses to the request. By virtue of completing the + * transaction, a non-2xx final response also prevents further sessions + * from being created as a result of the INVITE. The BYE request is + * used to terminate a specific session or attempted session. In this + * case, the specific session is the one with the peer UA on the other + * side of the dialog. When a BYE is received on a dialog, any session + * associated with that dialog SHOULD terminate. A UA MUST NOT send a + * BYE outside of a dialog. The caller's UA MAY send a BYE for either + * confirmed or early dialogs, and the callee's UA MAY send a BYE on + * confirmed dialogs, but MUST NOT send a BYE on early dialogs. + * + * However, the callee's UA MUST NOT send a BYE on a confirmed dialog + * until it has received an ACK for its 2xx response or until the server + * transaction times out. If no SIP extensions have defined other + * application layer states associated with the dialog, the BYE also + * terminates the dialog. + * + * https://tools.ietf.org/html/rfc3261#section-15 + * FIXME: Make these proper Exceptions... + * @param options BYE options bucket. + * @throws {Error} If callee's UA attempts a BYE on an early dialog. + * @throws {Error} If callee's UA attempts a BYE on a confirmed dialog + * while it's waiting on the ACK for its 2xx response. + */ + SessionDialog.prototype.bye = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending BYE request"); + // The caller's UA MAY send a BYE for either + // confirmed or early dialogs, and the callee's UA MAY send a BYE on + // confirmed dialogs, but MUST NOT send a BYE on early dialogs. + // + // However, the callee's UA MUST NOT send a BYE on a confirmed dialog + // until it has received an ACK for its 2xx response or until the server + // transaction times out. + // https://tools.ietf.org/html/rfc3261#section-15 + if (this.initialTransaction instanceof transactions_1.InviteServerTransaction) { + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("UAS MUST NOT send a BYE on early dialogs."); + } + if (this.ackWait && this.initialTransaction.state !== transactions_1.TransactionState.Terminated) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("UAS MUST NOT send a BYE on a confirmed dialog " + + "until it has received an ACK for its 2xx response " + + "or until the server transaction times out."); + } + } + // A BYE request is constructed as would any other request within a + // dialog, as described in Section 12. + // + // Once the BYE is constructed, the UAC core creates a new non-INVITE + // client transaction, and passes it the BYE request. The UAC MUST + // consider the session terminated (and therefore stop sending or + // listening for media) as soon as the BYE request is passed to the + // client transaction. If the response for the BYE is a 481 + // (Call/Transaction Does Not Exist) or a 408 (Request Timeout) or no + // response at all is received for the BYE (that is, a timeout is + // returned by the client transaction), the UAC MUST consider the + // session and the dialog terminated. + // https://tools.ietf.org/html/rfc3261#section-15.1.1 + return new bye_user_agent_client_1.ByeUserAgentClient(this, delegate, options); + }; + /** + * An INFO request can be associated with an Info Package (see + * Section 5), or associated with a legacy INFO usage (see Section 2). + * + * The construction of the INFO request is the same as any other + * non-target refresh request within an existing invite dialog usage as + * described in Section 12.2 of RFC 3261. + * https://tools.ietf.org/html/rfc6086#section-4.2.1 + * @param options Options bucket. + */ + SessionDialog.prototype.info = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending INFO request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + return new info_user_agent_client_1.InfoUserAgentClient(this, delegate, options); + }; + /** + * Modifying an Existing Session + * + * A successful INVITE request (see Section 13) establishes both a + * dialog between two user agents and a session using the offer-answer + * model. Section 12 explains how to modify an existing dialog using a + * target refresh request (for example, changing the remote target URI + * of the dialog). This section describes how to modify the actual + * session. This modification can involve changing addresses or ports, + * adding a media stream, deleting a media stream, and so on. This is + * accomplished by sending a new INVITE request within the same dialog + * that established the session. An INVITE request sent within an + * existing dialog is known as a re-INVITE. + * + * Note that a single re-INVITE can modify the dialog and the + * parameters of the session at the same time. + * + * Either the caller or callee can modify an existing session. + * https://tools.ietf.org/html/rfc3261#section-14 + * @param options Options bucket + */ + SessionDialog.prototype.invite = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending INVITE request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + // Note that a UAC MUST NOT initiate a new INVITE transaction within a + // dialog while another INVITE transaction is in progress in either + // direction. + // + // 1. If there is an ongoing INVITE client transaction, the TU MUST + // wait until the transaction reaches the completed or terminated + // state before initiating the new INVITE. + // + // 2. If there is an ongoing INVITE server transaction, the TU MUST + // wait until the transaction reaches the confirmed or terminated + // state before initiating the new INVITE. + // + // However, a UA MAY initiate a regular transaction while an INVITE + // transaction is in progress. A UA MAY also initiate an INVITE + // transaction while a regular transaction is in progress. + // https://tools.ietf.org/html/rfc3261#section-14.1 + if (this.reinviteUserAgentClient) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("There is an ongoing re-INVITE client transaction."); + } + if (this.reinviteUserAgentServer) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("There is an ongoing re-INVITE server transaction."); + } + return new re_invite_user_agent_client_1.ReInviteUserAgentClient(this, delegate, options); + }; + /** + * The NOTIFY mechanism defined in [2] MUST be used to inform the agent + * sending the REFER of the status of the reference. + * https://tools.ietf.org/html/rfc3515#section-2.4.4 + * @param options Options bucket. + */ + SessionDialog.prototype.notify = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending NOTIFY request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + return new notify_user_agent_client_1.NotifyUserAgentClient(this, delegate, options); + }; + /** + * Assuming the response is to be transmitted reliably, the UAC MUST + * create a new request with method PRACK. This request is sent within + * the dialog associated with the provisional response (indeed, the + * provisional response may have created the dialog). PRACK requests + * MAY contain bodies, which are interpreted according to their type and + * disposition. + * https://tools.ietf.org/html/rfc3262#section-4 + * @param options Options bucket. + */ + SessionDialog.prototype.prack = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending PRACK request"); + return new prack_user_agent_client_1.PrackUserAgentClient(this, delegate, options); + }; + /** + * REFER is a SIP request and is constructed as defined in [1]. A REFER + * request MUST contain exactly one Refer-To header field value. + * https://tools.ietf.org/html/rfc3515#section-2.4.1 + * @param options Options bucket. + */ + SessionDialog.prototype.refer = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending REFER request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + // FIXME: TODO: Validate Refer-To header field value. + return new refer_user_agent_client_1.ReferUserAgentClient(this, delegate, options); + }; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + SessionDialog.prototype.receiveRequest = function (message) { + this.logger.log("INVITE dialog " + this.id + " received " + message.method + " request"); + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + if (message.method === messages_1.C.ACK) { + // If ackWait is true, then this is the ACK to the initial INVITE, + // otherwise this is an ACK to an in dialog INVITE. In either case, + // guard to make sure the sequence number of the ACK matches the INVITE. + if (this.ackWait) { + if (this.initialTransaction instanceof transactions_1.InviteClientTransaction) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + if (this.initialTransaction.request.cseq !== message.cseq) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + this.ackWait = false; + } + else { + if (!this.reinviteUserAgentServer) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + if (this.reinviteUserAgentServer.transaction.request.cseq !== message.cseq) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + this.reinviteUserAgentServer = undefined; + } + this.signalingStateTransition(message); + if (this.delegate && this.delegate.onAck) { + this.delegate.onAck({ message: message }); + } + return; + } + // Request within a dialog out of sequence guard. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.sequenceGuard(message)) { + this.logger.log("INVITE dialog " + this.id + " rejected out of order " + message.method + " request."); + return; + } + if (message.method === messages_1.C.INVITE) { + // A UAS that receives a second INVITE before it sends the final + // response to a first INVITE with a lower CSeq sequence number on the + // same dialog MUST return a 500 (Server Internal Error) response to the + // second INVITE and MUST include a Retry-After header field with a + // randomly chosen value of between 0 and 10 seconds. + // https://tools.ietf.org/html/rfc3261#section-14.2 + if (this.reinviteUserAgentServer) { + // https://tools.ietf.org/html/rfc3261#section-20.33 + var retryAfter = Math.floor((Math.random() * 10)) + 1; + var extraHeaders = ["Retry-After: " + retryAfter]; + this.core.replyStateless(message, { statusCode: 500, extraHeaders: extraHeaders }); + return; + } + // A UAS that receives an INVITE on a dialog while an INVITE it had sent + // on that dialog is in progress MUST return a 491 (Request Pending) + // response to the received INVITE. + // https://tools.ietf.org/html/rfc3261#section-14.2 + if (this.reinviteUserAgentClient) { + this.core.replyStateless(message, { statusCode: 491 }); + return; + } + } + // Request within a dialog common processing. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + _super.prototype.receiveRequest.call(this, message); + // Requests within a dialog MAY contain Record-Route and Contact header + // fields. However, these requests do not cause the dialog's route set + // to be modified, although they may modify the remote target URI. + // Specifically, requests that are not target refresh requests do not + // modify the dialog's remote target URI, and requests that are target + // refresh requests do. For dialogs that have been established with an + // INVITE, the only target refresh request defined is re-INVITE (see + // Section 14). Other extensions may define different target refresh + // requests for dialogs established in other ways. + // + // Note that an ACK is NOT a target refresh request. + // + // Target refresh requests only update the dialog's remote target URI, + // and not the route set formed from the Record-Route. Updating the + // latter would introduce severe backwards compatibility problems with + // RFC 2543-compliant systems. + // https://tools.ietf.org/html/rfc3261#section-15 + if (message.method === messages_1.C.INVITE) { + // FIXME: parser needs to be typed... + var contact = message.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + this.dialogState.remoteTarget = contact.uri; + } + // Switch on method and then delegate. + switch (message.method) { + case messages_1.C.BYE: + // A UAS core receiving a BYE request for an existing dialog MUST follow + // the procedures of Section 12.2.2 to process the request. Once done, + // the UAS SHOULD terminate the session (and therefore stop sending and + // listening for media). The only case where it can elect not to are + // multicast sessions, where participation is possible even if the other + // participant in the dialog has terminated its involvement in the + // session. Whether or not it ends its participation on the session, + // the UAS core MUST generate a 2xx response to the BYE, and MUST pass + // that to the server transaction for transmission. + // + // The UAS MUST still respond to any pending requests received for that + // dialog. It is RECOMMENDED that a 487 (Request Terminated) response + // be generated to those pending requests. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + { + var uas = new bye_user_agent_server_1.ByeUserAgentServer(this, message); + this.delegate && this.delegate.onBye ? + this.delegate.onBye(uas) : + uas.accept(); + this.dispose(); + } + break; + case messages_1.C.INFO: + // If a UA receives an INFO request associated with an Info Package that + // the UA has not indicated willingness to receive, the UA MUST send a + // 469 (Bad Info Package) response (see Section 11.6), which contains a + // Recv-Info header field with Info Packages for which the UA is willing + // to receive INFO requests. + { + var uas = new info_user_agent_server_1.InfoUserAgentServer(this, message); + this.delegate && this.delegate.onInfo ? + this.delegate.onInfo(uas) : + uas.reject({ + statusCode: 469, + extraHeaders: ["Recv-Info :"] + }); + } + break; + case messages_1.C.INVITE: + // If the new session description is not acceptable, the UAS can reject + // it by returning a 488 (Not Acceptable Here) response for the re- + // INVITE. This response SHOULD include a Warning header field. + // https://tools.ietf.org/html/rfc3261#section-14.2 + { + var uas = new re_invite_user_agent_server_1.ReInviteUserAgentServer(this, message); + this.delegate && this.delegate.onInvite ? + this.delegate.onInvite(uas) : + uas.reject({ statusCode: 488 }); // TODO: Warning header field. + } + break; + case messages_1.C.NOTIFY: + // https://tools.ietf.org/html/rfc3515#section-2.4.4 + { + var uas = new notify_user_agent_server_1.NotifyUserAgentServer(this, message); + this.delegate && this.delegate.onNotify ? + this.delegate.onNotify(uas) : + uas.accept(); + } + break; + case messages_1.C.PRACK: + // https://tools.ietf.org/html/rfc3262#section-4 + { + var uas = new prack_user_agent_server_1.PrackUserAgentServer(this, message); + this.delegate && this.delegate.onPrack ? + this.delegate.onPrack(uas) : + uas.accept(); + } + break; + case messages_1.C.REFER: + // https://tools.ietf.org/html/rfc3515#section-2.4.2 + { + var uas = new refer_user_agent_server_1.ReferUserAgentServer(this, message); + this.delegate && this.delegate.onRefer ? + this.delegate.onRefer(uas) : + uas.reject(); + } + break; + default: + { + this.logger.log("INVITE dialog " + this.id + " received unimplemented " + message.method + " request"); + this.core.replyStateless(message, { statusCode: 501 }); + } + break; + } + }; + SessionDialog.prototype.reliableSequenceGuard = function (message) { + var statusCode = message.statusCode; + if (!statusCode) { + throw new Error("Status code undefined"); + } + if (statusCode > 100 && statusCode < 200) { + // If a provisional response is received for an initial request, and + // that response contains a Require header field containing the option + // tag 100rel, the response is to be sent reliably. If the response is + // a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be + // ignored, and the procedures below MUST NOT be used. + // https://tools.ietf.org/html/rfc3262#section-4 + var requireHeader = message.getHeader("require"); + var rseqHeader = message.getHeader("rseq"); + var rseq = requireHeader && requireHeader.includes("100rel") && rseqHeader ? Number(rseqHeader) : undefined; + if (rseq) { + // Handling of subsequent reliable provisional responses for the same + // initial request follows the same rules as above, with the following + // difference: reliable provisional responses are guaranteed to be in + // order. As a result, if the UAC receives another reliable provisional + // response to the same request, and its RSeq value is not one higher + // than the value of the sequence number, that response MUST NOT be + // acknowledged with a PRACK, and MUST NOT be processed further by the + // UAC. An implementation MAY discard the response, or MAY cache the + // response in the hopes of receiving the missing responses. + // https://tools.ietf.org/html/rfc3262#section-4 + if (this.rseq && this.rseq + 1 !== rseq) { + return false; + } + // Once a reliable provisional response is received, retransmissions of + // that response MUST be discarded. A response is a retransmission when + // its dialog ID, CSeq, and RSeq match the original response. The UAC + // MUST maintain a sequence number that indicates the most recently + // received in-order reliable provisional response for the initial + // request. This sequence number MUST be maintained until a final + // response is received for the initial request. Its value MUST be + // initialized to the RSeq header field in the first reliable + // provisional response received for the initial request. + // https://tools.ietf.org/html/rfc3262#section-4 + if (!this.rseq) { + this.rseq = rseq; + } + } + } + return true; + }; + /** + * Update the signaling state of the dialog. + * @param message The message to base the update off of. + */ + SessionDialog.prototype.signalingStateTransition = function (message) { + var body = messages_1.getBody(message); + // No body, no session. No, woman, no cry. + if (!body || body.contentDisposition !== "session") { + return; + } + // We're in UAS role, receiving incoming request with session description + if (message instanceof messages_1.IncomingRequestMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveRemoteOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.HaveRemoteOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAC role, receiving incoming response with session description + if (message instanceof messages_1.IncomingResponseMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveRemoteOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.HaveRemoteOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAC role, sending outgoing request with session description + if (message instanceof messages_1.OutgoingRequestMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveLocalOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.HaveRemoteOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAS role, sending outgoing response with session description + if (messages_1.isBody(message)) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveLocalOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.HaveRemoteOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + }; + SessionDialog.prototype.start2xxRetransmissionTimer = function () { + var _this = this; + if (this.initialTransaction instanceof transactions_1.InviteServerTransaction) { + var transaction_1 = this.initialTransaction; + // Once the response has been constructed, it is passed to the INVITE + // server transaction. In order to ensure reliable end-to-end + // transport of the response, it is necessary to periodically pass + // the response directly to the transport until the ACK arrives. The + // 2xx response is passed to the transport with an interval that + // starts at T1 seconds and doubles for each retransmission until it + // reaches T2 seconds (T1 and T2 are defined in Section 17). + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + var timeout_1 = timers_1.Timers.T1; + var retransmission_1 = function () { + if (!_this.ackWait) { + _this.invite2xxTimer = undefined; + return; + } + _this.logger.log("No ACK for 2xx response received, attempting retransmission"); + transaction_1.retransmitAcceptedResponse(); + timeout_1 = Math.min(timeout_1 * 2, timers_1.Timers.T2); + _this.invite2xxTimer = setTimeout(retransmission_1, timeout_1); + }; + this.invite2xxTimer = setTimeout(retransmission_1, timeout_1); + // If the server retransmits the 2xx response for 64*T1 seconds without + // receiving an ACK, the dialog is confirmed, but the session SHOULD be + // terminated. This is accomplished with a BYE, as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + var stateChanged_1 = function () { + if (transaction_1.state === transactions_1.TransactionState.Terminated) { + transaction_1.removeListener("stateChanged", stateChanged_1); + if (_this.invite2xxTimer) { + clearTimeout(_this.invite2xxTimer); + _this.invite2xxTimer = undefined; + } + if (_this.ackWait) { + if (_this.delegate && _this.delegate.onAckTimeout) { + _this.delegate.onAckTimeout(); + } + else { + _this.bye(); + } + } + } + }; + transaction_1.addListener("stateChanged", stateChanged_1); + } + }; + // FIXME: Refactor + SessionDialog.prototype.startReInvite2xxRetransmissionTimer = function () { + var _this = this; + if (this.reinviteUserAgentServer && this.reinviteUserAgentServer.transaction instanceof transactions_1.InviteServerTransaction) { + var transaction_2 = this.reinviteUserAgentServer.transaction; + // Once the response has been constructed, it is passed to the INVITE + // server transaction. In order to ensure reliable end-to-end + // transport of the response, it is necessary to periodically pass + // the response directly to the transport until the ACK arrives. The + // 2xx response is passed to the transport with an interval that + // starts at T1 seconds and doubles for each retransmission until it + // reaches T2 seconds (T1 and T2 are defined in Section 17). + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + var timeout_2 = timers_1.Timers.T1; + var retransmission_2 = function () { + if (!_this.reinviteUserAgentServer) { + _this.invite2xxTimer = undefined; + return; + } + _this.logger.log("No ACK for 2xx response received, attempting retransmission"); + transaction_2.retransmitAcceptedResponse(); + timeout_2 = Math.min(timeout_2 * 2, timers_1.Timers.T2); + _this.invite2xxTimer = setTimeout(retransmission_2, timeout_2); + }; + this.invite2xxTimer = setTimeout(retransmission_2, timeout_2); + // If the server retransmits the 2xx response for 64*T1 seconds without + // receiving an ACK, the dialog is confirmed, but the session SHOULD be + // terminated. This is accomplished with a BYE, as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + var stateChanged_2 = function () { + if (transaction_2.state === transactions_1.TransactionState.Terminated) { + transaction_2.removeListener("stateChanged", stateChanged_2); + if (_this.invite2xxTimer) { + clearTimeout(_this.invite2xxTimer); + _this.invite2xxTimer = undefined; + } + if (_this.reinviteUserAgentServer) { + // FIXME: TODO: What to do here + } + } + }; + transaction_2.addListener("stateChanged", stateChanged_2); + } + }; + return SessionDialog; +}(dialog_1.Dialog)); +exports.SessionDialog = SessionDialog; + + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(25), exports); + + +/***/ }), +/* 25 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Session state. + * https://tools.ietf.org/html/rfc3261#section-13 + */ +var SessionState; +(function (SessionState) { + SessionState["Initial"] = "Initial"; + SessionState["Early"] = "Early"; + SessionState["AckWait"] = "AckWait"; + SessionState["Confirmed"] = "Confirmed"; + SessionState["Terminated"] = "Terminated"; +})(SessionState = exports.SessionState || (exports.SessionState = {})); +/** + * Offer/Answer State + * + * Offer Answer RFC Ini Est Early + * ------------------------------------------------------------------- + * 1. INVITE Req. 2xx INVITE Resp. RFC 3261 Y Y N + * 2. 2xx INVITE Resp. ACK Req. RFC 3261 Y Y N + * 3. INVITE Req. 1xx-rel INVITE Resp. RFC 3262 Y Y N + * 4. 1xx-rel INVITE Resp. PRACK Req. RFC 3262 Y Y N + * 5. PRACK Req. 200 PRACK Resp. RFC 3262 N Y Y + * 6. UPDATE Req. 2xx UPDATE Resp. RFC 3311 N Y Y + * + * Table 1: Summary of SIP Usage of the Offer/Answer Model + * https://tools.ietf.org/html/rfc6337#section-2.2 + */ +var SignalingState; +(function (SignalingState) { + SignalingState["Initial"] = "Initial"; + SignalingState["HaveLocalOffer"] = "HaveLocalOffer"; + SignalingState["HaveRemoteOffer"] = "HaveRemoteOffer"; + SignalingState["Stable"] = "Stable"; + SignalingState["Closed"] = "Closed"; +})(SignalingState = exports.SignalingState || (exports.SignalingState = {})); + + +/***/ }), +/* 26 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var T1 = 500; +var T2 = 4000; +var T4 = 5000; +exports.Timers = { + T1: T1, + T2: T2, + T4: T4, + TIMER_B: 64 * T1, + TIMER_D: 0 * T1, + TIMER_F: 64 * T1, + TIMER_H: 64 * T1, + TIMER_I: 0 * T4, + TIMER_J: 0 * T1, + TIMER_K: 0 * T4, + TIMER_L: 64 * T1, + TIMER_M: 64 * T1, + TIMER_N: 64 * T1, + PROVISIONAL_RESPONSE_INTERVAL: 60000 // See RFC 3261 Section 13.3.1.1 +}; + + +/***/ }), +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(28), exports); +tslib_1.__exportStar(__webpack_require__(35), exports); +tslib_1.__exportStar(__webpack_require__(37), exports); +tslib_1.__exportStar(__webpack_require__(39), exports); +tslib_1.__exportStar(__webpack_require__(40), exports); +tslib_1.__exportStar(__webpack_require__(35), exports); +tslib_1.__exportStar(__webpack_require__(38), exports); +tslib_1.__exportStar(__webpack_require__(36), exports); +tslib_1.__exportStar(__webpack_require__(29), exports); + + +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transaction_1 = __webpack_require__(29); +/** + * Client Transaction + * + * The client transaction provides its functionality through the + * maintenance of a state machine. + * + * The TU communicates with the client transaction through a simple + * interface. When the TU wishes to initiate a new transaction, it + * creates a client transaction and passes it the SIP request to send + * and an IP address, port, and transport to which to send it. The + * client transaction begins execution of its state machine. Valid + * responses are passed up to the TU from the client transaction. + * https://tools.ietf.org/html/rfc3261#section-17.1 + */ +var ClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(ClientTransaction, _super); + function ClientTransaction(_request, transport, user, state, loggerCategory) { + var _this = _super.call(this, transport, user, ClientTransaction.makeId(_request), state, loggerCategory) || this; + _this._request = _request; + _this.user = user; + // The Via header field indicates the transport used for the transaction + // and identifies the location where the response is to be sent. A Via + // header field value is added only after the transport that will be + // used to reach the next hop has been selected (which may involve the + // usage of the procedures in [4]). + // https://tools.ietf.org/html/rfc3261#section-8.1.1.7 + // FIXME: Transport's server property is not typed (as of writing this). + var scheme = transport.server && transport.server.scheme ? transport.server.scheme : undefined; + _request.setViaHeader(_this.id, scheme); + return _this; + } + ClientTransaction.makeId = function (request) { + if (request.method === "CANCEL") { + if (!request.branch) { + throw new Error("Outgoing CANCEL request without a branch."); + } + return request.branch; + } + else { + return "z9hG4bK" + Math.floor(Math.random() * 10000000); + } + }; + Object.defineProperty(ClientTransaction.prototype, "request", { + /** The outgoing request the transaction handling. */ + get: function () { + return this._request; + }, + enumerable: true, + configurable: true + }); + /** + * A 408 to non-INVITE will always arrive too late to be useful ([3]), + * The client already has full knowledge of the timeout. The only + * information this message would convey is whether or not the server + * believed the transaction timed out. However, with the current design + * of the NIT, a client cannot do anything with this knowledge. Thus, + * the 408 is simply wasting network resources and contributes to the + * response bombardment illustrated in [3]. + * https://tools.ietf.org/html/rfc4320#section-4.1 + */ + ClientTransaction.prototype.onRequestTimeout = function () { + if (this.user.onRequestTimeout) { + this.user.onRequestTimeout(); + } + }; + return ClientTransaction; +}(transaction_1.Transaction)); +exports.ClientTransaction = ClientTransaction; + + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var exceptions_1 = __webpack_require__(31); +/** + * Transaction + * + * SIP is a transactional protocol: interactions between components take + * place in a series of independent message exchanges. Specifically, a + * SIP transaction consists of a single request and any responses to + * that request, which include zero or more provisional responses and + * one or more final responses. In the case of a transaction where the + * request was an INVITE (known as an INVITE transaction), the + * transaction also includes the ACK only if the final response was not + * a 2xx response. If the response was a 2xx, the ACK is not considered + * part of the transaction. + * https://tools.ietf.org/html/rfc3261#section-17 + */ +var Transaction = /** @class */ (function (_super) { + tslib_1.__extends(Transaction, _super); + function Transaction(_transport, _user, _id, _state, loggerCategory) { + var _this = _super.call(this) || this; + _this._transport = _transport; + _this._user = _user; + _this._id = _id; + _this._state = _state; + _this.logger = _user.loggerFactory.getLogger(loggerCategory, _id); + _this.logger.debug("Constructing " + _this.typeToString() + " with id " + _this.id + "."); + return _this; + } + /** + * Destructor. + * Once the transaction is in the "terminated" state, it is destroyed + * immediately and there is no need to call `dispose`. However, if a + * transaction needs to be ended prematurely, the transaction user may + * do so by calling this method (for example, perhaps the UA is shutting down). + * No state transition will occur upon calling this method, all outstanding + * transmission timers will be cancelled, and use of the transaction after + * calling `dispose` is undefined. + */ + Transaction.prototype.dispose = function () { + this.logger.debug("Destroyed " + this.typeToString() + " with id " + this.id + "."); + }; + Object.defineProperty(Transaction.prototype, "id", { + /** Transaction id. */ + get: function () { + return this._id; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + throw new Error("Invalid kind."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "state", { + /** Transaction state. */ + get: function () { + return this._state; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "transport", { + /** Transaction transport. */ + get: function () { + return this._transport; + }, + enumerable: true, + configurable: true + }); + Transaction.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + Transaction.prototype.logTransportError = function (error, message) { + this.logger.error(error.message); + this.logger.error("Transport error occurred in " + this.typeToString() + " with id " + this.id + "."); + this.logger.error(message); + }; + /** + * Pass message to transport for transmission. If transport fails, + * the transaction user is notified by callback to onTransportError(). + * @throws {TransportError} If transport fails. + */ + Transaction.prototype.send = function (message) { + var _this = this; + return this.transport.send(message).catch(function (error) { + // FIXME: Transport is not, yet, typed and it is not clear + // yet what send() may or may not send our way. So for now, + // make sure we convert it to a TransportError if need be. + if (error instanceof exceptions_1.TransportError) { + _this.onTransportError(error); + return; + } + var transportError; + if (error && typeof error.message === "string") { + transportError = new exceptions_1.TransportError(error.message); + } + else { + transportError = new exceptions_1.TransportError(); + } + _this.onTransportError(transportError); + throw transportError; + }); + }; + Transaction.prototype.setState = function (state) { + this.logger.debug("State change to \"" + state + "\" on " + this.typeToString() + " with id " + this.id + "."); + this._state = state; + if (this._user.onStateChange) { + this._user.onStateChange(state); + } + this.emit("stateChanged"); + }; + Transaction.prototype.typeToString = function () { + return "UnknownType"; + }; + return Transaction; +}(events_1.EventEmitter)); +exports.Transaction = Transaction; + + +/***/ }), +/* 30 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +var R = typeof Reflect === 'object' ? Reflect : null +var ReflectApply = R && typeof R.apply === 'function' + ? R.apply + : function ReflectApply(target, receiver, args) { + return Function.prototype.apply.call(target, receiver, args); + } + +var ReflectOwnKeys +if (R && typeof R.ownKeys === 'function') { + ReflectOwnKeys = R.ownKeys +} else if (Object.getOwnPropertySymbols) { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); + }; +} else { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); + }; +} + +function ProcessEmitWarning(warning) { + if (console && console.warn) console.warn(warning); +} + +var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { + return value !== value; +} + +function EventEmitter() { + EventEmitter.init.call(this); +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); + } + defaultMaxListeners = arg; + } +}); + +EventEmitter.init = function() { + + if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +}; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); + } + this._maxListeners = n; + return this; +}; + +function $getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return $getMaxListeners(this); +}; + +EventEmitter.prototype.emit = function emit(type) { + var args = []; + for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); + var doError = (type === 'error'); + + var events = this._events; + if (events !== undefined) + doError = (doError && events.error === undefined); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + var er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + // At least give some kind of context to the user + var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); + err.context = er; + throw err; // Unhandled 'error' event + } + + var handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + ReflectApply(handler, this, args); + } else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + ReflectApply(listeners[i], this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = $getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' ' + String(type) + ' listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + ProcessEmitWarning(w); + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + var args = []; + for (var i = 0; i < arguments.length; i++) args.push(arguments[i]); + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + ReflectApply(this.listener, this.target, args); + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + + events = this._events; + if (events === undefined) + return this; + + list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (events === undefined) + return this; + + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = Object.keys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (events === undefined) + return []; + + var evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events !== undefined) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + + +/***/ }), +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(32), exports); +tslib_1.__exportStar(__webpack_require__(33), exports); +tslib_1.__exportStar(__webpack_require__(34), exports); + + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +/** + * An Exception is considered a condition that a reasonable application may wish to catch. + * An Error indicates serious problems that a reasonable application should not try to catch. + * @public + */ +var Exception = /** @class */ (function (_super) { + tslib_1.__extends(Exception, _super); + function Exception(message) { + var _newTarget = this.constructor; + var _this = _super.call(this, message) || this; + Object.setPrototypeOf(_this, _newTarget.prototype); // restore prototype chain + return _this; + } + return Exception; +}(Error)); +exports.Exception = Exception; + + +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var exception_1 = __webpack_require__(32); +/** + * Indicates that the operation could not be completed given the current transaction state. + * @public + */ +var TransactionStateError = /** @class */ (function (_super) { + tslib_1.__extends(TransactionStateError, _super); + function TransactionStateError(message) { + return _super.call(this, message ? message : "Transaction state error.") || this; + } + return TransactionStateError; +}(exception_1.Exception)); +exports.TransactionStateError = TransactionStateError; + + +/***/ }), +/* 34 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var exception_1 = __webpack_require__(32); +/** + * Transport error. + * @public + */ +var TransportError = /** @class */ (function (_super) { + tslib_1.__extends(TransportError, _super); + function TransportError(message) { + return _super.call(this, message ? message : "Unspecified transport error.") || this; + } + return TransportError; +}(exception_1.Exception)); +exports.TransportError = TransportError; + + +/***/ }), +/* 35 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var timers_1 = __webpack_require__(26); +var client_transaction_1 = __webpack_require__(28); +var transaction_state_1 = __webpack_require__(36); +/** + * INVITE Client Transaction + * + * The INVITE transaction consists of a three-way handshake. The client + * transaction sends an INVITE, the server transaction sends responses, + * and the client transaction sends an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + */ +var InviteClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(InviteClientTransaction, _super); + /** + * Constructor. + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + * @param request The outgoing INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + function InviteClientTransaction(request, transport, user) { + var _this = _super.call(this, request, transport, user, transaction_state_1.TransactionState.Calling, "sip.transaction.ict") || this; + /** + * Map of 2xx to-tag => ACK. + * If value is not undefined, value is the ACK which was sent. + * If key exists but value is undefined, a 2xx was received but the ACK not yet sent. + * Otherwise, a 2xx was not (yet) received for this transaction. + */ + _this.ackRetransmissionCache = new Map(); + // FIXME: Timer A for unreliable transport not implemented + // + // If an unreliable transport is being used, the client transaction + // MUST start timer A with a value of T1. If a reliable transport is being used, + // the client transaction SHOULD NOT start timer A (Timer A controls request retransmissions). + // For any transport, the client transaction MUST start timer B with a value + // of 64*T1 seconds (Timer B controls transaction timeouts). + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + // + // While not spelled out in the RFC, Timer B is the maximum amount of time that a sender + // will wait for an INVITE message to be acknowledged (a SIP response message is received). + // So Timer B should be cleared when the transaction state proceeds from "Calling". + _this.B = setTimeout(function () { return _this.timer_B(); }, timers_1.Timers.TIMER_B); + _this.send(request.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send initial outgoing request."); + }); + return _this; + } + /** + * Destructor. + */ + InviteClientTransaction.prototype.dispose = function () { + if (this.B) { + clearTimeout(this.B); + this.B = undefined; + } + if (this.D) { + clearTimeout(this.D); + this.D = undefined; + } + if (this.M) { + clearTimeout(this.M); + this.M = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(InviteClientTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "ict"; + }, + enumerable: true, + configurable: true + }); + /** + * ACK a 2xx final response. + * + * The transaction includes the ACK only if the final response was not a 2xx response (the + * transaction will generate and send the ACK to the transport automagically). If the + * final response was a 2xx, the ACK is not considered part of the transaction (the + * transaction user needs to generate and send the ACK). + * + * This library is not strictly RFC compliant with regard to ACK handling for 2xx final + * responses. Specifically, retransmissions of ACKs to a 2xx final responses is handled + * by the transaction layer (instead of the UAC core). The "standard" approach is for + * the UAC core to receive all 2xx responses and manage sending ACK retransmissions to + * the transport directly. Herein the transaction layer manages sending ACKs to 2xx responses + * and any retransmissions of those ACKs as needed. + * + * @param ack The outgoing ACK request. + */ + InviteClientTransaction.prototype.ackResponse = function (ack) { + var _this = this; + var toTag = ack.toTag; + if (!toTag) { + throw new Error("To tag undefined."); + } + var id = "z9hG4bK" + Math.floor(Math.random() * 10000000); + // FIXME: Transport's server property is not typed (as of writing this). + var scheme = this.transport.server && this.transport.server.scheme ? this.transport.server.scheme : undefined; + ack.setViaHeader(id, scheme); + this.ackRetransmissionCache.set(toTag, ack); // Add to ACK retransmission cache + this.send(ack.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send ACK to 2xx response."); + }); + }; + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + InviteClientTransaction.prototype.receiveResponse = function (response) { + var _this = this; + var statusCode = response.statusCode; + if (!statusCode || statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Calling: + // If the client transaction receives a provisional response while in + // the "Calling" state, it transitions to the "Proceeding" state. In the + // "Proceeding" state, the client transaction SHOULD NOT retransmit the + // request any longer. Furthermore, the provisional response MUST be + // passed to the TU. Any further provisional responses MUST be passed + // up to the TU while in the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + if (statusCode >= 100 && statusCode <= 199) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When a 2xx response is received while in either the "Calling" or + // "Proceeding" states, the client transaction MUST transition to + // the "Accepted" state... The 2xx response MUST be passed up to the TU. + // The client transaction MUST NOT generate an ACK to the 2xx response -- its + // handling is delegated to the TU. A UAC core will send an ACK to + // the 2xx response using a new transaction. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + this.stateTransition(transaction_state_1.TransactionState.Accepted); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When in either the "Calling" or "Proceeding" states, reception of + // a response with status code from 300-699 MUST cause the client + // transaction to transition to "Completed". The client transaction + // MUST pass the received response up to the TU, and the client + // transaction MUST generate an ACK request, even if the transport is + // reliable (guidelines for constructing the ACK from the response + // are given in Section 17.1.1.3), and then pass the ACK to the + // transport layer for transmission. The ACK MUST be sent to the + // same address, port, and transport to which the original request was sent. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.ack(response); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // In the "Proceeding" state, the client transaction SHOULD NOT retransmit the + // request any longer. Furthermore, the provisional response MUST be + // passed to the TU. Any further provisional responses MUST be passed + // up to the TU while in the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + if (statusCode >= 100 && statusCode <= 199) { + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When a 2xx response is received while in either the "Calling" or "Proceeding" states, + // the client transaction MUST transition to the "Accepted" state... + // The 2xx response MUST be passed up to the TU. The client + // transaction MUST NOT generate an ACK to the 2xx response -- its + // handling is delegated to the TU. A UAC core will send an ACK to + // the 2xx response using a new transaction. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + this.stateTransition(transaction_state_1.TransactionState.Accepted); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When in either the "Calling" or "Proceeding" states, reception of + // a response with status code from 300-699 MUST cause the client + // transaction to transition to "Completed". The client transaction + // MUST pass the received response up to the TU, and the client + // transaction MUST generate an ACK request, even if the transport is + // reliable (guidelines for constructing the ACK from the response + // are given in Section 17.1.1.3), and then pass the ACK to the + // transport layer for transmission. The ACK MUST be sent to the + // same address, port, and transport to which the original request was sent. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.ack(response); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // The purpose of the "Accepted" state is to allow the client + // transaction to continue to exist to receive, and pass to the TU, + // any retransmissions of the 2xx response and any additional 2xx + // responses from other branches of the INVITE if it forked + // downstream. Timer M reflects the amount of time that the + // transaction user will wait for such messages. + // + // Any 2xx responses that match this client transaction and that are + // received while in the "Accepted" state MUST be passed up to the + // TU. The client transaction MUST NOT generate an ACK to the 2xx + // response. The client transaction takes no further action. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + // NOTE: This implementation herein is intentionally not RFC compliant. + // While the first 2xx response for a given branch is passed up to the TU, + // retransmissions of 2xx responses are absorbed and the ACK associated + // with the original response is resent. This approach is taken because + // our current transaction users are not currently in a good position to + // deal with 2xx retransmission. This SHOULD NOT cause any compliance issues - ;) + // + // If we don't have a cache hit, pass the response to the TU. + if (!this.ackRetransmissionCache.has(response.toTag)) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // If we have a cache hit, try pulling the ACK from cache and retransmitting it. + var ack = this.ackRetransmissionCache.get(response.toTag); + if (ack) { + this.send(ack.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of ACK to 2xx response."); + }); + return; + } + // If an ACK was not found in cache then we have received a retransmitted 2xx + // response before the TU responded to the original response (we don't have an ACK yet). + // So discard this response under the assumption that the TU will eventually + // get us a ACK for the original response. + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Any retransmissions of a response with status code 300-699 that + // are received while in the "Completed" state MUST cause the ACK to + // be re-passed to the transport layer for retransmission, but the + // newly received response MUST NOT be passed up to the TU. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.ack(response); + return; + } + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + // Any response received that does not match an existing client + // transaction state machine is simply dropped. (Implementations are, + // of course, free to log or do other implementation-specific things + // with such responses, but the implementer should be sure to consider + // the impact of large numbers of malicious stray responses.) + // https://tools.ietf.org/html/rfc6026#section-7.2 + var message = "Received unexpected " + statusCode + " response while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * The client transaction SHOULD inform the TU that a transport failure + * has occurred, and the client transaction SHOULD transition directly + * to the "Terminated" state. The TU will handle the failover + * mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error The error. + */ + InviteClientTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + InviteClientTransaction.prototype.typeToString = function () { + return "INVITE client transaction"; + }; + InviteClientTransaction.prototype.ack = function (response) { + var _this = this; + // The ACK request constructed by the client transaction MUST contain + // values for the Call-ID, From, and Request-URI that are equal to the + // values of those header fields in the request passed to the transport + // by the client transaction (call this the "original request"). The To + // header field in the ACK MUST equal the To header field in the + // response being acknowledged, and therefore will usually differ from + // the To header field in the original request by the addition of the + // tag parameter. The ACK MUST contain a single Via header field, and + // this MUST be equal to the top Via header field of the original + // request. The CSeq header field in the ACK MUST contain the same + // value for the sequence number as was present in the original request, + // but the method parameter MUST be equal to "ACK". + // + // If the INVITE request whose response is being acknowledged had Route + // header fields, those header fields MUST appear in the ACK. This is + // to ensure that the ACK can be routed properly through any downstream + // stateless proxies. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.3 + var ruri = this.request.ruri; + var callId = this.request.callId; + var cseq = this.request.cseq; + var from = this.request.getHeader("from"); + var to = response.getHeader("to"); + var via = this.request.getHeader("via"); + var route = this.request.getHeader("route"); + if (!from) { + throw new Error("From undefined."); + } + if (!to) { + throw new Error("To undefined."); + } + if (!via) { + throw new Error("Via undefined."); + } + var ack = "ACK " + ruri + " SIP/2.0\r\n"; + if (route) { + ack += "Route: " + route + "\r\n"; + } + ack += "Via: " + via + "\r\n"; + ack += "To: " + to + "\r\n"; + ack += "From: " + from + "\r\n"; + ack += "Call-ID: " + callId + "\r\n"; + ack += "CSeq: " + cseq + " ACK\r\n"; + ack += "Max-Forwards: 70\r\n"; + ack += "Content-Length: 0\r\n\r\n"; + // TOOO: "User-Agent" header + this.send(ack).catch(function (error) { + _this.logTransportError(error, "Failed to send ACK to non-2xx response."); + }); + return; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + InviteClientTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Calling: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Calling) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Accepted: + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Calling && + this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Calling && + this.state !== transaction_state_1.TransactionState.Accepted && + this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // While not spelled out in the RFC, Timer B is the maximum amount of time that a sender + // will wait for an INVITE message to be acknowledged (a SIP response message is received). + // So Timer B should be cleared when the transaction state proceeds from "Calling". + if (this.B) { + clearTimeout(this.B); + this.B = undefined; + } + if (newState === transaction_state_1.TransactionState.Proceeding) { + // Timers have no effect on "Proceeding" state. + // In the "Proceeding" state, the client transaction + // SHOULD NOT retransmit the request any longer. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + } + // The client transaction MUST start Timer D when it enters the "Completed" state + // for any reason, with a value of at least 32 seconds for unreliable transports, + // and a value of zero seconds for reliable transports. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (newState === transaction_state_1.TransactionState.Completed) { + this.D = setTimeout(function () { return _this.timer_D(); }, timers_1.Timers.TIMER_D); + } + // The client transaction MUST transition to the "Accepted" state, + // and Timer M MUST be started with a value of 64*T1. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (newState === transaction_state_1.TransactionState.Accepted) { + this.M = setTimeout(function () { return _this.timer_M(); }, timers_1.Timers.TIMER_M); + } + // Once the transaction is in the "Terminated" state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * When timer A fires, the client transaction MUST retransmit the + * request by passing it to the transport layer, and MUST reset the + * timer with a value of 2*T1. + * When timer A fires 2*T1 seconds later, the request MUST be + * retransmitted again (assuming the client transaction is still in this + * state). This process MUST continue so that the request is + * retransmitted with intervals that double after each transmission. + * These retransmissions SHOULD only be done while the client + * transaction is in the "Calling" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + InviteClientTransaction.prototype.timer_A = function () { + // TODO + }; + /** + * If the client transaction is still in the "Calling" state when timer + * B fires, the client transaction SHOULD inform the TU that a timeout + * has occurred. The client transaction MUST NOT generate an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + InviteClientTransaction.prototype.timer_B = function () { + this.logger.debug("Timer B expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Calling) { + this.onRequestTimeout(); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer D fires while the client transaction is in the "Completed" state, + * the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + InviteClientTransaction.prototype.timer_D = function () { + this.logger.debug("Timer D expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer M fires while the client transaction is in the "Accepted" + * state, the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + InviteClientTransaction.prototype.timer_M = function () { + this.logger.debug("Timer M expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Accepted) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return InviteClientTransaction; +}(client_transaction_1.ClientTransaction)); +exports.InviteClientTransaction = InviteClientTransaction; + + +/***/ }), +/* 36 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** Transaction state. */ +var TransactionState; +(function (TransactionState) { + TransactionState["Accepted"] = "Accepted"; + TransactionState["Calling"] = "Calling"; + TransactionState["Completed"] = "Completed"; + TransactionState["Confirmed"] = "Confirmed"; + TransactionState["Proceeding"] = "Proceeding"; + TransactionState["Terminated"] = "Terminated"; + TransactionState["Trying"] = "Trying"; +})(TransactionState = exports.TransactionState || (exports.TransactionState = {})); + + +/***/ }), +/* 37 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var timers_1 = __webpack_require__(26); +var server_transaction_1 = __webpack_require__(38); +var transaction_state_1 = __webpack_require__(36); +/** + * INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ +var InviteServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(InviteServerTransaction, _super); + /** + * Constructor. + * Upon construction, a "100 Trying" reply will be immediately sent. + * After construction the transaction will be in the "proceeding" state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + * @param request Incoming INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + function InviteServerTransaction(request, transport, user) { + return _super.call(this, request, transport, user, transaction_state_1.TransactionState.Proceeding, "sip.transaction.ist") || this; + } + /** + * Destructor. + */ + InviteServerTransaction.prototype.dispose = function () { + this.stopProgressExtensionTimer(); + if (this.H) { + clearTimeout(this.H); + this.H = undefined; + } + if (this.I) { + clearTimeout(this.I); + this.I = undefined; + } + if (this.L) { + clearTimeout(this.L); + this.L = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(InviteServerTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "ist"; + }, + enumerable: true, + configurable: true + }); + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + InviteServerTransaction.prototype.receiveRequest = function (request) { + var _this = this; + switch (this.state) { + case transaction_state_1.TransactionState.Proceeding: + // If a request retransmission is received while in the "Proceeding" state, the most + // recent provisional response that was received from the TU MUST be passed to the + // transport layer for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE) { + if (this.lastProvisionalResponse) { + this.send(this.lastProvisionalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + } + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // While in the "Accepted" state, any retransmissions of the INVITE + // received will match this transaction state machine and will be + // absorbed by the machine without changing its state. These + // retransmissions are not passed onto the TU. + // https://tools.ietf.org/html/rfc6026#section-7.1 + if (request.method === messages_1.C.INVITE) { + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Furthermore, while in the "Completed" state, if a request retransmission is + // received, the server SHOULD pass the response to the transport for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE) { + if (!this.lastFinalResponse) { + throw new Error("Last final response undefined."); + } + this.send(this.lastFinalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of final response."); + }); + return; + } + // If an ACK is received while the server transaction is in the "Completed" state, + // the server transaction MUST transition to the "Confirmed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.ACK) { + this.stateTransition(transaction_state_1.TransactionState.Confirmed); + return; + } + break; + case transaction_state_1.TransactionState.Confirmed: + // The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, + // triggered from retransmissions of the final response. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE || request.method === messages_1.C.ACK) { + return; + } + break; + case transaction_state_1.TransactionState.Terminated: + // For good measure absorb any additional messages that arrive (should not happen). + if (request.method === messages_1.C.INVITE || request.method === messages_1.C.ACK) { + return; + } + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "INVITE server transaction received unexpected " + request.method + " request while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of response. + * @param response Response. + */ + InviteServerTransaction.prototype.receiveResponse = function (statusCode, response) { + var _this = this; + if (statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Proceeding: + // The TU passes any number of provisional responses to the server + // transaction. So long as the server transaction is in the + // "Proceeding" state, each of these MUST be passed to the transport + // layer for transmission. They are not sent reliably by the + // transaction layer (they are not retransmitted by it) and do not cause + // a change in the state of the server transaction. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (statusCode >= 100 && statusCode <= 199) { + this.lastProvisionalResponse = response; + // Start the progress extension timer only for a non-100 provisional response. + if (statusCode > 100) { + this.startProgressExtensionTimer(); // FIXME: remove + } + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 1xx response."); + }); + return; + } + // If, while in the "Proceeding" state, the TU passes a 2xx response + // to the server transaction, the server transaction MUST pass this + // response to the transport layer for transmission. It is not + // retransmitted by the server transaction; retransmissions of 2xx + // responses are handled by the TU. The server transaction MUST then + // transition to the "Accepted" state. + // https://tools.ietf.org/html/rfc6026#section-8.5 + if (statusCode >= 200 && statusCode <= 299) { + this.lastFinalResponse = response; + this.stateTransition(transaction_state_1.TransactionState.Accepted); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + return; + } + // While in the "Proceeding" state, if the TU passes a response with + // status code from 300 to 699 to the server transaction, the response + // MUST be passed to the transport layer for transmission, and the state + // machine MUST enter the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (statusCode >= 300 && statusCode <= 699) { + this.lastFinalResponse = response; + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send non-2xx final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // While in the "Accepted" state, if the TU passes a 2xx response, + // the server transaction MUST pass the response to the transport layer for transmission. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (statusCode >= 200 && statusCode <= 299) { + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Completed: + break; + case transaction_state_1.TransactionState.Confirmed: + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "INVITE server transaction received unexpected " + statusCode + " response from TU while in state " + this.state + "."; + this.logger.error(message); + throw new Error(message); + }; + /** + * Retransmit the last 2xx response. This is a noop if not in the "accepted" state. + */ + InviteServerTransaction.prototype.retransmitAcceptedResponse = function () { + var _this = this; + if (this.state === transaction_state_1.TransactionState.Accepted && this.lastFinalResponse) { + this.send(this.lastFinalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + } + }; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and MUST remain in the current state. + * https://tools.ietf.org/html/rfc6026#section-8.8 + */ + InviteServerTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + }; + /** For logging. */ + InviteServerTransaction.prototype.typeToString = function () { + return "INVITE server transaction"; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + InviteServerTransaction.prototype.stateTransition = function (newState) { + var _this = this; + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Proceeding: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Accepted: + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Confirmed: + if (this.state !== transaction_state_1.TransactionState.Completed) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Accepted && + this.state !== transaction_state_1.TransactionState.Completed && + this.state !== transaction_state_1.TransactionState.Confirmed) { + invalidStateTransition(); + } + break; + default: + invalidStateTransition(); + } + // On any state transition, stop resending provisonal responses + this.stopProgressExtensionTimer(); + // The purpose of the "Accepted" state is to absorb retransmissions of an accepted INVITE request. + // Any such retransmissions are absorbed entirely within the server transaction. + // They are not passed up to the TU since any downstream UAS cores that accepted the request have + // taken responsibility for reliability and will already retransmit their 2xx responses if necessary. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Accepted) { + this.L = setTimeout(function () { return _this.timer_L(); }, timers_1.Timers.TIMER_L); + } + // When the "Completed" state is entered, timer H MUST be set to fire in 64*T1 seconds for all transports. + // Timer H determines when the server transaction abandons retransmitting the response. + // If an ACK is received while the server transaction is in the "Completed" state, + // the server transaction MUST transition to the "Confirmed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (newState === transaction_state_1.TransactionState.Completed) { + // FIXME: Missing timer G for unreliable transports. + this.H = setTimeout(function () { return _this.timer_H(); }, timers_1.Timers.TIMER_H); + } + // The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, + // triggered from retransmissions of the final response. When this state is entered, timer I + // is set to fire in T4 seconds for unreliable transports, and zero seconds for reliable + // transports. Once timer I fires, the server MUST transition to the "Terminated" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (newState === transaction_state_1.TransactionState.Confirmed) { + // FIXME: This timer is not getting set correctly for unreliable transports. + this.I = setTimeout(function () { return _this.timer_I(); }, timers_1.Timers.TIMER_I); + } + // Once the transaction is in the "Terminated" state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * FIXME: UAS Provisional Retransmission Timer. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + InviteServerTransaction.prototype.startProgressExtensionTimer = function () { + var _this = this; + // Start the progress extension timer only for the first non-100 provisional response. + if (this.progressExtensionTimer === undefined) { + this.progressExtensionTimer = setInterval(function () { + _this.logger.debug("Progress extension timer expired for INVITE server transaction " + _this.id + "."); + if (!_this.lastProvisionalResponse) { + throw new Error("Last provisional response undefined."); + } + _this.send(_this.lastProvisionalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + }, timers_1.Timers.PROVISIONAL_RESPONSE_INTERVAL); + } + }; + /** + * FIXME: UAS Provisional Retransmission Timer id. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + InviteServerTransaction.prototype.stopProgressExtensionTimer = function () { + if (this.progressExtensionTimer !== undefined) { + clearInterval(this.progressExtensionTimer); + this.progressExtensionTimer = undefined; + } + }; + /** + * While in the "Proceeding" state, if the TU passes a response with status code + * from 300 to 699 to the server transaction, the response MUST be passed to the + * transport layer for transmission, and the state machine MUST enter the "Completed" state. + * For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for + * reliable transports. If timer G fires, the response is passed to the transport layer once + * more for retransmission, and timer G is set to fire in MIN(2*T1, T2) seconds. From then on, + * when timer G fires, the response is passed to the transport again for transmission, and + * timer G is reset with a value that doubles, unless that value exceeds T2, in which case + * it is reset with the value of T2. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_G = function () { + // TODO + }; + /** + * If timer H fires while in the "Completed" state, it implies that the ACK was never received. + * In this case, the server transaction MUST transition to the "Terminated" state, and MUST + * indicate to the TU that a transaction failure has occurred. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_H = function () { + this.logger.debug("Timer H expired for INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.logger.warn("ACK to negative final response was never received, terminating transaction."); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * Once timer I fires, the server MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_I = function () { + this.logger.debug("Timer I expired for INVITE server transaction " + this.id + "."); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + }; + /** + * When Timer L fires and the state machine is in the "Accepted" state, the machine MUST + * transition to the "Terminated" state. Once the transaction is in the "Terminated" state, + * it MUST be destroyed immediately. Timer L reflects the amount of time the server + * transaction could receive 2xx responses for retransmission from the + * TU while it is waiting to receive an ACK. + * https://tools.ietf.org/html/rfc6026#section-7.1 + * https://tools.ietf.org/html/rfc6026#section-8.7 + */ + InviteServerTransaction.prototype.timer_L = function () { + this.logger.debug("Timer L expired for INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Accepted) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return InviteServerTransaction; +}(server_transaction_1.ServerTransaction)); +exports.InviteServerTransaction = InviteServerTransaction; + + +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transaction_1 = __webpack_require__(29); +/** + * Server Transaction + * The server transaction is responsible for the delivery of requests to + * the TU and the reliable transmission of responses. It accomplishes + * this through a state machine. Server transactions are created by the + * core when a request is received, and transaction handling is desired + * for that request (this is not always the case). + * https://tools.ietf.org/html/rfc3261#section-17.2 + */ +var ServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(ServerTransaction, _super); + function ServerTransaction(_request, transport, user, state, loggerCategory) { + var _this = _super.call(this, transport, user, _request.viaBranch, state, loggerCategory) || this; + _this._request = _request; + _this.user = user; + return _this; + } + Object.defineProperty(ServerTransaction.prototype, "request", { + /** The incoming request the transaction handling. */ + get: function () { + return this._request; + }, + enumerable: true, + configurable: true + }); + return ServerTransaction; +}(transaction_1.Transaction)); +exports.ServerTransaction = ServerTransaction; + + +/***/ }), +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var timers_1 = __webpack_require__(26); +var client_transaction_1 = __webpack_require__(28); +var transaction_state_1 = __webpack_require__(36); +/** + * Non-INVITE Client Transaction + * + * Non-INVITE transactions do not make use of ACK. + * They are simple request-response interactions. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + */ +var NonInviteClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(NonInviteClientTransaction, _super); + /** + * Constructor + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + * @param request The outgoing Non-INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + function NonInviteClientTransaction(request, transport, user) { + var _this = _super.call(this, request, transport, user, transaction_state_1.TransactionState.Trying, "sip.transaction.nict") || this; + // FIXME: Timer E for unreliable transports not implemented. + // + // The "Trying" state is entered when the TU initiates a new client + // transaction with a request. When entering this state, the client + // transaction SHOULD set timer F to fire in 64*T1 seconds. The request + // MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + _this.F = setTimeout(function () { return _this.timer_F(); }, timers_1.Timers.TIMER_F); + _this.send(request.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send initial outgoing request."); + }); + return _this; + } + /** + * Destructor. + */ + NonInviteClientTransaction.prototype.dispose = function () { + if (this.F) { + clearTimeout(this.F); + this.F = undefined; + } + if (this.K) { + clearTimeout(this.K); + this.K = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(NonInviteClientTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "nict"; + }, + enumerable: true, + configurable: true + }); + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + NonInviteClientTransaction.prototype.receiveResponse = function (response) { + var statusCode = response.statusCode; + if (!statusCode || statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // If a provisional response is received while in the "Trying" state, the + // response MUST be passed to the TU, and then the client transaction + // SHOULD move to the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 100 && statusCode <= 199) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // If a final response (status codes 200-699) is received while in the + // "Trying" state, the response MUST be passed to the TU, and the + // client transaction MUST transition to the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + if (statusCode === 408) { + this.onRequestTimeout(); + return; + } + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // If a provisional response is received while in the "Proceeding" state, + // the response MUST be passed to the TU. (From Figure 6) + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 100 && statusCode <= 199) { + if (this.user.receiveResponse) { + return this.user.receiveResponse(response); + } + } + // If a final response (status codes 200-699) is received while in the + // "Proceeding" state, the response MUST be passed to the TU, and the + // client transaction MUST transition to the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + if (statusCode === 408) { + this.onRequestTimeout(); + return; + } + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + case transaction_state_1.TransactionState.Completed: + // The "Completed" state exists to buffer any additional response + // retransmissions that may be received (which is why the client + // transaction remains there only for unreliable transports). + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + return; + case transaction_state_1.TransactionState.Terminated: + // For good measure just absorb additional response retransmissions. + return; + default: + throw new Error("Invalid state " + this.state); + } + var message = "Non-INVITE client transaction received unexpected " + statusCode + " response while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * The client transaction SHOULD inform the TU that a transport failure has occurred, + * and the client transaction SHOULD transition directly to the "Terminated" state. + * The TU will handle the failover mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error Trasnsport error + */ + NonInviteClientTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + NonInviteClientTransaction.prototype.typeToString = function () { + return "non-INVITE client transaction"; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + NonInviteClientTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Trying: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Trying) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Trying && + this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Trying && + this.state !== transaction_state_1.TransactionState.Proceeding && + this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // Once the client transaction enters the "Completed" state, it MUST set + // Timer K to fire in T4 seconds for unreliable transports, and zero + // seconds for reliable transports The "Completed" state exists to + // buffer any additional response retransmissions that may be received + // (which is why the client transaction remains there only for unreliable transports). + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (newState === transaction_state_1.TransactionState.Completed) { + if (this.F) { + clearTimeout(this.F); + this.F = undefined; + } + this.K = setTimeout(function () { return _this.timer_K(); }, timers_1.Timers.TIMER_K); + } + // Once the transaction is in the terminated state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * If Timer F fires while the client transaction is still in the + * "Trying" state, the client transaction SHOULD inform the TU about the + * timeout, and then it SHOULD enter the "Terminated" state. + * If timer F fires while in the "Proceeding" state, the TU MUST be informed of + * a timeout, and the client transaction MUST transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + NonInviteClientTransaction.prototype.timer_F = function () { + this.logger.debug("Timer F expired for non-INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Trying || this.state === transaction_state_1.TransactionState.Proceeding) { + this.onRequestTimeout(); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer K fires while in this (COMPLETED) state, the client transaction + * MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + NonInviteClientTransaction.prototype.timer_K = function () { + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return NonInviteClientTransaction; +}(client_transaction_1.ClientTransaction)); +exports.NonInviteClientTransaction = NonInviteClientTransaction; + + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var timers_1 = __webpack_require__(26); +var server_transaction_1 = __webpack_require__(38); +var transaction_state_1 = __webpack_require__(36); +/** + * Non-INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ +var NonInviteServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(NonInviteServerTransaction, _super); + /** + * Constructor. + * After construction the transaction will be in the "trying": state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + * @param request Incoming Non-INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + function NonInviteServerTransaction(request, transport, user) { + return _super.call(this, request, transport, user, transaction_state_1.TransactionState.Trying, "sip.transaction.nist") || this; + } + /** + * Destructor. + */ + NonInviteServerTransaction.prototype.dispose = function () { + if (this.J) { + clearTimeout(this.J); + this.J = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(NonInviteServerTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "nist"; + }, + enumerable: true, + configurable: true + }); + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + NonInviteServerTransaction.prototype.receiveRequest = function (request) { + var _this = this; + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // Once in the "Trying" state, any further request retransmissions are discarded. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + break; + case transaction_state_1.TransactionState.Proceeding: + // If a retransmission of the request is received while in the "Proceeding" state, + // the most recently sent provisional response MUST be passed to the transport layer for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (!this.lastResponse) { + throw new Error("Last response undefined."); + } + this.send(this.lastResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + break; + case transaction_state_1.TransactionState.Completed: + // While in the "Completed" state, the server transaction MUST pass the final response to the transport + // layer for retransmission whenever a retransmission of the request is received. Any other final responses + // passed by the TU to the server transaction MUST be discarded while in the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (!this.lastResponse) { + throw new Error("Last response undefined."); + } + this.send(this.lastResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of final response."); + }); + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + }; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of repsonse. 101-199 not allowed per RFC 4320. + * @param response Response to send. + */ + NonInviteServerTransaction.prototype.receiveResponse = function (statusCode, response) { + var _this = this; + if (statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + // An SIP element MUST NOT send any provisional response with a + // Status-Code other than 100 to a non-INVITE request. + // An SIP element MUST NOT respond to a non-INVITE request with a + // Status-Code of 100 over any unreliable transport, such as UDP, + // before the amount of time it takes a client transaction's Timer E to be reset to T2. + // An SIP element MAY respond to a non-INVITE request with a + // Status-Code of 100 over a reliable transport at any time. + // https://tools.ietf.org/html/rfc4320#section-4.1 + if (statusCode > 100 && statusCode <= 199) { + throw new Error("Provisional response other than 100 not allowed."); + } + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // While in the "Trying" state, if the TU passes a provisional response + // to the server transaction, the server transaction MUST enter the "Proceeding" state. + // The response MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + this.lastResponse = response; + if (statusCode >= 100 && statusCode < 200) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send provisional response."); + }); + return; + } + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // Any further provisional responses that are received from the TU while + // in the "Proceeding" state MUST be passed to the transport layer for transmission. + // If the TU passes a final response (status codes 200-699) to the server while in + // the "Proceeding" state, the transaction MUST enter the "Completed" state, and + // the response MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + this.lastResponse = response; + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Any other final responses passed by the TU to the server + // transaction MUST be discarded while in the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + return; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "Non-INVITE server transaction received unexpected " + statusCode + " response from TU while in state " + this.state + "."; + this.logger.error(message); + throw new Error(message); + }; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and SHOULD transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.2.4 + */ + NonInviteServerTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + NonInviteServerTransaction.prototype.typeToString = function () { + return "non-INVITE server transaction"; + }; + NonInviteServerTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Trying: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Trying) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Trying && this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Proceeding && this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // When the server transaction enters the "Completed" state, it MUST set Timer J to fire + // in 64*T1 seconds for unreliable transports, and zero seconds for reliable transports. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (newState === transaction_state_1.TransactionState.Completed) { + this.J = setTimeout(function () { return _this.timer_J(); }, timers_1.Timers.TIMER_J); + } + // The server transaction MUST be destroyed the instant it enters the "Terminated" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + this.setState(newState); + }; + /** + * The server transaction remains in this state until Timer J fires, + * at which point it MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ + NonInviteServerTransaction.prototype.timer_J = function () { + this.logger.debug("Timer J expired for NON-INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return NonInviteServerTransaction; +}(server_transaction_1.ServerTransaction)); +exports.NonInviteServerTransaction = NonInviteServerTransaction; + + +/***/ }), +/* 41 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var ByeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ByeUserAgentClient, _super); + function ByeUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.BYE, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.dispose(); + return _this; + } + return ByeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ByeUserAgentClient = ByeUserAgentClient; + + +/***/ }), +/* 42 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +/* + * User Agent Client (UAC): A user agent client is a logical entity + * that creates a new request, and then uses the client + * transaction state machinery to send it. The role of UAC lasts + * only for the duration of that transaction. In other words, if + * a piece of software initiates a request, it acts as a UAC for + * the duration of that transaction. If it receives a request + * later, it assumes the role of a user agent server for the + * processing of that transaction. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentClient = /** @class */ (function () { + function UserAgentClient(transactionConstructor, core, message, delegate) { + this.transactionConstructor = transactionConstructor; + this.core = core; + this.message = message; + this.delegate = delegate; + this.challenged = false; + this.stale = false; + this.logger = this.loggerFactory.getLogger("sip.user-agent-client"); + this.init(); + } + UserAgentClient.prototype.dispose = function () { + this.transaction.dispose(); + }; + Object.defineProperty(UserAgentClient.prototype, "loggerFactory", { + get: function () { + return this.core.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentClient.prototype, "transaction", { + /** The transaction associated with this request. */ + get: function () { + if (!this._transaction) { + throw new Error("Transaction undefined."); + } + return this._transaction; + }, + enumerable: true, + configurable: true + }); + /** + * Since requests other than INVITE are responded to immediately, sending a + * CANCEL for a non-INVITE request would always create a race condition. + * A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + * https://tools.ietf.org/html/rfc3261#section-9.1 + * @param options Cancel options bucket. + */ + UserAgentClient.prototype.cancel = function (reason, options) { + var _this = this; + if (options === void 0) { options = {}; } + if (!this.transaction) { + throw new Error("Transaction undefined."); + } + if (!this.message.to) { + throw new Error("To undefined."); + } + if (!this.message.from) { + throw new Error("From undefined."); + } + // The following procedures are used to construct a CANCEL request. The + // Request-URI, Call-ID, To, the numeric part of CSeq, and From header + // fields in the CANCEL request MUST be identical to those in the + // request being cancelled, including tags. A CANCEL constructed by a + // client MUST have only a single Via header field value matching the + // top Via value in the request being cancelled. Using the same values + // for these header fields allows the CANCEL to be matched with the + // request it cancels (Section 9.2 indicates how such matching occurs). + // However, the method part of the CSeq header field MUST have a value + // of CANCEL. This allows it to be identified and processed as a + // transaction in its own right (See Section 17). + // https://tools.ietf.org/html/rfc3261#section-9.1 + var message = this.core.makeOutgoingRequestMessage(messages_1.C.CANCEL, this.message.ruri, this.message.from.uri, this.message.to.uri, { + toTag: this.message.toTag, + fromTag: this.message.fromTag, + callId: this.message.callId, + cseq: this.message.cseq + }, options.extraHeaders); + // TODO: Revisit this. + // The CANCEL needs to use the same branch parameter so that + // it matches the INVITE transaction, but this is a hacky way to do this. + // Or at the very least not well documented. If the the branch parameter + // is set on the outgoing request, the transaction will use it. + // Otherwise the transaction will make a new one. + message.branch = this.message.branch; + if (this.message.headers.Route) { + message.headers.Route = this.message.headers.Route; + } + if (reason) { + message.setHeader("Reason", reason); + } + // If no provisional response has been received, the CANCEL request MUST + // NOT be sent; rather, the client MUST wait for the arrival of a + // provisional response before sending the request. If the original + // request has generated a final response, the CANCEL SHOULD NOT be + // sent, as it is an effective no-op, since CANCEL has no effect on + // requests that have already generated a final response. + // https://tools.ietf.org/html/rfc3261#section-9.1 + if (this.transaction.state === transactions_1.TransactionState.Proceeding) { + var uac = new UserAgentClient(transactions_1.NonInviteClientTransaction, this.core, message); + } + else { + this.transaction.once("stateChanged", function () { + if (_this.transaction && _this.transaction.state === transactions_1.TransactionState.Proceeding) { + var uac = new UserAgentClient(transactions_1.NonInviteClientTransaction, _this.core, message); + } + }); + } + return message; + }; + /** + * If a 401 (Unauthorized) or 407 (Proxy Authentication Required) + * response is received, the UAC SHOULD follow the authorization + * procedures of Section 22.2 and Section 22.3 to retry the request with + * credentials. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + * 22 Usage of HTTP Authentication + * https://tools.ietf.org/html/rfc3261#section-22 + * 22.1 Framework + * https://tools.ietf.org/html/rfc3261#section-22.1 + * 22.2 User-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.2 + * 22.3 Proxy-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.3 + * + * FIXME: This "guard for and retry the request with credentials" + * implementation is not complete and at best minimally passable. + * @param response The incoming response to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise the request is retried with credentials and current request processing must stop. + */ + UserAgentClient.prototype.authenticationGuard = function (message) { + var statusCode = message.statusCode; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + // If a 401 (Unauthorized) or 407 (Proxy Authentication Required) + // response is received, the UAC SHOULD follow the authorization + // procedures of Section 22.2 and Section 22.3 to retry the request with + // credentials. + // https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + if (statusCode !== 401 && statusCode !== 407) { + return true; + } + // Get and parse the appropriate WWW-Authenticate or Proxy-Authenticate header. + var challenge; + var authorizationHeaderName; + if (statusCode === 401) { + challenge = message.parseHeader("www-authenticate"); + authorizationHeaderName = "authorization"; + } + else { + challenge = message.parseHeader("proxy-authenticate"); + authorizationHeaderName = "proxy-authorization"; + } + // Verify it seems a valid challenge. + if (!challenge) { + this.logger.warn(statusCode + " with wrong or missing challenge, cannot authenticate"); + return true; + } + // Avoid infinite authentications. + if (this.challenged && (this.stale || challenge.stale !== true)) { + this.logger.warn(statusCode + " apparently in authentication loop, cannot authenticate"); + return true; + } + // Get credentials. + if (!this.credentials) { + this.credentials = this.core.configuration.authenticationFactory(); + if (!this.credentials) { + this.logger.warn("Unable to obtain credentials, cannot authenticate"); + return true; + } + } + // Verify that the challenge is really valid. + if (!this.credentials.authenticate(this.message, challenge)) { + return true; + } + this.challenged = true; + if (challenge.stale) { + this.stale = true; + } + var cseq = this.message.cseq += 1; + this.message.setHeader("cseq", cseq + " " + this.message.method); + this.message.setHeader(authorizationHeaderName, this.credentials.toString()); + // Calling init (again) will swap out our existing client transaction with a new one. + // FIXME: HACK: An assumption is being made here that there is nothing that needs to + // be cleaned up beyond the client transaction which is being replaced. For example, + // it is assumed that no early dialogs have been created. + this.init(); + return false; + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + UserAgentClient.prototype.receiveResponse = function (message) { + if (!this.authenticationGuard(message)) { + return; + } + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + break; + case /^1[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ message: message }); + } + break; + case /^2[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ message: message }); + } + break; + case /^3[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + break; + case /^[4-6][0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + break; + default: + throw new Error("Invalid status code " + statusCode); + } + }; + UserAgentClient.prototype.init = function () { + var _this = this; + // We are the transaction user. + var user = { + loggerFactory: this.loggerFactory, + onRequestTimeout: function () { return _this.onRequestTimeout(); }, + onStateChange: function (newState) { + if (newState === transactions_1.TransactionState.Terminated) { + // Remove the terminated transaction from the core. + _this.core.userAgentClients.delete(userAgentClientId); + // FIXME: HACK: Our transaction may have been swapped out with a new one + // post authentication (see above), so make sure to only to dispose of + // ourselves if this terminating transaction is our current transaction. + if (transaction === _this._transaction) { + _this.dispose(); + } + } + }, + onTransportError: function (error) { return _this.onTransportError(error); }, + receiveResponse: function (message) { return _this.receiveResponse(message); } + }; + // Create a new transaction with us as the user. + var transaction = new this.transactionConstructor(this.message, this.core.transport, user); + this._transaction = transaction; + // Add the new transaction to the core. + var userAgentClientId = transaction.id + transaction.request.method; + this.core.userAgentClients.set(userAgentClientId, this); + }; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + UserAgentClient.prototype.onRequestTimeout = function () { + this.logger.warn("User agent client request timed out. Generating internal 408 Request Timeout."); + var message = new messages_1.IncomingResponseMessage(); + message.statusCode = 408; + message.reasonPhrase = "Request Timeout"; + this.receiveResponse(message); + return; + }; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + UserAgentClient.prototype.onTransportError = function (error) { + this.logger.error(error.message); + this.logger.error("User agent client request transport error. Generating internal 503 Service Unavailable."); + var message = new messages_1.IncomingResponseMessage(); + message.statusCode = 503; + message.reasonPhrase = "Service Unavailable"; + this.receiveResponse(message); + }; + return UserAgentClient; +}()); +exports.UserAgentClient = UserAgentClient; + + +/***/ }), +/* 43 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var ByeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ByeUserAgentServer, _super); + function ByeUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return ByeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ByeUserAgentServer = ByeUserAgentServer; + + +/***/ }), +/* 44 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var exceptions_1 = __webpack_require__(31); +var messages_1 = __webpack_require__(5); +var utils_1 = __webpack_require__(16); +var transactions_1 = __webpack_require__(27); +/** + * User Agent Server (UAS): A user agent server is a logical entity + * that generates a response to a SIP request. The response + * accepts, rejects, or redirects the request. This role lasts + * only for the duration of that transaction. In other words, if + * a piece of software responds to a request, it acts as a UAS for + * the duration of that transaction. If it generates a request + * later, it assumes the role of a user agent client for the + * processing of that transaction. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentServer = /** @class */ (function () { + function UserAgentServer(transactionConstructor, core, message, delegate) { + this.transactionConstructor = transactionConstructor; + this.core = core; + this.message = message; + this.delegate = delegate; + this.logger = this.loggerFactory.getLogger("sip.user-agent-server"); + this.toTag = message.toTag ? message.toTag : utils_1.newTag(); + this.init(); + } + UserAgentServer.prototype.dispose = function () { + this.transaction.dispose(); + }; + Object.defineProperty(UserAgentServer.prototype, "loggerFactory", { + get: function () { + return this.core.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "transaction", { + /** The transaction associated with this request. */ + get: function () { + if (!this._transaction) { + throw new Error("Transaction undefined."); + } + return this._transaction; + }, + enumerable: true, + configurable: true + }); + UserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (!this.acceptable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not acceptable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 200 || statusCode > 299) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + if (!this.progressable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not progressable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 101 || statusCode > 199) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.redirect = function (contacts, options) { + if (options === void 0) { options = { statusCode: 302 }; } + if (!this.redirectable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not redirectable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 300 || statusCode > 399) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var contactHeaders = new Array(); + contacts.forEach(function (contact) { return contactHeaders.push("Contact: " + contact.toString()); }); + options.extraHeaders = (options.extraHeaders || []).concat(contactHeaders); + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.reject = function (options) { + if (options === void 0) { options = { statusCode: 480 }; } + if (!this.rejectable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not rejectable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 400 || statusCode > 699) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.trying = function (options) { + if (!this.tryingable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not tryingable in state " + this.transaction.state + "."); + } + var response = this.reply({ statusCode: 100 }); + return response; + }; + /** + * If the UAS did not find a matching transaction for the CANCEL + * according to the procedure above, it SHOULD respond to the CANCEL + * with a 481 (Call Leg/Transaction Does Not Exist). If the transaction + * for the original request still exists, the behavior of the UAS on + * receiving a CANCEL request depends on whether it has already sent a + * final response for the original request. If it has, the CANCEL + * request has no effect on the processing of the original request, no + * effect on any session state, and no effect on the responses generated + * for the original request. If the UAS has not issued a final response + * for the original request, its behavior depends on the method of the + * original request. If the original request was an INVITE, the UAS + * SHOULD immediately respond to the INVITE with a 487 (Request + * Terminated). A CANCEL request has no impact on the processing of + * transactions with any other method defined in this specification. + * https://tools.ietf.org/html/rfc3261#section-9.2 + * @param request Incoming CANCEL request. + */ + UserAgentServer.prototype.receiveCancel = function (message) { + // Note: Currently CANCEL is being handled as a special case. + // No UAS is created to handle the CANCEL and the response to + // it CANCEL is being handled statelessly by the user agent core. + // As such, there is currently no way to externally impact the + // response to the a CANCEL request. + if (this.delegate && this.delegate.onCancel) { + this.delegate.onCancel(message); + } + }; + Object.defineProperty(UserAgentServer.prototype, "acceptable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Proceeding || + this.transaction.state === transactions_1.TransactionState.Accepted); + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "progressable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return false; // https://tools.ietf.org/html/rfc4320#section-4.1 + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "redirectable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "rejectable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "tryingable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Trying; + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + /** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * + * Once all procedures associated with the creation of a response have + * been completed, the UAS hands the response back to the server + * transaction from which it received the request. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + * @param statusCode Status code to reply with. + * @param options Reply options bucket. + */ + UserAgentServer.prototype.reply = function (options) { + if (!options.toTag && options.statusCode !== 100) { + options.toTag = this.toTag; + } + options.userAgent = options.userAgent || this.core.configuration.userAgentHeaderFieldValue; + options.supported = options.supported || this.core.configuration.supportedOptionTagsResponse; + var response = messages_1.constructOutgoingResponse(this.message, options); + this.transaction.receiveResponse(options.statusCode, response.message); + return response; + }; + UserAgentServer.prototype.init = function () { + var _this = this; + // We are the transaction user. + var user = { + loggerFactory: this.loggerFactory, + onStateChange: function (newState) { + if (newState === transactions_1.TransactionState.Terminated) { + // Remove the terminated transaction from the core. + _this.core.userAgentServers.delete(userAgentServerId); + _this.dispose(); + } + }, + onTransportError: function (error) { + _this.logger.error(error.message); + if (_this.delegate && _this.delegate.onTransportError) { + _this.delegate.onTransportError(error); + } + else { + _this.logger.error("User agent server response transport error."); + } + } + }; + // Create a new transaction with us as the user. + var transaction = new this.transactionConstructor(this.message, this.core.transport, user); + this._transaction = transaction; + // Add the new transaction to the core. + var userAgentServerId = transaction.id; + this.core.userAgentServers.set(transaction.id, this); + }; + return UserAgentServer; +}()); +exports.UserAgentServer = UserAgentServer; + + +/***/ }), +/* 45 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var InfoUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(InfoUserAgentClient, _super); + function InfoUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.INFO, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return InfoUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.InfoUserAgentClient = InfoUserAgentClient; + + +/***/ }), +/* 46 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var InfoUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(InfoUserAgentServer, _super); + function InfoUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return InfoUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.InfoUserAgentServer = InfoUserAgentServer; + + +/***/ }), +/* 47 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var NotifyUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(NotifyUserAgentClient, _super); + function NotifyUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.NOTIFY, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return NotifyUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.NotifyUserAgentClient = NotifyUserAgentClient; + + +/***/ }), +/* 48 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var NotifyUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(NotifyUserAgentServer, _super); + /** + * NOTIFY UAS constructor. + * @param dialogOrCore Dialog for in dialog NOTIFY, UserAgentCore for out of dialog NOTIFY (deprecated). + * @param message Incoming NOTIFY request message. + */ + function NotifyUserAgentServer(dialogOrCore, message, delegate) { + var _this = this; + var userAgentCore = instanceOfDialog(dialogOrCore) ? + dialogOrCore.userAgentCore : + dialogOrCore; + _this = _super.call(this, transactions_1.NonInviteServerTransaction, userAgentCore, message, delegate) || this; + return _this; + } + return NotifyUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.NotifyUserAgentServer = NotifyUserAgentServer; +function instanceOfDialog(object) { + return object.userAgentCore !== undefined; +} + + +/***/ }), +/* 49 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var PrackUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(PrackUserAgentClient, _super); + function PrackUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.PRACK, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.signalingStateTransition(message); + return _this; + } + return PrackUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.PrackUserAgentClient = PrackUserAgentClient; + + +/***/ }), +/* 50 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var PrackUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(PrackUserAgentServer, _super); + function PrackUserAgentServer(dialog, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + // Update dialog signaling state with offer/answer in body + dialog.signalingStateTransition(message); + _this.dialog = dialog; + return _this; + } + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + PrackUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (options.body) { + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(options.body); + } + return _super.prototype.accept.call(this, options); + }; + return PrackUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.PrackUserAgentServer = PrackUserAgentServer; + + +/***/ }), +/* 51 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.1 UAC Behavior + * https://tools.ietf.org/html/rfc3261#section-14.1 + */ +var ReInviteUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReInviteUserAgentClient, _super); + function ReInviteUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.INVITE, options); + _this = _super.call(this, transactions_1.InviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + _this.delegate = delegate; + dialog.signalingStateTransition(message); + // FIXME: TODO: next line obviously needs to be improved... + dialog.reinviteUserAgentClient = _this; // let the dialog know re-invite request sent + _this.dialog = dialog; + return _this; + } + ReInviteUserAgentClient.prototype.receiveResponse = function (message) { + var _this = this; + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + break; + case /^1[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ + message: message, + session: this.dialog, + prack: function (options) { + throw new Error("Unimplemented."); + } + }); + } + break; + case /^2[0-9]{2}$/.test(statusCode): + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(message); + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ + message: message, + session: this.dialog, + ack: function (options) { + var outgoingAckRequest = _this.dialog.ack(options); + return outgoingAckRequest; + } + }); + } + break; + case /^3[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + break; + case /^[4-6][0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + else { + // If a UA receives a non-2xx final response to a re-INVITE, the session + // parameters MUST remain unchanged, as if no re-INVITE had been issued. + // Note that, as stated in Section 12.2.1.2, if the non-2xx final + // response is a 481 (Call/Transaction Does Not Exist), or a 408 + // (Request Timeout), or no response at all is received for the re- + // INVITE (that is, a timeout is returned by the INVITE client + // transaction), the UAC will terminate the dialog. + // + // If a UAC receives a 491 response to a re-INVITE, it SHOULD start a + // timer with a value T chosen as follows: + // + // 1. If the UAC is the owner of the Call-ID of the dialog ID + // (meaning it generated the value), T has a randomly chosen value + // between 2.1 and 4 seconds in units of 10 ms. + // + // 2. If the UAC is not the owner of the Call-ID of the dialog ID, T + // has a randomly chosen value of between 0 and 2 seconds in units + // of 10 ms. + // + // When the timer fires, the UAC SHOULD attempt the re-INVITE once more, + // if it still desires for that session modification to take place. For + // example, if the call was already hung up with a BYE, the re-INVITE + // would not take place. + // https://tools.ietf.org/html/rfc3261#section-14.1 + // FIXME: TODO: The above. + } + break; + default: + throw new Error("Invalid status code " + statusCode); + } + }; + return ReInviteUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReInviteUserAgentClient = ReInviteUserAgentClient; + + +/***/ }), +/* 52 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.2 UAS Behavior + * https://tools.ietf.org/html/rfc3261#section-14.2 + */ +var ReInviteUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReInviteUserAgentServer, _super); + function ReInviteUserAgentServer(dialog, message, delegate) { + var _this = _super.call(this, transactions_1.InviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.reinviteUserAgentServer = _this; + _this.dialog = dialog; + return _this; + } + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + ReInviteUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + // FIXME: The next two lines SHOULD go away, but I suppose it's technically harmless... + // These are here because some versions of SIP.js prior to 0.13.8 set the route set + // of all in dialog ACKs based on the Record-Route headers in the associated 2xx + // response. While this worked for dialog forming 2xx responses, it was technically + // broken for re-INVITE ACKS as it only worked if the UAS populated the Record-Route + // headers in the re-INVITE 2xx response (which is not required and a waste of bandwidth + // as the should be ignored if present in re-INVITE ACKS) and the UAS populated + // the Record-Route headers with the correct values (would be weird not too, but...). + // Anyway, for now the technically useless Record-Route headers are being added + // to maintain "backwards compatibility" with the older broken versions of SIP.js. + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(this.dialog.routeSet.map(function (route) { return "Record-Route: " + route; })); + // Send and return the response + var response = _super.prototype.accept.call(this, options); + var session = this.dialog; + var result = tslib_1.__assign({}, response, { session: session }); + if (options.body) { + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(options.body); + } + // Update dialog + this.dialog.reConfirm(); + return result; + }; + /** + * Update the dialog signaling state on a 1xx response. + * @param options Progress options bucket. + */ + ReInviteUserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + // Send and return the response + var response = _super.prototype.progress.call(this, options); + var session = this.dialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.dialog.signalingStateTransition(options.body); + } + return result; + }; + return ReInviteUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReInviteUserAgentServer = ReInviteUserAgentServer; + + +/***/ }), +/* 53 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var ReferUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReferUserAgentClient, _super); + function ReferUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.REFER, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return ReferUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReferUserAgentClient = ReferUserAgentClient; + + +/***/ }), +/* 54 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var ReferUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReferUserAgentServer, _super); + /** + * REFER UAS constructor. + * @param dialogOrCore Dialog for in dialog REFER, UserAgentCore for out of dialog REFER. + * @param message Incoming REFER request message. + */ + function ReferUserAgentServer(dialogOrCore, message, delegate) { + var _this = this; + var userAgentCore = instanceOfSessionDialog(dialogOrCore) ? + dialogOrCore.userAgentCore : + dialogOrCore; + _this = _super.call(this, transactions_1.NonInviteServerTransaction, userAgentCore, message, delegate) || this; + return _this; + } + return ReferUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReferUserAgentServer = ReferUserAgentServer; +function instanceOfSessionDialog(object) { + return object.userAgentCore !== undefined; +} + + +/***/ }), +/* 55 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var subscription_1 = __webpack_require__(56); +var timers_1 = __webpack_require__(26); +var allowed_methods_1 = __webpack_require__(58); +var notify_user_agent_server_1 = __webpack_require__(48); +var re_subscribe_user_agent_client_1 = __webpack_require__(59); +var dialog_1 = __webpack_require__(4); +/** + * SIP-Specific Event Notification + * + * Abstract + * + * This document describes an extension to the Session Initiation + * Protocol (SIP) defined by RFC 3261. The purpose of this extension is + * to provide an extensible framework by which SIP nodes can request + * notification from remote nodes indicating that certain events have + * occurred. + * + * Note that the event notification mechanisms defined herein are NOT + * intended to be a general-purpose infrastructure for all classes of + * event subscription and notification. + * + * This document represents a backwards-compatible improvement on the + * original mechanism described by RFC 3265, taking into account several + * years of implementation experience. Accordingly, this document + * obsoletes RFC 3265. This document also updates RFC 4660 slightly to + * accommodate some small changes to the mechanism that were discussed + * in that document. + * + * https://tools.ietf.org/html/rfc6665 + */ +var SubscriptionDialog = /** @class */ (function (_super) { + tslib_1.__extends(SubscriptionDialog, _super); + function SubscriptionDialog(subscriptionEvent, subscriptionExpires, subscriptionState, core, state, delegate) { + var _this = _super.call(this, core, state) || this; + _this.delegate = delegate; + _this._autoRefresh = false; + _this._subscriptionEvent = subscriptionEvent; + _this._subscriptionExpires = subscriptionExpires; + _this._subscriptionExpiresInitial = subscriptionExpires; + _this._subscriptionExpiresLastSet = Math.floor(Date.now() / 1000); + _this._subscriptionRefresh = undefined; + _this._subscriptionRefreshLastSet = undefined; + _this._subscriptionState = subscriptionState; + _this.logger = core.loggerFactory.getLogger("sip.subscribe-dialog"); + _this.logger.log("SUBSCRIBE dialog " + _this.id + " constructed"); + return _this; + } + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + SubscriptionDialog.initialDialogStateForSubscription = function (outgoingSubscribeRequestMessage, incomingNotifyRequestMessage) { + // If the request was sent over TLS, and the Request-URI contained a + // SIPS URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the response, taken in reverse order and preserving + // all URI parameters. If no Record-Route header field is present in + // the response, the route set MUST be set to the empty set. This route + // set, even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the response. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var routeSet = incomingNotifyRequestMessage.getHeaders("record-route"); + var contact = incomingNotifyRequestMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The local sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The remote sequence + // number MUST be empty (it is established when the remote UA sends a + // request within the dialog). The call identifier component of the + // dialog ID MUST be set to the value of the Call-ID in the request. + // The local tag component of the dialog ID MUST be set to the tag in + // the From field in the request, and the remote tag component of the + // dialog ID MUST be set to the tag in the To field of the response. A + // UAC MUST be prepared to receive a response without a tag in the To + // field, in which case the tag is considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate To tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var localSequenceNumber = outgoingSubscribeRequestMessage.cseq; + var remoteSequenceNumber = undefined; + var callId = outgoingSubscribeRequestMessage.callId; + var localTag = outgoingSubscribeRequestMessage.fromTag; + var remoteTag = incomingNotifyRequestMessage.fromTag; + if (!callId) { // TODO: Review to make sure this will never happen + throw new Error("Call id undefined."); + } + if (!localTag) { // TODO: Review to make sure this will never happen + throw new Error("From tag undefined."); + } + if (!remoteTag) { // TODO: Review to make sure this will never happen + throw new Error("To tag undefined."); // FIXME: No backwards compatibility with RFC 2543 + } + // The remote URI MUST be set to the URI in the To field, and the local + // URI MUST be set to the URI in the From field. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + if (!outgoingSubscribeRequestMessage.from) { // TODO: Review to make sure this will never happen + throw new Error("From undefined."); + } + if (!outgoingSubscribeRequestMessage.to) { // TODO: Review to make sure this will never happen + throw new Error("To undefined."); + } + var localURI = outgoingSubscribeRequestMessage.from.uri; + var remoteURI = outgoingSubscribeRequestMessage.to.uri; + // A dialog can also be in the "early" state, which occurs when it is + // created with a provisional response, and then transition to the + // "confirmed" state when a 2xx final response arrives. + // https://tools.ietf.org/html/rfc3261#section-12 + var early = false; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + SubscriptionDialog.prototype.dispose = function () { + _super.prototype.dispose.call(this); + if (this.N) { + clearTimeout(this.N); + this.N = undefined; + } + this.refreshTimerClear(); + this.logger.log("SUBSCRIBE dialog " + this.id + " destroyed"); + }; + Object.defineProperty(SubscriptionDialog.prototype, "autoRefresh", { + get: function () { + return this._autoRefresh; + }, + set: function (autoRefresh) { + this._autoRefresh = true; + this.refreshTimerSet(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionEvent", { + get: function () { + return this._subscriptionEvent; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionExpires", { + /** Number of seconds until subscription expires. */ + get: function () { + var secondsSinceLastSet = Math.floor(Date.now() / 1000) - this._subscriptionExpiresLastSet; + var secondsUntilExpires = this._subscriptionExpires - secondsSinceLastSet; + return Math.max(secondsUntilExpires, 0); + }, + set: function (expires) { + if (expires < 0) { + throw new Error("Expires must be greater than or equal to zero."); + } + this._subscriptionExpires = expires; + this._subscriptionExpiresLastSet = Math.floor(Date.now() / 1000); + if (this.autoRefresh) { + var refresh = this.subscriptionRefresh; + if (refresh === undefined || refresh >= expires) { + this.refreshTimerSet(); + } + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionExpiresInitial", { + get: function () { + return this._subscriptionExpiresInitial; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionRefresh", { + /** Number of seconds until subscription auto refresh. */ + get: function () { + if (this._subscriptionRefresh === undefined || this._subscriptionRefreshLastSet === undefined) { + return undefined; + } + var secondsSinceLastSet = Math.floor(Date.now() / 1000) - this._subscriptionRefreshLastSet; + var secondsUntilExpires = this._subscriptionRefresh - secondsSinceLastSet; + return Math.max(secondsUntilExpires, 0); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionState", { + get: function () { + return this._subscriptionState; + }, + enumerable: true, + configurable: true + }); + /** + * Receive in dialog request message from transport. + * @param message The incoming request message. + */ + SubscriptionDialog.prototype.receiveRequest = function (message) { + this.logger.log("SUBSCRIBE dialog " + this.id + " received " + message.method + " request"); + // Request within a dialog out of sequence guard. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.sequenceGuard(message)) { + this.logger.log("SUBSCRIBE dialog " + this.id + " rejected out of order " + message.method + " request."); + return; + } + // Request within a dialog common processing. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + _super.prototype.receiveRequest.call(this, message); + // Switch on method and then delegate. + switch (message.method) { + case messages_1.C.NOTIFY: + this.onNotify(message); + break; + default: + this.logger.log("SUBSCRIBE dialog " + this.id + " received unimplemented " + message.method + " request"); + this.core.replyStateless(message, { statusCode: 501 }); + break; + } + }; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + SubscriptionDialog.prototype.refresh = function () { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var options = {}; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push("Event: " + this.subscriptionEvent); + options.extraHeaders.push("Expires: " + this.subscriptionExpiresInitial); + options.extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + return this.subscribe(undefined, options); + }; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + * @param delegate Delegate to handle responses. + * @param options Options bucket. + */ + SubscriptionDialog.prototype.subscribe = function (delegate, options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.subscriptionState !== subscription_1.SubscriptionState.Pending && this.subscriptionState !== subscription_1.SubscriptionState.Active) { + // FIXME: This needs to be a proper exception + throw new Error("Invalid state " + this.subscriptionState + ". May only re-subscribe while in state \"pending\" or \"active\"."); + } + this.logger.log("SUBSCRIBE dialog " + this.id + " sending SUBSCRIBE request"); + var uac = new re_subscribe_user_agent_client_1.ReSubscribeUserAgentClient(this, delegate, options); + // When refreshing a subscription, a subscriber starts Timer N, set to + // 64*T1, when it sends the SUBSCRIBE request. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + this.N = setTimeout(function () { return _this.timer_N(); }, timers_1.Timers.TIMER_N); + return uac; + }; + /** + * 4.4.1. Dialog Creation and Termination + * A subscription is destroyed after a notifier sends a NOTIFY request + * with a "Subscription-State" of "terminated", or in certain error + * situations described elsewhere in this document. + * https://tools.ietf.org/html/rfc6665#section-4.4.1 + */ + SubscriptionDialog.prototype.terminate = function () { + this.stateTransition(subscription_1.SubscriptionState.Terminated); + this.onTerminated(); + }; + /** + * 4.1.2.3. Unsubscribing + * https://tools.ietf.org/html/rfc6665#section-4.1.2.3 + */ + SubscriptionDialog.prototype.unsubscribe = function () { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var options = {}; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push("Event: " + this.subscriptionEvent); + options.extraHeaders.push("Expires: 0"); + options.extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + return this.subscribe(undefined, options); + }; + /** + * Handle in dialog NOTIFY requests. + * This does not include the first NOTIFY which created the dialog. + * @param message The incoming NOTIFY request message. + */ + SubscriptionDialog.prototype.onNotify = function (message) { + // If, for some reason, the event package designated in the "Event" + // header field of the NOTIFY request is not supported, the subscriber + // will respond with a 489 (Bad Event) response. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var event = message.parseHeader("Event").event; + if (!event || event !== this.subscriptionEvent) { + this.core.replyStateless(message, { statusCode: 489 }); + return; + } + // In the state diagram, "Re-subscription times out" means that an + // attempt to refresh or update the subscription using a new SUBSCRIBE + // request does not result in a NOTIFY request before the corresponding + // Timer N expires. + // https://tools.ietf.org/html/rfc6665#section-4.1.2 + if (this.N) { + clearTimeout(this.N); + this.N = undefined; + } + // NOTIFY requests MUST contain "Subscription-State" header fields that + // indicate the status of the subscription. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = message.parseHeader("Subscription-State"); + if (!subscriptionState || !subscriptionState.state) { + this.core.replyStateless(message, { statusCode: 489 }); + return; + } + var state = subscriptionState.state; + var expires = subscriptionState.expires ? Math.max(subscriptionState.expires, 0) : undefined; + // Update our state and expiration. + switch (state) { + case "pending": + this.stateTransition(subscription_1.SubscriptionState.Pending, expires); + break; + case "active": + this.stateTransition(subscription_1.SubscriptionState.Active, expires); + break; + case "terminated": + this.stateTransition(subscription_1.SubscriptionState.Terminated, expires); + break; + default: + this.logger.warn("Unrecognized subscription state."); + break; + } + // Delegate remainder of NOTIFY handling. + var uas = new notify_user_agent_server_1.NotifyUserAgentServer(this, message); + if (this.delegate && this.delegate.onNotify) { + this.delegate.onNotify(uas); + } + else { + uas.accept(); + } + }; + SubscriptionDialog.prototype.onRefresh = function (request) { + if (this.delegate && this.delegate.onRefresh) { + this.delegate.onRefresh(request); + } + }; + SubscriptionDialog.prototype.onTerminated = function () { + if (this.delegate && this.delegate.onTerminated) { + this.delegate.onTerminated(); + } + }; + SubscriptionDialog.prototype.refreshTimerClear = function () { + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); + this.refreshTimer = undefined; + } + }; + SubscriptionDialog.prototype.refreshTimerSet = function () { + var _this = this; + this.refreshTimerClear(); + if (this.autoRefresh && this.subscriptionExpires > 0) { + var refresh = this.subscriptionExpires * 900; + this._subscriptionRefresh = Math.floor(refresh / 1000); + this._subscriptionRefreshLastSet = Math.floor(Date.now() / 1000); + this.refreshTimer = setTimeout(function () { + _this.refreshTimer = undefined; + _this._subscriptionRefresh = undefined; + _this._subscriptionRefreshLastSet = undefined; + _this.onRefresh(_this.refresh()); + }, refresh); + } + }; + SubscriptionDialog.prototype.stateTransition = function (newState, newExpires) { + var _this = this; + // Assert valid state transitions. + var invalidStateTransition = function () { + _this.logger.warn("Invalid subscription state transition from " + _this.subscriptionState + " to " + newState); + }; + switch (newState) { + case subscription_1.SubscriptionState.Initial: + invalidStateTransition(); + return; + case subscription_1.SubscriptionState.NotifyWait: + invalidStateTransition(); + return; + case subscription_1.SubscriptionState.Pending: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending) { + invalidStateTransition(); + return; + } + break; + case subscription_1.SubscriptionState.Active: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending && + this.subscriptionState !== subscription_1.SubscriptionState.Active) { + invalidStateTransition(); + return; + } + break; + case subscription_1.SubscriptionState.Terminated: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending && + this.subscriptionState !== subscription_1.SubscriptionState.Active) { + invalidStateTransition(); + return; + } + break; + default: + invalidStateTransition(); + return; + } + // If the "Subscription-State" value is "pending", the subscription has + // been received by the notifier, but there is insufficient policy + // information to grant or deny the subscription yet. If the header + // field also contains an "expires" parameter, the subscriber SHOULD + // take it as the authoritative subscription duration and adjust + // accordingly. No further action is necessary on the part of the + // subscriber. The "retry-after" and "reason" parameters have no + // semantics for "pending". + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + if (newState === subscription_1.SubscriptionState.Pending) { + if (newExpires) { + this.subscriptionExpires = newExpires; + } + } + // If the "Subscription-State" header field value is "active", it means + // that the subscription has been accepted and (in general) has been + // authorized. If the header field also contains an "expires" + // parameter, the subscriber SHOULD take it as the authoritative + // subscription duration and adjust accordingly. The "retry-after" and + // "reason" parameters have no semantics for "active". + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + if (newState === subscription_1.SubscriptionState.Active) { + if (newExpires) { + this.subscriptionExpires = newExpires; + } + } + // If the "Subscription-State" value is "terminated", the subscriber + // MUST consider the subscription terminated. The "expires" parameter + // has no semantics for "terminated" -- notifiers SHOULD NOT include an + // "expires" parameter on a "Subscription-State" header field with a + // value of "terminated", and subscribers MUST ignore any such + // parameter, if present. + if (newState === subscription_1.SubscriptionState.Terminated) { + this.dispose(); + } + this._subscriptionState = newState; + }; + /** + * When refreshing a subscription, a subscriber starts Timer N, set to + * 64*T1, when it sends the SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription terminated. If the subscriber receives a success + * response to the SUBSCRIBE request that indicates that no NOTIFY + * request will be generated -- such as the 204 response defined for use + * with the optional extension described in [RFC5839] -- then it MUST + * cancel Timer N. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + SubscriptionDialog.prototype.timer_N = function () { + if (this.subscriptionState !== subscription_1.SubscriptionState.Terminated) { + this.stateTransition(subscription_1.SubscriptionState.Terminated); + this.onTerminated(); + } + }; + return SubscriptionDialog; +}(dialog_1.Dialog)); +exports.SubscriptionDialog = SubscriptionDialog; + + +/***/ }), +/* 56 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(57), exports); + + +/***/ }), +/* 57 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Subscription state. + * https://tools.ietf.org/html/rfc6665#section-4.1.2 + */ +var SubscriptionState; +(function (SubscriptionState) { + SubscriptionState["Initial"] = "Initial"; + SubscriptionState["NotifyWait"] = "NotifyWait"; + SubscriptionState["Pending"] = "Pending"; + SubscriptionState["Active"] = "Active"; + SubscriptionState["Terminated"] = "Terminated"; +})(SubscriptionState = exports.SubscriptionState || (exports.SubscriptionState = {})); + + +/***/ }), +/* 58 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = __webpack_require__(5); +/** + * FIXME: TODO: Should be configurable/variable. + */ +exports.AllowedMethods = [ + messages_1.C.ACK, + messages_1.C.BYE, + messages_1.C.CANCEL, + messages_1.C.INFO, + messages_1.C.INVITE, + messages_1.C.MESSAGE, + messages_1.C.NOTIFY, + messages_1.C.OPTIONS, + messages_1.C.PRACK, + messages_1.C.REFER, + messages_1.C.SUBSCRIBE +]; + + +/***/ }), +/* 59 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var ReSubscribeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReSubscribeUserAgentClient, _super); + function ReSubscribeUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.SUBSCRIBE, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + _this.dialog = dialog; + return _this; + } + ReSubscribeUserAgentClient.prototype.waitNotifyStop = function () { + // TODO: Placeholder. Not utilized currently. + return; + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + ReSubscribeUserAgentClient.prototype.receiveResponse = function (message) { + if (message.statusCode && message.statusCode >= 200 && message.statusCode < 300) { + // The "Expires" header field in a 200-class response to SUBSCRIBE + // request indicates the actual duration for which the subscription will + // remain active (unless refreshed). The received value might be + // smaller than the value indicated in the SUBSCRIBE request but cannot + // be larger; see Section 4.2.1 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + var expires = message.getHeader("Expires"); + if (!expires) { + this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE"); + } + else { + var subscriptionExpiresReceived = Number(expires); + if (this.dialog.subscriptionExpires > subscriptionExpiresReceived) { + this.dialog.subscriptionExpires = subscriptionExpiresReceived; + } + } + } + if (message.statusCode && message.statusCode >= 400 && message.statusCode < 700) { + // If a SUBSCRIBE request to refresh a subscription receives a 404, 405, + // 410, 416, 480-485, 489, 501, or 604 response, the subscriber MUST + // consider the subscription terminated. (See [RFC5057] for further + // details and notes about the effect of error codes on dialogs and + // usages within dialog, such as subscriptions). If the subscriber + // wishes to re-subscribe to the state, he does so by composing an + // unrelated initial SUBSCRIBE request with a freshly generated Call-ID + // and a new, unique "From" tag (see Section 4.1.2.1). + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + var errorCodes = [404, 405, 410, 416, 480, 481, 482, 483, 484, 485, 489, 501, 604]; + if (errorCodes.includes(message.statusCode)) { + this.dialog.terminate(); + } + // If a SUBSCRIBE request to refresh a subscription fails with any error + // code other than those listed above, the original subscription is + // still considered valid for the duration of the most recently known + // "Expires" value as negotiated by the most recent successful SUBSCRIBE + // transaction, or as communicated by a NOTIFY request in its + // "Subscription-State" header field "expires" parameter. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + } + _super.prototype.receiveResponse.call(this, message); + }; + return ReSubscribeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReSubscribeUserAgentClient = ReSubscribeUserAgentClient; + + +/***/ }), +/* 60 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(61), exports); +tslib_1.__exportStar(__webpack_require__(62), exports); +tslib_1.__exportStar(__webpack_require__(63), exports); + + +/***/ }), +/* 61 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Log levels. + * @public + */ +var Levels; +(function (Levels) { + Levels[Levels["error"] = 0] = "error"; + Levels[Levels["warn"] = 1] = "warn"; + Levels[Levels["log"] = 2] = "log"; + Levels[Levels["debug"] = 3] = "debug"; +})(Levels = exports.Levels || (exports.Levels = {})); + + +/***/ }), +/* 62 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var levels_1 = __webpack_require__(61); +var logger_1 = __webpack_require__(63); +/** + * Logger. + * @public + */ +var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + this.builtinEnabled = true; + this._level = levels_1.Levels.log; + this.loggers = {}; + this.logger = this.getLogger("sip:loggerfactory"); + } + Object.defineProperty(LoggerFactory.prototype, "level", { + get: function () { return this._level; }, + set: function (newLevel) { + if (newLevel >= 0 && newLevel <= 3) { + this._level = newLevel; + } + else if (newLevel > 3) { + this._level = 3; + } + else if (levels_1.Levels.hasOwnProperty(newLevel)) { + this._level = newLevel; + } + else { + this.logger.error("invalid 'level' parameter value: " + JSON.stringify(newLevel)); + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(LoggerFactory.prototype, "connector", { + get: function () { + return this._connector; + }, + set: function (value) { + if (!value) { + this._connector = undefined; + } + else if (typeof value === "function") { + this._connector = value; + } + else { + this.logger.error("invalid 'connector' parameter value: " + JSON.stringify(value)); + } + }, + enumerable: true, + configurable: true + }); + LoggerFactory.prototype.getLogger = function (category, label) { + if (label && this.level === 3) { + return new logger_1.Logger(this, category, label); + } + else if (this.loggers[category]) { + return this.loggers[category]; + } + else { + var logger = new logger_1.Logger(this, category); + this.loggers[category] = logger; + return logger; + } + }; + LoggerFactory.prototype.genericLog = function (levelToLog, category, label, content) { + if (this.level >= levelToLog) { + if (this.builtinEnabled) { + this.print(levelToLog, category, label, content); + } + } + if (this.connector) { + this.connector(levels_1.Levels[levelToLog], category, label, content); + } + }; + LoggerFactory.prototype.print = function (levelToLog, category, label, content) { + if (typeof content === "string") { + var prefix = [new Date(), category]; + if (label) { + prefix.push(label); + } + content = prefix.concat(content).join(" | "); + } + switch (levelToLog) { + case levels_1.Levels.error: + // tslint:disable-next-line:no-console + console.error(content); + break; + case levels_1.Levels.warn: + // tslint:disable-next-line:no-console + console.warn(content); + break; + case levels_1.Levels.log: + // tslint:disable-next-line:no-console + console.log(content); + break; + case levels_1.Levels.debug: + // tslint:disable-next-line:no-console + console.debug(content); + break; + default: + break; + } + }; + return LoggerFactory; +}()); +exports.LoggerFactory = LoggerFactory; + + +/***/ }), +/* 63 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var levels_1 = __webpack_require__(61); +/** + * Logger. + * @public + */ +var Logger = /** @class */ (function () { + function Logger(logger, category, label) { + this.logger = logger; + this.category = category; + this.label = label; + } + Logger.prototype.error = function (content) { this.genericLog(levels_1.Levels.error, content); }; + Logger.prototype.warn = function (content) { this.genericLog(levels_1.Levels.warn, content); }; + Logger.prototype.log = function (content) { this.genericLog(levels_1.Levels.log, content); }; + Logger.prototype.debug = function (content) { this.genericLog(levels_1.Levels.debug, content); }; + Logger.prototype.genericLog = function (level, content) { + this.logger.genericLog(level, this.category, this.label, content); + }; + return Logger; +}()); +exports.Logger = Logger; + + +/***/ }), +/* 64 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(65), exports); + + +/***/ }), +/* 65 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agents_1 = __webpack_require__(66); +var allowed_methods_1 = __webpack_require__(58); +/** + * This is ported from UA.C.ACCEPTED_BODY_TYPES. + * FIXME: TODO: Should be configurable/variable. + */ +var acceptedBodyTypes = [ + "application/sdp", + "application/dtmf-relay" +]; +/** + * Core: Core designates the functions specific to a particular type + * of SIP entity, i.e., specific to either a stateful or stateless + * proxy, a user agent or registrar. All cores, except those for + * the stateless proxy, are transaction users. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAC Core: The set of processing functions required of a UAC that + * reside above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAS Core: The set of processing functions required at a UAS that + * resides above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentCore = /** @class */ (function () { + /** + * Constructor. + * @param configuration Configuration. + * @param delegate Delegate. + */ + function UserAgentCore(configuration, delegate) { + if (delegate === void 0) { delegate = {}; } + /** UACs. */ + this.userAgentClients = new Map(); + /** UASs. */ + this.userAgentServers = new Map(); + this.configuration = configuration; + this.delegate = delegate; + this.dialogs = new Map(); + this.subscribers = new Map(); + this.logger = configuration.loggerFactory.getLogger("sip.user-agent-core"); + } + /** Destructor. */ + UserAgentCore.prototype.dispose = function () { + this.reset(); + }; + /** Reset. */ + UserAgentCore.prototype.reset = function () { + this.dialogs.forEach(function (dialog) { return dialog.dispose(); }); + this.dialogs.clear(); + this.subscribers.forEach(function (subscriber) { return subscriber.dispose(); }); + this.subscribers.clear(); + this.userAgentClients.forEach(function (uac) { return uac.dispose(); }); + this.userAgentClients.clear(); + this.userAgentServers.forEach(function (uac) { return uac.dispose(); }); + this.userAgentServers.clear(); + }; + Object.defineProperty(UserAgentCore.prototype, "loggerFactory", { + /** Logger factory. */ + get: function () { + return this.configuration.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentCore.prototype, "transport", { + /** Transport. */ + get: function () { + var transport = this.configuration.transportAccessor(); + if (!transport) { + throw new Error("Transport undefined."); + } + return transport; + }, + enumerable: true, + configurable: true + }); + /** + * Send INVITE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.invite = function (request, delegate) { + return new user_agents_1.InviteUserAgentClient(this, request, delegate); + }; + /** + * Send MESSAGE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.message = function (request, delegate) { + return new user_agents_1.MessageUserAgentClient(this, request, delegate); + }; + /** + * Send PUBLISH. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.publish = function (request, delegate) { + return new user_agents_1.PublishUserAgentClient(this, request, delegate); + }; + /** + * Send REGISTER. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.register = function (request, delegate) { + return new user_agents_1.RegisterUserAgentClient(this, request, delegate); + }; + /** + * Send SUBSCRIBE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.subscribe = function (request, delegate) { + return new user_agents_1.SubscribeUserAgentClient(this, request, delegate); + }; + /** + * Send a request. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.request = function (request, delegate) { + return new user_agents_1.UserAgentClient(transactions_1.NonInviteClientTransaction, this, request, delegate); + }; + /** + * Outgoing request message factory function. + * @param method Method. + * @param requestURI Request-URI. + * @param fromURI From URI. + * @param toURI To URI. + * @param options Request options. + * @param extraHeaders Extra headers to add. + * @param body Message body. + */ + UserAgentCore.prototype.makeOutgoingRequestMessage = function (method, requestURI, fromURI, toURI, options, extraHeaders, body) { + // default values from user agent configuration + var callIdPrefix = this.configuration.sipjsId; + var fromDisplayName = this.configuration.displayName; + var forceRport = this.configuration.viaForceRport; + var hackViaTcp = this.configuration.hackViaTcp; + var optionTags = this.configuration.supportedOptionTags.slice(); + if (method === messages_1.C.REGISTER) { + optionTags.push("path", "gruu"); + } + if (method === messages_1.C.INVITE && (this.configuration.contact.pubGruu || this.configuration.contact.tempGruu)) { + optionTags.push("gruu"); + } + var routeSet = this.configuration.routeSet; + var userAgentString = this.configuration.userAgentHeaderFieldValue; + var viaHost = this.configuration.viaHost; + var defaultOptions = { + callIdPrefix: callIdPrefix, + forceRport: forceRport, + fromDisplayName: fromDisplayName, + hackViaTcp: hackViaTcp, + optionTags: optionTags, + routeSet: routeSet, + userAgentString: userAgentString, + viaHost: viaHost, + }; + // merge provided options with default options + var requestOptions = tslib_1.__assign({}, defaultOptions, options); + return new messages_1.OutgoingRequestMessage(method, requestURI, fromURI, toURI, requestOptions, extraHeaders, body); + }; + /** + * Handle an incoming request message from the transport. + * @param message Incoming request message from transport layer. + */ + UserAgentCore.prototype.receiveIncomingRequestFromTransport = function (message) { + this.receiveRequestFromTransport(message); + }; + /** + * Handle an incoming response message from the transport. + * @param message Incoming response message from transport layer. + */ + UserAgentCore.prototype.receiveIncomingResponseFromTransport = function (message) { + this.receiveResponseFromTransport(message); + }; + /** + * A stateless UAS is a UAS that does not maintain transaction state. + * It replies to requests normally, but discards any state that would + * ordinarily be retained by a UAS after a response has been sent. If a + * stateless UAS receives a retransmission of a request, it regenerates + * the response and re-sends it, just as if it were replying to the first + * instance of the request. A UAS cannot be stateless unless the request + * processing for that method would always result in the same response + * if the requests are identical. This rules out stateless registrars, + * for example. Stateless UASs do not use a transaction layer; they + * receive requests directly from the transport layer and send responses + * directly to the transport layer. + * https://tools.ietf.org/html/rfc3261#section-8.2.7 + * @param message Incoming request message to reply to. + * @param statusCode Status code to reply with. + */ + UserAgentCore.prototype.replyStateless = function (message, options) { + var userAgent = this.configuration.userAgentHeaderFieldValue; + var supported = this.configuration.supportedOptionTagsResponse; + options = tslib_1.__assign({}, options, { userAgent: userAgent, supported: supported }); + var response = messages_1.constructOutgoingResponse(message, options); + this.transport.send(response.message); + return response; + }; + /** + * In Section 18.2.1, replace the last paragraph with: + * + * Next, the server transport attempts to match the request to a + * server transaction. It does so using the matching rules described + * in Section 17.2.3. If a matching server transaction is found, the + * request is passed to that transaction for processing. If no match + * is found, the request is passed to the core, which may decide to + * construct a new server transaction for that request. + * https://tools.ietf.org/html/rfc6026#section-8.10 + * @param message Incoming request message from transport layer. + */ + UserAgentCore.prototype.receiveRequestFromTransport = function (message) { + // When a request is received from the network by the server, it has to + // be matched to an existing transaction. This is accomplished in the + // following manner. + // + // The branch parameter in the topmost Via header field of the request + // is examined. If it is present and begins with the magic cookie + // "z9hG4bK", the request was generated by a client transaction + // compliant to this specification. Therefore, the branch parameter + // will be unique across all transactions sent by that client. The + // request matches a transaction if: + // + // 1. the branch parameter in the request is equal to the one in the + // top Via header field of the request that created the + // transaction, and + // + // 2. the sent-by value in the top Via of the request is equal to the + // one in the request that created the transaction, and + // + // 3. the method of the request matches the one that created the + // transaction, except for ACK, where the method of the request + // that created the transaction is INVITE. + // + // This matching rule applies to both INVITE and non-INVITE transactions + // alike. + // + // The sent-by value is used as part of the matching process because + // there could be accidental or malicious duplication of branch + // parameters from different clients. + // https://tools.ietf.org/html/rfc3261#section-17.2.3 + var transactionId = message.viaBranch; // FIXME: Currently only using rule 1... + var uas = this.userAgentServers.get(transactionId); + // When receiving an ACK that matches an existing INVITE server + // transaction and that does not contain a branch parameter containing + // the magic cookie defined in RFC 3261, the matching transaction MUST + // be checked to see if it is in the "Accepted" state. If it is, then + // the ACK must be passed directly to the transaction user instead of + // being absorbed by the transaction state machine. This is necessary + // as requests from RFC 2543 clients will not include a unique branch + // parameter, and the mechanisms for calculating the transaction ID from + // such a request will be the same for both INVITE and ACKs. + // https://tools.ietf.org/html/rfc6026#section-6 + // Any ACKs received from the network while in the "Accepted" state MUST be + // passed directly to the TU and not absorbed. + // https://tools.ietf.org/html/rfc6026#section-7.1 + if (message.method === messages_1.C.ACK) { + if (uas && uas.transaction.state === transactions_1.TransactionState.Accepted) { + if (uas instanceof user_agents_1.InviteUserAgentServer) { + // These are ACKs matching an INVITE server transaction. + // These should never happen with RFC 3261 compliant user agents + // (would be a broken ACK to negative final response or something) + // but is apparently how RFC 2543 user agents do things. + // We are not currently supporting this case. + // NOTE: Not backwards compatible with RFC 2543 (no support for strict-routing). + this.logger.warn("Discarding out of dialog ACK after 2xx response sent on transaction " + transactionId + "."); + return; + } + } + } + // The CANCEL method requests that the TU at the server side cancel a + // pending transaction. The TU determines the transaction to be + // cancelled by taking the CANCEL request, and then assuming that the + // request method is anything but CANCEL or ACK and applying the + // transaction matching procedures of Section 17.2.3. The matching + // transaction is the one to be cancelled. + // https://tools.ietf.org/html/rfc3261#section-9.2 + if (message.method === messages_1.C.CANCEL) { + if (uas) { + // Regardless of the method of the original request, as long as the + // CANCEL matched an existing transaction, the UAS answers the CANCEL + // request itself with a 200 (OK) response. + // https://tools.ietf.org/html/rfc3261#section-9.2 + this.replyStateless(message, { statusCode: 200 }); + // If the transaction for the original request still exists, the behavior + // of the UAS on receiving a CANCEL request depends on whether it has already + // sent a final response for the original request. If it has, the CANCEL + // request has no effect on the processing of the original request, no + // effect on any session state, and no effect on the responses generated + // for the original request. If the UAS has not issued a final response + // for the original request, its behavior depends on the method of the + // original request. If the original request was an INVITE, the UAS + // SHOULD immediately respond to the INVITE with a 487 (Request + // Terminated). + // https://tools.ietf.org/html/rfc3261#section-9.2 + if (uas.transaction instanceof transactions_1.InviteServerTransaction && + uas.transaction.state === transactions_1.TransactionState.Proceeding) { + if (uas instanceof user_agents_1.InviteUserAgentServer) { + uas.receiveCancel(message); + } + // A CANCEL request has no impact on the processing of + // transactions with any other method defined in this specification. + // https://tools.ietf.org/html/rfc3261#section-9.2 + } + } + else { + // If the UAS did not find a matching transaction for the CANCEL + // according to the procedure above, it SHOULD respond to the CANCEL + // with a 481 (Call Leg/Transaction Does Not Exist). + // https://tools.ietf.org/html/rfc3261#section-9.2 + this.replyStateless(message, { statusCode: 481 }); + } + return; + } + // If a matching server transaction is found, the request is passed to that + // transaction for processing. + // https://tools.ietf.org/html/rfc6026#section-8.10 + if (uas) { + uas.transaction.receiveRequest(message); + return; + } + // If no match is found, the request is passed to the core, which may decide to + // construct a new server transaction for that request. + // https://tools.ietf.org/html/rfc6026#section-8.10 + this.receiveRequest(message); + return; + }; + /** + * UAC and UAS procedures depend strongly on two factors. First, based + * on whether the request or response is inside or outside of a dialog, + * and second, based on the method of a request. Dialogs are discussed + * thoroughly in Section 12; they represent a peer-to-peer relationship + * between user agents and are established by specific SIP methods, such + * as INVITE. + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveRequest = function (message) { + // 8.2 UAS Behavior + // UASs SHOULD process the requests in the order of the steps that + // follow in this section (that is, starting with authentication, then + // inspecting the method, the header fields, and so on throughout the + // remainder of this section). + // https://tools.ietf.org/html/rfc3261#section-8.2 + // 8.2.1 Method Inspection + // Once a request is authenticated (or authentication is skipped), the + // UAS MUST inspect the method of the request. If the UAS recognizes + // but does not support the method of a request, it MUST generate a 405 + // (Method Not Allowed) response. Procedures for generating responses + // are described in Section 8.2.6. The UAS MUST also add an Allow + // header field to the 405 (Method Not Allowed) response. The Allow + // header field MUST list the set of methods supported by the UAS + // generating the message. + // https://tools.ietf.org/html/rfc3261#section-8.2.1 + if (!allowed_methods_1.AllowedMethods.includes(message.method)) { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + this.replyStateless(message, { + statusCode: 405, + extraHeaders: [allowHeader] + }); + return; + } + // 8.2.2 Header Inspection + // https://tools.ietf.org/html/rfc3261#section-8.2.2 + if (!message.ruri) { // FIXME: A request message should always have an ruri + throw new Error("Request-URI undefined."); + } + // 8.2.2.1 To and Request-URI + // If the Request-URI uses a scheme not supported by the UAS, it SHOULD + // reject the request with a 416 (Unsupported URI Scheme) response. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + if (message.ruri.scheme !== "sip") { + this.replyStateless(message, { statusCode: 416 }); + return; + } + // 8.2.2.1 To and Request-URI + // If the Request-URI does not identify an address that the + // UAS is willing to accept requests for, it SHOULD reject + // the request with a 404 (Not Found) response. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + var ruri = message.ruri; + var ruriMatches = function (uri) { + return !!uri && uri.user === ruri.user; + }; + if (!ruriMatches(this.configuration.aor) && + !(ruriMatches(this.configuration.contact.uri) || + ruriMatches(this.configuration.contact.pubGruu) || + ruriMatches(this.configuration.contact.tempGruu))) { + this.logger.warn("Request-URI does not point to us."); + if (message.method !== messages_1.C.ACK) { + this.replyStateless(message, { statusCode: 404 }); + } + return; + } + // 8.2.2.1 To and Request-URI + // Other potential sources of received Request-URIs include + // the Contact header fields of requests and responses sent by the UA + // that establish or refresh dialogs. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + if (message.method === messages_1.C.INVITE) { + if (!message.hasHeader("Contact")) { + this.replyStateless(message, { + statusCode: 400, + reasonPhrase: "Missing Contact Header" + }); + return; + } + } + // 8.2.2.2 Merged Requests + // If the request has no tag in the To header field, the UAS core MUST + // check the request against ongoing transactions. If the From tag, + // Call-ID, and CSeq exactly match those associated with an ongoing + // transaction, but the request does not match that transaction (based + // on the matching rules in Section 17.2.3), the UAS core SHOULD + // generate a 482 (Loop Detected) response and pass it to the server + // transaction. + // + // The same request has arrived at the UAS more than once, following + // different paths, most likely due to forking. The UAS processes + // the first such request received and responds with a 482 (Loop + // Detected) to the rest of them. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.2 + if (!message.toTag) { + var transactionId = message.viaBranch; + if (!this.userAgentServers.has(transactionId)) { + var mergedRequest = Array.from(this.userAgentServers.values()) + .some(function (uas) { + return uas.transaction.request.fromTag === message.fromTag && + uas.transaction.request.callId === message.callId && + uas.transaction.request.cseq === message.cseq; + }); + if (mergedRequest) { + this.replyStateless(message, { statusCode: 482 }); + return; + } + } + } + // 8.2.2.3 Require + // https://tools.ietf.org/html/rfc3261#section-8.2.2.3 + // TODO + // 8.2.3 Content Processing + // https://tools.ietf.org/html/rfc3261#section-8.2.3 + // TODO + // 8.2.4 Applying Extensions + // https://tools.ietf.org/html/rfc3261#section-8.2.4 + // TODO + // 8.2.5 Processing the Request + // Assuming all of the checks in the previous subsections are passed, + // the UAS processing becomes method-specific. + // https://tools.ietf.org/html/rfc3261#section-8.2.5 + // The UAS will receive the request from the transaction layer. If the + // request has a tag in the To header field, the UAS core computes the + // dialog identifier corresponding to the request and compares it with + // existing dialogs. If there is a match, this is a mid-dialog request. + // In that case, the UAS first applies the same processing rules for + // requests outside of a dialog, discussed in Section 8.2. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (message.toTag) { + this.receiveInsideDialogRequest(message); + } + else { + this.receiveOutsideDialogRequest(message); + } + return; + }; + /** + * Once a dialog has been established between two UAs, either of them + * MAY initiate new transactions as needed within the dialog. The UA + * sending the request will take the UAC role for the transaction. The + * UA receiving the request will take the UAS role. Note that these may + * be different roles than the UAs held during the transaction that + * established the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2 + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveInsideDialogRequest = function (message) { + // NOTIFY requests are matched to such SUBSCRIBE requests if they + // contain the same "Call-ID", a "To" header field "tag" parameter that + // matches the "From" header field "tag" parameter of the SUBSCRIBE + // request, and the same "Event" header field. Rules for comparisons of + // the "Event" header fields are described in Section 8.2.1. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (message.method === messages_1.C.NOTIFY) { + var event_1 = message.parseHeader("Event"); + if (!event_1 || !event_1.event) { + this.replyStateless(message, { statusCode: 489 }); + return; + } + // FIXME: Subscriber id should also matching on event id. + var subscriberId = message.callId + message.toTag + event_1.event; + var subscriber = this.subscribers.get(subscriberId); + if (subscriber) { + var uas = new user_agents_1.NotifyUserAgentServer(this, message); + subscriber.onNotify(uas); + return; + } + } + // Requests sent within a dialog, as any other requests, are atomic. If + // a particular request is accepted by the UAS, all the state changes + // associated with it are performed. If the request is rejected, none + // of the state changes are performed. + // + // Note that some requests, such as INVITEs, affect several pieces of + // state. + // + // The UAS will receive the request from the transaction layer. If the + // request has a tag in the To header field, the UAS core computes the + // dialog identifier corresponding to the request and compares it with + // existing dialogs. If there is a match, this is a mid-dialog request. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + var dialogId = message.callId + message.toTag + message.fromTag; + var dialog = this.dialogs.get(dialogId); + if (dialog) { + // [Sip-implementors] Reg. SIP reinvite, UPDATE and OPTIONS + // You got the question right. + // + // And you got the right answer too. :-) + // + // Thanks, + // Paul + // + // Robert Sparks wrote: + // > So I've lost track of the question during the musing. + // > + // > I _think_ the fundamental question being asked is this: + // > + // > Is an endpoint required to reject (with a 481) an OPTIONS request that + // > arrives with at to-tag but does not match any existing dialog state. + // > (Assuming some earlier requirement hasn't forced another error code). Or + // > is it OK if it just sends + // > a 200 OK anyhow. + // > + // > My take on the collection of specs is that its _not_ ok for it to send + // > the 200 OK anyhow and that it is required to send + // > the 481. I base this primarily on these sentences from 11.2 in 3261: + // > + // > The response to an OPTIONS is constructed using the standard rules + // > for a SIP response as discussed in Section 8.2.6. The response code + // > chosen MUST be the same that would have been chosen had the request + // > been an INVITE. + // > + // > Did I miss the point of the question? + // > + // > On May 15, 2008, at 12:48 PM, Paul Kyzivat wrote: + // > + // >> [Including Robert in hopes of getting his insight on this.] + // https://lists.cs.columbia.edu/pipermail/sip-implementors/2008-May/019178.html + // + // Requests that do not change in any way the state of a dialog may be + // received within a dialog (for example, an OPTIONS request). They are + // processed as if they had been received outside the dialog. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (message.method === messages_1.C.OPTIONS) { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var acceptHeader = "Accept: " + acceptedBodyTypes.toString(); + this.replyStateless(message, { + statusCode: 200, + extraHeaders: [allowHeader, acceptHeader] + }); + return; + } + // Pass the incoming request to the dialog for further handling. + dialog.receiveRequest(message); + return; + } + // The most important behaviors of a stateless UAS are the following: + // ... + // o A stateless UAS MUST ignore ACK requests. + // ... + // https://tools.ietf.org/html/rfc3261#section-8.2.7 + if (message.method === messages_1.C.ACK) { + // If a final response to an INVITE was sent statelessly, + // the corresponding ACK: + // - will not match an existing transaction + // - may have tag in the To header field + // - not not match any existing dialogs + // Absorb unmatched ACKs. + return; + } + // If the request has a tag in the To header field, but the dialog + // identifier does not match any existing dialogs, the UAS may have + // crashed and restarted, or it may have received a request for a + // different (possibly failed) UAS (the UASs can construct the To tags + // so that a UAS can identify that the tag was for a UAS for which it is + // providing recovery). Another possibility is that the incoming + // request has been simply mis-routed. Based on the To tag, the UAS MAY + // either accept or reject the request. Accepting the request for + // acceptable To tags provides robustness, so that dialogs can persist + // even through crashes. UAs wishing to support this capability must + // take into consideration some issues such as choosing monotonically + // increasing CSeq sequence numbers even across reboots, reconstructing + // the route set, and accepting out-of-range RTP timestamps and sequence + // numbers. + // + // If the UAS wishes to reject the request because it does not wish to + // recreate the dialog, it MUST respond to the request with a 481 + // (Call/Transaction Does Not Exist) status code and pass that to the + // server transaction. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + this.replyStateless(message, { statusCode: 481 }); + return; + }; + /** + * Assuming all of the checks in the previous subsections are passed, + * the UAS processing becomes method-specific. + * https://tools.ietf.org/html/rfc3261#section-8.2.5 + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveOutsideDialogRequest = function (message) { + switch (message.method) { + case messages_1.C.ACK: + // Absorb stray out of dialog ACKs + break; + case messages_1.C.BYE: + // If the BYE does not match an existing dialog, the UAS core SHOULD + // generate a 481 (Call/Transaction Does Not Exist) response and pass + // that to the server transaction. This rule means that a BYE sent + // without tags by a UAC will be rejected. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + this.replyStateless(message, { statusCode: 481 }); + break; + case messages_1.C.CANCEL: + throw new Error("Unexpected out of dialog request method " + message.method + "."); + break; + case messages_1.C.INFO: + // Use of the INFO method does not constitute a separate dialog usage. + // INFO messages are always part of, and share the fate of, an invite + // dialog usage [RFC5057]. INFO messages cannot be sent as part of + // other dialog usages, or outside an existing dialog. + // https://tools.ietf.org/html/rfc6086#section-1 + this.replyStateless(message, { statusCode: 405 }); // Should never happen + break; + case messages_1.C.INVITE: + // https://tools.ietf.org/html/rfc3261#section-13.3.1 + { + var uas = new user_agents_1.InviteUserAgentServer(this, message); + this.delegate.onInvite ? + this.delegate.onInvite(uas) : + uas.reject(); + } + break; + case messages_1.C.MESSAGE: + // MESSAGE requests are discouraged inside a dialog. Implementations + // are restricted from creating a usage for the purpose of carrying a + // sequence of MESSAGE requests (though some implementations use it that + // way, against the standard recommendation). + // https://tools.ietf.org/html/rfc5057#section-5.3 + { + var uas = new user_agents_1.MessageUserAgentServer(this, message); + this.delegate.onMessage ? + this.delegate.onMessage(uas) : + uas.accept(); + } + break; + case messages_1.C.NOTIFY: + // Obsoleted by: RFC 6665 + // If any non-SUBSCRIBE mechanisms are defined to create subscriptions, + // it is the responsibility of the parties defining those mechanisms to + // ensure that correlation of a NOTIFY message to the corresponding + // subscription is possible. Designers of such mechanisms are also + // warned to make a distinction between sending a NOTIFY message to a + // subscriber who is aware of the subscription, and sending a NOTIFY + // message to an unsuspecting node. The latter behavior is invalid, and + // MUST receive a "481 Subscription does not exist" response (unless + // some other 400- or 500-class error code is more applicable), as + // described in section 3.2.4. In other words, knowledge of a + // subscription must exist in both the subscriber and the notifier to be + // valid, even if installed via a non-SUBSCRIBE mechanism. + // https://tools.ietf.org/html/rfc3265#section-3.2 + // + // NOTIFY requests are sent to inform subscribers of changes in state to + // which the subscriber has a subscription. Subscriptions are created + // using the SUBSCRIBE method. In legacy implementations, it is + // possible that other means of subscription creation have been used. + // However, this specification does not allow the creation of + // subscriptions except through SUBSCRIBE requests and (for backwards- + // compatibility) REFER requests [RFC3515]. + // https://tools.ietf.org/html/rfc6665#section-3.2 + { + var uas = new user_agents_1.NotifyUserAgentServer(this, message); + this.delegate.onNotify ? + this.delegate.onNotify(uas) : + this.replyStateless(message, { statusCode: 405 }); + } + break; + case messages_1.C.OPTIONS: + // https://tools.ietf.org/html/rfc3261#section-11.2 + { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var acceptHeader = "Accept: " + acceptedBodyTypes.toString(); + this.replyStateless(message, { + statusCode: 200, + extraHeaders: [allowHeader, acceptHeader] + }); + } + break; + case messages_1.C.REFER: + // https://tools.ietf.org/html/rfc3515#section-2.4.2 + { + var uas = new user_agents_1.ReferUserAgentServer(this, message); + this.delegate.onRefer ? + this.delegate.onRefer(uas) : + this.replyStateless(message, { statusCode: 405 }); + } + break; + case messages_1.C.SUBSCRIBE: + // https://tools.ietf.org/html/rfc6665#section-4.2 + { + var uas = new user_agents_1.SubscribeUserAgentServer(this, message); + this.delegate.onSubscribe ? + this.delegate.onSubscribe(uas) : + uas.reject(); + } + break; + default: + throw new Error("Unexpected out of dialog request method " + message.method + "."); + } + return; + }; + /** + * Responses are first processed by the transport layer and then passed + * up to the transaction layer. The transaction layer performs its + * processing and then passes the response up to the TU. The majority + * of response processing in the TU is method specific. However, there + * are some general behaviors independent of the method. + * https://tools.ietf.org/html/rfc3261#section-8.1.3 + * @param message Incoming response message from transport layer. + */ + UserAgentCore.prototype.receiveResponseFromTransport = function (message) { + // 8.1.3.1 Transaction Layer Errors + // https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + // Handled by transaction layer callbacks. + // 8.1.3.2 Unrecognized Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + // TODO + // 8.1.3.3 Vias + // https://tools.ietf.org/html/rfc3261#section-8.1.3.3 + if (message.getHeaders("via").length > 1) { + this.logger.warn("More than one Via header field present in the response, dropping"); + return; + } + // 8.1.3.4 Processing 3xx Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.4 + // TODO + // 8.1.3.5 Processing 4xx Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + // TODO + // When the transport layer in the client receives a response, it has to + // determine which client transaction will handle the response, so that + // the processing of Sections 17.1.1 and 17.1.2 can take place. The + // branch parameter in the top Via header field is used for this + // purpose. A response matches a client transaction under two + // conditions: + // + // 1. If the response has the same value of the branch parameter in + // the top Via header field as the branch parameter in the top + // Via header field of the request that created the transaction. + // + // 2. If the method parameter in the CSeq header field matches the + // method of the request that created the transaction. The + // method is needed since a CANCEL request constitutes a + // different transaction, but shares the same value of the branch + // parameter. + // https://tools.ietf.org/html/rfc3261#section-17.1.3 + var userAgentClientId = message.viaBranch + message.method; + var userAgentClient = this.userAgentClients.get(userAgentClientId); + // The client transport uses the matching procedures of Section + // 17.1.3 to attempt to match the response to an existing + // transaction. If there is a match, the response MUST be passed to + // that transaction. Otherwise, any element other than a stateless + // proxy MUST silently discard the response. + // https://tools.ietf.org/html/rfc6026#section-8.9 + if (userAgentClient) { + userAgentClient.transaction.receiveResponse(message); + } + else { + this.logger.warn("Discarding unmatched " + message.statusCode + " response to " + message.method + " " + userAgentClientId + "."); + } + }; + return UserAgentCore; +}()); +exports.UserAgentCore = UserAgentCore; + + +/***/ }), +/* 66 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(41), exports); +tslib_1.__exportStar(__webpack_require__(43), exports); +tslib_1.__exportStar(__webpack_require__(67), exports); +tslib_1.__exportStar(__webpack_require__(46), exports); +tslib_1.__exportStar(__webpack_require__(68), exports); +tslib_1.__exportStar(__webpack_require__(69), exports); +tslib_1.__exportStar(__webpack_require__(70), exports); +tslib_1.__exportStar(__webpack_require__(71), exports); +tslib_1.__exportStar(__webpack_require__(47), exports); +tslib_1.__exportStar(__webpack_require__(48), exports); +tslib_1.__exportStar(__webpack_require__(72), exports); +tslib_1.__exportStar(__webpack_require__(49), exports); +tslib_1.__exportStar(__webpack_require__(50), exports); +tslib_1.__exportStar(__webpack_require__(51), exports); +tslib_1.__exportStar(__webpack_require__(52), exports); +tslib_1.__exportStar(__webpack_require__(59), exports); +tslib_1.__exportStar(__webpack_require__(73), exports); +tslib_1.__exportStar(__webpack_require__(53), exports); +tslib_1.__exportStar(__webpack_require__(54), exports); +tslib_1.__exportStar(__webpack_require__(74), exports); +tslib_1.__exportStar(__webpack_require__(75), exports); +tslib_1.__exportStar(__webpack_require__(76), exports); +tslib_1.__exportStar(__webpack_require__(42), exports); +tslib_1.__exportStar(__webpack_require__(44), exports); + + +/***/ }), +/* 67 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var CancelUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(CancelUserAgentClient, _super); + function CancelUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return CancelUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.CancelUserAgentClient = CancelUserAgentClient; + + +/***/ }), +/* 68 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var dialogs_1 = __webpack_require__(3); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.2 UAC Processing + * https://tools.ietf.org/html/rfc3261#section-13.2 + */ +var InviteUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(InviteUserAgentClient, _super); + function InviteUserAgentClient(core, message, delegate) { + var _this = _super.call(this, transactions_1.InviteClientTransaction, core, message, delegate) || this; + _this.confirmedDialogAcks = new Map(); + _this.confirmedDialogs = new Map(); + _this.earlyDialogs = new Map(); + _this.delegate = delegate; + return _this; + } + InviteUserAgentClient.prototype.dispose = function () { + // The UAC core considers the INVITE transaction completed 64*T1 seconds + // after the reception of the first 2xx response. At this point all the + // early dialogs that have not transitioned to established dialogs are + // terminated. Once the INVITE transaction is considered completed by + // the UAC core, no more new 2xx responses are expected to arrive. + // + // If, after acknowledging any 2xx response to an INVITE, the UAC does + // not want to continue with that dialog, then the UAC MUST terminate + // the dialog by sending a BYE request as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + _super.prototype.dispose.call(this); + }; + /** + * Once the INVITE has been passed to the INVITE client transaction, the + * UAC waits for responses for the INVITE. + * https://tools.ietf.org/html/rfc3261#section-13.2.2 + * @param incomingResponse Incoming response to INVITE request. + */ + InviteUserAgentClient.prototype.receiveResponse = function (message) { + var _this = this; + if (!this.authenticationGuard(message)) { + return; + } + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + return; + case /^1[0-9]{2}$/.test(statusCode): + // Zero, one or multiple provisional responses may arrive before one or + // more final responses are received. Provisional responses for an + // INVITE request can create "early dialogs". If a provisional response + // has a tag in the To field, and if the dialog ID of the response does + // not match an existing dialog, one is constructed using the procedures + // defined in Section 12.1.2. + // + // The early dialog will only be needed if the UAC needs to send a + // request to its peer within the dialog before the initial INVITE + // transaction completes. Header fields present in a provisional + // response are applicable as long as the dialog is in the early state + // (for example, an Allow header field in a provisional response + // contains the methods that can be used in the dialog while this is in + // the early state). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.1 + { + // Provisional without to tag, no dialog to create. + if (!message.toTag) { + this.logger.warn("Non-100 1xx INVITE response received without a to tag, dropping."); + return; + } + // Compute dialog state. + var dialogState = dialogs_1.Dialog.initialDialogStateForUserAgentClient(this.message, message); + // Have existing early dialog or create a new one. + var earlyDialog = this.earlyDialogs.get(dialogState.id); + if (!earlyDialog) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + earlyDialog = new dialogs_1.SessionDialog(transaction, this.core, dialogState); + this.earlyDialogs.set(earlyDialog.id, earlyDialog); + } + // Guard against out of order reliable provisional responses. + // Note that this is where the rseq tracking is done. + if (!earlyDialog.reliableSequenceGuard(message)) { + this.logger.warn("1xx INVITE reliable response received out of order, dropping."); + return; + } + // Update dialog signaling state if need be. + earlyDialog.signalingStateTransition(message); + // Pass response to delegate. + var session_1 = earlyDialog; + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ + message: message, + session: session_1, + prack: function (options) { + var outgoingPrackRequest = session_1.prack(undefined, options); + return outgoingPrackRequest; + } + }); + } + } + return; + case /^2[0-9]{2}$/.test(statusCode): + // Multiple 2xx responses may arrive at the UAC for a single INVITE + // request due to a forking proxy. Each response is distinguished by + // the tag parameter in the To header field, and each represents a + // distinct dialog, with a distinct dialog identifier. + // + // If the dialog identifier in the 2xx response matches the dialog + // identifier of an existing dialog, the dialog MUST be transitioned to + // the "confirmed" state, and the route set for the dialog MUST be + // recomputed based on the 2xx response using the procedures of Section + // 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + // constructed using the procedures of Section 12.1.2. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + { + // Compute dialog state. + var dialogState = dialogs_1.Dialog.initialDialogStateForUserAgentClient(this.message, message); + // NOTE: Currently our transaction layer is caching the 2xx ACKs and + // handling retransmissions of the ACK which is an approach which is + // not to spec. In any event, this block is intended to provide a to + // spec implementation of ACK retransmissions, but it should not be + // hit currently. + var dialog = this.confirmedDialogs.get(dialogState.id); + if (dialog) { + // Once the ACK has been constructed, the procedures of [4] are used to + // determine the destination address, port and transport. However, the + // request is passed to the transport layer directly for transmission, + // rather than a client transaction. This is because the UAC core + // handles retransmissions of the ACK, not the transaction layer. The + // ACK MUST be passed to the client transport every time a + // retransmission of the 2xx final response that triggered the ACK + // arrives. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + var outgoingAckRequest = this.confirmedDialogAcks.get(dialogState.id); + if (outgoingAckRequest) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Client transaction not instance of InviteClientTransaction."); + } + transaction.ackResponse(outgoingAckRequest.message); + } + else { + // If still waiting for an ACK, drop the retransmission of the 2xx final response. + } + return; + } + // If the dialog identifier in the 2xx response matches the dialog + // identifier of an existing dialog, the dialog MUST be transitioned to + // the "confirmed" state, and the route set for the dialog MUST be + // recomputed based on the 2xx response using the procedures of Section + // 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + // constructed using the procedures of Section 12.1.2. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + dialog = this.earlyDialogs.get(dialogState.id); + if (dialog) { + dialog.confirm(); + dialog.recomputeRouteSet(message); + this.earlyDialogs.delete(dialog.id); + this.confirmedDialogs.set(dialog.id, dialog); + } + else { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + dialog = new dialogs_1.SessionDialog(transaction, this.core, dialogState); + this.confirmedDialogs.set(dialog.id, dialog); + } + // Update dialog signaling state if need be. + dialog.signalingStateTransition(message); + // Session Initiated! :) + var session_2 = dialog; + // The UAC core MUST generate an ACK request for each 2xx received from + // the transaction layer. The header fields of the ACK are constructed + // in the same way as for any request sent within a dialog (see Section + // 12) with the exception of the CSeq and the header fields related to + // authentication. The sequence number of the CSeq header field MUST be + // the same as the INVITE being acknowledged, but the CSeq method MUST + // be ACK. The ACK MUST contain the same credentials as the INVITE. If + // the 2xx contains an offer (based on the rules above), the ACK MUST + // carry an answer in its body. If the offer in the 2xx response is not + // acceptable, the UAC core MUST generate a valid answer in the ACK and + // then send a BYE immediately. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ + message: message, + session: session_2, + ack: function (options) { + var outgoingAckRequest = session_2.ack(options); + _this.confirmedDialogAcks.set(session_2.id, outgoingAckRequest); + return outgoingAckRequest; + } + }); + } + else { + var outgoingAckRequest = session_2.ack(); + this.confirmedDialogAcks.set(session_2.id, outgoingAckRequest); + } + } + return; + case /^3[0-9]{2}$/.test(statusCode): + // 12.3 Termination of a Dialog + // + // Independent of the method, if a request outside of a dialog generates + // a non-2xx final response, any early dialogs created through + // provisional responses to that request are terminated. The mechanism + // for terminating confirmed dialogs is method specific. In this + // specification, the BYE method terminates a session and the dialog + // associated with it. See Section 15 for details. + // https://tools.ietf.org/html/rfc3261#section-12.3 + // All early dialogs are considered terminated upon reception of the + // non-2xx final response. + // + // After having received the non-2xx final response the UAC core + // considers the INVITE transaction completed. The INVITE client + // transaction handles the generation of ACKs for the response (see + // Section 17). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + // A 3xx response may contain one or more Contact header field values + // providing new addresses where the callee might be reachable. + // Depending on the status code of the 3xx response (see Section 21.3), + // the UAC MAY choose to try those new addresses. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.2 + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + return; + case /^[4-6][0-9]{2}$/.test(statusCode): + // 12.3 Termination of a Dialog + // + // Independent of the method, if a request outside of a dialog generates + // a non-2xx final response, any early dialogs created through + // provisional responses to that request are terminated. The mechanism + // for terminating confirmed dialogs is method specific. In this + // specification, the BYE method terminates a session and the dialog + // associated with it. See Section 15 for details. + // https://tools.ietf.org/html/rfc3261#section-12.3 + // All early dialogs are considered terminated upon reception of the + // non-2xx final response. + // + // After having received the non-2xx final response the UAC core + // considers the INVITE transaction completed. The INVITE client + // transaction handles the generation of ACKs for the response (see + // Section 17). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + // A single non-2xx final response may be received for the INVITE. 4xx, + // 5xx and 6xx responses may contain a Contact header field value + // indicating the location where additional information about the error + // can be found. Subsequent final responses (which would only arrive + // under error conditions) MUST be ignored. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + return; + default: + throw new Error("Invalid status code " + statusCode); + } + throw new Error("Executing what should be an unreachable code path receiving " + statusCode + " response."); + }; + return InviteUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.InviteUserAgentClient = InviteUserAgentClient; + + +/***/ }), +/* 69 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var dialogs_1 = __webpack_require__(3); +var exceptions_1 = __webpack_require__(31); +var session_1 = __webpack_require__(24); +var transactions_1 = __webpack_require__(27); +var allowed_methods_1 = __webpack_require__(58); +var user_agent_server_1 = __webpack_require__(44); +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.3 UAS Processing + * https://tools.ietf.org/html/rfc3261#section-13.3 + */ +var InviteUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(InviteUserAgentServer, _super); + function InviteUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.InviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + InviteUserAgentServer.prototype.dispose = function () { + if (this.earlyDialog) { + this.earlyDialog.dispose(); + } + _super.prototype.dispose.call(this); + }; + /** + * 13.3.1.4 The INVITE is Accepted + * The UAS core generates a 2xx response. This response establishes a + * dialog, and therefore follows the procedures of Section 12.1.1 in + * addition to those of Section 8.2.6. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + * @param options Accept options bucket. + */ + InviteUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (!this.acceptable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not acceptable in state " + this.transaction.state + "."); + } + // This response establishes a dialog... + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!this.confirmedDialog) { + if (this.earlyDialog) { + this.earlyDialog.confirm(); + this.confirmedDialog = this.earlyDialog; + this.earlyDialog = undefined; + } + else { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteServerTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + var state = dialogs_1.Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag); + this.confirmedDialog = new dialogs_1.SessionDialog(transaction, this.core, state); + } + } + // When a UAS responds to a request with a response that establishes a + // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route + // header field values from the request into the response (including the + // URIs, URI parameters, and any Record-Route header field parameters, + // whether they are known or unknown to the UAS) and MUST maintain the + // order of those values. The UAS MUST add a Contact header field to + // the response. The Contact header field contains an address where the + // UAS would like to be contacted for subsequent requests in the dialog + // (which includes the ACK for a 2xx response in the case of an INVITE). + // Generally, the host portion of this URI is the IP address or FQDN of + // the host. The URI provided in the Contact header field MUST be a SIP + // or SIPS URI. If the request that initiated the dialog contained a + // SIPS URI in the Request-URI or in the top Record-Route header field + // value, if there was any, or the Contact header field if there was no + // Record-Route header field, the Contact header field in the response + // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the + // same URI can be used in messages outside this dialog). The same way, + // the scope of the URI in the Contact header field of the INVITE is not + // limited to this dialog either. It can therefore be used in messages + // to the UAC even outside this dialog. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var recordRouteHeader = this.message + .getHeaders("record-route") + .map(function (header) { return "Record-Route: " + header; }); + var contactHeader = "Contact: " + this.core.configuration.contact.toString(); + // A 2xx response to an INVITE SHOULD contain the Allow header field and + // the Supported header field, and MAY contain the Accept header field. + // Including these header fields allows the UAC to determine the + // features and extensions supported by the UAS for the duration of the + // call, without probing. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + // FIXME: TODO: This should not be hard coded. + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + // FIXME: TODO: Supported header (see reply()) + // FIXME: TODO: Accept header + // If the INVITE request contained an offer, and the UAS had not yet + // sent an answer, the 2xx MUST contain an answer. If the INVITE did + // not contain an offer, the 2xx MUST contain an offer if the UAS had + // not yet sent an offer. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!options.body) { + if (this.confirmedDialog.signalingState === session_1.SignalingState.Initial || + this.confirmedDialog.signalingState === session_1.SignalingState.HaveRemoteOffer) { + throw new Error("Response must have a body."); + } + } + // FIXME: TODO: Guard offer/answer + options.statusCode = options.statusCode || 200; + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push(contactHeader); + var response = _super.prototype.accept.call(this, options); + var session = this.confirmedDialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.confirmedDialog.signalingStateTransition(options.body); + } + return result; + }; + /** + * 13.3.1.1 Progress + * If the UAS is not able to answer the invitation immediately, it can + * choose to indicate some kind of progress to the UAC (for example, an + * indication that a phone is ringing). This is accomplished with a + * provisional response between 101 and 199. These provisional + * responses establish early dialogs and therefore follow the procedures + * of Section 12.1.1 in addition to those of Section 8.2.6. A UAS MAY + * send as many provisional responses as it likes. Each of these MUST + * indicate the same dialog ID. However, these will not be delivered + * reliably. + * + * If the UAS desires an extended period of time to answer the INVITE, + * it will need to ask for an "extension" in order to prevent proxies + * from canceling the transaction. A proxy has the option of canceling + * a transaction when there is a gap of 3 minutes between responses in a + * transaction. To prevent cancellation, the UAS MUST send a non-100 + * provisional response at every minute, to handle the possibility of + * lost provisional responses. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.1 + * @param options Progress options bucket. + */ + InviteUserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + if (!this.progressable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not progressable in state " + this.transaction.state + "."); + } + // This response establishes a dialog... + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!this.earlyDialog) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteServerTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + var state = dialogs_1.Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag, true); + this.earlyDialog = new dialogs_1.SessionDialog(transaction, this.core, state); + } + // When a UAS responds to a request with a response that establishes a + // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route + // header field values from the request into the response (including the + // URIs, URI parameters, and any Record-Route header field parameters, + // whether they are known or unknown to the UAS) and MUST maintain the + // order of those values. The UAS MUST add a Contact header field to + // the response. The Contact header field contains an address where the + // UAS would like to be contacted for subsequent requests in the dialog + // (which includes the ACK for a 2xx response in the case of an INVITE). + // Generally, the host portion of this URI is the IP address or FQDN of + // the host. The URI provided in the Contact header field MUST be a SIP + // or SIPS URI. If the request that initiated the dialog contained a + // SIPS URI in the Request-URI or in the top Record-Route header field + // value, if there was any, or the Contact header field if there was no + // Record-Route header field, the Contact header field in the response + // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the + // same URI can be used in messages outside this dialog). The same way, + // the scope of the URI in the Contact header field of the INVITE is not + // limited to this dialog either. It can therefore be used in messages + // to the UAC even outside this dialog. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var recordRouteHeader = this.message + .getHeaders("record-route") + .map(function (header) { return "Record-Route: " + header; }); + var contactHeader = "Contact: " + this.core.configuration.contact; + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); + options.extraHeaders.push(contactHeader); + var response = _super.prototype.progress.call(this, options); + var session = this.earlyDialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.earlyDialog.signalingStateTransition(options.body); + } + return result; + }; + /** + * 13.3.1.2 The INVITE is Redirected + * If the UAS decides to redirect the call, a 3xx response is sent. A + * 300 (Multiple Choices), 301 (Moved Permanently) or 302 (Moved + * Temporarily) response SHOULD contain a Contact header field + * containing one or more URIs of new addresses to be tried. The + * response is passed to the INVITE server transaction, which will deal + * with its retransmissions. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.2 + * @param options Reject options bucket. + */ + InviteUserAgentServer.prototype.redirect = function (contacts, options) { + if (options === void 0) { options = { statusCode: 302 }; } + return _super.prototype.redirect.call(this, contacts, options); + }; + /** + * 13.3.1.3 The INVITE is Rejected + * A common scenario occurs when the callee is currently not willing or + * able to take additional calls at this end system. A 486 (Busy Here) + * SHOULD be returned in such a scenario. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.3 + * @param options Reject options bucket. + */ + InviteUserAgentServer.prototype.reject = function (options) { + if (options === void 0) { options = { statusCode: 486 }; } + return _super.prototype.reject.call(this, options); + }; + return InviteUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.InviteUserAgentServer = InviteUserAgentServer; + + +/***/ }), +/* 70 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var MessageUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(MessageUserAgentClient, _super); + function MessageUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return MessageUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.MessageUserAgentClient = MessageUserAgentClient; + + +/***/ }), +/* 71 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var MessageUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(MessageUserAgentServer, _super); + function MessageUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + return MessageUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.MessageUserAgentServer = MessageUserAgentServer; + + +/***/ }), +/* 72 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var PublishUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(PublishUserAgentClient, _super); + function PublishUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return PublishUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.PublishUserAgentClient = PublishUserAgentClient; + + +/***/ }), +/* 73 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var ReSubscribeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReSubscribeUserAgentServer, _super); + function ReSubscribeUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return ReSubscribeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReSubscribeUserAgentServer = ReSubscribeUserAgentServer; + + +/***/ }), +/* 74 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var RegisterUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(RegisterUserAgentClient, _super); + function RegisterUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return RegisterUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.RegisterUserAgentClient = RegisterUserAgentClient; + + +/***/ }), +/* 75 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var subscription_dialog_1 = __webpack_require__(55); +var subscription_1 = __webpack_require__(56); +var timers_1 = __webpack_require__(26); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +/** + * 4.1. Subscriber Behavior + * https://tools.ietf.org/html/rfc6665#section-4.1 + * + * User agent client for installation of a single subscription per SUBSCRIBE request. + * TODO: Support for installation of multiple subscriptions on forked SUBSCRIBE reqeuests. + */ +var SubscribeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(SubscribeUserAgentClient, _super); + function SubscribeUserAgentClient(core, message, delegate) { + var _this = this; + // Get event from request message. + var event = message.getHeader("Event"); + if (!event) { + throw new Error("Event undefined"); + } + // Get expires from reqeust message. + var expires = message.getHeader("Expires"); + if (!expires) { + throw new Error("Expires undefined"); + } + _this = _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + _this.delegate = delegate; + // FIXME: Subscriber id should also be matching on event id. + _this.subscriberId = message.callId + message.fromTag + event; + _this.subscriptionExpiresRequested = _this.subscriptionExpires = Number(expires); + _this.subscriptionEvent = event; + _this.subscriptionState = subscription_1.SubscriptionState.NotifyWait; + // Start waiting for a NOTIFY we can use to create a subscription. + _this.waitNotifyStart(); + return _this; + } + /** + * Destructor. + * Note that Timer N may live on waiting for an initial NOTIFY and + * the delegate may still receive that NOTIFY. If you don't want + * that behavior then either clear the delegate so the delegate + * doesn't get called (a 200 will be sent in response to the NOTIFY) + * or call `waitNotifyStop` which will clear Timer N and remove this + * UAC from the core (a 481 will be sent in response to the NOTIFY). + */ + SubscribeUserAgentClient.prototype.dispose = function () { + _super.prototype.dispose.call(this); + }; + /** + * Handle out of dialog NOTIFY assoicated with SUBSCRIBE request. + * This is the first NOTIFY received after the SUBSCRIBE request. + * @param uas User agent server handling the subscription creating NOTIFY. + */ + SubscribeUserAgentClient.prototype.onNotify = function (uas) { + // NOTIFY requests are matched to such SUBSCRIBE requests if they + // contain the same "Call-ID", a "To" header field "tag" parameter that + // matches the "From" header field "tag" parameter of the SUBSCRIBE + // request, and the same "Event" header field. Rules for comparisons of + // the "Event" header fields are described in Section 8.2.1. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + var event = uas.message.parseHeader("Event").event; + if (!event || event !== this.subscriptionEvent) { + this.logger.warn("Failed to parse event."); + uas.reject({ statusCode: 489 }); + return; + } + // NOTIFY requests MUST contain "Subscription-State" header fields that + // indicate the status of the subscription. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = uas.message.parseHeader("Subscription-State"); + if (!subscriptionState || !subscriptionState.state) { + this.logger.warn("Failed to parse subscription state."); + uas.reject({ statusCode: 489 }); + return; + } + // Validate subscription state. + var state = subscriptionState.state; + switch (state) { + case "pending": + break; + case "active": + break; + case "terminated": + break; + default: + this.logger.warn("Invalid subscription state " + state); + uas.reject({ statusCode: 489 }); + return; + } + // Dialogs usages are created upon completion of a NOTIFY transaction + // for a new subscription, unless the NOTIFY request contains a + // "Subscription-State" of "terminated." + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (state !== "terminated") { + // The Contact header field MUST be present and contain exactly one SIP + // or SIPS URI in any request that can result in the establishment of a + // dialog. + // https://tools.ietf.org/html/rfc3261#section-8.1.1.8 + var contact = uas.message.parseHeader("contact"); + if (!contact) { + this.logger.warn("Failed to parse contact."); + uas.reject({ statusCode: 489 }); + return; + } + } + // In accordance with the rules for proxying non-INVITE requests as + // defined in [RFC3261], successful SUBSCRIBE requests will receive only + // one 200-class response; however, due to forking, the subscription may + // have been accepted by multiple nodes. The subscriber MUST therefore + // be prepared to receive NOTIFY requests with "From:" tags that differ + // from the "To:" tag received in the SUBSCRIBE 200-class response. + // + // If multiple NOTIFY requests are received in different dialogs in + // response to a single SUBSCRIBE request, each dialog represents a + // different destination to which the SUBSCRIBE request was forked. + // Subscriber handling in such situations varies by event package; see + // Section 5.4.9 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.4 + // Each event package MUST specify whether forked SUBSCRIBE requests are + // allowed to install multiple subscriptions. + // + // If such behavior is not allowed, the first potential dialog- + // establishing message will create a dialog. All subsequent NOTIFY + // requests that correspond to the SUBSCRIBE request (i.e., have + // matching "To", "From", "Call-ID", and "Event" header fields, as well + // as "From" header field "tag" parameter and "Event" header field "id" + // parameter) but that do not match the dialog would be rejected with a + // 481 response. Note that the 200-class response to the SUBSCRIBE + // request can arrive after a matching NOTIFY request has been received; + // such responses might not correlate to the same dialog established by + // the NOTIFY request. Except as required to complete the SUBSCRIBE + // transaction, such non-matching 200-class responses are ignored. + // + // If installing of multiple subscriptions by way of a single forked + // SUBSCRIBE request is allowed, the subscriber establishes a new dialog + // towards each notifier by returning a 200-class response to each + // NOTIFY request. Each dialog is then handled as its own entity and is + // refreshed independently of the other dialogs. + // + // In the case that multiple subscriptions are allowed, the event + // package MUST specify whether merging of the notifications to form a + // single state is required, and how such merging is to be performed. + // Note that it is possible that some event packages may be defined in + // such a way that each dialog is tied to a mutually exclusive state + // that is unaffected by the other dialogs; this MUST be clearly stated + // if it is the case. + // https://tools.ietf.org/html/rfc6665#section-5.4.9 + // *** NOTE: This implementation is only for event packages which + // do not allow forked requests to install muliple subscriptions. + // As such and in accordance with the specificaiton, we stop waiting + // and any future NOTIFY requests will be rejected with a 481. + if (this.dialog) { + throw new Error("Dialog already created. This implementation only supports install of single subscriptions."); + } + this.waitNotifyStop(); + // Update expires. + this.subscriptionExpires = + subscriptionState.expires ? + Math.min(this.subscriptionExpires, Math.max(subscriptionState.expires, 0)) : + this.subscriptionExpires; + // Update subscriptoin state. + switch (state) { + case "pending": + this.subscriptionState = subscription_1.SubscriptionState.Pending; + break; + case "active": + this.subscriptionState = subscription_1.SubscriptionState.Active; + break; + case "terminated": + this.subscriptionState = subscription_1.SubscriptionState.Terminated; + break; + default: + throw new Error("Unrecognized state " + state + "."); + } + // Dialogs usages are created upon completion of a NOTIFY transaction + // for a new subscription, unless the NOTIFY request contains a + // "Subscription-State" of "terminated." + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (this.subscriptionState !== subscription_1.SubscriptionState.Terminated) { + // Because the dialog usage is established by the NOTIFY request, the + // route set at the subscriber is taken from the NOTIFY request itself, + // as opposed to the route set present in the 200-class response to the + // SUBSCRIBE request. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + var dialogState = subscription_dialog_1.SubscriptionDialog.initialDialogStateForSubscription(this.message, uas.message); + // Subscription Initiated! :) + this.dialog = new subscription_dialog_1.SubscriptionDialog(this.subscriptionEvent, this.subscriptionExpires, this.subscriptionState, this.core, dialogState); + } + // Delegate. + if (this.delegate && this.delegate.onNotify) { + var request = uas; + var subscription = this.dialog; + this.delegate.onNotify({ request: request, subscription: subscription }); + } + else { + uas.accept(); + } + }; + SubscribeUserAgentClient.prototype.waitNotifyStart = function () { + var _this = this; + if (!this.N) { + // Add ourselves to the core's subscriber map. + // This allows the core to route out of dialog NOTIFY messages to us. + this.core.subscribers.set(this.subscriberId, this); + this.N = setTimeout(function () { return _this.timer_N(); }, timers_1.Timers.TIMER_N); + } + }; + SubscribeUserAgentClient.prototype.waitNotifyStop = function () { + if (this.N) { + // Remove ourselves to the core's subscriber map. + // Any future out of dialog NOTIFY messages will be rejected with a 481. + this.core.subscribers.delete(this.subscriberId); + clearTimeout(this.N); + this.N = undefined; + } + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + SubscribeUserAgentClient.prototype.receiveResponse = function (message) { + if (!this.authenticationGuard(message)) { + return; + } + if (message.statusCode && message.statusCode >= 200 && message.statusCode < 300) { + // The "Expires" header field in a 200-class response to SUBSCRIBE + // request indicates the actual duration for which the subscription will + // remain active (unless refreshed). The received value might be + // smaller than the value indicated in the SUBSCRIBE request but cannot + // be larger; see Section 4.2.1 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + // The "Expires" values present in SUBSCRIBE 200-class responses behave + // in the same way as they do in REGISTER responses: the server MAY + // shorten the interval but MUST NOT lengthen it. + // + // If the duration specified in a SUBSCRIBE request is unacceptably + // short, the notifier may be able to send a 423 response, as + // described earlier in this section. + // + // 200-class responses to SUBSCRIBE requests will not generally contain + // any useful information beyond subscription duration; their primary + // purpose is to serve as a reliability mechanism. State information + // will be communicated via a subsequent NOTIFY request from the + // notifier. + // https://tools.ietf.org/html/rfc6665#section-4.2.1.1 + var expires = message.getHeader("Expires"); + if (!expires) { + this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE"); + } + else { + var subscriptionExpiresReceived = Number(expires); + if (subscriptionExpiresReceived > this.subscriptionExpiresRequested) { + this.logger.warn("Expires header in a 200-class response to SUBSCRIBE with a higher value than the one in the request"); + } + if (subscriptionExpiresReceived < this.subscriptionExpires) { + this.subscriptionExpires = subscriptionExpiresReceived; + } + } + // If a NOTIFY arrived before 200-class response a dialog may have been created. + // Updated the dialogs expiration only if this indicates earlier expiration. + if (this.dialog) { + if (this.dialog.subscriptionExpires > this.subscriptionExpires) { + this.dialog.subscriptionExpires = this.subscriptionExpires; + } + } + } + if (message.statusCode && message.statusCode >= 300 && message.statusCode < 700) { + this.waitNotifyStop(); // No NOTIFY will be sent after a negative final response. + } + _super.prototype.receiveResponse.call(this, message); + }; + /** + * To ensure that subscribers do not wait indefinitely for a + * subscription to be established, a subscriber starts a Timer N, set to + * 64*T1, when it sends a SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription failed, and cleans up any state associated with the + * subscription attempt. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + */ + SubscribeUserAgentClient.prototype.timer_N = function () { + this.logger.warn("Timer N expired for SUBSCRIBE user agent client. Timed out waiting for NOTIFY."); + this.waitNotifyStop(); + if (this.delegate && this.delegate.onNotifyTimeout) { + this.delegate.onNotifyTimeout(); + } + }; + return SubscribeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.SubscribeUserAgentClient = SubscribeUserAgentClient; + + +/***/ }), +/* 76 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var SubscribeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(SubscribeUserAgentServer, _super); + function SubscribeUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + return SubscribeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.SubscribeUserAgentServer = SubscribeUserAgentServer; + + +/***/ }), +/* 77 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +/** + * Transport + * @remarks + * Abstract transport layer base class. + * @param logger - Logger. + * @param options - Options bucket. + * @public + */ +var Transport = /** @class */ (function (_super) { + tslib_1.__extends(Transport, _super); + function Transport(logger, options) { + var _this = _super.call(this) || this; + _this.logger = logger; + return _this; + } + /** + * Returns the promise designated by the child layer then emits a connected event. + * Automatically emits an event upon resolution, unless overrideEvent is set. If you + * override the event in this fashion, you should emit it in your implementation of connectPromise + * @param options - Options bucket. + */ + Transport.prototype.connect = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.connectPromise(options).then(function (data) { + if (!data.overrideEvent) { + _this.emit("connected"); + } + }); + }; + /** + * Sends a message then emits a 'messageSent' event. Automatically emits an + * event upon resolution, unless data.overrideEvent is set. If you override + * the event in this fashion, you should emit it in your implementation of sendPromise + * @param msg - Message. + * @param options - Options bucket. + */ + Transport.prototype.send = function (msg, options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.sendPromise(msg).then(function (data) { + if (!data.overrideEvent) { + _this.emit("messageSent", data.msg); + } + }); + }; + /** + * Returns the promise designated by the child layer then emits a + * disconnected event. Automatically emits an event upon resolution, + * unless overrideEvent is set. If you override the event in this fashion, + * you should emit it in your implementation of disconnectPromise + * @param options - Options bucket + */ + Transport.prototype.disconnect = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.disconnectPromise(options).then(function (data) { + if (!data.overrideEvent) { + _this.emit("disconnected"); + } + }); + }; + Transport.prototype.afterConnected = function (callback) { + if (this.isConnected()) { + callback(); + } + else { + this.once("connected", callback); + } + }; + /** + * Returns a promise which resolves once the UA is connected. DEPRECATION WARNING: just use afterConnected() + */ + Transport.prototype.waitForConnected = function () { + var _this = this; + // tslint:disable-next-line:no-console + console.warn("DEPRECATION WARNING Transport.waitForConnected(): use afterConnected() instead"); + return new Promise(function (resolve) { + _this.afterConnected(resolve); + }); + }; + return Transport; +}(events_1.EventEmitter)); +exports.Transport = Transport; + + +/***/ }), +/* 78 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var Enums_1 = __webpack_require__(81); +var Utils_1 = __webpack_require__(82); +var ClientContext = /** @class */ (function (_super) { + tslib_1.__extends(ClientContext, _super); + function ClientContext(ua, method, target, options) { + var _this = _super.call(this) || this; + _this.data = {}; + ClientContext.initializer(_this, ua, method, target, options); + return _this; + } + ClientContext.initializer = function (objToConstruct, ua, method, originalTarget, options) { + objToConstruct.type = Enums_1.TypeStrings.ClientContext; + // Validate arguments + if (originalTarget === undefined) { + throw new TypeError("Not enough arguments"); + } + objToConstruct.ua = ua; + objToConstruct.logger = ua.getLogger("sip.clientcontext"); + objToConstruct.method = method; + var target = ua.normalizeTarget(originalTarget); + if (!target) { + throw new TypeError("Invalid target: " + originalTarget); + } + /* Options + * - extraHeaders + * - params + * - contentType + * - body + */ + options = Object.create(options || Object.prototype); + var extraHeaders = (options.extraHeaders || []).slice(); + var params = options.params || {}; + var bodyObj; + if (options.body) { + bodyObj = { + body: options.body, + contentType: options.contentType ? options.contentType : "application/sdp" + }; + objToConstruct.body = bodyObj; + } + var body; + if (bodyObj) { + body = Utils_1.Utils.fromBodyObj(bodyObj); + } + // Build the request + objToConstruct.request = ua.userAgentCore.makeOutgoingRequestMessage(method, target, params.fromUri ? params.fromUri : ua.userAgentCore.configuration.aor, params.toUri ? params.toUri : target, params, extraHeaders, body); + /* Set other properties from the request */ + if (objToConstruct.request.from) { + objToConstruct.localIdentity = objToConstruct.request.from; + } + if (objToConstruct.request.to) { + objToConstruct.remoteIdentity = objToConstruct.request.to; + } + }; + ClientContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.request(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + ClientContext.prototype.receiveResponse = function (response) { + var statusCode = response.statusCode || 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + this.emit("progress", response, cause); + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + if (this.ua.applicants[this.toString()]) { + delete this.ua.applicants[this.toString()]; + } + this.emit("accepted", response, cause); + break; + default: + if (this.ua.applicants[this.toString()]) { + delete this.ua.applicants[this.toString()]; + } + this.emit("rejected", response, cause); + this.emit("failed", response, cause); + break; + } + }; + ClientContext.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + ClientContext.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + return ClientContext; +}(events_1.EventEmitter)); +exports.ClientContext = ClientContext; + + +/***/ }), +/* 79 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +// tslint:disable-next-line:no-var-requires +var pkg = __webpack_require__(80); +var C; +(function (C) { + C.USER_AGENT = pkg.title + "/" + pkg.version; + // SIP scheme + C.SIP = "sip"; + C.SIPS = "sips"; + // End and Failure causes + var causes; + (function (causes) { + // Generic error causes + causes["CONNECTION_ERROR"] = "Connection Error"; + causes["INTERNAL_ERROR"] = "Internal Error"; + causes["REQUEST_TIMEOUT"] = "Request Timeout"; + causes["SIP_FAILURE_CODE"] = "SIP Failure Code"; + // SIP error causes + causes["ADDRESS_INCOMPLETE"] = "Address Incomplete"; + causes["AUTHENTICATION_ERROR"] = "Authentication Error"; + causes["BUSY"] = "Busy"; + causes["DIALOG_ERROR"] = "Dialog Error"; + causes["INCOMPATIBLE_SDP"] = "Incompatible SDP"; + causes["NOT_FOUND"] = "Not Found"; + causes["REDIRECTED"] = "Redirected"; + causes["REJECTED"] = "Rejected"; + causes["UNAVAILABLE"] = "Unavailable"; + // Session error causes + causes["BAD_MEDIA_DESCRIPTION"] = "Bad Media Description"; + causes["CANCELED"] = "Canceled"; + causes["EXPIRES"] = "Expires"; + causes["NO_ACK"] = "No ACK"; + causes["NO_ANSWER"] = "No Answer"; + causes["NO_PRACK"] = "No PRACK"; + causes["RTP_TIMEOUT"] = "RTP Timeout"; + causes["USER_DENIED_MEDIA_ACCESS"] = "User Denied Media Access"; + causes["WEBRTC_ERROR"] = "WebRTC Error"; + causes["WEBRTC_NOT_SUPPORTED"] = "WebRTC Not Supported"; + })(causes = C.causes || (C.causes = {})); + var supported; + (function (supported) { + supported["REQUIRED"] = "required"; + supported["SUPPORTED"] = "supported"; + supported["UNSUPPORTED"] = "none"; + })(supported = C.supported || (C.supported = {})); + C.SIP_ERROR_CAUSES = { + ADDRESS_INCOMPLETE: [484], + AUTHENTICATION_ERROR: [401, 407], + BUSY: [486, 600], + INCOMPATIBLE_SDP: [488, 606], + NOT_FOUND: [404, 604], + REDIRECTED: [300, 301, 302, 305, 380], + REJECTED: [403, 603], + UNAVAILABLE: [480, 410, 408, 430] + }; + // SIP Methods + C.ACK = "ACK"; + C.BYE = "BYE"; + C.CANCEL = "CANCEL"; + C.INFO = "INFO"; + C.INVITE = "INVITE"; + C.MESSAGE = "MESSAGE"; + C.NOTIFY = "NOTIFY"; + C.OPTIONS = "OPTIONS"; + C.REGISTER = "REGISTER"; + C.UPDATE = "UPDATE"; + C.SUBSCRIBE = "SUBSCRIBE"; + C.PUBLISH = "PUBLISH"; + C.REFER = "REFER"; + C.PRACK = "PRACK"; + /* SIP Response Reasons + * DOC: http://www.iana.org/assignments/sip-parameters + * Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7 + */ + C.REASON_PHRASE = { + 100: "Trying", + 180: "Ringing", + 181: "Call Is Being Forwarded", + 182: "Queued", + 183: "Session Progress", + 199: "Early Dialog Terminated", + 200: "OK", + 202: "Accepted", + 204: "No Notification", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Moved Temporarily", + 305: "Use Proxy", + 380: "Alternative Service", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 410: "Gone", + 412: "Conditional Request Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Unsupported URI Scheme", + 417: "Unknown Resource-Priority", + 420: "Bad Extension", + 421: "Extension Required", + 422: "Session Interval Too Small", + 423: "Interval Too Brief", + 428: "Use Identity Header", + 429: "Provide Referrer Identity", + 430: "Flow Failed", + 433: "Anonymity Disallowed", + 436: "Bad Identity-Info", + 437: "Unsupported Certificate", + 438: "Invalid Identity Header", + 439: "First Hop Lacks Outbound Support", + 440: "Max-Breadth Exceeded", + 469: "Bad Info Package", + 470: "Consent Needed", + 478: "Unresolvable Destination", + 480: "Temporarily Unavailable", + 481: "Call/Transaction Does Not Exist", + 482: "Loop Detected", + 483: "Too Many Hops", + 484: "Address Incomplete", + 485: "Ambiguous", + 486: "Busy Here", + 487: "Request Terminated", + 488: "Not Acceptable Here", + 489: "Bad Event", + 491: "Request Pending", + 493: "Undecipherable", + 494: "Security Agreement Required", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Server Time-out", + 505: "Version Not Supported", + 513: "Message Too Large", + 580: "Precondition Failure", + 600: "Busy Everywhere", + 603: "Decline", + 604: "Does Not Exist Anywhere", + 606: "Not Acceptable" + }; + /* SIP Option Tags + * DOC: http://www.iana.org/assignments/sip-parameters/sip-parameters.xhtml#sip-parameters-4 + */ + C.OPTION_TAGS = { + "100rel": true, + "199": true, + "answermode": true, + "early-session": true, + "eventlist": true, + "explicitsub": true, + "from-change": true, + "geolocation-http": true, + "geolocation-sip": true, + "gin": true, + "gruu": true, + "histinfo": true, + "ice": true, + "join": true, + "multiple-refer": true, + "norefersub": true, + "nosub": true, + "outbound": true, + "path": true, + "policy": true, + "precondition": true, + "pref": true, + "privacy": true, + "recipient-list-invite": true, + "recipient-list-message": true, + "recipient-list-subscribe": true, + "replaces": true, + "resource-priority": true, + "sdp-anat": true, + "sec-agree": true, + "tdialog": true, + "timer": true, + "uui": true // RFC 7433 + }; + var dtmfType; + (function (dtmfType) { + dtmfType["INFO"] = "info"; + dtmfType["RTP"] = "rtp"; + })(dtmfType = C.dtmfType || (C.dtmfType = {})); +})(C = exports.C || (exports.C = {})); + + +/***/ }), +/* 80 */ +/***/ (function(module) { + +module.exports = {"name":"sip.js","title":"SIP.js","description":"A simple, intuitive, and powerful JavaScript signaling library","version":"0.14.2","license":"MIT","main":"./lib/index.js","types":"./lib/index.d.ts","homepage":"https://sipjs.com","author":"OnSIP (https://sipjs.com/aboutus/)","contributors":[{"url":"https://github.com/onsip/SIP.js/blob/master/THANKS.md"}],"repository":{"type":"git","url":"https://github.com/onsip/SIP.js.git"},"keywords":["sip","webrtc","library","websocket","javascript","typescript"],"dependencies":{"crypto-js":"^3.1.9-1"},"devDependencies":{"@types/crypto-js":"^3.1.43","@types/jasmine":"^3.3.13","@types/node":"^12.0.4","circular-dependency-plugin":"^5.0.2","jasmine-core":"^3.4.0","karma":"^4.1.0","karma-chrome-launcher":"^2.2.0","karma-cli":"^2.0.0","karma-jasmine":"^2.0.1","karma-jasmine-html-reporter":"^1.4.2","karma-mocha-reporter":"^2.2.5","karma-sourcemap-loader":"^0.3.7","karma-webpack":"^3.0.5","pegjs":"^0.10.0","ts-loader":"^6.0.2","ts-pegjs":"0.2.5","tslint":"^5.17.0","typescript":"^3.5.1","webpack":"^4.32.2","webpack-cli":"^3.3.2"},"engines":{"node":">=8.0"},"scripts":{"prebuild":"tslint -p tsconfig-base.json -c tslint.json","generate-grammar":"node build/grammarGenerator.js","build-reg-bundle":"webpack --progress --config build/webpack.config.js --env.buildType reg","build-min-bundle":"webpack --progress --config build/webpack.config.js --env.buildType min","build-bundles":"npm run build-reg-bundle && npm run build-min-bundle","build-lib":"tsc -p src","build-test":"tsc -p test","copy-dist-files":"cp dist/sip.js dist/sip-$npm_package_version.js && cp dist/sip.min.js dist/sip-$npm_package_version.min.js","build":"npm run generate-grammar && npm run build-lib && npm run build-reg-bundle && npm run build-min-bundle && npm run copy-dist-files","browserTest":"npm run build-test && sleep 2 && open http://0.0.0.0:9876/debug.html & karma start --reporters kjhtml --no-single-run","commandLineTest":"npm run build-test && karma start --reporters mocha --browsers ChromeHeadless --single-run","buildAndTest":"npm run build && npm run commandLineTest","buildAndBrowserTest":"npm run build && npm run browserTest"}}; + +/***/ }), +/* 81 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +// enums can't really be declared, so they are set here. +// pulled out of individual files to avoid circular dependencies +Object.defineProperty(exports, "__esModule", { value: true }); +var DialogStatus; +(function (DialogStatus) { + DialogStatus[DialogStatus["STATUS_EARLY"] = 1] = "STATUS_EARLY"; + DialogStatus[DialogStatus["STATUS_CONFIRMED"] = 2] = "STATUS_CONFIRMED"; +})(DialogStatus = exports.DialogStatus || (exports.DialogStatus = {})); +var SessionStatus; +(function (SessionStatus) { + // Session states + SessionStatus[SessionStatus["STATUS_NULL"] = 0] = "STATUS_NULL"; + SessionStatus[SessionStatus["STATUS_INVITE_SENT"] = 1] = "STATUS_INVITE_SENT"; + SessionStatus[SessionStatus["STATUS_1XX_RECEIVED"] = 2] = "STATUS_1XX_RECEIVED"; + SessionStatus[SessionStatus["STATUS_INVITE_RECEIVED"] = 3] = "STATUS_INVITE_RECEIVED"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_ANSWER"] = 4] = "STATUS_WAITING_FOR_ANSWER"; + SessionStatus[SessionStatus["STATUS_ANSWERED"] = 5] = "STATUS_ANSWERED"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_PRACK"] = 6] = "STATUS_WAITING_FOR_PRACK"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_ACK"] = 7] = "STATUS_WAITING_FOR_ACK"; + SessionStatus[SessionStatus["STATUS_CANCELED"] = 8] = "STATUS_CANCELED"; + SessionStatus[SessionStatus["STATUS_TERMINATED"] = 9] = "STATUS_TERMINATED"; + SessionStatus[SessionStatus["STATUS_ANSWERED_WAITING_FOR_PRACK"] = 10] = "STATUS_ANSWERED_WAITING_FOR_PRACK"; + SessionStatus[SessionStatus["STATUS_EARLY_MEDIA"] = 11] = "STATUS_EARLY_MEDIA"; + SessionStatus[SessionStatus["STATUS_CONFIRMED"] = 12] = "STATUS_CONFIRMED"; +})(SessionStatus = exports.SessionStatus || (exports.SessionStatus = {})); +var TypeStrings; +(function (TypeStrings) { + TypeStrings[TypeStrings["ClientContext"] = 0] = "ClientContext"; + TypeStrings[TypeStrings["ConfigurationError"] = 1] = "ConfigurationError"; + TypeStrings[TypeStrings["Dialog"] = 2] = "Dialog"; + TypeStrings[TypeStrings["DigestAuthentication"] = 3] = "DigestAuthentication"; + TypeStrings[TypeStrings["DTMF"] = 4] = "DTMF"; + TypeStrings[TypeStrings["IncomingMessage"] = 5] = "IncomingMessage"; + TypeStrings[TypeStrings["IncomingRequest"] = 6] = "IncomingRequest"; + TypeStrings[TypeStrings["IncomingResponse"] = 7] = "IncomingResponse"; + TypeStrings[TypeStrings["InvalidStateError"] = 8] = "InvalidStateError"; + TypeStrings[TypeStrings["InviteClientContext"] = 9] = "InviteClientContext"; + TypeStrings[TypeStrings["InviteServerContext"] = 10] = "InviteServerContext"; + TypeStrings[TypeStrings["Logger"] = 11] = "Logger"; + TypeStrings[TypeStrings["LoggerFactory"] = 12] = "LoggerFactory"; + TypeStrings[TypeStrings["MethodParameterError"] = 13] = "MethodParameterError"; + TypeStrings[TypeStrings["NameAddrHeader"] = 14] = "NameAddrHeader"; + TypeStrings[TypeStrings["NotSupportedError"] = 15] = "NotSupportedError"; + TypeStrings[TypeStrings["OutgoingRequest"] = 16] = "OutgoingRequest"; + TypeStrings[TypeStrings["Parameters"] = 17] = "Parameters"; + TypeStrings[TypeStrings["PublishContext"] = 18] = "PublishContext"; + TypeStrings[TypeStrings["ReferClientContext"] = 19] = "ReferClientContext"; + TypeStrings[TypeStrings["ReferServerContext"] = 20] = "ReferServerContext"; + TypeStrings[TypeStrings["RegisterContext"] = 21] = "RegisterContext"; + TypeStrings[TypeStrings["RenegotiationError"] = 22] = "RenegotiationError"; + TypeStrings[TypeStrings["RequestSender"] = 23] = "RequestSender"; + TypeStrings[TypeStrings["ServerContext"] = 24] = "ServerContext"; + TypeStrings[TypeStrings["Session"] = 25] = "Session"; + TypeStrings[TypeStrings["SessionDescriptionHandler"] = 26] = "SessionDescriptionHandler"; + TypeStrings[TypeStrings["SessionDescriptionHandlerError"] = 27] = "SessionDescriptionHandlerError"; + TypeStrings[TypeStrings["SessionDescriptionHandlerObserver"] = 28] = "SessionDescriptionHandlerObserver"; + TypeStrings[TypeStrings["Subscription"] = 29] = "Subscription"; + TypeStrings[TypeStrings["Transport"] = 30] = "Transport"; + TypeStrings[TypeStrings["UA"] = 31] = "UA"; + TypeStrings[TypeStrings["URI"] = 32] = "URI"; +})(TypeStrings = exports.TypeStrings || (exports.TypeStrings = {})); +// UA status codes +var UAStatus; +(function (UAStatus) { + UAStatus[UAStatus["STATUS_INIT"] = 0] = "STATUS_INIT"; + UAStatus[UAStatus["STATUS_STARTING"] = 1] = "STATUS_STARTING"; + UAStatus[UAStatus["STATUS_READY"] = 2] = "STATUS_READY"; + UAStatus[UAStatus["STATUS_USER_CLOSED"] = 3] = "STATUS_USER_CLOSED"; + UAStatus[UAStatus["STATUS_NOT_READY"] = 4] = "STATUS_NOT_READY"; +})(UAStatus = exports.UAStatus || (exports.UAStatus = {})); + + +/***/ }), +/* 82 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var Constants_1 = __webpack_require__(79); +var grammar_1 = __webpack_require__(11); +var uri_1 = __webpack_require__(15); +var Utils; +(function (Utils) { + function defer() { + var deferred = {}; + deferred.promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; + } + Utils.defer = defer; + function reducePromises(arr, val) { + return arr.reduce(function (acc, fn) { + acc = acc.then(fn); + return acc; + }, Promise.resolve(val)); + } + Utils.reducePromises = reducePromises; + function str_utf8_length(str) { + return encodeURIComponent(str).replace(/%[A-F\d]{2}/g, "U").length; + } + Utils.str_utf8_length = str_utf8_length; + function generateFakeSDP(body) { + if (!body) { + return; + } + var start = body.indexOf("o="); + var end = body.indexOf("\r\n", start); + return "v=0\r\n" + body.slice(start, end) + "\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0"; + } + Utils.generateFakeSDP = generateFakeSDP; + function isDecimal(num) { + var numAsNum = parseInt(num, 10); + return !isNaN(numAsNum) && (parseFloat(num) === numAsNum); + } + Utils.isDecimal = isDecimal; + function createRandomToken(size, base) { + if (base === void 0) { base = 32; } + var token = ""; + for (var i = 0; i < size; i++) { + var r = Math.floor(Math.random() * base); + token += r.toString(base); + } + return token; + } + Utils.createRandomToken = createRandomToken; + function newTag() { + // used to use the constant in UA + return Utils.createRandomToken(10); + } + Utils.newTag = newTag; + // http://stackoverflow.com/users/109538/broofa + function newUUID() { + var UUID = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + var r = Math.floor(Math.random() * 16); + var v = c === "x" ? r : (r % 4 + 8); + return v.toString(16); + }); + return UUID; + } + Utils.newUUID = newUUID; + /* + * Normalize SIP URI. + * NOTE: It does not allow a SIP URI without username. + * Accepts 'sip', 'sips' and 'tel' URIs and convert them into 'sip'. + * Detects the domain part (if given) and properly hex-escapes the user portion. + * If the user portion has only 'tel' number symbols the user portion is clean of 'tel' visual separators. + * @private + * @param {String} target + * @param {String} [domain] + */ + function normalizeTarget(target, domain) { + // If no target is given then raise an error. + if (!target) { + return; + // If a SIP.URI instance is given then return it. + } + else if (target instanceof uri_1.URI) { + return target; + // If a string is given split it by '@': + // - Last fragment is the desired domain. + // - Otherwise append the given domain argument. + } + else if (typeof target === "string") { + var targetArray = target.split("@"); + var targetUser = void 0; + var targetDomain = void 0; + switch (targetArray.length) { + case 1: + if (!domain) { + return; + } + targetUser = target; + targetDomain = domain; + break; + case 2: + targetUser = targetArray[0]; + targetDomain = targetArray[1]; + break; + default: + targetUser = targetArray.slice(0, targetArray.length - 1).join("@"); + targetDomain = targetArray[targetArray.length - 1]; + } + // Remove the URI scheme (if present). + targetUser = targetUser.replace(/^(sips?|tel):/i, ""); + // Remove 'tel' visual separators if the user portion just contains 'tel' number symbols. + if (/^[\-\.\(\)]*\+?[0-9\-\.\(\)]+$/.test(targetUser)) { + targetUser = targetUser.replace(/[\-\.\(\)]/g, ""); + } + // Build the complete SIP URI. + target = Constants_1.C.SIP + ":" + Utils.escapeUser(targetUser) + "@" + targetDomain; + // Finally parse the resulting URI. + return grammar_1.Grammar.URIParse(target); + } + else { + return; + } + } + Utils.normalizeTarget = normalizeTarget; + /* + * Hex-escape a SIP URI user. + * @private + * @param {String} user + */ + function escapeUser(user) { + // Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F). + return encodeURIComponent(decodeURIComponent(user)) + .replace(/%3A/ig, ":") + .replace(/%2B/ig, "+") + .replace(/%3F/ig, "?") + .replace(/%2F/ig, "/"); + } + Utils.escapeUser = escapeUser; + function headerize(str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; + } + Utils.headerize = headerize; + function sipErrorCause(statusCode) { + for (var cause in Constants_1.C.SIP_ERROR_CAUSES) { + if (Constants_1.C.SIP_ERROR_CAUSES[cause].indexOf(statusCode) !== -1) { + return Constants_1.C.causes[cause]; + } + } + return Constants_1.C.causes.SIP_FAILURE_CODE; + } + Utils.sipErrorCause = sipErrorCause; + function getReasonPhrase(code, specific) { + return specific || Constants_1.C.REASON_PHRASE[code] || ""; + } + Utils.getReasonPhrase = getReasonPhrase; + function getReasonHeaderValue(code, reason) { + reason = Utils.getReasonPhrase(code, reason); + return "SIP;cause=" + code + ';text="' + reason + '"'; + } + Utils.getReasonHeaderValue = getReasonHeaderValue; + function getCancelReason(code, reason) { + if (code && code < 200 || code > 699) { + throw new TypeError("Invalid statusCode: " + code); + } + else if (code) { + return Utils.getReasonHeaderValue(code, reason); + } + } + Utils.getCancelReason = getCancelReason; + function buildStatusLine(code, reason) { + // Validate code and reason values + if (!code || (code < 100 || code > 699)) { + throw new TypeError("Invalid statusCode: " + code); + } + else if (reason && typeof reason !== "string" && !(reason instanceof String)) { + throw new TypeError("Invalid reason: " + reason); + } + reason = Utils.getReasonPhrase(code, reason); + return "SIP/2.0 " + code + " " + reason + "\r\n"; + } + Utils.buildStatusLine = buildStatusLine; + /** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ + function fromBodyObj(bodyObj) { + var content = bodyObj.body; + var contentType = bodyObj.contentType; + var contentDisposition = contentTypeToContentDisposition(contentType); + var body = { contentDisposition: contentDisposition, contentType: contentType, content: content }; + return body; + } + Utils.fromBodyObj = fromBodyObj; + /** + * Create a BodyObj given a Body. + * @param bodyObj Body Object + */ + function toBodyObj(body) { + var bodyObj = { + body: body.content, + contentType: body.contentType + }; + return bodyObj; + } + Utils.toBodyObj = toBodyObj; + // If the Content-Disposition header field is missing, bodies of + // Content-Type application/sdp imply the disposition "session", while + // other content types imply "render". + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + function contentTypeToContentDisposition(contentType) { + if (contentType === "application/sdp") { + return "session"; + } + else { + return "render"; + } + } +})(Utils = exports.Utils || (exports.Utils = {})); + + +/***/ }), +/* 83 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +// tslint:disable:max-classes-per-file +var Exceptions; +(function (Exceptions) { + /** + * Indicates the session description handler has closed. + * Occurs when getDescription() or setDescription() are called after close() has been called. + * Occurs when close() is called while getDescription() or setDescription() are in progress. + */ + var ClosedSessionDescriptionHandlerError = /** @class */ (function (_super) { + tslib_1.__extends(ClosedSessionDescriptionHandlerError, _super); + function ClosedSessionDescriptionHandlerError() { + return _super.call(this, "The session description handler has closed.") || this; + } + return ClosedSessionDescriptionHandlerError; + }(core_1.Exception)); + Exceptions.ClosedSessionDescriptionHandlerError = ClosedSessionDescriptionHandlerError; + /** + * Indicates the session terminated before the action completed. + */ + var TerminatedSessionError = /** @class */ (function (_super) { + tslib_1.__extends(TerminatedSessionError, _super); + function TerminatedSessionError() { + return _super.call(this, "The session has terminated.") || this; + } + return TerminatedSessionError; + }(core_1.Exception)); + Exceptions.TerminatedSessionError = TerminatedSessionError; + /** + * Unsupported session description content type. + */ + var UnsupportedSessionDescriptionContentTypeError = /** @class */ (function (_super) { + tslib_1.__extends(UnsupportedSessionDescriptionContentTypeError, _super); + function UnsupportedSessionDescriptionContentTypeError(message) { + return _super.call(this, message ? message : "Unsupported session description content type.") || this; + } + return UnsupportedSessionDescriptionContentTypeError; + }(core_1.Exception)); + Exceptions.UnsupportedSessionDescriptionContentTypeError = UnsupportedSessionDescriptionContentTypeError; +})(Exceptions = exports.Exceptions || (exports.Exceptions = {})); +/** + * DEPRECATED: The original implementation of exceptions in this library attempted to + * deal with the lack of type checking in JavaScript by adding a "type" attribute + * to objects and using that to discriminate. On top of that it layered allcoated + * "code" numbers and constant "name" strings. All of that is unnecessary when using + * TypeScript, inheriting from Error and properly setting up the prototype chain... + */ +var LegacyException = /** @class */ (function (_super) { + tslib_1.__extends(LegacyException, _super); + function LegacyException(code, name, message) { + var _this = _super.call(this, message) || this; + _this.code = code; + _this.name = name; + _this.message = message; + return _this; + } + return LegacyException; +}(core_1.Exception)); +(function (Exceptions) { + var ConfigurationError = /** @class */ (function (_super) { + tslib_1.__extends(ConfigurationError, _super); + function ConfigurationError(parameter, value) { + var _this = _super.call(this, 1, "CONFIGURATION_ERROR", (!value) ? "Missing parameter: " + parameter : + "Invalid value " + JSON.stringify(value) + " for parameter '" + parameter + "'") || this; + _this.type = Enums_1.TypeStrings.ConfigurationError; + _this.parameter = parameter; + _this.value = value; + return _this; + } + return ConfigurationError; + }(LegacyException)); + Exceptions.ConfigurationError = ConfigurationError; + var InvalidStateError = /** @class */ (function (_super) { + tslib_1.__extends(InvalidStateError, _super); + function InvalidStateError(status) { + var _this = _super.call(this, 2, "INVALID_STATE_ERROR", "Invalid status: " + status) || this; + _this.type = Enums_1.TypeStrings.InvalidStateError; + _this.status = status; + return _this; + } + return InvalidStateError; + }(LegacyException)); + Exceptions.InvalidStateError = InvalidStateError; + var NotSupportedError = /** @class */ (function (_super) { + tslib_1.__extends(NotSupportedError, _super); + function NotSupportedError(message) { + var _this = _super.call(this, 3, "NOT_SUPPORTED_ERROR", message) || this; + _this.type = Enums_1.TypeStrings.NotSupportedError; + return _this; + } + return NotSupportedError; + }(LegacyException)); + Exceptions.NotSupportedError = NotSupportedError; + // 4 was GetDescriptionError, which was deprecated and now removed + var RenegotiationError = /** @class */ (function (_super) { + tslib_1.__extends(RenegotiationError, _super); + function RenegotiationError(message) { + var _this = _super.call(this, 5, "RENEGOTIATION_ERROR", message) || this; + _this.type = Enums_1.TypeStrings.RenegotiationError; + return _this; + } + return RenegotiationError; + }(LegacyException)); + Exceptions.RenegotiationError = RenegotiationError; + var MethodParameterError = /** @class */ (function (_super) { + tslib_1.__extends(MethodParameterError, _super); + function MethodParameterError(method, parameter, value) { + var _this = _super.call(this, 6, "METHOD_PARAMETER_ERROR", (!value) ? + "Missing parameter: " + parameter : + "Invalid value " + JSON.stringify(value) + " for parameter '" + parameter + "'") || this; + _this.type = Enums_1.TypeStrings.MethodParameterError; + _this.method = method; + _this.parameter = parameter; + _this.value = value; + return _this; + } + return MethodParameterError; + }(LegacyException)); + Exceptions.MethodParameterError = MethodParameterError; + // 7 was TransportError, which was replaced + var SessionDescriptionHandlerError = /** @class */ (function (_super) { + tslib_1.__extends(SessionDescriptionHandlerError, _super); + function SessionDescriptionHandlerError(method, error, message) { + var _this = _super.call(this, 8, "SESSION_DESCRIPTION_HANDLER_ERROR", message || "Error with Session Description Handler") || this; + _this.type = Enums_1.TypeStrings.SessionDescriptionHandlerError; + _this.method = method; + _this.error = error; + return _this; + } + return SessionDescriptionHandlerError; + }(LegacyException)); + Exceptions.SessionDescriptionHandlerError = SessionDescriptionHandlerError; +})(Exceptions = exports.Exceptions || (exports.Exceptions = {})); + + +/***/ }), +/* 84 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var core_1 = __webpack_require__(2); +/** + * Extract and parse every header of a SIP message. + * @namespace + */ +var Parser; +(function (Parser) { + function getHeader(data, headerStart) { + // 'start' position of the header. + var start = headerStart; + // 'end' position of the header. + var end = 0; + // 'partial end' position of the header. + var partialEnd = 0; + // End of message. + if (data.substring(start, start + 2).match(/(^\r\n)/)) { + return -2; + } + while (end === 0) { + // Partial End of Header. + partialEnd = data.indexOf("\r\n", start); + // 'indexOf' returns -1 if the value to be found never occurs. + if (partialEnd === -1) { + return partialEnd; + } + if (!data.substring(partialEnd + 2, partialEnd + 4).match(/(^\r\n)/) && + data.charAt(partialEnd + 2).match(/(^\s+)/)) { + // Not the end of the message. Continue from the next position. + start = partialEnd + 2; + } + else { + end = partialEnd; + } + } + return end; + } + Parser.getHeader = getHeader; + function parseHeader(message, data, headerStart, headerEnd) { + var hcolonIndex = data.indexOf(":", headerStart); + var headerName = data.substring(headerStart, hcolonIndex).trim(); + var headerValue = data.substring(hcolonIndex + 1, headerEnd).trim(); + var parsed; + // If header-field is well-known, parse it. + switch (headerName.toLowerCase()) { + case "via": + case "v": + message.addHeader("via", headerValue); + if (message.getHeaders("via").length === 1) { + parsed = message.parseHeader("Via"); + if (parsed) { + message.via = parsed; + message.viaBranch = parsed.branch; + } + } + else { + parsed = 0; + } + break; + case "from": + case "f": + message.setHeader("from", headerValue); + parsed = message.parseHeader("from"); + if (parsed) { + message.from = parsed; + message.fromTag = parsed.getParam("tag"); + } + break; + case "to": + case "t": + message.setHeader("to", headerValue); + parsed = message.parseHeader("to"); + if (parsed) { + message.to = parsed; + message.toTag = parsed.getParam("tag"); + } + break; + case "record-route": + parsed = core_1.Grammar.parse(headerValue, "Record_Route"); + if (parsed === -1) { + parsed = undefined; + break; + } + for (var header in parsed) { + if (parsed[header]) { + message.addHeader("record-route", headerValue.substring(parsed[header].position, parsed[header].offset)); + message.headers["Record-Route"][message.getHeaders("record-route").length - 1].parsed = + parsed[header].parsed; + } + } + break; + case "call-id": + case "i": + message.setHeader("call-id", headerValue); + parsed = message.parseHeader("call-id"); + if (parsed) { + message.callId = headerValue; + } + break; + case "contact": + case "m": + parsed = core_1.Grammar.parse(headerValue, "Contact"); + if (parsed === -1) { + parsed = undefined; + break; + } + if (!(parsed instanceof Array)) { + parsed = undefined; + break; + } + parsed.forEach(function (header) { + message.addHeader("contact", headerValue.substring(header.position, header.offset)); + message.headers.Contact[message.getHeaders("contact").length - 1].parsed = header.parsed; + }); + break; + case "content-length": + case "l": + message.setHeader("content-length", headerValue); + parsed = message.parseHeader("content-length"); + break; + case "content-type": + case "c": + message.setHeader("content-type", headerValue); + parsed = message.parseHeader("content-type"); + break; + case "cseq": + message.setHeader("cseq", headerValue); + parsed = message.parseHeader("cseq"); + if (parsed) { + message.cseq = parsed.value; + } + if (message instanceof core_1.IncomingResponseMessage) { + message.method = parsed.method; + } + break; + case "max-forwards": + message.setHeader("max-forwards", headerValue); + parsed = message.parseHeader("max-forwards"); + break; + case "www-authenticate": + message.setHeader("www-authenticate", headerValue); + parsed = message.parseHeader("www-authenticate"); + break; + case "proxy-authenticate": + message.setHeader("proxy-authenticate", headerValue); + parsed = message.parseHeader("proxy-authenticate"); + break; + case "refer-to": + case "r": + message.setHeader("refer-to", headerValue); + parsed = message.parseHeader("refer-to"); + if (parsed) { + message.referTo = parsed; + } + break; + default: + // Do not parse this header. + message.setHeader(headerName, headerValue); + parsed = 0; + } + if (parsed === undefined) { + return { + error: "error parsing header '" + headerName + "'" + }; + } + else { + return true; + } + } + Parser.parseHeader = parseHeader; + /** Parse SIP Message + * @function + * @param {String} message SIP message. + * @param {Object} logger object. + * @returns {SIP.IncomingRequest|SIP.IncomingResponse|undefined} + */ + function parseMessage(data, logger) { + var headerStart = 0; + var headerEnd = data.indexOf("\r\n"); + if (headerEnd === -1) { + logger.warn("no CRLF found, not a SIP message, discarded"); + return; + } + // Parse first line. Check if it is a Request or a Reply. + var firstLine = data.substring(0, headerEnd); + var parsed = core_1.Grammar.parse(firstLine, "Request_Response"); + var message; + if (parsed === -1) { + logger.warn('error parsing first line of SIP message: "' + firstLine + '"'); + return; + } + else if (!parsed.status_code) { + message = new core_1.IncomingRequestMessage(); + message.method = parsed.method; + message.ruri = parsed.uri; + } + else { + message = new core_1.IncomingResponseMessage(); + message.statusCode = parsed.status_code; + message.reasonPhrase = parsed.reason_phrase; + } + message.data = data; + headerStart = headerEnd + 2; + /* Loop over every line in data. Detect the end of each header and parse + * it or simply add to the headers collection. + */ + var bodyStart; + while (true) { + headerEnd = getHeader(data, headerStart); + // The SIP message has normally finished. + if (headerEnd === -2) { + bodyStart = headerStart + 2; + break; + } + else if (headerEnd === -1) { + // data.indexOf returned -1 due to a malformed message. + logger.error("malformed message"); + return; + } + var parsedHeader = parseHeader(message, data, headerStart, headerEnd); + if (parsedHeader !== true) { + logger.error(parsed.error); + return; + } + headerStart = headerEnd + 2; + } + /* RFC3261 18.3. + * If there are additional bytes in the transport packet + * beyond the end of the body, they MUST be discarded. + */ + if (message.hasHeader("content-length")) { + message.body = data.substr(bodyStart, Number(message.getHeader("content-length"))); + } + else { + message.body = data.substring(bodyStart); + } + return message; + } + Parser.parseMessage = parseMessage; +})(Parser = exports.Parser || (exports.Parser = {})); + + +/***/ }), +/* 85 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +/** + * SIP Publish (SIP Extension for Event State Publication RFC3903) + * @class Class creating a SIP PublishContext. + */ +var PublishContext = /** @class */ (function (_super) { + tslib_1.__extends(PublishContext, _super); + function PublishContext(ua, target, event, options) { + if (options === void 0) { options = {}; } + var _this = this; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.contentType = (options.contentType || "text/plain"); + if (typeof options.expires !== "number" || (options.expires % 1) !== 0) { + options.expires = 3600; + } + else { + options.expires = Number(options.expires); + } + if (typeof (options.unpublishOnClose) !== "boolean") { + options.unpublishOnClose = true; + } + if (target === undefined || target === null || target === "") { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Target", target); + } + else { + target = ua.normalizeTarget(target); + if (target === undefined) { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Target", target); + } + } + _this = _super.call(this, ua, Constants_1.C.PUBLISH, target, options) || this; + _this.type = Enums_1.TypeStrings.PublishContext; + _this.options = options; + _this.target = target; + if (event === undefined || event === null || event === "") { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Event", event); + } + else { + _this.event = event; + } + _this.logger = ua.getLogger("sip.publish"); + _this.pubRequestExpires = _this.options.expires; + ua.on("transportCreated", function (transport) { + transport.on("transportError", function () { return _this.onTransportError(); }); + }); + return _this; + } + /** + * Publish + * @param {string} Event body to publish, optional + */ + PublishContext.prototype.publish = function (body) { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + // is Inital or Modify request + this.options.body = body; + this.pubRequestBody = this.options.body; + if (this.pubRequestExpires === 0) { + // This is Initial request after unpublish + this.pubRequestExpires = this.options.expires; + this.pubRequestEtag = undefined; + } + if (!(this.ua.publishers[this.target.toString() + ":" + this.event])) { + this.ua.publishers[this.target.toString() + ":" + this.event] = this; + } + this.sendPublishRequest(); + }; + /** + * Unpublish + */ + PublishContext.prototype.unpublish = function () { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestExpires = 0; + if (this.pubRequestEtag !== undefined) { + this.sendPublishRequest(); + } + }; + /** + * Close + */ + PublishContext.prototype.close = function () { + // Send unpublish, if requested + if (this.options.unpublishOnClose) { + this.unpublish(); + } + else { + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestExpires = 0; + this.pubRequestEtag = undefined; + } + if (this.ua.publishers[this.target.toString() + ":" + this.event]) { + delete this.ua.publishers[this.target.toString() + ":" + this.event]; + } + }; + PublishContext.prototype.onRequestTimeout = function () { + _super.prototype.onRequestTimeout.call(this); + this.emit("unpublished", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + PublishContext.prototype.onTransportError = function () { + _super.prototype.onTransportError.call(this); + this.emit("unpublished", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + PublishContext.prototype.receiveResponse = function (response) { + var _this = this; + var statusCode = response.statusCode || 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + this.emit("progress", response, cause); + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + // Set SIP-Etag + if (response.hasHeader("SIP-ETag")) { + this.pubRequestEtag = response.getHeader("SIP-ETag"); + } + else { + this.logger.warn("SIP-ETag header missing in a 200-class response to PUBLISH"); + } + // Update Expire + if (response.hasHeader("Expires")) { + var expires = Number(response.getHeader("Expires")); + if (typeof expires === "number" && expires >= 0 && expires <= this.pubRequestExpires) { + this.pubRequestExpires = expires; + } + else { + this.logger.warn("Bad Expires header in a 200-class response to PUBLISH"); + } + } + else { + this.logger.warn("Expires header missing in a 200-class response to PUBLISH"); + } + if (this.pubRequestExpires !== 0) { + // Schedule refresh + this.publishRefreshTimer = setTimeout(function () { return _this.refreshRequest(); }, this.pubRequestExpires * 900); + this.emit("published", response, cause); + } + else { + this.emit("unpublished", response, cause); + } + break; + case /^412$/.test(statusCode.toString()): + // 412 code means no matching ETag - possibly the PUBLISH expired + // Resubmit as new request, if the current request is not a "remove" + if (this.pubRequestEtag !== undefined && this.pubRequestExpires !== 0) { + this.logger.warn("412 response to PUBLISH, recovering"); + this.pubRequestEtag = undefined; + this.emit("progress", response, cause); + this.publish(this.options.body); + } + else { + this.logger.warn("412 response to PUBLISH, recovery failed"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + break; + case /^423$/.test(statusCode.toString()): + // 423 code means we need to adjust the Expires interval up + if (this.pubRequestExpires !== 0 && response.hasHeader("Min-Expires")) { + var minExpires = Number(response.getHeader("Min-Expires")); + if (typeof minExpires === "number" || minExpires > this.pubRequestExpires) { + this.logger.warn("423 code in response to PUBLISH, adjusting the Expires value and trying to recover"); + this.pubRequestExpires = minExpires; + this.emit("progress", response, cause); + this.publish(this.options.body); + } + else { + this.logger.warn("Bad 423 response Min-Expires header received for PUBLISH"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + } + else { + this.logger.warn("423 response to PUBLISH, recovery failed"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + break; + default: + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + break; + } + // Do the cleanup + if (this.pubRequestExpires === 0) { + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestEtag = undefined; + } + }; + PublishContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.publish(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + PublishContext.prototype.refreshRequest = function () { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + // This is Refresh request + this.pubRequestBody = undefined; + if (this.pubRequestEtag === undefined) { + // Request not valid + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Body", undefined); + } + if (this.pubRequestExpires === 0) { + // Request not valid + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Expire", this.pubRequestExpires); + } + this.sendPublishRequest(); + }; + PublishContext.prototype.sendPublishRequest = function () { + var reqOptions = Object.create(this.options || Object.prototype); + reqOptions.extraHeaders = (this.options.extraHeaders || []).slice(); + reqOptions.extraHeaders.push("Event: " + this.event); + reqOptions.extraHeaders.push("Expires: " + this.pubRequestExpires); + if (this.pubRequestEtag !== undefined) { + reqOptions.extraHeaders.push("SIP-If-Match: " + this.pubRequestEtag); + } + var ruri = this.target instanceof core_1.URI ? this.target : this.ua.normalizeTarget(this.target); + if (!ruri) { + throw new Error("ruri undefined."); + } + var params = this.options.params || {}; + var bodyObj; + if (this.pubRequestBody !== undefined) { + bodyObj = { + body: this.pubRequestBody, + contentType: this.options.contentType + }; + } + var body; + if (bodyObj) { + body = Utils_1.Utils.fromBodyObj(bodyObj); + } + this.request = this.ua.userAgentCore.makeOutgoingRequestMessage(Constants_1.C.PUBLISH, ruri, params.fromUri ? params.fromUri : this.ua.userAgentCore.configuration.aor, params.toUri ? params.toUri : this.target, params, reqOptions.extraHeaders, body); + this.send(); + }; + return PublishContext; +}(ClientContext_1.ClientContext)); +exports.PublishContext = PublishContext; + + +/***/ }), +/* 86 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var ServerContext_1 = __webpack_require__(87); +// tslint:disable-next-line:max-classes-per-file +var ReferClientContext = /** @class */ (function (_super) { + tslib_1.__extends(ReferClientContext, _super); + function ReferClientContext(ua, applicant, target, options) { + if (options === void 0) { options = {}; } + var _this = this; + if (ua === undefined || applicant === undefined || target === undefined) { + throw new TypeError("Not enough arguments"); + } + _this = _super.call(this, ua, Constants_1.C.REFER, applicant.remoteIdentity.uri.toString(), options) || this; + _this.type = Enums_1.TypeStrings.ReferClientContext; + _this.options = options; + _this.extraHeaders = (_this.options.extraHeaders || []).slice(); + _this.applicant = applicant; + _this.target = _this.initReferTo(target); + if (_this.ua) { + _this.extraHeaders.push("Referred-By: <" + _this.ua.configuration.uri + ">"); + } + // TODO: Check that this is correct isc/icc + _this.extraHeaders.push("Contact: " + applicant.contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + _this.extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + _this.extraHeaders.push("Refer-To: " + _this.target); + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + ReferClientContext.prototype.refer = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var extraHeaders = (this.extraHeaders || []).slice(); + if (options.extraHeaders) { + extraHeaders.concat(options.extraHeaders); + } + this.applicant.sendRequest(Constants_1.C.REFER, { + extraHeaders: this.extraHeaders, + receiveResponse: function (response) { + var statusCode = response && response.statusCode ? response.statusCode.toString() : ""; + if (/^1[0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestProgress", _this); + } + else if (/^2[0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestAccepted", _this); + } + else if (/^[4-6][0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestRejected", _this); + } + if (options.receiveResponse) { + options.receiveResponse(response); + } + } + }); + return this; + }; + ReferClientContext.prototype.receiveNotify = function (request) { + // If we can correctly handle this, then we need to send a 200 OK! + var contentType = request.message.hasHeader("Content-Type") ? + request.message.getHeader("Content-Type") : undefined; + if (contentType && contentType.search(/^message\/sipfrag/) !== -1) { + var messageBody = core_1.Grammar.parse(request.message.body, "sipfrag"); + if (messageBody === -1) { + request.reject({ + statusCode: 489, + reasonPhrase: "Bad Event" + }); + return; + } + switch (true) { + case (/^1[0-9]{2}$/.test(messageBody.status_code)): + this.emit("referProgress", this); + break; + case (/^2[0-9]{2}$/.test(messageBody.status_code)): + this.emit("referAccepted", this); + if (!this.options.activeAfterTransfer && this.applicant.terminate) { + this.applicant.terminate(); + } + break; + default: + this.emit("referRejected", this); + break; + } + request.accept(); + this.emit("notify", request.message); + return; + } + request.reject({ + statusCode: 489, + reasonPhrase: "Bad Event" + }); + }; + ReferClientContext.prototype.initReferTo = function (target) { + var stringOrURI; + if (typeof target === "string") { + // REFER without Replaces (Blind Transfer) + var targetString = core_1.Grammar.parse(target, "Refer_To"); + stringOrURI = targetString && targetString.uri ? targetString.uri : target; + // Check target validity + var targetUri = this.ua.normalizeTarget(target); + if (!targetUri) { + throw new TypeError("Invalid target: " + target); + } + stringOrURI = targetUri; + } + else { + // REFER with Replaces (Attended Transfer) + if (!target.session) { + throw new Error("Session undefined."); + } + var displayName = target.remoteIdentity.friendlyName; + var remoteTarget = target.session.remoteTarget.toString(); + var callId = target.session.callId; + var remoteTag = target.session.remoteTag; + var localTag = target.session.localTag; + var replaces = encodeURIComponent(callId + ";to-tag=" + remoteTag + ";from-tag=" + localTag); + stringOrURI = "\"" + displayName + "\" <" + remoteTarget + "?Replaces=" + replaces + ">"; + } + return stringOrURI; + }; + return ReferClientContext; +}(ClientContext_1.ClientContext)); +exports.ReferClientContext = ReferClientContext; +// tslint:disable-next-line:max-classes-per-file +var ReferServerContext = /** @class */ (function (_super) { + tslib_1.__extends(ReferServerContext, _super); + function ReferServerContext(ua, incomingRequest, session) { + var _this = _super.call(this, ua, incomingRequest) || this; + _this.session = session; + _this.type = Enums_1.TypeStrings.ReferServerContext; + _this.ua = ua; + _this.status = Enums_1.SessionStatus.STATUS_INVITE_RECEIVED; + _this.fromTag = _this.request.fromTag; + _this.id = _this.request.callId + _this.fromTag; + _this.contact = _this.ua.contact.toString(); + _this.logger = ua.getLogger("sip.referservercontext", _this.id); + // Needed to send the NOTIFY's + _this.cseq = Math.floor(Math.random() * 10000); + _this.callId = _this.request.callId; + _this.fromUri = _this.request.to.uri; + _this.fromTag = _this.request.to.parameters.tag; + _this.remoteTarget = _this.request.headers.Contact[0].parsed.uri; + _this.toUri = _this.request.from.uri; + _this.toTag = _this.request.fromTag; + _this.routeSet = _this.request.getHeaders("record-route"); + // RFC 3515 2.4.1 + if (!_this.request.hasHeader("refer-to")) { + _this.logger.warn("Invalid REFER packet. A refer-to header is required. Rejecting refer."); + _this.reject(); + return _this; + } + _this.referTo = _this.request.parseHeader("refer-to"); + // TODO: Must set expiration timer and send 202 if there is no response by then + _this.referredSession = _this.ua.findSession(_this.request); + if (_this.request.hasHeader("referred-by")) { + _this.referredBy = _this.request.getHeader("referred-by"); + } + if (_this.referTo.uri.hasHeader("replaces")) { + _this.replaces = _this.referTo.uri.getHeader("replaces"); + } + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + return _this; + } + ReferServerContext.prototype.progress = function () { + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.incomingRequest.trying(); + }; + ReferServerContext.prototype.reject = function (options) { + if (options === void 0) { options = {}; } + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.logger.log("Rejecting refer"); + this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + _super.prototype.reject.call(this, options); + this.emit("referRequestRejected", this); + }; + ReferServerContext.prototype.accept = function (options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + } + else { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.incomingRequest.accept({ + statusCode: 202, + reasonPhrase: "Accepted" + }); + this.emit("referRequestAccepted", this); + if (options.followRefer) { + this.logger.log("Accepted refer, attempting to automatically follow it"); + var target = this.referTo.uri; + if (!target.scheme || !target.scheme.match("^sips?$")) { + this.logger.error("SIP.js can only automatically follow SIP refer target"); + this.reject(); + return; + } + var inviteOptions = options.inviteOptions || {}; + var extraHeaders = (inviteOptions.extraHeaders || []).slice(); + if (this.replaces) { + // decodeURIComponent is a holdover from 2c086eb4. Not sure that it is actually necessary + extraHeaders.push("Replaces: " + decodeURIComponent(this.replaces)); + } + if (this.referredBy) { + extraHeaders.push("Referred-By: " + this.referredBy); + } + inviteOptions.extraHeaders = extraHeaders; + target.clearHeaders(); + this.targetSession = this.ua.invite(target.toString(), inviteOptions, modifiers); + this.emit("referInviteSent", this); + if (this.targetSession) { + this.targetSession.once("progress", function (response) { + var statusCode = response.statusCode || 100; + var reasonPhrase = response.reasonPhrase; + _this.sendNotify(("SIP/2.0 " + statusCode + " " + reasonPhrase).trim()); + _this.emit("referProgress", _this); + if (_this.referredSession) { + _this.referredSession.emit("referProgress", _this); + } + }); + this.targetSession.once("accepted", function () { + _this.logger.log("Successfully followed the refer"); + _this.sendNotify("SIP/2.0 200 OK"); + _this.emit("referAccepted", _this); + if (_this.referredSession) { + _this.referredSession.emit("referAccepted", _this); + } + }); + var referFailed = function (response) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; // No throw here because it is possible this gets called multiple times + } + _this.logger.log("Refer was not successful. Resuming session"); + if (response && response.statusCode === 429) { + _this.logger.log("Alerting referrer that identity is required."); + _this.sendNotify("SIP/2.0 429 Provide Referrer Identity"); + return; + } + _this.sendNotify("SIP/2.0 603 Declined"); + // Must change the status after sending the final Notify or it will not send due to check + _this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + _this.emit("referRejected", _this); + if (_this.referredSession) { + _this.referredSession.emit("referRejected"); + } + }; + this.targetSession.once("rejected", referFailed); + this.targetSession.once("failed", referFailed); + } + } + else { + this.logger.log("Accepted refer, but did not automatically follow it"); + this.sendNotify("SIP/2.0 200 OK"); + this.emit("referAccepted", this); + if (this.referredSession) { + this.referredSession.emit("referAccepted", this); + } + } + }; + ReferServerContext.prototype.sendNotify = function (bodyStr) { + // FIXME: Ported this. Clean it up. Session knows its state. + if (this.status !== Enums_1.SessionStatus.STATUS_ANSWERED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (core_1.Grammar.parse(bodyStr, "sipfrag") === -1) { + throw new Error("sipfrag body is required to send notify for refer"); + } + var body = { + contentDisposition: "render", + contentType: "message/sipfrag", + content: bodyStr + }; + // NOTIFY requests sent in same dialog as in dialog REFER. + if (this.session) { + this.session.notify(undefined, { + extraHeaders: [ + "Event: refer", + "Subscription-State: terminated", + ], + body: body + }); + return; + } + // The implicit subscription created by a REFER is the same as a + // subscription created with a SUBSCRIBE request. The agent issuing the + // REFER can terminate this subscription prematurely by unsubscribing + // using the mechanisms described in [2]. Terminating a subscription, + // either by explicitly unsubscribing or rejecting NOTIFY, is not an + // indication that the referenced request should be withdrawn or + // abandoned. + // https://tools.ietf.org/html/rfc3515#section-2.4.4 + // NOTIFY requests sent in new dialog for out of dialog REFER. + // FIXME: TODO: This should be done in a subscribe dialog to satisfy the above. + var request = this.ua.userAgentCore.makeOutgoingRequestMessage(Constants_1.C.NOTIFY, this.remoteTarget, this.fromUri, this.toUri, { + cseq: this.cseq += 1, + callId: this.callId, + fromTag: this.fromTag, + toTag: this.toTag, + routeSet: this.routeSet + }, [ + "Event: refer", + "Subscription-State: terminated", + "Content-Type: message/sipfrag" + ], body); + var transport = this.ua.transport; + if (!transport) { + throw new Error("Transport undefined."); + } + var user = { + loggerFactory: this.ua.getLoggerFactory() + }; + var nic = new core_1.NonInviteClientTransaction(request, transport, user); + }; + ReferServerContext.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + return ReferServerContext; +}(ServerContext_1.ServerContext)); +exports.ReferServerContext = ReferServerContext; + + +/***/ }), +/* 87 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Utils_1 = __webpack_require__(82); +var ServerContext = /** @class */ (function (_super) { + tslib_1.__extends(ServerContext, _super); + function ServerContext(ua, incomingRequest) { + var _this = _super.call(this) || this; + _this.incomingRequest = incomingRequest; + _this.data = {}; + ServerContext.initializer(_this, ua, incomingRequest); + return _this; + } + // hack to get around our multiple inheritance issues + ServerContext.initializer = function (objectToConstruct, ua, incomingRequest) { + var request = incomingRequest.message; + objectToConstruct.type = Enums_1.TypeStrings.ServerContext; + objectToConstruct.ua = ua; + objectToConstruct.logger = ua.getLogger("sip.servercontext"); + objectToConstruct.request = request; + if (request.body) { + objectToConstruct.body = request.body; + } + if (request.hasHeader("Content-Type")) { + objectToConstruct.contentType = request.getHeader("Content-Type"); + } + objectToConstruct.method = request.method; + objectToConstruct.localIdentity = request.to; + objectToConstruct.remoteIdentity = request.from; + var hasAssertedIdentity = request.hasHeader("P-Asserted-Identity"); + if (hasAssertedIdentity) { + var assertedIdentity = request.getHeader("P-Asserted-Identity"); + if (assertedIdentity) { + objectToConstruct.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(assertedIdentity); + } + } + }; + ServerContext.prototype.progress = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 180; + options.minCode = 100; + options.maxCode = 199; + options.events = ["progress"]; + return this.reply(options); + }; + ServerContext.prototype.accept = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 200; + options.minCode = 200; + options.maxCode = 299; + options.events = ["accepted"]; + return this.reply(options); + }; + ServerContext.prototype.reject = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 480; + options.minCode = 300; + options.maxCode = 699; + options.events = ["rejected", "failed"]; + return this.reply(options); + }; + ServerContext.prototype.reply = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 100; + var minCode = options.minCode || 100; + var maxCode = options.maxCode || 699; + var reasonPhrase = Utils_1.Utils.getReasonPhrase(statusCode, options.reasonPhrase); + var extraHeaders = options.extraHeaders || []; + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + var events = options.events || []; + if (statusCode < minCode || statusCode > maxCode) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var responseOptions = { + statusCode: statusCode, + reasonPhrase: reasonPhrase, + extraHeaders: extraHeaders, + body: body + }; + var response; + var statusCodeString = statusCode.toString(); + switch (true) { + case /^100$/.test(statusCodeString): + response = this.incomingRequest.trying(responseOptions).message; + break; + case /^1[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.progress(responseOptions).message; + break; + case /^2[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.accept(responseOptions).message; + break; + case /^3[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.redirect([], responseOptions).message; + break; + case /^[4-6][0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.reject(responseOptions).message; + break; + default: + throw new Error("Invalid status code " + statusCode); + } + events.forEach(function (event) { + _this.emit(event, response, reasonPhrase); + }); + return this; + }; + ServerContext.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + ServerContext.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + return ServerContext; +}(events_1.EventEmitter)); +exports.ServerContext = ServerContext; + + +/***/ }), +/* 88 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +/** + * Configuration load. + * @private + * returns {any} + */ +function loadConfig(configuration) { + var settings = { + expires: 600, + extraContactHeaderParams: [], + instanceId: undefined, + params: {}, + regId: undefined, + registrar: undefined, + }; + var configCheck = getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + if (value instanceof Array && value.length === 0) { + continue; + } + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if (value === null || value === "" || value === undefined || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + return settings; +} +function getConfigurationCheck() { + return { + mandatory: {}, + optional: { + expires: function (expires) { + if (Utils_1.Utils.isDecimal(expires)) { + var value = Number(expires); + if (value >= 0) { + return value; + } + } + }, + extraContactHeaderParams: function (extraContactHeaderParams) { + if (extraContactHeaderParams instanceof Array) { + return extraContactHeaderParams.filter(function (contactHeaderParam) { return (typeof contactHeaderParam === "string"); }); + } + }, + instanceId: function (instanceId) { + if (typeof instanceId !== "string") { + return; + } + if ((/^uuid:/i.test(instanceId))) { + instanceId = instanceId.substr(5); + } + if (core_1.Grammar.parse(instanceId, "uuid") === -1) { + return; + } + else { + return instanceId; + } + }, + params: function (params) { + if (typeof params === "object") { + return params; + } + }, + regId: function (regId) { + if (Utils_1.Utils.isDecimal(regId)) { + var value = Number(regId); + if (value >= 0) { + return value; + } + } + }, + registrar: function (registrar) { + if (typeof registrar !== "string") { + return; + } + if (!/^sip:/i.test(registrar)) { + registrar = Constants_1.C.SIP + ":" + registrar; + } + var parsed = core_1.Grammar.URIParse(registrar); + if (!parsed) { + return; + } + else if (parsed.user) { + return; + } + else { + return parsed; + } + } + } + }; +} +var RegisterContext = /** @class */ (function (_super) { + tslib_1.__extends(RegisterContext, _super); + function RegisterContext(ua, options) { + if (options === void 0) { options = {}; } + var _this = this; + var settings = loadConfig(options); + if (settings.regId && !settings.instanceId) { + settings.instanceId = Utils_1.Utils.newUUID(); + } + else if (!settings.regId && settings.instanceId) { + settings.regId = 1; + } + settings.params.toUri = settings.params.toUri || ua.configuration.uri; + settings.params.toDisplayName = settings.params.toDisplayName || ua.configuration.displayName; + settings.params.callId = settings.params.callId || Utils_1.Utils.createRandomToken(22); + settings.params.cseq = settings.params.cseq || Math.floor(Math.random() * 10000); + /* If no 'registrarServer' is set use the 'uri' value without user portion. */ + if (!settings.registrar) { + var registrarServer = {}; + if (typeof ua.configuration.uri === "object") { + registrarServer = ua.configuration.uri.clone(); + registrarServer.user = undefined; + } + else { + registrarServer = ua.configuration.uri; + } + settings.registrar = registrarServer; + } + _this = _super.call(this, ua, Constants_1.C.REGISTER, settings.registrar, settings) || this; + _this.type = Enums_1.TypeStrings.RegisterContext; + _this.options = settings; + _this.logger = ua.getLogger("sip.registercontext"); + _this.logger.log("configuration parameters for RegisterContext after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + _this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + // Registration expires + _this.expires = settings.expires; + // Contact header + _this.contact = ua.contact.toString(); + // Set status + _this.registered = false; + ua.on("transportCreated", function (transport) { + transport.on("disconnected", function () { return _this.onTransportDisconnected(); }); + }); + return _this; + } + RegisterContext.prototype.register = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Handle Options + this.options = tslib_1.__assign({}, this.options, options); + var extraHeaders = (this.options.extraHeaders || []).slice(); + extraHeaders.push("Contact: " + this.generateContactHeader(this.expires)); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + // Save original extraHeaders to be used in .close + this.closeHeaders = this.options.closeWithHeaders ? + (this.options.extraHeaders || []).slice() : []; + this.receiveResponse = function (response) { + // Discard responses to older REGISTER/un-REGISTER requests. + if (response.cseq !== _this.request.cseq) { + return; + } + // Clear registration timer + if (_this.registrationTimer !== undefined) { + clearTimeout(_this.registrationTimer); + _this.registrationTimer = undefined; + } + var statusCode = (response.statusCode || 0).toString(); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode): + _this.emit("progress", response); + break; + case /^2[0-9]{2}$/.test(statusCode): + _this.emit("accepted", response); + var expires = void 0; + if (response.hasHeader("expires")) { + expires = Number(response.getHeader("expires")); + } + if (_this.registrationExpiredTimer !== undefined) { + clearTimeout(_this.registrationExpiredTimer); + _this.registrationExpiredTimer = undefined; + } + // Search the Contact pointing to us and update the expires value accordingly. + var contacts = response.getHeaders("contact").length; + if (!contacts) { + _this.logger.warn("no Contact header in response to REGISTER, response ignored"); + break; + } + var contact = void 0; + while (contacts--) { + contact = response.parseHeader("contact", contacts); + if (contact.uri.user === _this.ua.contact.uri.user) { + expires = contact.getParam("expires"); + break; + } + else { + contact = undefined; + } + } + if (!contact) { + _this.logger.warn("no Contact header pointing to us, response ignored"); + break; + } + if (expires === undefined) { + expires = _this.expires; + } + // Re-Register before the expiration interval has elapsed. + // For that, decrease the expires value. ie: 3 seconds + _this.registrationTimer = setTimeout(function () { + _this.registrationTimer = undefined; + _this.register(_this.options); + }, (expires * 1000) - 3000); + _this.registrationExpiredTimer = setTimeout(function () { + _this.logger.warn("registration expired"); + if (_this.registered) { + _this.unregistered(undefined, Constants_1.C.causes.EXPIRES); + } + }, expires * 1000); + // Save gruu values + if (contact.hasParam("temp-gruu")) { + _this.ua.contact.tempGruu = core_1.Grammar.URIParse(contact.getParam("temp-gruu").replace(/"/g, "")); + } + if (contact.hasParam("pub-gruu")) { + _this.ua.contact.pubGruu = core_1.Grammar.URIParse(contact.getParam("pub-gruu").replace(/"/g, "")); + } + _this.registered = true; + _this.emit("registered", response || undefined); + break; + // Interval too brief RFC3261 10.2.8 + case /^423$/.test(statusCode): + if (response.hasHeader("min-expires")) { + // Increase our registration interval to the suggested minimum + _this.expires = Number(response.getHeader("min-expires")); + // Attempt the registration again immediately + _this.register(_this.options); + } + else { // This response MUST contain a Min-Expires header field + _this.logger.warn("423 response received for REGISTER without Min-Expires"); + _this.registrationFailure(response, Constants_1.C.causes.SIP_FAILURE_CODE); + } + break; + default: + _this.registrationFailure(response, Utils_1.Utils.sipErrorCause(response.statusCode || 0)); + } + }; + this.onRequestTimeout = function () { + _this.registrationFailure(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + this.onTransportError = function () { + _this.registrationFailure(undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + this.request.cseq++; + this.request.setHeader("cseq", this.request.cseq + " REGISTER"); + this.request.extraHeaders = extraHeaders; + this.send(); + }; + RegisterContext.prototype.close = function () { + var options = { + all: false, + extraHeaders: this.closeHeaders + }; + this.registeredBefore = this.registered; + if (this.registered) { + this.unregister(options); + } + }; + RegisterContext.prototype.unregister = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (!this.registered && !options.all) { + this.logger.warn("Already unregistered, but sending an unregister anyways."); + } + var extraHeaders = (options.extraHeaders || []).slice(); + this.registered = false; + // Clear the registration timer. + if (this.registrationTimer !== undefined) { + clearTimeout(this.registrationTimer); + this.registrationTimer = undefined; + } + if (options.all) { + extraHeaders.push("Contact: *"); + extraHeaders.push("Expires: 0"); + } + else { + extraHeaders.push("Contact: " + this.generateContactHeader(0)); + } + this.receiveResponse = function (response) { + var statusCode = (response && response.statusCode) ? response.statusCode.toString() : ""; + switch (true) { + case /^1[0-9]{2}$/.test(statusCode): + _this.emit("progress", response); + break; + case /^2[0-9]{2}$/.test(statusCode): + _this.emit("accepted", response); + if (_this.registrationExpiredTimer !== undefined) { + clearTimeout(_this.registrationExpiredTimer); + _this.registrationExpiredTimer = undefined; + } + _this.unregistered(response); + break; + default: + _this.unregistered(response, Utils_1.Utils.sipErrorCause(response.statusCode || 0)); + } + }; + this.onRequestTimeout = function () { + // Not actually unregistered... + // this.unregistered(undefined, SIP.C.causes.REQUEST_TIMEOUT); + }; + this.request.cseq++; + this.request.setHeader("cseq", this.request.cseq + " REGISTER"); + this.request.extraHeaders = extraHeaders; + this.send(); + }; + RegisterContext.prototype.unregistered = function (response, cause) { + this.registered = false; + this.emit("unregistered", response || undefined, cause || undefined); + }; + RegisterContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.register(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + RegisterContext.prototype.registrationFailure = function (response, cause) { + this.emit("failed", response || undefined, cause || undefined); + }; + RegisterContext.prototype.onTransportDisconnected = function () { + this.registeredBefore = this.registered; + if (this.registrationTimer !== undefined) { + clearTimeout(this.registrationTimer); + this.registrationTimer = undefined; + } + if (this.registrationExpiredTimer !== undefined) { + clearTimeout(this.registrationExpiredTimer); + this.registrationExpiredTimer = undefined; + } + if (this.registered) { + this.unregistered(undefined, Constants_1.C.causes.CONNECTION_ERROR); + } + }; + /** + * Helper Function to generate Contact Header + * @private + * returns {String} + */ + RegisterContext.prototype.generateContactHeader = function (expires) { + if (expires === void 0) { expires = 0; } + var contact = this.contact; + if (this.options.regId && this.options.instanceId) { + contact += ";reg-id=" + this.options.regId; + contact += ';+sip.instance=""'; + } + if (this.options.extraContactHeaderParams) { + this.options.extraContactHeaderParams.forEach(function (header) { + contact += ";" + header; + }); + } + contact += ";expires=" + expires; + return contact; + }; + return RegisterContext; +}(ClientContext_1.ClientContext)); +exports.RegisterContext = RegisterContext; + + +/***/ }), +/* 89 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var ReferContext_1 = __webpack_require__(86); +var ServerContext_1 = __webpack_require__(87); +var DTMF_1 = __webpack_require__(90); +var Utils_1 = __webpack_require__(82); +/* + * @param {function returning SIP.sessionDescriptionHandler} [sessionDescriptionHandlerFactory] + * (See the documentation for the sessionDescriptionHandlerFactory argument of the UA constructor.) + */ +var Session = /** @class */ (function (_super) { + tslib_1.__extends(Session, _super); + function Session(sessionDescriptionHandlerFactory) { + var _this = _super.call(this) || this; + _this.data = {}; + _this.type = Enums_1.TypeStrings.Session; + if (!sessionDescriptionHandlerFactory) { + throw new Exceptions_1.Exceptions.SessionDescriptionHandlerError("A session description handler is required for the session to function"); + } + _this.status = Session.C.STATUS_NULL; + _this.pendingReinvite = false; + _this.sessionDescriptionHandlerFactory = sessionDescriptionHandlerFactory; + _this.hasOffer = false; + _this.hasAnswer = false; + // Session Timers + _this.timers = { + ackTimer: undefined, + expiresTimer: undefined, + invite2xxTimer: undefined, + userNoAnswerTimer: undefined, + rel1xxTimer: undefined, + prackTimer: undefined + }; + // Session info + _this.startTime = undefined; + _this.endTime = undefined; + _this.tones = undefined; + // Hold state + _this.localHold = false; + _this.earlySdp = undefined; + _this.rel100 = Constants_1.C.supported.UNSUPPORTED; + return _this; + } + Session.prototype.dtmf = function (tones, options) { + var _this = this; + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + // Check tones + if (!tones || !tones.toString().match(/^[0-9A-D#*,]+$/i)) { + throw new TypeError("Invalid tones: " + tones); + } + var sendDTMF = function () { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED || !_this.tones || _this.tones.length === 0) { + // Stop sending DTMF + _this.tones = undefined; + return; + } + var dtmf = _this.tones.shift(); + var timeout; + if (dtmf.tone === ",") { + timeout = 2000; + } + else { + dtmf.on("failed", function () { _this.tones = undefined; }); + dtmf.send(options); + timeout = dtmf.duration + dtmf.interToneGap; + } + // Set timeout for the next tone + setTimeout(sendDTMF, timeout); + }; + tones = tones.toString(); + var dtmfType = this.ua.configuration.dtmfType; + if (this.sessionDescriptionHandler && dtmfType === Constants_1.C.dtmfType.RTP) { + var sent = this.sessionDescriptionHandler.sendDtmf(tones, options); + if (!sent) { + this.logger.warn("Attempt to use dtmfType 'RTP' has failed, falling back to INFO packet method"); + dtmfType = Constants_1.C.dtmfType.INFO; + } + } + if (dtmfType === Constants_1.C.dtmfType.INFO) { + var dtmfs = []; + var tonesArray = tones.split(""); + while (tonesArray.length > 0) { + dtmfs.push(new DTMF_1.DTMF(this, tonesArray.shift(), options)); + } + if (this.tones) { + // Tones are already queued, just add to the queue + this.tones = this.tones.concat(dtmfs); + return this; + } + this.tones = dtmfs; + sendDTMF(); + } + return this; + }; + Session.prototype.bye = function (options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + this.logger.error("Error: Attempted to send BYE in a terminated session."); + return this; + } + this.logger.log("terminating Session"); + var statusCode = options.statusCode; + if (statusCode && (statusCode < 200 || statusCode >= 700)) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + options.receiveResponse = function () { }; + return this.sendRequest(Constants_1.C.BYE, options).terminated(); + }; + Session.prototype.refer = function (target, options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.referContext = new ReferContext_1.ReferClientContext(this.ua, this, target, options); + this.emit("referRequested", this.referContext); + this.referContext.refer(options); + return this.referContext; + }; + /** + * Sends in dialog request. + * @param method Request method. + * @param options Options bucket. + */ + Session.prototype.sendRequest = function (method, options) { + if (options === void 0) { options = {}; } + if (!this.session) { + throw new Error("Session undefined."); + } + // Convert any "body" option to a Body. + if (options.body) { + options.body = Utils_1.Utils.fromBodyObj(options.body); + } + // Convert any "receiveResponse" callback option passed to an OutgoingRequestDelegate. + var delegate; + var callback = options.receiveResponse; + if (callback) { + delegate = { + onAccept: function (response) { return callback(response.message); }, + onProgress: function (response) { return callback(response.message); }, + onRedirect: function (response) { return callback(response.message); }, + onReject: function (response) { return callback(response.message); }, + onTrying: function (response) { return callback(response.message); } + }; + } + var request; + var requestOptions = options; + switch (method) { + case Constants_1.C.BYE: + request = this.session.bye(delegate, requestOptions); + break; + case Constants_1.C.INVITE: + request = this.session.invite(delegate, requestOptions); + break; + case Constants_1.C.REFER: + request = this.session.refer(delegate, requestOptions); + break; + default: + throw new Error("Unexpected " + method + ". Method not implemented by user agent core."); + } + // Ported - Emit the request event + this.emit(method.toLowerCase(), request.message); + return this; + }; + Session.prototype.close = function () { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.logger.log("closing INVITE session " + this.id); + // 1st Step. Terminate media. + if (this.sessionDescriptionHandler) { + this.sessionDescriptionHandler.close(); + } + // 2nd Step. Terminate signaling. + // Clear session timers + for (var timer in this.timers) { + if (this.timers[timer]) { + clearTimeout(this.timers[timer]); + } + } + this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + if (this.ua.transport) { + this.ua.transport.removeListener("transportError", this.errorListener); + } + delete this.ua.sessions[this.id]; + return this; + }; + Session.prototype.hold = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK && this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (this.localHold) { + this.logger.log("Session is already on hold, cannot put it on hold again"); + return; + } + options.modifiers = modifiers; + if (this.sessionDescriptionHandler) { + options.modifiers.push(this.sessionDescriptionHandler.holdModifier); + } + this.localHold = true; + this.sendReinvite(options); + }; + Session.prototype.unhold = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK && this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (!this.localHold) { + this.logger.log("Session is not on hold, cannot unhold it"); + return; + } + options.modifiers = modifiers; + this.localHold = false; + this.sendReinvite(options); + }; + Session.prototype.reinvite = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + options.modifiers = modifiers; + return this.sendReinvite(options); + }; + Session.prototype.terminate = function (options) { + // here for types and to be overridden + return this; + }; + Session.prototype.onTransportError = function () { + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(undefined, Constants_1.C.causes.CONNECTION_ERROR); + } + }; + Session.prototype.onRequestTimeout = function () { + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.terminated(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + } + else if (this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + this.terminated(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + } + }; + Session.prototype.onDialogError = function (response) { + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.terminated(response, Constants_1.C.causes.DIALOG_ERROR); + } + else if (this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(response, Constants_1.C.causes.DIALOG_ERROR); + this.terminated(response, Constants_1.C.causes.DIALOG_ERROR); + } + }; + Session.prototype.on = function (name, callback) { + return _super.prototype.on.call(this, name, callback); + }; + Session.prototype.onAck = function (incomingRequest) { + var _this = this; + var confirmSession = function () { + clearTimeout(_this.timers.ackTimer); + clearTimeout(_this.timers.invite2xxTimer); + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var contentDisp = incomingRequest.message.getHeader("Content-Disposition"); + if (contentDisp && contentDisp.type === "render") { + _this.renderbody = incomingRequest.message.body; + _this.rendertype = incomingRequest.message.getHeader("Content-Type"); + } + _this.emit("confirmed", incomingRequest.message); + }; + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + if (this.sessionDescriptionHandler && + this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + this.hasAnswer = true; + this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).catch(function (e) { + _this.logger.warn(e); + _this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + _this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + _this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + throw e; + }).then(function () { return confirmSession(); }); + } + else { + confirmSession(); + } + } + }; + Session.prototype.receiveRequest = function (incomingRequest) { + switch (incomingRequest.message.method) { // TODO: This needs a default case + case Constants_1.C.BYE: + incomingRequest.accept(); + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.emit("bye", incomingRequest.message); + this.terminated(incomingRequest.message, Constants_1.C.BYE); + } + break; + case Constants_1.C.INVITE: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.logger.log("re-INVITE received"); + this.receiveReinvite(incomingRequest); + } + break; + case Constants_1.C.INFO: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED || this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + if (this.onInfo) { + return this.onInfo(incomingRequest.message); + } + var contentType = incomingRequest.message.getHeader("content-type"); + if (contentType) { + if (contentType.match(/^application\/dtmf-relay/i)) { + if (incomingRequest.message.body) { + var body = incomingRequest.message.body.split("\r\n", 2); + if (body.length === 2) { + var tone = void 0; + var duration = void 0; + var regTone = /^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/; + if (regTone.test(body[0])) { + tone = body[0].replace(regTone, "$2"); + } + var regDuration = /^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/; + if (regDuration.test(body[1])) { + duration = parseInt(body[1].replace(regDuration, "$2"), 10); + } + if (tone && duration) { + new DTMF_1.DTMF(this, tone, { duration: duration }).init_incoming(incomingRequest); + } + } + } + } + else { + incomingRequest.reject({ + statusCode: 415, + extraHeaders: ["Accept: application/dtmf-relay"] + }); + } + } + } + break; + case Constants_1.C.REFER: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.logger.log("REFER received"); + this.referContext = new ReferContext_1.ReferServerContext(this.ua, incomingRequest, this.session); + if (this.listeners("referRequested").length) { + this.emit("referRequested", this.referContext); + } + else { + this.logger.log("No referRequested listeners, automatically accepting and following the refer"); + var options = { followRefer: true }; + if (this.passedOptions) { + options.inviteOptions = this.passedOptions; + } + this.referContext.accept(options, this.modifiers); + } + } + break; + case Constants_1.C.NOTIFY: + if (this.referContext && + this.referContext.type === Enums_1.TypeStrings.ReferClientContext && + incomingRequest.message.hasHeader("event") && + /^refer(;.*)?$/.test(incomingRequest.message.getHeader("event"))) { + this.referContext.receiveNotify(incomingRequest); + return; + } + incomingRequest.accept(); + this.emit("notify", incomingRequest.message); + break; + } + }; + // In dialog INVITE Reception + Session.prototype.receiveReinvite = function (incomingRequest) { + // TODO: Should probably check state of the session + var _this = this; + this.emit("reinvite", this, incomingRequest.message); + if (incomingRequest.message.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = + core_1.Grammar.nameAddrHeaderParse(incomingRequest.message.getHeader("P-Asserted-Identity")); + } + var promise; + if (!this.sessionDescriptionHandler) { + this.logger.warn("No SessionDescriptionHandler to reinvite"); + return; + } + if (incomingRequest.message.getHeader("Content-Length") === "0" && + !incomingRequest.message.getHeader("Content-Type")) { // Invite w/o SDP + promise = this.sessionDescriptionHandler.getDescription(this.sessionDescriptionHandlerOptions, this.modifiers); + } + else if (this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + // Invite w/ SDP + promise = this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).then(this.sessionDescriptionHandler.getDescription.bind(this.sessionDescriptionHandler, this.sessionDescriptionHandlerOptions, this.modifiers)); + } + else { // Bad Packet (should never get hit) + incomingRequest.reject({ statusCode: 415 }); + this.emit("reinviteFailed", this); + return; + } + promise.catch(function (e) { + var statusCode; + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + statusCode = 500; + } + else if (e.type === Enums_1.TypeStrings.RenegotiationError) { + _this.emit("renegotiationError", e); + _this.logger.warn(e.toString()); + statusCode = 488; + } + else { + _this.logger.error(e); + statusCode = 488; + } + incomingRequest.reject({ statusCode: statusCode }); + _this.emit("reinviteFailed", _this); + // TODO: This could be better + throw e; + }).then(function (description) { + var extraHeaders = ["Contact: " + _this.contact]; + incomingRequest.accept({ + statusCode: 200, + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(description) + }); + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK; + _this.emit("reinviteAccepted", _this); + }); + }; + Session.prototype.sendReinvite = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.pendingReinvite) { + this.logger.warn("Reinvite in progress. Please wait until complete, then try again."); + return; + } + if (!this.sessionDescriptionHandler) { + this.logger.warn("No SessionDescriptionHandler, can't reinvite.."); + return; + } + this.pendingReinvite = true; + options.modifiers = options.modifiers || []; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Contact: " + this.contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + this.sessionDescriptionHandler.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function (description) { + if (!_this.session) { + throw new Error("Session undefined."); + } + var delegate = { + onAccept: function (response) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + _this.logger.error("Received reinvite response, but in STATUS_TERMINATED"); + // TODO: Do we need to send a SIP response? + return; + } + if (!_this.pendingReinvite) { + _this.logger.error("Received reinvite response, but have no pending reinvite"); + // TODO: Do we need to send a SIP response? + return; + } + // FIXME: Why is this set here? + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + // 17.1.1.1 - For each final response that is received at the client transaction, + // the client transaction sends an ACK, + _this.emit("ack", response.ack()); + _this.pendingReinvite = false; + // TODO: All of these timers should move into the Transaction layer + clearTimeout(_this.timers.invite2xxTimer); + if (!_this.sessionDescriptionHandler || + !_this.sessionDescriptionHandler.hasDescription(response.message.getHeader("Content-Type") || "")) { + _this.logger.error("2XX response received to re-invite but did not have a description"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("2XX response received to re-invite but did not have a description")); + return; + } + _this.sessionDescriptionHandler + .setDescription(response.message.body, _this.sessionDescriptionHandlerOptions, _this.modifiers) + .catch(function (e) { + _this.logger.error("Could not set the description in 2XX response"); + _this.logger.error(e); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", e); + _this.sendRequest(Constants_1.C.BYE, { + extraHeaders: ["Reason: " + Utils_1.Utils.getReasonHeaderValue(488, "Not Acceptable Here")] + }); + _this.terminated(undefined, Constants_1.C.causes.INCOMPATIBLE_SDP); + throw e; + }) + .then(function () { + _this.emit("reinviteAccepted", _this); + }); + }, + onProgress: function (response) { + return; + }, + onRedirect: function (response) { + // FIXME: Does ACK need to be sent? + _this.pendingReinvite = false; + _this.logger.log("Received a non 1XX or 2XX response to a re-invite"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("Invalid response to a re-invite")); + }, + onReject: function (response) { + // FIXME: Does ACK need to be sent? + _this.pendingReinvite = false; + _this.logger.log("Received a non 1XX or 2XX response to a re-invite"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("Invalid response to a re-invite")); + }, + onTrying: function (response) { + return; + } + }; + var requestOptions = { + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(description) + }; + _this.session.invite(delegate, requestOptions); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.RenegotiationError) { + _this.pendingReinvite = false; + _this.emit("renegotiationError", e); + _this.logger.warn("Renegotiation Error"); + _this.logger.warn(e.toString()); + throw e; + } + _this.logger.error("sessionDescriptionHandler error"); + _this.logger.error(e); + throw e; + }); + }; + Session.prototype.failed = function (response, cause) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.emit("failed", response, cause); + return this; + }; + Session.prototype.rejected = function (response, cause) { + this.emit("rejected", response, cause); + return this; + }; + Session.prototype.canceled = function () { + if (this.sessionDescriptionHandler) { + this.sessionDescriptionHandler.close(); + } + this.emit("cancel"); + return this; + }; + Session.prototype.accepted = function (response, cause) { + if (!(response instanceof String)) { + cause = Utils_1.Utils.getReasonPhrase((response && response.statusCode) || 0, cause); + } + this.startTime = new Date(); + if (this.replacee) { + this.replacee.emit("replaced", this); + this.replacee.terminate(); + } + this.emit("accepted", response, cause); + return this; + }; + Session.prototype.terminated = function (message, cause) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.endTime = new Date(); + this.close(); + this.emit("terminated", message, cause); + return this; + }; + Session.prototype.connecting = function (request) { + this.emit("connecting", { request: request }); + return this; + }; + Session.C = Enums_1.SessionStatus; + return Session; +}(events_1.EventEmitter)); +exports.Session = Session; +// tslint:disable-next-line:max-classes-per-file +var InviteServerContext = /** @class */ (function (_super) { + tslib_1.__extends(InviteServerContext, _super); + function InviteServerContext(ua, incomingInviteRequest) { + var _this = this; + if (!ua.configuration.sessionDescriptionHandlerFactory) { + ua.logger.warn("Can't build ISC without SDH Factory"); + throw new Error("ISC Constructor Failed"); + } + _this = _super.call(this, ua.configuration.sessionDescriptionHandlerFactory) || this; + _this._canceled = false; + _this.rseq = Math.floor(Math.random() * 10000); + _this.incomingRequest = incomingInviteRequest; + var request = incomingInviteRequest.message; + ServerContext_1.ServerContext.initializer(_this, ua, incomingInviteRequest); + _this.type = Enums_1.TypeStrings.InviteServerContext; + var contentDisp = request.parseHeader("Content-Disposition"); + if (contentDisp && contentDisp.type === "render") { + _this.renderbody = request.body; + _this.rendertype = request.getHeader("Content-Type"); + } + _this.status = Enums_1.SessionStatus.STATUS_INVITE_RECEIVED; + _this.fromTag = request.fromTag; + _this.id = request.callId + _this.fromTag; + _this.request = request; + _this.contact = _this.ua.contact.toString(); + _this.logger = ua.getLogger("sip.inviteservercontext", _this.id); + // Save the session into the ua sessions collection. + _this.ua.sessions[_this.id] = _this; + // Set 100rel if necessary + var set100rel = function (header, relSetting) { + if (request.hasHeader(header) && request.getHeader(header).toLowerCase().indexOf("100rel") >= 0) { + _this.rel100 = relSetting; + } + }; + set100rel("require", Constants_1.C.supported.REQUIRED); + set100rel("supported", Constants_1.C.supported.SUPPORTED); + // Set the toTag on the incoming request to the toTag which + // will be used in the response to the incoming request!!! + // FIXME: HACK: This is a hack to port an existing behavior. + // The behavior being ported appears to be a hack itself, + // so this is a hack to port a hack. At least one test spec + // relies on it (which is yet another hack). + _this.request.toTag = incomingInviteRequest.toTag; + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + // Set userNoAnswerTimer + _this.timers.userNoAnswerTimer = setTimeout(function () { + incomingInviteRequest.reject({ statusCode: 408 }); + _this.failed(request, Constants_1.C.causes.NO_ANSWER); + _this.terminated(request, Constants_1.C.causes.NO_ANSWER); + }, _this.ua.configuration.noAnswerTimeout || 60); + /* Set expiresTimer + * RFC3261 13.3.1 + */ + // Get the Expires header value if exists + if (request.hasHeader("expires")) { + var expires = Number(request.getHeader("expires") || 0) * 1000; + _this.timers.expiresTimer = setTimeout(function () { + if (_this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + incomingInviteRequest.reject({ statusCode: 487 }); + _this.failed(request, Constants_1.C.causes.EXPIRES); + _this.terminated(request, Constants_1.C.causes.EXPIRES); + } + }, expires); + } + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + Object.defineProperty(InviteServerContext.prototype, "autoSendAnInitialProvisionalResponse", { + /** + * If true, a first provisional response after the 100 Trying + * will be sent automatically. This is false it the UAC required + * reliable provisional responses (100rel in Require header), + * otherwise it is true. The provisional is sent by calling + * `progress()` without any options. + * + * FIXME: TODO: It seems reasonable that the ISC user should + * be able to optionally disable this behavior. As the provisional + * is sent prior to the "invite" event being emitted, it's a known + * issue that the ISC user cannot register listeners or do any other + * setup prior to the call to `progress()`. As an example why this is + * an issue, setting `ua.configuration.rel100` to REQUIRED will result + * in an attempt by `progress()` to send a 183 with SDP produced by + * calling `getDescription()` on a session description handler, but + * the ISC user cannot perform any potentially required session description + * handler initialization (thus preventing the utilization of setting + * `ua.configuration.rel100` to REQUIRED). That begs the question of + * why this behavior is disabled when the UAC requires 100rel but not + * when the UAS requires 100rel? But ignoring that, it's just one example + * of a class of cases where the ISC user needs to do something prior + * to the first call to `progress()` and is unable to do so. + */ + get: function () { + return this.rel100 === Constants_1.C.supported.REQUIRED ? false : true; + }, + enumerable: true, + configurable: true + }); + // type hack for servercontext interface + InviteServerContext.prototype.reply = function (options) { + if (options === void 0) { options = {}; } + return this; + }; + // typing note: this was the only function using its super in ServerContext + // so the bottom half of this function is copied and paired down from that + InviteServerContext.prototype.reject = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.logger.log("rejecting RTCSession"); + var statusCode = options.statusCode || 480; + var reasonPhrase = Utils_1.Utils.getReasonPhrase(statusCode, options.reasonPhrase); + var extraHeaders = options.extraHeaders || []; + if (statusCode < 300 || statusCode > 699) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + // FIXME: Need to redirect to someplae + var response = statusCode < 400 ? + this.incomingRequest.redirect([], { statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }) : + this.incomingRequest.reject({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + (["rejected", "failed"]).forEach(function (event) { + _this.emit(event, response.message, reasonPhrase); + }); + return this.terminated(); + }; + /** + * Accept the incoming INVITE request to start a Session. + * Replies to the INVITE request with a 200 Ok response. + * @param options Options bucket. + */ + InviteServerContext.prototype.accept = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // FIXME: Need guard against calling more than once. + this._accept(options) + .then(function (_a) { + var message = _a.message, session = _a.session; + session.delegate = { + onAck: function (ackRequest) { return _this.onAck(ackRequest); }, + onAckTimeout: function () { return _this.onAckTimeout(); }, + onBye: function (byeRequest) { return _this.receiveRequest(byeRequest); }, + onInfo: function (infoRequest) { return _this.receiveRequest(infoRequest); }, + onInvite: function (inviteRequest) { return _this.receiveRequest(inviteRequest); }, + onNotify: function (notifyRequest) { return _this.receiveRequest(notifyRequest); }, + onPrack: function (prackRequest) { return _this.receiveRequest(prackRequest); }, + onRefer: function (referRequest) { return _this.receiveRequest(referRequest); } + }; + _this.session = session; + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK; + _this.accepted(message, Utils_1.Utils.getReasonPhrase(200)); + }) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + }; + /** + * Report progress to the the caller. + * Replies to the INVITE request with a 1xx provisional response. + * @param options Options bucket. + */ + InviteServerContext.prototype.progress = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Ported + var statusCode = options.statusCode || 180; + if (statusCode < 100 || statusCode > 199) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + // Ported + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + this.logger.warn("Unexpected call for progress while terminated, ignoring"); + return this; + } + // Added + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED) { + this.logger.warn("Unexpected call for progress while answered, ignoring"); + return this; + } + // Added + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + this.logger.warn("Unexpected call for progress while answered (waiting for prack), ignoring"); + return this; + } + // After the first reliable provisional response for a request has been + // acknowledged, the UAS MAY send additional reliable provisional + // responses. The UAS MUST NOT send a second reliable provisional + // response until the first is acknowledged. After the first, it is + // RECOMMENDED that the UAS not send an additional reliable provisional + // response until the previous is acknowledged. The first reliable + // provisional response receives special treatment because it conveys + // the initial sequence number. If additional reliable provisional + // responses were sent before the first was acknowledged, the UAS could + // not be certain these were received in order. + // https://tools.ietf.org/html/rfc3262#section-3 + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + this.logger.warn("Unexpected call for progress while waiting for prack, ignoring"); + return this; + } + // Ported + if (options.statusCode === 100) { + try { + this.incomingRequest.trying(); + } + catch (error) { + this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!this._canceled) { + throw error; + } + } + return this; + } + // Standard provisional response. + if (!(this.rel100 === Constants_1.C.supported.REQUIRED) && + !(this.rel100 === Constants_1.C.supported.SUPPORTED && options.rel100) && + !(this.rel100 === Constants_1.C.supported.SUPPORTED && this.ua.configuration.rel100 === Constants_1.C.supported.REQUIRED)) { + this._progress(options) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + } + // Reliable provisional response. + this._reliableProgressWaitForPrack(options) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + }; + /** + * Reject an unaccepted incoming INVITE request or send BYE if established session. + * @param options Options bucket. FIXME: This options bucket needs to be typed. + */ + InviteServerContext.prototype.terminate = function (options) { + // The caller's UA MAY send a BYE for either confirmed or early dialogs, + // and the callee's UA MAY send a BYE on confirmed dialogs, but MUST NOT + // send a BYE on early dialogs. However, the callee's UA MUST NOT send a + // BYE on a confirmed dialog until it has received an ACK for its 2xx + // response or until the server transaction times out. + // https://tools.ietf.org/html/rfc3261#section-15 + var _this = this; + if (options === void 0) { options = {}; } + // We don't yet have a dialog, so reject request. + if (!this.session) { + this.reject(options); + return this; + } + switch (this.session.sessionState) { + case core_1.SessionState.Initial: + this.reject(options); + return this; + case core_1.SessionState.Early: + this.reject(options); + return this; + case core_1.SessionState.AckWait: + this.session.delegate = { + // When ACK shows up, say BYE. + onAck: function () { + _this.sendRequest(Constants_1.C.BYE, options); + }, + // Or the server transaction times out before the ACK arrives. + onAckTimeout: function () { + _this.sendRequest(Constants_1.C.BYE, options); + } + }; + // Ported + this.emit("bye", this.request); + this.terminated(); + return this; + case core_1.SessionState.Confirmed: + this.bye(options); + return this; + case core_1.SessionState.Terminated: + return this; + default: + return this; + } + }; + InviteServerContext.prototype.onCancel = function (message) { + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER || + this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_EARLY_MEDIA || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED) { + this.status = Enums_1.SessionStatus.STATUS_CANCELED; + this.incomingRequest.reject({ statusCode: 487 }); + this.canceled(); + this.rejected(message, Constants_1.C.causes.CANCELED); + this.failed(message, Constants_1.C.causes.CANCELED); + this.terminated(message, Constants_1.C.causes.CANCELED); + } + }; + InviteServerContext.prototype.receiveRequest = function (incomingRequest) { + var _this = this; + switch (incomingRequest.message.method) { + case Constants_1.C.PRACK: + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + if (!this.hasAnswer) { + this.sessionDescriptionHandler = this.setupSessionDescriptionHandler(); + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + if (this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + this.hasAnswer = true; + this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).then(function () { + clearTimeout(_this.timers.rel1xxTimer); + clearTimeout(_this.timers.prackTimer); + incomingRequest.accept(); + if (_this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + _this.accept(); + } + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + }, function (e) { + _this.logger.warn(e); + _this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + _this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + _this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + }); + } + else { + this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + } + } + else { + clearTimeout(this.timers.rel1xxTimer); + clearTimeout(this.timers.prackTimer); + incomingRequest.accept(); + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + this.accept(); + } + this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + } + } + else if (this.status === Enums_1.SessionStatus.STATUS_EARLY_MEDIA) { + incomingRequest.accept(); + } + break; + default: + _super.prototype.receiveRequest.call(this, incomingRequest); + break; + } + }; + // Internal Function to setup the handler consistently + InviteServerContext.prototype.setupSessionDescriptionHandler = function () { + if (this.sessionDescriptionHandler) { + return this.sessionDescriptionHandler; + } + return this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions); + }; + InviteServerContext.prototype.generateResponseOfferAnswer = function (options) { + if (!this.session) { + var body = core_1.getBody(this.incomingRequest.message); + if (!body || body.contentDisposition !== "session") { + return this.getOffer(options); + } + else { + return this.setOfferAndGetAnswer(body, options); + } + } + else { + switch (this.session.signalingState) { + case core_1.SignalingState.Initial: + return this.getOffer(options); + case core_1.SignalingState.Stable: + return Promise.resolve(undefined); + case core_1.SignalingState.HaveLocalOffer: + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + return Promise.resolve(undefined); + case core_1.SignalingState.HaveRemoteOffer: + if (!this.session.offer) { + throw new Error("Session offer undefined"); + } + return this.setOfferAndGetAnswer(this.session.offer, options); + case core_1.SignalingState.Closed: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + default: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + } + } + }; + InviteServerContext.prototype.handlePrackOfferAnswer = function (request, options) { + if (!this.session) { + throw new Error("Session undefined."); + } + // If the PRACK doesn't have an offer/answer, nothing to be done. + var body = core_1.getBody(request.message); + if (!body || body.contentDisposition !== "session") { + return Promise.resolve(undefined); + } + // If the UAC receives a reliable provisional response with an offer + // (this would occur if the UAC sent an INVITE without an offer, in + // which case the first reliable provisional response will contain the + // offer), it MUST generate an answer in the PRACK. If the UAC receives + // a reliable provisional response with an answer, it MAY generate an + // additional offer in the PRACK. If the UAS receives a PRACK with an + // offer, it MUST place the answer in the 2xx to the PRACK. + // https://tools.ietf.org/html/rfc3262#section-5 + switch (this.session.signalingState) { + case core_1.SignalingState.Initial: + // State should never be reached as first reliable provisional response must have answer/offer. + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + case core_1.SignalingState.Stable: + // Receved answer. + return this.setAnswer(body, options).then(function () { return undefined; }); + case core_1.SignalingState.HaveLocalOffer: + // State should never be reached as local offer would be answered by this PRACK + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + case core_1.SignalingState.HaveRemoteOffer: + // Receved offer, generate answer. + return this.setOfferAndGetAnswer(body, options); + case core_1.SignalingState.Closed: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + default: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + } + }; + /** + * Called when session canceled. + */ + InviteServerContext.prototype.canceled = function () { + this._canceled = true; + return _super.prototype.canceled.call(this); + }; + /** + * Called when session terminated. + * Using it here just for the PRACK timeout. + */ + InviteServerContext.prototype.terminated = function (message, cause) { + this.prackNeverArrived(); + return _super.prototype.terminated.call(this, message, cause); + }; + /** + * A version of `accept` which resolves a session when the 200 Ok response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `accept()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._accept = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // FIXME: Ported - callback for in dialog INFO requests. + // Turns out accept() can be called more than once if we are waiting + // for a PRACK in which case "options" get completely tossed away. + // So this is broken in that case (and potentially other uses of options). + // Tempted to just try to fix it now, but leaving it broken for the moment. + this.onInfo = options.onInfo; + // The UAS MAY send a final response to the initial request before + // having received PRACKs for all unacknowledged reliable provisional + // responses, unless the final response is 2xx and any of the + // unacknowledged reliable provisional responses contained a session + // description. In that case, it MUST NOT send a final response until + // those provisional responses are acknowledged. If the UAS does send a + // final response when reliable responses are still unacknowledged, it + // SHOULD NOT continue to retransmit the unacknowledged reliable + // provisional responses, but it MUST be prepared to process PRACK + // requests for those outstanding responses. A UAS MUST NOT send new + // reliable provisional responses (as opposed to retransmissions of + // unacknowledged ones) after sending a final response to a request. + // https://tools.ietf.org/html/rfc3262#section-3 + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK; + return this.waitForArrivalOfPrack() + .then(function () { + _this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + clearTimeout(_this.timers.userNoAnswerTimer); // Ported + }) + .then(function () { return _this.generateResponseOfferAnswer(options); }) + .then(function (body) { return _this.incomingRequest.accept({ statusCode: 200, body: body }); }); + } + // Ported + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + } + else { + return Promise.reject(new Exceptions_1.Exceptions.InvalidStateError(this.status)); + } + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + clearTimeout(this.timers.userNoAnswerTimer); // Ported + return this.generateResponseOfferAnswer(options) + .then(function (body) { return _this.incomingRequest.accept({ statusCode: 200, body: body }); }); + }; + /** + * A version of `progress` which resolves when the provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._progress = function (options) { + if (options === void 0) { options = {}; } + // Ported + var statusCode = options.statusCode || 180; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + try { + var progressResponse = this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + this.emit("progress", progressResponse.message, reasonPhrase); // Ported + this.session = progressResponse.session; + return Promise.resolve(progressResponse); + } + catch (error) { + return Promise.reject(error); + } + }; + /** + * A version of `progress` which resolves when the reliable provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._reliableProgress = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 183; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Require: 100rel"); + extraHeaders.push("RSeq: " + Math.floor(Math.random() * 10000)); + // Get an offer/answer and send a reply. + return this.generateResponseOfferAnswer(options) + .then(function (body) { return _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); }) + .then(function (progressResponse) { + _this.emit("progress", progressResponse.message, reasonPhrase); // Ported + _this.session = progressResponse.session; + return progressResponse; + }); + }; + /** + * A version of `progress` which resolves when the reliable provisional response is acknowledged. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._reliableProgressWaitForPrack = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 183; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Require: 100rel"); + extraHeaders.push("RSeq: " + this.rseq++); + var body; + // Ported - set status. + this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK; + return new Promise(function (resolve, reject) { + var waitingForPrack = true; + return _this.generateResponseOfferAnswer(options) + .then(function (offerAnswer) { + body = offerAnswer; + return _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + }) + .then(function (progressResponse) { + _this.emit("progress", progressResponse.message, reasonPhrase); // Ported + _this.session = progressResponse.session; + var prackRequest; + var prackResponse; + progressResponse.session.delegate = { + onPrack: function (request) { + prackRequest = request; + clearTimeout(prackWaitTimeoutTimer); + clearTimeout(rel1xxRetransmissionTimer); + if (!waitingForPrack) { + return; + } + waitingForPrack = false; + _this.handlePrackOfferAnswer(prackRequest, options) + .then(function (prackResponseBody) { + try { + prackResponse = prackRequest.accept({ statusCode: 200, body: prackResponseBody }); + // Ported - set status. + if (_this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + } + _this.prackArrived(); + resolve({ prackRequest: prackRequest, prackResponse: prackResponse, progressResponse: progressResponse }); + } + catch (error) { + reject(error); + } + }); + } + }; + // https://tools.ietf.org/html/rfc3262#section-3 + var prackWaitTimeout = function () { + if (!waitingForPrack) { + return; + } + waitingForPrack = false; + _this.logger.warn("No PRACK received, rejecting INVITE."); + clearTimeout(rel1xxRetransmissionTimer); + try { + _this.incomingRequest.reject({ statusCode: 504 }); + _this.terminated(undefined, Constants_1.C.causes.NO_PRACK); + reject(new Exceptions_1.Exceptions.TerminatedSessionError()); + } + catch (error) { + reject(error); + } + }; + var prackWaitTimeoutTimer = setTimeout(prackWaitTimeout, core_1.Timers.T1 * 64); + // https://tools.ietf.org/html/rfc3262#section-3 + var rel1xxRetransmission = function () { + try { + _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + } + catch (error) { + waitingForPrack = false; + reject(error); + return; + } + rel1xxRetransmissionTimer = setTimeout(rel1xxRetransmission, timeout *= 2); + }; + var timeout = core_1.Timers.T1; + var rel1xxRetransmissionTimer = setTimeout(rel1xxRetransmission, timeout); + }); + }); + }; + /** + * Callback for when ACK for a 2xx response is never received. + * @param session Session the ACK never arrived for + */ + InviteServerContext.prototype.onAckTimeout = function () { + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + this.logger.log("no ACK received for an extended period of time, terminating the call"); + if (!this.session) { + throw new Error("Session undefined."); + } + this.session.bye(); + this.terminated(undefined, Constants_1.C.causes.NO_ACK); + } + }; + /** + * FIXME: TODO: The current library interface presents async methods without a + * proper async error handling mechanism. Arguably a promise based interface + * would be an improvement over the pattern of returning `this`. The approach has + * been generally along the lines of log a error and terminate. + */ + InviteServerContext.prototype.onContextError = function (error) { + var statusCode = 480; + if (error instanceof core_1.Exception) { // There might be interest in catching these Exceptions. + if (error instanceof Exceptions_1.Exceptions.SessionDescriptionHandlerError) { + this.logger.error(error.message); + if (error.error) { + this.logger.error(error.error); + } + } + else if (error instanceof Exceptions_1.Exceptions.TerminatedSessionError) { + // PRACK never arrived, so we timed out waiting for it. + this.logger.warn("Incoming session terminated while waiting for PRACK."); + } + else if (error instanceof Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError) { + statusCode = 415; + } + else if (error instanceof core_1.Exception) { + this.logger.error(error.message); + } + } + else if (error instanceof Error) { // Other Errors hould go uncaught. + this.logger.error(error.message); + } + else { + // We don't actually know what a session description handler implementation might throw + // our way, so as a last resort, just assume we are getting an "any" and log it. + this.logger.error("An error occurred in the session description handler."); + this.logger.error(error); + } + try { + this.incomingRequest.reject({ statusCode: statusCode }); // "Temporarily Unavailable" + this.failed(this.incomingRequest.message, error.message); + this.terminated(this.incomingRequest.message, error.message); + } + catch (error) { + return; + } + }; + InviteServerContext.prototype.prackArrived = function () { + if (this.waitingForPrackResolve) { + this.waitingForPrackResolve(); + } + this.waitingForPrackPromise = undefined; + this.waitingForPrackResolve = undefined; + this.waitingForPrackReject = undefined; + }; + InviteServerContext.prototype.prackNeverArrived = function () { + if (this.waitingForPrackReject) { + this.waitingForPrackReject(new Exceptions_1.Exceptions.TerminatedSessionError()); + } + this.waitingForPrackPromise = undefined; + this.waitingForPrackResolve = undefined; + this.waitingForPrackReject = undefined; + }; + /** + * @throws {Exceptions.TerminatedSessionError} The session terminated before being accepted (i.e. cancel arrived). + */ + InviteServerContext.prototype.waitForArrivalOfPrack = function () { + var _this = this; + if (this.waitingForPrackPromise) { + throw new Error("Already waiting for PRACK"); + } + this.waitingForPrackPromise = new Promise(function (resolve, reject) { + _this.waitingForPrackResolve = resolve; + _this.waitingForPrackReject = reject; + }); + return this.waitingForPrackPromise; + }; + InviteServerContext.prototype.getOffer = function (options) { + this.hasOffer = true; + var sdh = this.getSessionDescriptionHandler(); + return sdh + .getDescription(options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function (bodyObj) { return Utils_1.Utils.fromBodyObj(bodyObj); }); + }; + InviteServerContext.prototype.setAnswer = function (answer, options) { + this.hasAnswer = true; + var sdh = this.getSessionDescriptionHandler(); + if (!sdh.hasDescription(answer.contentType)) { + return Promise.reject(new Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError()); + } + return sdh + .setDescription(answer.content, options.sessionDescriptionHandlerOptions, options.modifiers); + }; + InviteServerContext.prototype.setOfferAndGetAnswer = function (offer, options) { + this.hasOffer = true; + this.hasAnswer = true; + var sdh = this.getSessionDescriptionHandler(); + if (!sdh.hasDescription(offer.contentType)) { + return Promise.reject(new Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError()); + } + return sdh + .setDescription(offer.content, options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function () { return sdh.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers); }) + .then(function (bodyObj) { return Utils_1.Utils.fromBodyObj(bodyObj); }); + }; + InviteServerContext.prototype.getSessionDescriptionHandler = function () { + // Create our session description handler if not already done so... + var sdh = this.sessionDescriptionHandler = this.setupSessionDescriptionHandler(); + // FIXME: Ported - this can get emitted multiple times even when only created once... don't we care? + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + // Return. + return sdh; + }; + return InviteServerContext; +}(Session)); +exports.InviteServerContext = InviteServerContext; +// tslint:disable-next-line:max-classes-per-file +var InviteClientContext = /** @class */ (function (_super) { + tslib_1.__extends(InviteClientContext, _super); + function InviteClientContext(ua, target, options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + var _this = this; + if (!ua.configuration.sessionDescriptionHandlerFactory) { + ua.logger.warn("Can't build ISC without SDH Factory"); + throw new Error("ICC Constructor Failed"); + } + options.params = options.params || {}; + var anonymous = options.anonymous || false; + var fromTag = Utils_1.Utils.newTag(); + options.params.fromTag = fromTag; + /* Do not add ;ob in initial forming dialog requests if the registration over + * the current connection got a GRUU URI. + */ + var contact = ua.contact.toString({ + anonymous: anonymous, + outbound: anonymous ? !ua.contact.tempGruu : !ua.contact.pubGruu + }); + var extraHeaders = (options.extraHeaders || []).slice(); + if (anonymous && ua.configuration.uri) { + options.params.from_displayName = "Anonymous"; + options.params.from_uri = "sip:anonymous@anonymous.invalid"; + extraHeaders.push("P-Preferred-Identity: " + ua.configuration.uri.toString()); + extraHeaders.push("Privacy: id"); + } + extraHeaders.push("Contact: " + contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + if (ua.configuration.rel100 === Constants_1.C.supported.REQUIRED) { + extraHeaders.push("Require: 100rel"); + } + if (ua.configuration.replaces === Constants_1.C.supported.REQUIRED) { + extraHeaders.push("Require: replaces"); + } + options.extraHeaders = extraHeaders; + _this = _super.call(this, ua.configuration.sessionDescriptionHandlerFactory) || this; + ClientContext_1.ClientContext.initializer(_this, ua, Constants_1.C.INVITE, target, options); + _this.earlyMediaSessionDescriptionHandlers = new Map(); + _this.type = Enums_1.TypeStrings.InviteClientContext; + _this.passedOptions = options; // Save for later to use with refer + _this.sessionDescriptionHandlerOptions = options.sessionDescriptionHandlerOptions || {}; + _this.modifiers = modifiers; + _this.inviteWithoutSdp = options.inviteWithoutSdp || false; + // Set anonymous property + _this.anonymous = options.anonymous || false; + // Custom data to be sent either in INVITE or in ACK + _this.renderbody = options.renderbody || undefined; + _this.rendertype = options.rendertype || "text/plain"; + // Session parameter initialization + _this.fromTag = fromTag; + _this.contact = contact; + // Check Session Status + if (_this.status !== Enums_1.SessionStatus.STATUS_NULL) { + throw new Exceptions_1.Exceptions.InvalidStateError(_this.status); + } + // OutgoingSession specific parameters + _this.isCanceled = false; + _this.received100 = false; + _this.method = Constants_1.C.INVITE; + _this.logger = ua.getLogger("sip.inviteclientcontext"); + ua.applicants[_this.toString()] = _this; + _this.id = _this.request.callId + _this.fromTag; + _this.onInfo = options.onInfo; + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + InviteClientContext.prototype.receiveResponse = function (response) { + throw new Error("Unimplemented."); + }; + // hack for getting around ClientContext interface + InviteClientContext.prototype.send = function () { + this.sendInvite(); + return this; + }; + InviteClientContext.prototype.invite = function () { + var _this = this; + // Save the session into the ua sessions collection. + // Note: placing in constructor breaks call to request.cancel on close... User does not need this anyway + this.ua.sessions[this.id] = this; + // This should allow the function to return so that listeners can be set up for these events + Promise.resolve().then(function () { + // FIXME: There is a race condition where cancel (or terminate) can be called synchronously after invite. + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + if (_this.inviteWithoutSdp) { + // just send an invite with no sdp... + if (_this.renderbody && _this.rendertype) { + _this.request.body = { + body: _this.renderbody, + contentType: _this.rendertype + }; + } + _this.status = Enums_1.SessionStatus.STATUS_INVITE_SENT; + _this.send(); + } + else { + // Initialize Media Session + _this.sessionDescriptionHandler = _this.sessionDescriptionHandlerFactory(_this, _this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + _this.emit("SessionDescriptionHandler-created", _this.sessionDescriptionHandler); + _this.sessionDescriptionHandler.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers) + .then(function (description) { + // FIXME: There is a race condition where cancel (or terminate) can be called (a)synchronously after invite. + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.hasOffer = true; + _this.request.body = description; + _this.status = Enums_1.SessionStatus.STATUS_INVITE_SENT; + _this.send(); + }, function (err) { + if (err.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + _this.logger.log(err.message); + if (err.error) { + _this.logger.log(err.error); + } + } + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.failed(undefined, Constants_1.C.causes.WEBRTC_ERROR); + _this.terminated(undefined, Constants_1.C.causes.WEBRTC_ERROR); + }); + } + }); + return this; + }; + InviteClientContext.prototype.cancel = function (options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED || this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (this.isCanceled) { + throw new Exceptions_1.Exceptions.InvalidStateError(Enums_1.SessionStatus.STATUS_CANCELED); + } + this.isCanceled = true; + this.logger.log("Canceling session"); + var cancelReason = Utils_1.Utils.getCancelReason(options.statusCode, options.reasonPhrase); + options.extraHeaders = (options.extraHeaders || []).slice(); + if (this.outgoingInviteRequest) { + this.logger.warn("Canceling session before it was created"); + this.outgoingInviteRequest.cancel(cancelReason, options); + } + return this.canceled(); + }; + InviteClientContext.prototype.terminate = function (options) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK || this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.bye(options); + } + else { + this.cancel(options); + } + return this; + }; + /** + * 13.2.1 Creating the Initial INVITE + * + * Since the initial INVITE represents a request outside of a dialog, + * its construction follows the procedures of Section 8.1.1. Additional + * processing is required for the specific case of INVITE. + * + * An Allow header field (Section 20.5) SHOULD be present in the INVITE. + * It indicates what methods can be invoked within a dialog, on the UA + * sending the INVITE, for the duration of the dialog. For example, a + * UA capable of receiving INFO requests within a dialog [34] SHOULD + * include an Allow header field listing the INFO method. + * + * A Supported header field (Section 20.37) SHOULD be present in the + * INVITE. It enumerates all the extensions understood by the UAC. + * + * An Accept (Section 20.1) header field MAY be present in the INVITE. + * It indicates which Content-Types are acceptable to the UA, in both + * the response received by it, and in any subsequent requests sent to + * it within dialogs established by the INVITE. The Accept header field + * is especially useful for indicating support of various session + * description formats. + * + * The UAC MAY add an Expires header field (Section 20.19) to limit the + * validity of the invitation. If the time indicated in the Expires + * header field is reached and no final answer for the INVITE has been + * received, the UAC core SHOULD generate a CANCEL request for the + * INVITE, as per Section 9. + * + * A UAC MAY also find it useful to add, among others, Subject (Section + * 20.36), Organization (Section 20.25) and User-Agent (Section 20.41) + * header fields. They all contain information related to the INVITE. + * + * The UAC MAY choose to add a message body to the INVITE. Section + * 8.1.1.10 deals with how to construct the header fields -- Content- + * Type among others -- needed to describe the message body. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.1 + */ + InviteClientContext.prototype.sendInvite = function () { + // There are special rules for message bodies that contain a session + // description - their corresponding Content-Disposition is "session". + // SIP uses an offer/answer model where one UA sends a session + // description, called the offer, which contains a proposed description + // of the session. The offer indicates the desired communications means + // (audio, video, games), parameters of those means (such as codec + // types) and addresses for receiving media from the answerer. The + // other UA responds with another session description, called the + // answer, which indicates which communications means are accepted, the + // parameters that apply to those means, and addresses for receiving + // media from the offerer. An offer/answer exchange is within the + // context of a dialog, so that if a SIP INVITE results in multiple + // dialogs, each is a separate offer/answer exchange. The offer/answer + // model defines restrictions on when offers and answers can be made + // (for example, you cannot make a new offer while one is in progress). + // This results in restrictions on where the offers and answers can + // appear in SIP messages. In this specification, offers and answers + // can only appear in INVITE requests and responses, and ACK. The usage + // of offers and answers is further restricted. For the initial INVITE + // transaction, the rules are: + // + // o The initial offer MUST be in either an INVITE or, if not there, + // in the first reliable non-failure message from the UAS back to + // the UAC. In this specification, that is the final 2xx + // response. + // + // o If the initial offer is in an INVITE, the answer MUST be in a + // reliable non-failure message from UAS back to UAC which is + // correlated to that INVITE. For this specification, that is + // only the final 2xx response to that INVITE. That same exact + // answer MAY also be placed in any provisional responses sent + // prior to the answer. The UAC MUST treat the first session + // description it receives as the answer, and MUST ignore any + // session descriptions in subsequent responses to the initial + // INVITE. + // + // o If the initial offer is in the first reliable non-failure + // message from the UAS back to UAC, the answer MUST be in the + // acknowledgement for that message (in this specification, ACK + // for a 2xx response). + // + // o After having sent or received an answer to the first offer, the + // UAC MAY generate subsequent offers in requests based on rules + // specified for that method, but only if it has received answers + // to any previous offers, and has not sent any offers to which it + // hasn't gotten an answer. + // + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + var _this = this; + // 5 The Offer/Answer Model and PRACK + // + // RFC 3261 describes guidelines for the sets of messages in which + // offers and answers [3] can appear. Based on those guidelines, this + // extension provides additional opportunities for offer/answer + // exchanges. + // If the INVITE contained an offer, the UAS MAY generate an answer in a + // reliable provisional response (assuming these are supported by the + // UAC). That results in the establishment of the session before + // completion of the call. Similarly, if a reliable provisional + // response is the first reliable message sent back to the UAC, and the + // INVITE did not contain an offer, one MUST appear in that reliable + // provisional response. + // If the UAC receives a reliable provisional response with an offer + // (this would occur if the UAC sent an INVITE without an offer, in + // which case the first reliable provisional response will contain the + // offer), it MUST generate an answer in the PRACK. If the UAC receives + // a reliable provisional response with an answer, it MAY generate an + // additional offer in the PRACK. If the UAS receives a PRACK with an + // offer, it MUST place the answer in the 2xx to the PRACK. + // Once an answer has been sent or received, the UA SHOULD establish the + // session based on the parameters of the offer and answer, even if the + // original INVITE itself has not been responded to. + // If the UAS had placed a session description in any reliable + // provisional response that is unacknowledged when the INVITE is + // accepted, the UAS MUST delay sending the 2xx until the provisional + // response is acknowledged. Otherwise, the reliability of the 1xx + // cannot be guaranteed, and reliability is needed for proper operation + // of the offer/answer exchange. + // All user agents that support this extension MUST support all + // offer/answer exchanges that are possible based on the rules in + // Section 13.2 of RFC 3261, based on the existence of INVITE and PRACK + // as requests, and 2xx and reliable 1xx as non-failure reliable + // responses. + // + // https://tools.ietf.org/html/rfc3262#section-5 + //// + // The Offer/Answer Model Implementation + // + // The offer/answer model is straight forward, but one MUST READ the specifications... + // + // 13.2.1 Creating the Initial INVITE (paragraph 8 in particular) + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // 5 The Offer/Answer Model and PRACK + // https://tools.ietf.org/html/rfc3262#section-5 + // + // Session Initiation Protocol (SIP) Usage of the Offer/Answer Model + // https://tools.ietf.org/html/rfc6337 + // + // *** IMPORTANT IMPLEMENTATION CHOICES *** + // + // TLDR... + // + // 1) Only one offer/answer exchange permitted during initial INVITE. + // 2) No "early media" if the initial offer is in an INVITE. + // + // + // 1) Initial Offer/Answer Restriction. + // + // Our implementation replaces the following bullet point... + // + // o After having sent or received an answer to the first offer, the + // UAC MAY generate subsequent offers in requests based on rules + // specified for that method, but only if it has received answers + // to any previous offers, and has not sent any offers to which it + // hasn't gotten an answer. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // ...with... + // + // o After having sent or received an answer to the first offer, the + // UAC MUST NOT generate subsequent offers in requests based on rules + // specified for that method. + // + // ...which in combination with this bullet point... + // + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // ...ensures that EXACTLY ONE offer/answer exchange will occur + // during an initial out of dialog INVITE request made by our UAC. + // + // + // 2) Early Media Restriction. + // + // While our implementation adheres to the following bullet point... + // + // o If the initial offer is in an INVITE, the answer MUST be in a + // reliable non-failure message from UAS back to UAC which is + // correlated to that INVITE. For this specification, that is + // only the final 2xx response to that INVITE. That same exact + // answer MAY also be placed in any provisional responses sent + // prior to the answer. The UAC MUST treat the first session + // description it receives as the answer, and MUST ignore any + // session descriptions in subsequent responses to the initial + // INVITE. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // We have made the following implementation decision with regard to early media... + // + // o If the initial offer is in the INVITE, the answer from the + // UAS back to the UAC will establish a media session only + // only after the final 2xx response to that INVITE is received. + // + // The reason for this decision is rooted in a restriction currently + // inherent in WebRTC. Specifically, while a SIP INVITE request with an + // initial offer may fork resulting in more than one provisional answer, + // there is currently no easy/good way to to "fork" an offer generated + // by a peer connection. In particular, a WebRTC offer currently may only + // be matched with one answer and we have no good way to know which + // "provisional answer" is going to be the "final answer". So we have + // decided to punt and not create any "early media" sessions in this case. + // + // The upshot is that if you want "early media", you must not put the + // initial offer in the INVITE. Instead, force the UAS to provide the + // initial offer by sending an INVITE without an offer. In the WebRTC + // case this allows us to create a unique peer connection with a unique + // answer for every provisional offer with "early media" on all of them. + //// + //// + // ROADMAP: The Offer/Answer Model Implementation + // + // The "no early media if offer in INVITE" implementation is not a + // welcome one. The masses want it. The want it and they want it + // to work for WebRTC (so they want to have their cake and eat too). + // + // So while we currently cannot make the offer in INVITE+forking+webrtc + // case work, we decided to do the following... + // + // 1) modify SDH Factory to provide an initial offer without giving us the SDH, and then... + // 2) stick that offer in the initial INVITE, and when 183 with initial answer is received... + // 3) ask SDH Factory if it supports "earlyRemoteAnswer" + // a) if true, ask SDH Factory to createSDH(localOffer).then((sdh) => sdh.setDescription(remoteAnswer) + // b) if false, defer getting a SDH until 2xx response is received + // + // Our supplied WebRTC SDH will default to behavior 3b which works in forking environment (without) + // early media if initial offer is in the INVITE). We will, however, provide an "inviteWillNotFork" + // option which if set to "true" will have our supplied WebRTC SDH behave in the 3a manner. + // That will result in + // - early media working with initial offer in the INVITE, and... + // - if the INVITE forks, the session terminating with an ERROR that reads like + // "You set 'inviteWillNotFork' to true but the INVITE forked. You can't eat your cake, and have it too." + // - furthermore, we accept that users will report that error to us as "bug" regardless + // + // So, SDH Factory is going to end up with a new interface along the lines of... + // + // interface SessionDescriptionHandlerFactory { + // makeLocalOffer(): Promise; + // makeSessionDescriptionHandler( + // initialOffer: ContentTypeAndBody, offerType: "local" | "remote" + // ): Promise; + // supportsEarlyRemoteAnswer: boolean; + // supportsContentType(contentType: string): boolean; + // getDescription(description: ContentTypeAndBody): Promise + // setDescription(description: ContentTypeAndBody): Promise + // } + // + // We should be able to get rid of all the hasOffer/hasAnswer tracking code and otherwise code + // it up to the same interaction with the SDH Factory and SDH regardless of signaling scenario. + //// + // Send the INVITE request. + this.outgoingInviteRequest = this.ua.userAgentCore.invite(this.request, { + onAccept: function (inviteResponse) { return _this.onAccept(inviteResponse); }, + onProgress: function (inviteResponse) { return _this.onProgress(inviteResponse); }, + onRedirect: function (inviteResponse) { return _this.onRedirect(inviteResponse); }, + onReject: function (inviteResponse) { return _this.onReject(inviteResponse); }, + onTrying: function (inviteResponse) { return _this.onTrying(inviteResponse); } + }); + }; + InviteClientContext.prototype.ackAndBye = function (inviteResponse, session, statusCode, reasonPhrase) { + if (!this.ua.userAgentCore) { + throw new Error("Method requires user agent core."); + } + var extraHeaders = []; + if (statusCode) { + extraHeaders.push("Reason: " + Utils_1.Utils.getReasonHeaderValue(statusCode, reasonPhrase)); + } + var outgoingAckRequest = inviteResponse.ack(); + this.emit("ack", outgoingAckRequest.message); + var outgoingByeRequest = session.bye(undefined, { extraHeaders: extraHeaders }); + this.emit("bye", outgoingByeRequest.message); + }; + InviteClientContext.prototype.disposeEarlyMedia = function () { + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + this.earlyMediaSessionDescriptionHandlers.forEach(function (sessionDescriptionHandler) { + sessionDescriptionHandler.close(); + }); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 2xx response. + */ + InviteClientContext.prototype.onAccept = function (inviteResponse) { + var _this = this; + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + var response = inviteResponse.message; + var session = inviteResponse.session; + // Our transaction layer is "non-standard" in that it will only + // pass us a 2xx response once per branch, so there is no need to + // worry about dealing with 2xx retransmissions. However, we can + // and do still get 2xx responses for multiple branches (when an + // INVITE is forked) which may create multiple confirmed dialogs. + // Herein we are acking and sending a bye to any confirmed dialogs + // which arrive beyond the first one. This is the desired behavior + // for most applications (but certainly not all). + // If we already received a confirmed dialog, ack & bye this session. + if (this.session) { + this.ackAndBye(inviteResponse, session); + return; + } + // If the user requested cancellation, ack & bye this session. + if (this.isCanceled) { + this.ackAndBye(inviteResponse, session); + this.emit("bye", this.request); // FIXME: Ported this odd second "bye" emit + return; + } + // Ported behavior. + if (response.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(response.getHeader("P-Asserted-Identity")); + } + // We have a confirmed dialog. + this.session = session; + this.session.delegate = { + onAck: function (ackRequest) { return _this.onAck(ackRequest); }, + onBye: function (byeRequest) { return _this.receiveRequest(byeRequest); }, + onInfo: function (infoRequest) { return _this.receiveRequest(infoRequest); }, + onInvite: function (inviteRequest) { return _this.receiveRequest(inviteRequest); }, + onNotify: function (notifyRequest) { return _this.receiveRequest(notifyRequest); }, + onPrack: function (prackRequest) { return _this.receiveRequest(prackRequest); }, + onRefer: function (referRequest) { return _this.receiveRequest(referRequest); } + }; + switch (session.signalingState) { + case core_1.SignalingState.Initial: + // INVITE without Offer, so MUST have Offer at this point, so invalid state. + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + case core_1.SignalingState.HaveLocalOffer: + // INVITE with Offer, so MUST have Answer at this point, so invalid state. + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + case core_1.SignalingState.HaveRemoteOffer: + // INVITE without Offer, received offer in 2xx, so MUST send Answer in ACK. + var sdh_1 = this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + this.sessionDescriptionHandler = sdh_1; + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + if (!sdh_1.hasDescription(response.getHeader("Content-Type") || "")) { + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + } + this.hasOffer = true; + sdh_1 + .setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { return sdh_1.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers); }) + .then(function (description) { + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + _this.hasAnswer = true; + var body = { + contentDisposition: "session", contentType: description.contentType, content: description.body + }; + var ackRequest = inviteResponse.ack({ body: body }); + _this.emit("ack", ackRequest.message); + _this.accepted(response); + }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + _this.logger.warn("invalid description"); + _this.logger.warn(e.toString()); + // TODO: This message is inconsistent + _this.ackAndBye(inviteResponse, session, 488, "Invalid session description"); + _this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + } + else { + throw e; + } + }); + break; + case core_1.SignalingState.Stable: + // This session has completed an initial offer/answer exchange... + var options_1; + if (this.renderbody && this.rendertype) { + options_1 = { body: { contentDisposition: "render", contentType: this.rendertype, content: this.renderbody } }; + } + // If INVITE with Offer and we have been waiting till now to apply the answer. + if (this.hasOffer && !this.hasAnswer) { + if (!this.sessionDescriptionHandler) { + throw new Error("Session description handler undefined."); + } + var answer = session.answer; + if (!answer) { + throw new Error("Answer is undefined."); + } + this.sessionDescriptionHandler + .setDescription(answer.content, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { + _this.hasAnswer = true; + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var ackRequest = inviteResponse.ack(options_1); + _this.emit("ack", ackRequest.message); + _this.accepted(response); + }) + .catch(function (error) { + _this.logger.error(error); + _this.ackAndBye(inviteResponse, session, 488, "Not Acceptable Here"); + _this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + // FIME: DON'T EAT UNHANDLED ERRORS! + }); + } + else { + // Otherwise INVITE with or without Offer and we have already completed the initial exchange. + this.sessionDescriptionHandler = this.earlyMediaSessionDescriptionHandlers.get(session.id); + if (!this.sessionDescriptionHandler) { + throw new Error("Session description handler undefined."); + } + this.earlyMediaSessionDescriptionHandlers.delete(session.id); + this.hasOffer = true; + this.hasAnswer = true; + this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var ackRequest = inviteResponse.ack(); + this.emit("ack", ackRequest.message); + this.accepted(response); + } + break; + case core_1.SignalingState.Closed: + // Dialog has terminated. + break; + default: + throw new Error("Unknown session signaling state."); + } + this.disposeEarlyMedia(); + }; + /** + * Handle provisional response to initial INVITE. + * @param inviteResponse 1xx response. + */ + InviteClientContext.prototype.onProgress = function (inviteResponse) { + var _this = this; + // Ported - User requested cancellation. + if (this.isCanceled) { + return; + } + if (!this.outgoingInviteRequest) { + throw new Error("Outgoing INVITE request undefined."); + } + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + var response = inviteResponse.message; + var session = inviteResponse.session; + // Ported - Set status. + this.status = Enums_1.SessionStatus.STATUS_1XX_RECEIVED; + // Ported - Set assertedIdentity. + if (response.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(response.getHeader("P-Asserted-Identity")); + } + // The provisional response MUST establish a dialog if one is not yet created. + // https://tools.ietf.org/html/rfc3262#section-4 + if (!session) { + // A response with a to tag MUST create a session (should never get here). + throw new Error("Session undefined."); + } + // If a provisional response is received for an initial request, and + // that response contains a Require header field containing the option + // tag 100rel, the response is to be sent reliably. If the response is + // a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be + // ignored, and the procedures below MUST NOT be used. + // https://tools.ietf.org/html/rfc3262#section-4 + var requireHeader = response.getHeader("require"); + var rseqHeader = response.getHeader("rseq"); + var rseq = requireHeader && requireHeader.includes("100rel") && rseqHeader ? Number(rseqHeader) : undefined; + var responseReliable = !!rseq; + var extraHeaders = []; + if (responseReliable) { + extraHeaders.push("RAck: " + response.getHeader("rseq") + " " + response.getHeader("cseq")); + } + // INVITE without Offer and session still has no offer (and no answer). + if (session.signalingState === core_1.SignalingState.Initial) { + // Similarly, if a reliable provisional + // response is the first reliable message sent back to the UAC, and the + // INVITE did not contain an offer, one MUST appear in that reliable + // provisional response. + // https://tools.ietf.org/html/rfc3262#section-5 + if (responseReliable) { + this.logger.warn("First reliable provisional response received MUST contain an offer when INVITE does not contain an offer."); + // FIXME: Known popular UA's currently end up here... + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + this.emit("progress", response); + return; + } + // INVITE with Offer and session only has that initial local offer. + if (session.signalingState === core_1.SignalingState.HaveLocalOffer) { + if (responseReliable) { + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + this.emit("progress", response); + return; + } + // INVITE without Offer and received initial offer in provisional response + if (session.signalingState === core_1.SignalingState.HaveRemoteOffer) { + // The initial offer MUST be in either an INVITE or, if not there, + // in the first reliable non-failure message from the UAS back to + // the UAC. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // According to Section 13.2.1 of [RFC3261], 'The first reliable + // non-failure message' must have an offer if there is no offer in the + // INVITE request. This means that the User Agent (UA) that receives + // the INVITE request without an offer must include an offer in the + // first reliable response with 100rel extension. If no reliable + // provisional response has been sent, the User Agent Server (UAS) must + // include an offer when sending 2xx response. + // https://tools.ietf.org/html/rfc6337#section-2.2 + if (!responseReliable) { + this.logger.warn("Non-reliable provisional response MUST NOT contain an initial offer, discarding response."); + return; + } + // If the initial offer is in the first reliable non-failure + // message from the UAS back to UAC, the answer MUST be in the + // acknowledgement for that message + var sdh_2 = this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + this.emit("SessionDescriptionHandler-created", sdh_2); + this.earlyMediaSessionDescriptionHandlers.set(session.id, sdh_2); + sdh_2 + .setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { return sdh_2.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers); }) + .then(function (description) { + var body = { + contentDisposition: "session", contentType: description.contentType, content: description.body + }; + inviteResponse.prack({ extraHeaders: extraHeaders, body: body }); + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + _this.emit("progress", response); + }) + .catch(function (error) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.failed(undefined, Constants_1.C.causes.WEBRTC_ERROR); + _this.terminated(undefined, Constants_1.C.causes.WEBRTC_ERROR); + }); + return; + } + // This session has completed an initial offer/answer exchange, so... + // - INVITE with SDP and this provisional response MAY be reliable + // - INVITE without SDP and this provisional response MAY be reliable + if (session.signalingState === core_1.SignalingState.Stable) { + if (responseReliable) { + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + // Note: As documented, no early media if offer was in INVITE, so nothing to be done. + // FIXME: TODO: Add a flag/hack to allow early media in this case. There are people + // in non-forking environments (think straight to FreeSWITCH) who want + // early media on a 183. Not sure how to actually make it work, basically + // something like... + if (false) {} + this.emit("progress", response); + return; + } + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 3xx response. + */ + InviteClientContext.prototype.onRedirect = function (inviteResponse) { + this.disposeEarlyMedia(); + var response = inviteResponse.message; + var statusCode = response.statusCode; + var cause = Utils_1.Utils.sipErrorCause(statusCode || 0); + this.rejected(response, cause); + this.failed(response, cause); + this.terminated(response, cause); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 4xx, 5xx, or 6xx response. + */ + InviteClientContext.prototype.onReject = function (inviteResponse) { + this.disposeEarlyMedia(); + var response = inviteResponse.message; + var statusCode = response.statusCode; + var cause = Utils_1.Utils.sipErrorCause(statusCode || 0); + this.rejected(response, cause); + this.failed(response, cause); + this.terminated(response, cause); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 100 response. + */ + InviteClientContext.prototype.onTrying = function (inviteResponse) { + this.received100 = true; + this.emit("progress", inviteResponse.message); + }; + return InviteClientContext; +}(Session)); +exports.InviteClientContext = InviteClientContext; + + +/***/ }), +/* 90 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +/** + * @class DTMF + * @param {SIP.Session} session + */ +var DTMF = /** @class */ (function (_super) { + tslib_1.__extends(DTMF, _super); + function DTMF(session, tone, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.C = { + MIN_DURATION: 70, + MAX_DURATION: 6000, + DEFAULT_DURATION: 100, + MIN_INTER_TONE_GAP: 50, + DEFAULT_INTER_TONE_GAP: 500 + }; + _this.type = Enums_1.TypeStrings.DTMF; + if (tone === undefined) { + throw new TypeError("Not enough arguments"); + } + _this.logger = session.ua.getLogger("sip.invitecontext.dtmf", session.id); + _this.owner = session; + // Check tone type + if (typeof tone === "string") { + tone = tone.toUpperCase(); + } + else if (typeof tone === "number") { + tone = tone.toString(); + } + else { + throw new TypeError("Invalid tone: " + tone); + } + // Check tone value + if (!tone.match(/^[0-9A-D#*]$/)) { + throw new TypeError("Invalid tone: " + tone); + } + else { + _this.tone = tone; + } + var duration = options.duration; + var interToneGap = options.interToneGap; + // Check duration + if (duration && !Utils_1.Utils.isDecimal(duration)) { + throw new TypeError("Invalid tone duration: " + duration); + } + else if (!duration) { + duration = _this.C.DEFAULT_DURATION; + } + else if (duration < _this.C.MIN_DURATION) { + _this.logger.warn("'duration' value is lower than the minimum allowed, setting it to " + + _this.C.MIN_DURATION + " milliseconds"); + duration = _this.C.MIN_DURATION; + } + else if (duration > _this.C.MAX_DURATION) { + _this.logger.warn("'duration' value is greater than the maximum allowed, setting it to " + + _this.C.MAX_DURATION + " milliseconds"); + duration = _this.C.MAX_DURATION; + } + else { + duration = Math.abs(duration); + } + _this.duration = duration; + // Check interToneGap + if (interToneGap && !Utils_1.Utils.isDecimal(interToneGap)) { + throw new TypeError("Invalid interToneGap: " + interToneGap); + } + else if (!interToneGap) { + interToneGap = _this.C.DEFAULT_INTER_TONE_GAP; + } + else if (interToneGap < _this.C.MIN_INTER_TONE_GAP) { + _this.logger.warn("'interToneGap' value is lower than the minimum allowed, setting it to " + + _this.C.MIN_INTER_TONE_GAP + " milliseconds"); + interToneGap = _this.C.MIN_INTER_TONE_GAP; + } + else { + interToneGap = Math.abs(interToneGap); + } + _this.interToneGap = interToneGap; + return _this; + } + DTMF.prototype.send = function (options) { + if (options === void 0) { options = {}; } + // Check RTCSession Status + if (this.owner.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && + this.owner.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.owner.status); + } + // Get DTMF options + var extraHeaders = options.extraHeaders ? options.extraHeaders.slice() : []; + var body = { + contentType: "application/dtmf-relay", + body: "Signal= " + this.tone + "\r\nDuration= " + this.duration + }; + if (this.owner.session) { + var request = this.owner.session.info(undefined, { + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(body) + }); + this.owner.emit("dtmf", request.message, this); + return; + } + }; + DTMF.prototype.init_incoming = function (request) { + request.accept(); + if (!this.tone || !this.duration) { + this.logger.warn("invalid INFO DTMF received, discarded"); + } + else { + this.owner.emit("dtmf", request.message, this); + } + }; + DTMF.prototype.receiveResponse = function (response) { + var statusCode = response && response.statusCode ? response.statusCode : 0; + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + // Ignore provisional responses. + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + this.emit("succeeded", { + originator: "remote", + response: response + }); + break; + default: + var cause = Utils_1.Utils.sipErrorCause(statusCode); + this.emit("failed", response, cause); + break; + } + }; + DTMF.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + this.owner.onRequestTimeout(); + }; + DTMF.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + this.owner.onTransportError(); + }; + DTMF.prototype.onDialogError = function (response) { + this.emit("failed", response, Constants_1.C.causes.DIALOG_ERROR); + this.owner.onDialogError(response); + }; + return DTMF; +}(events_1.EventEmitter)); +exports.DTMF = DTMF; + + +/***/ }), +/* 91 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var allowed_methods_1 = __webpack_require__(58); +var Enums_1 = __webpack_require__(81); +var Utils_1 = __webpack_require__(82); +/** + * While this class is named `Subscription`, it is closer to + * an implementation of a "subscriber" as defined in RFC 6665 + * "SIP-Specific Event Notifications". + * https://tools.ietf.org/html/rfc6665 + * @class Class creating a SIP Subscriber. + */ +var Subscription = /** @class */ (function (_super) { + tslib_1.__extends(Subscription, _super); + /** + * Constructor. + * @param ua User agent. + * @param target Subscription target. + * @param event Subscription event. + * @param options Options bucket. + */ + function Subscription(ua, target, event, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.data = {}; + _this.method = Constants_1.C.SUBSCRIBE; + _this.body = undefined; + // ClientContext interface + _this.type = Enums_1.TypeStrings.Subscription; + _this.ua = ua; + _this.logger = ua.getLogger("sip.subscription"); + if (options.body) { + _this.body = { + body: options.body, + contentType: options.contentType ? options.contentType : "application/sdp" + }; + } + // Target URI + var uri = ua.normalizeTarget(target); + if (!uri) { + throw new TypeError("Invalid target: " + target); + } + _this.uri = uri; + // Subscription event + _this.event = event; + // Subscription expires + if (options.expires === undefined) { + _this.expires = 3600; + } + else if (typeof options.expires !== "number") { // pre-typescript type guard + ua.logger.warn("Option \"expires\" must be a number. Using default of 3600."); + _this.expires = 3600; + } + else { + _this.expires = options.expires; + } + // Subscription extra headers + _this.extraHeaders = (options.extraHeaders || []).slice(); + // Subscription context. + _this.context = _this.initContext(); + _this.disposed = false; + // ClientContext interface + _this.request = _this.context.message; + if (!_this.request.from) { + throw new Error("From undefined."); + } + if (!_this.request.to) { + throw new Error("From undefined."); + } + _this.localIdentity = _this.request.from; + _this.remoteIdentity = _this.request.to; + // Add to UA's collection + _this.id = _this.request.callId + _this.request.from.parameters.tag + _this.event; + _this.ua.subscriptions[_this.id] = _this; + return _this; + } + /** + * Destructor. + */ + Subscription.prototype.dispose = function () { + if (this.disposed) { + return; + } + if (this.retryAfterTimer) { + clearTimeout(this.retryAfterTimer); + this.retryAfterTimer = undefined; + } + this.context.dispose(); + this.disposed = true; + // Remove from UA's collection + delete this.ua.subscriptions[this.id]; + }; + Subscription.prototype.on = function (name, callback) { + return _super.prototype.on.call(this, name, callback); + }; + Subscription.prototype.emit = function (event) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + return _super.prototype.emit.apply(this, [event].concat(args)); + }; + /** + * Gracefully terminate. + */ + Subscription.prototype.close = function () { + if (this.disposed) { + return; + } + this.dispose(); + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + this.onTerminated(); + break; + case core_1.SubscriptionState.NotifyWait: + this.onTerminated(); + break; + case core_1.SubscriptionState.Pending: + this.unsubscribe(); + break; + case core_1.SubscriptionState.Active: + this.unsubscribe(); + break; + case core_1.SubscriptionState.Terminated: + this.onTerminated(); + break; + default: + break; + } + }; + /** + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + Subscription.prototype.refresh = function () { + var _this = this; + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + break; + case core_1.SubscriptionState.Active: + if (this.subscription) { + var request = this.subscription.refresh(); + request.delegate = { + onAccept: (function (response) { return _this.onAccepted(response); }), + onRedirect: (function (response) { return _this.onFailed(response); }), + onReject: (function (response) { return _this.onFailed(response); }), + }; + } + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + }; + /** + * Send an initial SUBSCRIBE request if no subscription. + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + Subscription.prototype.subscribe = function () { + var _this = this; + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + this.context.subscribe().then(function (result) { + if (result.success) { + if (result.success.subscription) { + _this.subscription = result.success.subscription; + _this.subscription.delegate = { + onNotify: function (request) { return _this.onNotify(request); }, + onRefresh: function (request) { return _this.onRefresh(request); }, + onTerminated: function () { return _this.close(); } + }; + } + _this.onNotify(result.success.request); + } + else if (result.failure) { + _this.onFailed(result.failure.response); + } + }); + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + break; + case core_1.SubscriptionState.Active: + this.refresh(); + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + return this; + }; + /** + * Send a re-SUBSCRIBE request if there is a "pending" or "active" subscription. + */ + Subscription.prototype.unsubscribe = function () { + this.dispose(); + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + if (this.subscription) { + this.subscription.unsubscribe(); + // responses intentionally ignored + } + break; + case core_1.SubscriptionState.Active: + if (this.subscription) { + this.subscription.unsubscribe(); + // responses intentionally ignored + } + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + this.onTerminated(); + }; + Subscription.prototype.onAccepted = function (response) { + var statusCode = response.message.statusCode ? response.message.statusCode : 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + this.emit("accepted", response.message, cause); + }; + Subscription.prototype.onFailed = function (response) { + this.close(); + if (response) { + var statusCode = response.message.statusCode ? response.message.statusCode : 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + this.emit("failed", response.message, cause); + this.emit("rejected", response.message, cause); + } + }; + Subscription.prototype.onNotify = function (request) { + var _this = this; + request.accept(); // Send 200 response. + this.emit("notify", { request: request.message }); + // If we've set state to done, no further processing should take place + // and we are only interested in cleaning up after the appropriate NOTIFY. + if (this.disposed) { + return; + } + // If the "Subscription-State" value is "terminated", the subscriber + // MUST consider the subscription terminated. The "expires" parameter + // has no semantics for "terminated" -- notifiers SHOULD NOT include an + // "expires" parameter on a "Subscription-State" header field with a + // value of "terminated", and subscribers MUST ignore any such + // parameter, if present. If a reason code is present, the client + // should behave as described below. If no reason code or an unknown + // reason code is present, the client MAY attempt to re-subscribe at any + // time (unless a "retry-after" parameter is present, in which case the + // client SHOULD NOT attempt re-subscription until after the number of + // seconds specified by the "retry-after" parameter). The reason codes + // defined by this document are: + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = request.message.parseHeader("Subscription-State"); + if (subscriptionState && subscriptionState.state) { + switch (subscriptionState.state) { + case "terminated": + if (subscriptionState.reason) { + this.logger.log("Terminated subscription with reason " + subscriptionState.reason); + switch (subscriptionState.reason) { + case "deactivated": + case "timeout": + this.initContext(); + this.subscribe(); + return; + case "probation": + case "giveup": + this.initContext(); + if (subscriptionState.params && subscriptionState.params["retry-after"]) { + this.retryAfterTimer = setTimeout(function () { return _this.subscribe(); }, subscriptionState.params["retry-after"]); + } + else { + this.subscribe(); + } + return; + case "rejected": + case "noresource": + case "invariant": + break; + } + } + this.close(); + break; + default: + break; + } + } + }; + Subscription.prototype.onRefresh = function (request) { + var _this = this; + request.delegate = { + onAccept: function (response) { return _this.onAccepted(response); } + }; + }; + Subscription.prototype.onTerminated = function () { + this.emit("terminated"); + }; + Subscription.prototype.initContext = function () { + var _this = this; + var options = { + extraHeaders: this.extraHeaders, + body: this.body ? Utils_1.Utils.fromBodyObj(this.body) : undefined + }; + this.context = new SubscribeClientContext(this.ua.userAgentCore, this.uri, this.event, this.expires, options); + this.context.delegate = { + onAccept: (function (response) { return _this.onAccepted(response); }) + }; + return this.context; + }; + return Subscription; +}(events_1.EventEmitter)); +exports.Subscription = Subscription; +// tslint:disable-next-line:max-classes-per-file +var SubscribeClientContext = /** @class */ (function () { + function SubscribeClientContext(core, target, event, expires, options, delegate) { + this.core = core; + this.target = target; + this.event = event; + this.expires = expires; + this.subscribed = false; + this.logger = core.loggerFactory.getLogger("sip.subscription"); + this.delegate = delegate; + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var extraHeaders = (options && options.extraHeaders || []).slice(); + extraHeaders.push(allowHeader); + extraHeaders.push("Event: " + this.event); + extraHeaders.push("Expires: " + this.expires); + extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + var body = options && options.body; + this.message = core.makeOutgoingRequestMessage(Constants_1.C.SUBSCRIBE, this.target, this.core.configuration.aor, this.target, {}, extraHeaders, body); + } + /** Destructor. */ + SubscribeClientContext.prototype.dispose = function () { + if (this.subscription) { + this.subscription.dispose(); + } + if (this.request) { + this.request.waitNotifyStop(); + this.request.dispose(); + } + }; + Object.defineProperty(SubscribeClientContext.prototype, "state", { + /** Subscription state. */ + get: function () { + if (this.subscription) { + return this.subscription.subscriptionState; + } + else if (this.subscribed) { + return core_1.SubscriptionState.NotifyWait; + } + else { + return core_1.SubscriptionState.Initial; + } + }, + enumerable: true, + configurable: true + }); + /** + * Establish subscription. + * @param options Options bucket. + */ + SubscribeClientContext.prototype.subscribe = function () { + var _this = this; + if (this.subscribed) { + return Promise.reject(new Error("Not in initial state. Did you call subscribe more than once?")); + } + this.subscribed = true; + return new Promise(function (resolve, reject) { + if (!_this.message) { + throw new Error("Message undefined."); + } + _this.request = _this.core.subscribe(_this.message, { + // This SUBSCRIBE request will be confirmed with a final response. + // 200-class responses indicate that the subscription has been accepted + // and that a NOTIFY request will be sent immediately. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onAccept: function (response) { + if (_this.delegate && _this.delegate.onAccept) { + _this.delegate.onAccept(response); + } + }, + // Due to the potential for out-of-order messages, packet loss, and + // forking, the subscriber MUST be prepared to receive NOTIFY requests + // before the SUBSCRIBE transaction has completed. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + onNotify: function (requestWithSubscription) { + _this.subscription = requestWithSubscription.subscription; + if (_this.subscription) { + _this.subscription.autoRefresh = true; + } + resolve({ success: requestWithSubscription }); + }, + // If this Timer N expires prior to the receipt of a NOTIFY request, + // the subscriber considers the subscription failed, and cleans up + // any state associated with the subscription attempt. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + onNotifyTimeout: function () { + resolve({ failure: {} }); + }, + // This SUBSCRIBE request will be confirmed with a final response. + // Non-200-class final responses indicate that no subscription or new + // dialog usage has been created, and no subsequent NOTIFY request will + // be sent. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onRedirect: function (response) { + resolve({ failure: { response: response } }); + }, + // This SUBSCRIBE request will be confirmed with a final response. + // Non-200-class final responses indicate that no subscription or new + // dialog usage has been created, and no subsequent NOTIFY request will + // be sent. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onReject: function (response) { + resolve({ failure: { response: response } }); + } + }); + }); + }; + return SubscribeClientContext; +}()); + + +/***/ }), +/* 92 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Parser_1 = __webpack_require__(84); +var PublishContext_1 = __webpack_require__(85); +var ReferContext_1 = __webpack_require__(86); +var RegisterContext_1 = __webpack_require__(88); +var ServerContext_1 = __webpack_require__(87); +var Session_1 = __webpack_require__(89); +var Subscription_1 = __webpack_require__(91); +var Utils_1 = __webpack_require__(82); +var SessionDescriptionHandler_1 = __webpack_require__(94); +var Transport_1 = __webpack_require__(97); +var environment = global.window || global; +/** + * @class Class creating a SIP User Agent. + * @param {function returning SIP.sessionDescriptionHandler} [configuration.sessionDescriptionHandlerFactory] + * A function will be invoked by each of the UA's Sessions to build the sessionDescriptionHandler for that Session. + * If no (or a falsy) value is provided, each Session will use a default (WebRTC) sessionDescriptionHandler. + */ +var UA = /** @class */ (function (_super) { + tslib_1.__extends(UA, _super); + function UA(configuration) { + var _this = _super.call(this) || this; + _this.type = Enums_1.TypeStrings.UA; + _this.log = new core_1.LoggerFactory(); + _this.logger = _this.getLogger("sip.ua"); + _this.configuration = {}; + // User actions outside any session/dialog (MESSAGE) + _this.applicants = {}; + _this.data = {}; + _this.sessions = {}; + _this.subscriptions = {}; + _this.publishers = {}; + _this.status = Enums_1.UAStatus.STATUS_INIT; + /** + * Load configuration + * + * @throws {SIP.Exceptions.ConfigurationError} + * @throws {TypeError} + */ + if (configuration === undefined) { + configuration = {}; + } + else if (typeof configuration === "string" || configuration instanceof String) { + configuration = { + uri: configuration + }; + } + // Apply log configuration if present + if (configuration.log) { + if (configuration.log.hasOwnProperty("builtinEnabled")) { + _this.log.builtinEnabled = configuration.log.builtinEnabled; + } + if (configuration.log.hasOwnProperty("connector")) { + _this.log.connector = configuration.log.connector; + } + if (configuration.log.hasOwnProperty("level")) { + var level = configuration.log.level; + var normalized = void 0; + if (typeof level === "string") { + switch (level) { + case "error": + normalized = core_1.Levels.error; + break; + case "warn": + normalized = core_1.Levels.warn; + break; + case "log": + normalized = core_1.Levels.log; + break; + case "debug": + normalized = core_1.Levels.debug; + break; + default: + break; + } + } + else { + switch (level) { + case 0: + normalized = core_1.Levels.error; + break; + case 1: + normalized = core_1.Levels.warn; + break; + case 2: + normalized = core_1.Levels.log; + break; + case 3: + normalized = core_1.Levels.debug; + break; + default: + break; + } + } + // avoid setting level when invalid, use default level instead + if (normalized === undefined) { + _this.logger.error("Invalid \"level\" parameter value: " + JSON.stringify(level)); + } + else { + _this.log.level = normalized; + } + } + } + try { + _this.loadConfig(configuration); + } + catch (e) { + _this.status = Enums_1.UAStatus.STATUS_NOT_READY; + _this.error = UA.C.CONFIGURATION_ERROR; + throw e; + } + var userAgentCoreConfiguration = makeUserAgentCoreConfigurationFromUA(_this); + // The Replaces header contains information used to match an existing + // SIP dialog (call-id, to-tag, and from-tag). Upon receiving an INVITE + // with a Replaces header, the User Agent (UA) attempts to match this + // information with a confirmed or early dialog. + // https://tools.ietf.org/html/rfc3891#section-3 + var handleInviteWithReplacesHeader = function (context, request) { + if (_this.configuration.replaces !== Constants_1.C.supported.UNSUPPORTED) { + var replaces = request.parseHeader("replaces"); + if (replaces) { + var targetSession = _this.sessions[replaces.call_id + replaces.replaces_from_tag] || + _this.sessions[replaces.call_id + replaces.replaces_to_tag] || + undefined; + if (!targetSession) { + _this.userAgentCore.replyStateless(request, { statusCode: 481 }); + return; + } + if (targetSession.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + _this.userAgentCore.replyStateless(request, { statusCode: 603 }); + return; + } + var targetDialogId = replaces.call_id + replaces.replaces_to_tag + replaces.replaces_from_tag; + var targetDialog = _this.userAgentCore.dialogs.get(targetDialogId); + if (!targetDialog) { + _this.userAgentCore.replyStateless(request, { statusCode: 481 }); + return; + } + if (!targetDialog.early && replaces.early_only) { + _this.userAgentCore.replyStateless(request, { statusCode: 486 }); + return; + } + context.replacee = targetSession; + } + } + }; + var userAgentCoreDelegate = { + onInvite: function (incomingInviteRequest) { + // FIXME: Ported - 100 Trying send should be configurable. + // Only required if TU will not respond in 200ms. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + incomingInviteRequest.trying(); + incomingInviteRequest.delegate = { + onCancel: function (cancel) { + context.onCancel(cancel); + }, + onTransportError: function (error) { + context.onTransportError(); + } + }; + var context = new Session_1.InviteServerContext(_this, incomingInviteRequest); + // Ported - handling of out of dialog INVITE with Replaces. + handleInviteWithReplacesHeader(context, incomingInviteRequest.message); + // Ported - make the first call to progress automatically. + if (context.autoSendAnInitialProvisionalResponse) { + context.progress(); + } + _this.emit("invite", context); + }, + onMessage: function (incomingMessageRequest) { + // Ported - handling of out of dialog MESSAGE. + var serverContext = new ServerContext_1.ServerContext(_this, incomingMessageRequest); + serverContext.body = incomingMessageRequest.message.body; + serverContext.contentType = incomingMessageRequest.message.getHeader("Content-Type") || "text/plain"; + incomingMessageRequest.accept(); + _this.emit("message", serverContext); // TODO: Review. Why is a "ServerContext" emitted? What use it is? + }, + onNotify: function (incomingNotifyRequest) { + // DEPRECATED: Out of dialog NOTIFY is an obsolete usage. + // Ported - handling of out of dialog NOTIFY. + if (_this.configuration.allowLegacyNotifications && _this.listeners("notify").length > 0) { + incomingNotifyRequest.accept(); + _this.emit("notify", { request: incomingNotifyRequest.message }); + } + else { + incomingNotifyRequest.reject({ statusCode: 481 }); + } + }, + onRefer: function (incomingReferRequest) { + // Ported - handling of out of dialog REFER. + _this.logger.log("Received an out of dialog refer"); + if (!_this.configuration.allowOutOfDialogRefers) { + incomingReferRequest.reject({ statusCode: 405 }); + } + _this.logger.log("Allow out of dialog refers is enabled on the UA"); + var referContext = new ReferContext_1.ReferServerContext(_this, incomingReferRequest); + if (_this.listeners("outOfDialogReferRequested").length) { + _this.emit("outOfDialogReferRequested", referContext); + } + else { + _this.logger.log("No outOfDialogReferRequest listeners, automatically accepting and following the out of dialog refer"); + referContext.accept({ followRefer: true }); + } + }, + onSubscribe: function (incomingSubscribeRequest) { + _this.emit("subscribe", incomingSubscribeRequest); + }, + }; + _this.userAgentCore = new core_1.UserAgentCore(userAgentCoreConfiguration, userAgentCoreDelegate); + // Initialize registerContext + _this.registerContext = new RegisterContext_1.RegisterContext(_this, configuration.registerOptions); + _this.registerContext.on("failed", _this.emit.bind(_this, "registrationFailed")); + _this.registerContext.on("registered", _this.emit.bind(_this, "registered")); + _this.registerContext.on("unregistered", _this.emit.bind(_this, "unregistered")); + if (_this.configuration.autostart) { + _this.start(); + } + return _this; + } + // ================= + // High Level API + // ================= + UA.prototype.register = function (options) { + if (options === void 0) { options = {}; } + if (options.register) { + this.configuration.register = true; + } + this.registerContext.register(options); + return this; + }; + /** + * Unregister. + * + * @param {Boolean} [all] unregister all user bindings. + * + */ + UA.prototype.unregister = function (options) { + var _this = this; + this.configuration.register = false; + if (this.transport) { + this.transport.afterConnected(function () { + _this.registerContext.unregister(options); + }); + } + return this; + }; + UA.prototype.isRegistered = function () { + return this.registerContext.registered; + }; + /** + * Make an outgoing call. + * + * @param {String} target + * @param {Object} views + * @param {Object} [options.media] gets passed to SIP.sessionDescriptionHandler.getDescription as mediaHint + * + * @throws {TypeError} + * + */ + UA.prototype.invite = function (target, options, modifiers) { + var _this = this; + var context = new Session_1.InviteClientContext(this, target, options, modifiers); + // Delay sending actual invite until the next 'tick' if we are already + // connected, so that API consumers can register to events fired by the + // the session. + if (this.transport) { + this.transport.afterConnected(function () { + context.invite(); + _this.emit("inviteSent", context); + }); + } + return context; + }; + UA.prototype.subscribe = function (target, event, options) { + var sub = new Subscription_1.Subscription(this, target, event, options); + if (this.transport) { + this.transport.afterConnected(function () { return sub.subscribe(); }); + } + return sub; + }; + /** + * Send PUBLISH Event State Publication (RFC3903) + * + * @param {String} target + * @param {String} event + * @param {String} body + * @param {Object} [options] + * + * @throws {SIP.Exceptions.MethodParameterError} + */ + UA.prototype.publish = function (target, event, body, options) { + var pub = new PublishContext_1.PublishContext(this, target, event, options); + if (this.transport) { + this.transport.afterConnected(function () { + pub.publish(body); + }); + } + return pub; + }; + /** + * Send a message. + * + * @param {String} target + * @param {String} body + * @param {Object} [options] + * + * @throws {TypeError} + */ + UA.prototype.message = function (target, body, options) { + if (options === void 0) { options = {}; } + if (body === undefined) { + throw new TypeError("Not enough arguments"); + } + // There is no Message module, so it is okay that the UA handles defaults here. + options.contentType = options.contentType || "text/plain"; + options.body = body; + return this.request(Constants_1.C.MESSAGE, target, options); + }; + UA.prototype.request = function (method, target, options) { + var req = new ClientContext_1.ClientContext(this, method, target, options); + if (this.transport) { + this.transport.afterConnected(function () { return req.send(); }); + } + return req; + }; + /** + * Gracefully close. + */ + UA.prototype.stop = function () { + this.logger.log("user requested closure..."); + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + this.logger.warn("UA already closed"); + return this; + } + // Close registerContext + this.logger.log("closing registerContext"); + this.registerContext.close(); + // Run terminate on every Session + for (var session in this.sessions) { + if (this.sessions[session]) { + this.logger.log("closing session " + session); + this.sessions[session].terminate(); + } + } + // Run unsubscribe on every Subscription + for (var subscription in this.subscriptions) { + if (this.subscriptions[subscription]) { + this.logger.log("unsubscribe " + subscription); + this.subscriptions[subscription].unsubscribe(); + } + } + // Run close on every Publisher + for (var publisher in this.publishers) { + if (this.publishers[publisher]) { + this.logger.log("unpublish " + publisher); + this.publishers[publisher].close(); + } + } + // Run close on every applicant + for (var applicant in this.applicants) { + if (this.applicants[applicant]) { + this.applicants[applicant].close(); + } + } + this.status = Enums_1.UAStatus.STATUS_USER_CLOSED; + // Disconnect the transport and reset user agent core + if (this.transport) { + this.transport.disconnect(); + } + this.userAgentCore.reset(); + if (typeof environment.removeEventListener === "function") { + // Google Chrome Packaged Apps don't allow 'unload' listeners: + // unload is not available in packaged apps + if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) { + environment.removeEventListener("unload", this.environListener); + } + } + return this; + }; + /** + * Connect to the WS server if status = STATUS_INIT. + * Resume UA after being closed. + * + */ + UA.prototype.start = function () { + var _this = this; + this.logger.log("user requested startup..."); + if (this.status === Enums_1.UAStatus.STATUS_INIT) { + this.status = Enums_1.UAStatus.STATUS_STARTING; + if (!this.configuration.transportConstructor) { + throw new core_1.TransportError("Transport constructor not set"); + } + this.transport = new this.configuration.transportConstructor(this.getLogger("sip.transport"), this.configuration.transportOptions); + this.setTransportListeners(); + this.emit("transportCreated", this.transport); + this.transport.connect(); + } + else if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + this.logger.log("resuming"); + this.status = Enums_1.UAStatus.STATUS_READY; + if (this.transport) { + this.transport.connect(); + } + } + else if (this.status === Enums_1.UAStatus.STATUS_STARTING) { + this.logger.log("UA is in STARTING status, not opening new connection"); + } + else if (this.status === Enums_1.UAStatus.STATUS_READY) { + this.logger.log("UA is in READY status, not resuming"); + } + else { + this.logger.error("Connection is down. Auto-Recovery system is trying to connect"); + } + if (this.configuration.autostop && typeof environment.addEventListener === "function") { + // Google Chrome Packaged Apps don't allow 'unload' listeners: + // unload is not available in packaged apps + if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) { + this.environListener = this.stop; + environment.addEventListener("unload", function () { return _this.environListener(); }); + } + } + return this; + }; + /** + * Normalize a string into a valid SIP request URI + * + * @param {String} target + * + * @returns {SIP.URI|undefined} + */ + UA.prototype.normalizeTarget = function (target) { + return Utils_1.Utils.normalizeTarget(target, this.configuration.hostportParams); + }; + UA.prototype.getLogger = function (category, label) { + return this.log.getLogger(category, label); + }; + UA.prototype.getLoggerFactory = function () { + return this.log; + }; + UA.prototype.getSupportedResponseOptions = function () { + var optionTags = []; + if (this.contact.pubGruu || this.contact.tempGruu) { + optionTags.push("gruu"); + } + if (this.configuration.rel100 === Constants_1.C.supported.SUPPORTED) { + optionTags.push("100rel"); + } + if (this.configuration.replaces === Constants_1.C.supported.SUPPORTED) { + optionTags.push("replaces"); + } + optionTags.push("outbound"); + optionTags = optionTags.concat(this.configuration.extraSupported || []); + var allowUnregistered = this.configuration.hackAllowUnregisteredOptionTags || false; + var optionTagSet = {}; + optionTags = optionTags.filter(function (optionTag) { + var registered = Constants_1.C.OPTION_TAGS[optionTag]; + var unique = !optionTagSet[optionTag]; + optionTagSet[optionTag] = true; + return (registered || allowUnregistered) && unique; + }); + return optionTags; + }; + /** + * Get the session to which the request belongs to, if any. + * @param {SIP.IncomingRequest} request. + * @returns {SIP.OutgoingSession|SIP.IncomingSession|undefined} + */ + UA.prototype.findSession = function (request) { + return this.sessions[request.callId + request.fromTag] || + this.sessions[request.callId + request.toTag] || + undefined; + }; + UA.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + // ============================== + // Event Handlers + // ============================== + UA.prototype.onTransportError = function () { + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + return; + } + if (!this.error || this.error !== UA.C.NETWORK_ERROR) { + this.status = Enums_1.UAStatus.STATUS_NOT_READY; + this.error = UA.C.NETWORK_ERROR; + } + }; + /** + * Helper function. Sets transport listeners + */ + UA.prototype.setTransportListeners = function () { + var _this = this; + if (this.transport) { + this.transport.on("connected", function () { return _this.onTransportConnected(); }); + this.transport.on("message", function (message) { return _this.onTransportReceiveMsg(message); }); + this.transport.on("transportError", function () { return _this.onTransportError(); }); + } + }; + /** + * Transport connection event. + * @event + * @param {SIP.Transport} transport. + */ + UA.prototype.onTransportConnected = function () { + var _this = this; + if (this.configuration.register) { + // In an effor to maintain behavior from when we "initialized" an + // authentication factory, this is in a Promise.then + Promise.resolve().then(function () { return _this.registerContext.register(); }); + } + }; + /** + * Handle SIP message received from the transport. + * @param messageString The message. + */ + UA.prototype.onTransportReceiveMsg = function (messageString) { + var _this = this; + var message = Parser_1.Parser.parseMessage(messageString, this.getLogger("sip.parser")); + if (!message) { + this.logger.warn("UA failed to parse incoming SIP message - discarding."); + return; + } + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED && message instanceof core_1.IncomingRequestMessage) { + this.logger.warn("UA received message when status = USER_CLOSED - aborting"); + return; + } + // A valid SIP request formulated by a UAC MUST, at a minimum, contain + // the following header fields: To, From, CSeq, Call-ID, Max-Forwards, + // and Via; all of these header fields are mandatory in all SIP + // requests. + // https://tools.ietf.org/html/rfc3261#section-8.1.1 + var hasMinimumHeaders = function () { + var mandatoryHeaders = ["from", "to", "call_id", "cseq", "via"]; + for (var _i = 0, mandatoryHeaders_1 = mandatoryHeaders; _i < mandatoryHeaders_1.length; _i++) { + var header = mandatoryHeaders_1[_i]; + if (!message.hasHeader(header)) { + _this.logger.warn("Missing mandatory header field : " + header + "."); + return false; + } + } + return true; + }; + // Request Checks + if (message instanceof core_1.IncomingRequestMessage) { + // This is port of SanityCheck.minimumHeaders(). + if (!hasMinimumHeaders()) { + this.logger.warn("Request missing mandatory header field. Dropping."); + return; + } + // FIXME: This is non-standard and should be a configruable behavior (desirable regardless). + // Custom SIP.js check to reject request from ourself (this instance of SIP.js). + // This is port of SanityCheck.rfc3261_16_3_4(). + if (!message.toTag && message.callId.substr(0, 5) === this.configuration.sipjsId) { + this.userAgentCore.replyStateless(message, { statusCode: 482 }); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to reject requests if body length wrong. + // This is port of SanityCheck.rfc3261_18_3_request(). + var len = Utils_1.Utils.str_utf8_length(message.body); + var contentLength = message.getHeader("content-length"); + if (contentLength && len < Number(contentLength)) { + this.userAgentCore.replyStateless(message, { statusCode: 400 }); + return; + } + } + // Reponse Checks + if (message instanceof core_1.IncomingResponseMessage) { + // This is port of SanityCheck.minimumHeaders(). + if (!hasMinimumHeaders()) { + this.logger.warn("Response missing mandatory header field. Dropping."); + return; + } + // Custom SIP.js check to drop responses if multiple Via headers. + // This is port of SanityCheck.rfc3261_8_1_3_3(). + if (message.getHeaders("via").length > 1) { + this.logger.warn("More than one Via header field present in the response. Dropping."); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to drop responses if bad Via header. + // This is port of SanityCheck.rfc3261_18_1_2(). + if (message.via.host !== this.configuration.viaHost || message.via.port !== undefined) { + this.logger.warn("Via sent-by in the response does not match UA Via host value. Dropping."); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to reject requests if body length wrong. + // This is port of SanityCheck.rfc3261_18_3_response(). + var len = Utils_1.Utils.str_utf8_length(message.body); + var contentLength = message.getHeader("content-length"); + if (contentLength && len < Number(contentLength)) { + this.logger.warn("Message body length is lower than the value in Content-Length header field. Dropping."); + return; + } + } + // Handle Request + if (message instanceof core_1.IncomingRequestMessage) { + this.userAgentCore.receiveIncomingRequestFromTransport(message); + return; + } + // Handle Response + if (message instanceof core_1.IncomingResponseMessage) { + this.userAgentCore.receiveIncomingResponseFromTransport(message); + return; + } + throw new Error("Invalid message type."); + }; + // ================= + // Utils + // ================= + UA.prototype.checkAuthenticationFactory = function (authenticationFactory) { + if (!(authenticationFactory instanceof Function)) { + return; + } + if (!authenticationFactory.initialize) { + authenticationFactory.initialize = function () { + return Promise.resolve(); + }; + } + return authenticationFactory; + }; + /** + * Configuration load. + * returns {void} + */ + UA.prototype.loadConfig = function (configuration) { + var _this = this; + // Settings and default values + var settings = { + /* Host address + * Value to be set in Via sent_by and host part of Contact FQDN + */ + viaHost: Utils_1.Utils.createRandomToken(12) + ".invalid", + uri: new core_1.URI("sip", "anonymous." + Utils_1.Utils.createRandomToken(6), "anonymous.invalid", undefined, undefined), + // Custom Configuration Settings + custom: {}, + // Display name + displayName: "", + // Password + password: undefined, + register: true, + // Registration parameters + registerOptions: {}, + // Transport related parameters + transportConstructor: Transport_1.Transport, + transportOptions: {}, + // string to be inserted into User-Agent request header + userAgentString: Constants_1.C.USER_AGENT, + // Session parameters + noAnswerTimeout: 60, + // Hacks + hackViaTcp: false, + hackIpInContact: false, + hackWssInTransport: false, + hackAllowUnregisteredOptionTags: false, + // Session Description Handler Options + sessionDescriptionHandlerFactoryOptions: { + constraints: {}, + peerConnectionOptions: {} + }, + extraSupported: [], + contactName: Utils_1.Utils.createRandomToken(8), + contactTransport: "ws", + forceRport: false, + // autostarting + autostart: true, + autostop: true, + // Reliable Provisional Responses + rel100: Constants_1.C.supported.UNSUPPORTED, + // DTMF type: 'info' or 'rtp' (RFC 4733) + // RTP Payload Spec: https://tools.ietf.org/html/rfc4733 + // WebRTC Audio Spec: https://tools.ietf.org/html/rfc7874 + dtmfType: Constants_1.C.dtmfType.INFO, + // Replaces header (RFC 3891) + // http://tools.ietf.org/html/rfc3891 + replaces: Constants_1.C.supported.UNSUPPORTED, + sessionDescriptionHandlerFactory: SessionDescriptionHandler_1.SessionDescriptionHandler.defaultFactory, + authenticationFactory: this.checkAuthenticationFactory(function (ua) { + return new core_1.DigestAuthentication(ua.getLoggerFactory(), _this.configuration.authorizationUser, _this.configuration.password); + }), + allowLegacyNotifications: false, + allowOutOfDialogRefers: false, + experimentalFeatures: false + }; + var configCheck = this.getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if ((value instanceof Array && value.length === 0) || + (value === null || value === "" || value === undefined) || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Post Configuration Process + // Allow passing 0 number as displayName. + if (settings.displayName === 0) { + settings.displayName = "0"; + } + // sipjsId instance parameter. Static random tag of length 5 + settings.sipjsId = Utils_1.Utils.createRandomToken(5); + // String containing settings.uri without scheme and user. + var hostportParams = settings.uri.clone(); + hostportParams.user = undefined; + settings.hostportParams = hostportParams.toRaw().replace(/^sip:/i, ""); + /* Check whether authorizationUser is explicitly defined. + * Take 'settings.uri.user' value if not. + */ + if (!settings.authorizationUser) { + settings.authorizationUser = settings.uri.user; + } + // User noAnswerTimeout + settings.noAnswerTimeout = settings.noAnswerTimeout * 1000; + // Via Host + if (settings.hackIpInContact) { + if (typeof settings.hackIpInContact === "boolean") { + var from = 1; + var to = 254; + var octet = Math.floor(Math.random() * (to - from + 1) + from); + // random Test-Net IP (http://tools.ietf.org/html/rfc5735) + settings.viaHost = "192.0.2." + octet; + } + else if (typeof settings.hackIpInContact === "string") { + settings.viaHost = settings.hackIpInContact; + } + } + // Contact transport parameter + if (settings.hackWssInTransport) { + settings.contactTransport = "wss"; + } + this.contact = { + pubGruu: undefined, + tempGruu: undefined, + uri: new core_1.URI("sip", settings.contactName, settings.viaHost, undefined, { transport: settings.contactTransport }), + toString: function (options) { + if (options === void 0) { options = {}; } + var anonymous = options.anonymous || false; + var outbound = options.outbound || false; + var contact = "<"; + if (anonymous) { + contact += (_this.contact.tempGruu || + ("sip:anonymous@anonymous.invalid;transport=" + settings.contactTransport)).toString(); + } + else { + contact += (_this.contact.pubGruu || _this.contact.uri).toString(); + } + if (outbound) { + contact += ";ob"; + } + contact += ">"; + return contact; + } + }; + var skeleton = {}; + // Fill the value of the configuration_skeleton + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + skeleton[parameter] = settings[parameter]; + } + } + Object.assign(this.configuration, skeleton); + this.logger.log("configuration parameters after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + switch (parameter) { + case "uri": + case "sessionDescriptionHandlerFactory": + this.logger.log("· " + parameter + ": " + settings[parameter]); + break; + case "password": + this.logger.log("· " + parameter + ": " + "NOT SHOWN"); + break; + case "transportConstructor": + this.logger.log("· " + parameter + ": " + settings[parameter].name); + break; + default: + this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + } + return; + }; + /** + * Configuration checker. + * @return {Boolean} + */ + UA.prototype.getConfigurationCheck = function () { + return { + mandatory: {}, + optional: { + uri: function (uri) { + if (!(/^sip:/i).test(uri)) { + uri = Constants_1.C.SIP + ":" + uri; + } + var parsed = core_1.Grammar.URIParse(uri); + if (!parsed || !parsed.user) { + return; + } + else { + return parsed; + } + }, + transportConstructor: function (transportConstructor) { + if (transportConstructor instanceof Function) { + return transportConstructor; + } + }, + transportOptions: function (transportOptions) { + if (typeof transportOptions === "object") { + return transportOptions; + } + }, + authorizationUser: function (authorizationUser) { + if (core_1.Grammar.parse('"' + authorizationUser + '"', "quoted_string") === -1) { + return; + } + else { + return authorizationUser; + } + }, + displayName: function (displayName) { + if (core_1.Grammar.parse('"' + displayName + '"', "displayName") === -1) { + return; + } + else { + return displayName; + } + }, + dtmfType: function (dtmfType) { + switch (dtmfType) { + case Constants_1.C.dtmfType.RTP: + return Constants_1.C.dtmfType.RTP; + case Constants_1.C.dtmfType.INFO: + // Fall through + default: + return Constants_1.C.dtmfType.INFO; + } + }, + hackViaTcp: function (hackViaTcp) { + if (typeof hackViaTcp === "boolean") { + return hackViaTcp; + } + }, + hackIpInContact: function (hackIpInContact) { + if (typeof hackIpInContact === "boolean") { + return hackIpInContact; + } + else if (typeof hackIpInContact === "string" && core_1.Grammar.parse(hackIpInContact, "host") !== -1) { + return hackIpInContact; + } + }, + hackWssInTransport: function (hackWssInTransport) { + if (typeof hackWssInTransport === "boolean") { + return hackWssInTransport; + } + }, + hackAllowUnregisteredOptionTags: function (hackAllowUnregisteredOptionTags) { + if (typeof hackAllowUnregisteredOptionTags === "boolean") { + return hackAllowUnregisteredOptionTags; + } + }, + contactTransport: function (contactTransport) { + if (typeof contactTransport === "string") { + return contactTransport; + } + }, + extraSupported: function (optionTags) { + if (!(optionTags instanceof Array)) { + return; + } + for (var _i = 0, optionTags_1 = optionTags; _i < optionTags_1.length; _i++) { + var tag = optionTags_1[_i]; + if (typeof tag !== "string") { + return; + } + } + return optionTags; + }, + forceRport: function (forceRport) { + if (typeof forceRport === "boolean") { + return forceRport; + } + }, + noAnswerTimeout: function (noAnswerTimeout) { + if (Utils_1.Utils.isDecimal(noAnswerTimeout)) { + var value = Number(noAnswerTimeout); + if (value > 0) { + return value; + } + } + }, + password: function (password) { + return String(password); + }, + rel100: function (rel100) { + if (rel100 === Constants_1.C.supported.REQUIRED) { + return Constants_1.C.supported.REQUIRED; + } + else if (rel100 === Constants_1.C.supported.SUPPORTED) { + return Constants_1.C.supported.SUPPORTED; + } + else { + return Constants_1.C.supported.UNSUPPORTED; + } + }, + replaces: function (replaces) { + if (replaces === Constants_1.C.supported.REQUIRED) { + return Constants_1.C.supported.REQUIRED; + } + else if (replaces === Constants_1.C.supported.SUPPORTED) { + return Constants_1.C.supported.SUPPORTED; + } + else { + return Constants_1.C.supported.UNSUPPORTED; + } + }, + register: function (register) { + if (typeof register === "boolean") { + return register; + } + }, + registerOptions: function (registerOptions) { + if (typeof registerOptions === "object") { + return registerOptions; + } + }, + userAgentString: function (userAgentString) { + if (typeof userAgentString === "string") { + return userAgentString; + } + }, + autostart: function (autostart) { + if (typeof autostart === "boolean") { + return autostart; + } + }, + autostop: function (autostop) { + if (typeof autostop === "boolean") { + return autostop; + } + }, + sessionDescriptionHandlerFactory: function (sessionDescriptionHandlerFactory) { + if (sessionDescriptionHandlerFactory instanceof Function) { + return sessionDescriptionHandlerFactory; + } + }, + sessionDescriptionHandlerFactoryOptions: function (options) { + if (typeof options === "object") { + return options; + } + }, + authenticationFactory: this.checkAuthenticationFactory, + allowLegacyNotifications: function (allowLegacyNotifications) { + if (typeof allowLegacyNotifications === "boolean") { + return allowLegacyNotifications; + } + }, + custom: function (custom) { + if (typeof custom === "object") { + return custom; + } + }, + contactName: function (contactName) { + if (typeof contactName === "string") { + return contactName; + } + }, + experimentalFeatures: function (experimentalFeatures) { + if (typeof experimentalFeatures === "boolean") { + return experimentalFeatures; + } + }, + } + }; + }; + UA.C = { + // UA status codes + STATUS_INIT: 0, + STATUS_STARTING: 1, + STATUS_READY: 2, + STATUS_USER_CLOSED: 3, + STATUS_NOT_READY: 4, + // UA error codes + CONFIGURATION_ERROR: 1, + NETWORK_ERROR: 2, + ALLOWED_METHODS: [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ], + ACCEPTED_BODY_TYPES: [ + "application/sdp", + "application/dtmf-relay" + ], + MAX_FORWARDS: 70, + TAG_LENGTH: 10 + }; + return UA; +}(events_1.EventEmitter)); +exports.UA = UA; +(function (UA) { + var DtmfType; + (function (DtmfType) { + DtmfType["RTP"] = "rtp"; + DtmfType["INFO"] = "info"; + })(DtmfType = UA.DtmfType || (UA.DtmfType = {})); +})(UA = exports.UA || (exports.UA = {})); +exports.UA = UA; +/** + * Factory function to generate configuration give a UA. + * @param ua UA + */ +function makeUserAgentCoreConfigurationFromUA(ua) { + // FIXME: Configuration URI is a bad mix of types currently. It also needs to exist. + if (!(ua.configuration.uri instanceof core_1.URI)) { + throw new Error("Configuration URI not instance of URI."); + } + var aor = ua.configuration.uri; + var contact = ua.contact; + var displayName = ua.configuration.displayName ? ua.configuration.displayName : ""; + var hackViaTcp = ua.configuration.hackViaTcp ? true : false; + var routeSet = ua.configuration.usePreloadedRoute && ua.transport ? [ua.transport.server.sipUri] : []; + var sipjsId = ua.configuration.sipjsId || Utils_1.Utils.createRandomToken(5); + var supportedOptionTags = []; + supportedOptionTags.push("outbound"); // TODO: is this really supported? + if (ua.configuration.rel100 === Constants_1.C.supported.SUPPORTED) { + supportedOptionTags.push("100rel"); + } + if (ua.configuration.replaces === Constants_1.C.supported.SUPPORTED) { + supportedOptionTags.push("replaces"); + } + if (ua.configuration.extraSupported) { + supportedOptionTags.push.apply(supportedOptionTags, ua.configuration.extraSupported); + } + if (!ua.configuration.hackAllowUnregisteredOptionTags) { + supportedOptionTags = supportedOptionTags.filter(function (optionTag) { return Constants_1.C.OPTION_TAGS[optionTag]; }); + } + supportedOptionTags = Array.from(new Set(supportedOptionTags)); // array of unique values + var supportedOptionTagsResponse = ua.getSupportedResponseOptions(); + var userAgentHeaderFieldValue = ua.configuration.userAgentString || "sipjs"; + if (!(ua.configuration.viaHost)) { + throw new Error("Configuration via host undefined"); + } + var viaForceRport = ua.configuration.forceRport ? true : false; + var viaHost = ua.configuration.viaHost; + var configuration = { + aor: aor, + contact: contact, + displayName: displayName, + hackViaTcp: hackViaTcp, + loggerFactory: ua.getLoggerFactory(), + routeSet: routeSet, + sipjsId: sipjsId, + supportedOptionTags: supportedOptionTags, + supportedOptionTagsResponse: supportedOptionTagsResponse, + userAgentHeaderFieldValue: userAgentHeaderFieldValue, + viaForceRport: viaForceRport, + viaHost: viaHost, + authenticationFactory: function () { + if (ua.configuration.authenticationFactory) { + return ua.configuration.authenticationFactory(ua); + } + return undefined; + }, + transportAccessor: function () { return ua.transport; } + }; + return configuration; +} +exports.makeUserAgentCoreConfigurationFromUA = makeUserAgentCoreConfigurationFromUA; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }), +/* 93 */ +/***/ (function(module, exports) { + +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || new Function("return this")(); +} catch (e) { + // This works if the window reference is available + if (typeof window === "object") g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; + + +/***/ }), +/* 94 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +var Modifiers = tslib_1.__importStar(__webpack_require__(95)); +var SessionDescriptionHandlerObserver_1 = __webpack_require__(96); +/* SessionDescriptionHandler + * @class PeerConnection helper Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandler = /** @class */ (function (_super) { + tslib_1.__extends(SessionDescriptionHandler, _super); + function SessionDescriptionHandler(logger, observer, options) { + var _this = _super.call(this) || this; + _this.type = Enums_1.TypeStrings.SessionDescriptionHandler; + // TODO: Validate the options + _this.options = options || {}; + _this.logger = logger; + _this.observer = observer; + _this.dtmfSender = undefined; + _this.shouldAcquireMedia = true; + _this.CONTENT_TYPE = "application/sdp"; + _this.C = { + DIRECTION: { + NULL: null, + SENDRECV: "sendrecv", + SENDONLY: "sendonly", + RECVONLY: "recvonly", + INACTIVE: "inactive" + } + }; + _this.logger.log("SessionDescriptionHandlerOptions: " + JSON.stringify(_this.options)); + _this.direction = _this.C.DIRECTION.NULL; + _this.modifiers = _this.options.modifiers || []; + if (!Array.isArray(_this.modifiers)) { + _this.modifiers = [_this.modifiers]; + } + var environment = global.window || global; + _this.WebRTC = { + MediaStream: environment.MediaStream, + getUserMedia: environment.navigator.mediaDevices.getUserMedia.bind(environment.navigator.mediaDevices), + RTCPeerConnection: environment.RTCPeerConnection + }; + _this.iceGatheringTimeout = false; + _this.initPeerConnection(_this.options.peerConnectionOptions); + _this.constraints = _this.checkAndDefaultConstraints(_this.options.constraints); + return _this; + } + /** + * @param {SIP.Session} session + * @param {Object} [options] + */ + SessionDescriptionHandler.defaultFactory = function (session, options) { + var logger = session.ua.getLogger("sip.invitecontext.sessionDescriptionHandler", session.id); + var observer = new SessionDescriptionHandlerObserver_1.SessionDescriptionHandlerObserver(session, options); + return new SessionDescriptionHandler(logger, observer, options); + }; + // Functions the sesssion can use + /** + * Destructor + */ + SessionDescriptionHandler.prototype.close = function () { + this.logger.log("closing PeerConnection"); + // have to check signalingState since this.close() gets called multiple times + if (this.peerConnection && this.peerConnection.signalingState !== "closed") { + if (this.peerConnection.getSenders) { + this.peerConnection.getSenders().forEach(function (sender) { + if (sender.track) { + sender.track.stop(); + } + }); + } + else { + this.logger.warn("Using getLocalStreams which is deprecated"); + this.peerConnection.getLocalStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + if (this.peerConnection.getReceivers) { + this.peerConnection.getReceivers().forEach(function (receiver) { + if (receiver.track) { + receiver.track.stop(); + } + }); + } + else { + this.logger.warn("Using getRemoteStreams which is deprecated"); + this.peerConnection.getRemoteStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + }; + /** + * Gets the local description from the underlying media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves with the local description to be used for the session + */ + SessionDescriptionHandler.prototype.getDescription = function (options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + // Merge passed constraints with saved constraints and save + var newConstraints = Object.assign({}, this.constraints, options.constraints); + newConstraints = this.checkAndDefaultConstraints(newConstraints); + if (JSON.stringify(newConstraints) !== JSON.stringify(this.constraints)) { + this.constraints = newConstraints; + this.shouldAcquireMedia = true; + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + return Promise.resolve().then(function () { + if (_this.shouldAcquireMedia) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return _this.createOfferOrAnswer(options.RTCOfferOptions, modifiers); }) + .then(function (description) { + if (description.sdp === undefined) { + throw new Exceptions_1.Exceptions.SessionDescriptionHandlerError("getDescription", undefined, "SDP undefined"); + } + _this.emit("getDescription", description); + return { + body: description.sdp, + contentType: _this.CONTENT_TYPE + }; + }); + }; + /** + * Check if the Session Description Handler can handle the Content-Type described by a SIP Message + * @param {String} contentType The content type that is in the SIP Message + * @returns {boolean} + */ + SessionDescriptionHandler.prototype.hasDescription = function (contentType) { + return contentType === this.CONTENT_TYPE; + }; + /** + * The modifier that should be used when the session would like to place the call on hold + * @param {String} [sdp] The description that will be modified + * @returns {Promise} Promise that resolves with modified SDP + */ + SessionDescriptionHandler.prototype.holdModifier = function (description) { + if (!description.sdp) { + return Promise.resolve(description); + } + if (!(/a=(sendrecv|sendonly|recvonly|inactive)/).test(description.sdp)) { + description.sdp = description.sdp.replace(/(m=[^\r]*\r\n)/g, "$1a=sendonly\r\n"); + } + else { + description.sdp = description.sdp.replace(/a=sendrecv\r\n/g, "a=sendonly\r\n"); + description.sdp = description.sdp.replace(/a=recvonly\r\n/g, "a=inactive\r\n"); + } + return Promise.resolve(description); + }; + /** + * Set the remote description to the underlying media implementation + * @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves once the description is set + */ + SessionDescriptionHandler.prototype.setDescription = function (sessionDescription, options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + var description = { + type: this.hasOffer("local") ? "answer" : "offer", + sdp: sessionDescription + }; + return Promise.resolve().then(function () { + // Media should be acquired in getDescription unless we need to do it sooner for some reason (FF61+) + if (_this.shouldAcquireMedia && _this.options.alwaysAcquireMediaFirst) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return Utils_1.Utils.reducePromises(modifiers, description); }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e, "The modifiers did not resolve successfully"); + _this.logger.error(error.message); + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function (modifiedDescription) { + _this.emit("setDescription", modifiedDescription); + return _this.peerConnection.setRemoteDescription(modifiedDescription); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + // Check the original SDP for video, and ensure that we have want to do audio fallback + if ((/^m=video.+$/gm).test(sessionDescription) && !options.disableAudioFallback) { + // Do not try to audio fallback again + options.disableAudioFallback = true; + // Remove video first, then do the other modifiers + return _this.setDescription(sessionDescription, options, [Modifiers.stripVideo].concat(modifiers)); + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e); + if (error.error) { + _this.logger.error(error.error); + } + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function () { + if (_this.peerConnection.getReceivers) { + _this.emit("setRemoteDescription", _this.peerConnection.getReceivers()); + } + else { + _this.emit("setRemoteDescription", _this.peerConnection.getRemoteStreams()); + } + _this.emit("confirmed", _this); + }); + }; + /** + * Send DTMF via RTP (RFC 4733) + * @param {String} tones A string containing DTMF digits + * @param {Object} [options] Options object to be used by sendDtmf + * @returns {boolean} true if DTMF send is successful, false otherwise + */ + SessionDescriptionHandler.prototype.sendDtmf = function (tones, options) { + if (options === void 0) { options = {}; } + if (!this.dtmfSender && this.hasBrowserGetSenderSupport()) { + var senders = this.peerConnection.getSenders(); + if (senders.length > 0) { + this.dtmfSender = senders[0].dtmf; + } + } + if (!this.dtmfSender && this.hasBrowserTrackSupport()) { + var streams = this.peerConnection.getLocalStreams(); + if (streams.length > 0) { + var audioTracks = streams[0].getAudioTracks(); + if (audioTracks.length > 0) { + this.dtmfSender = this.peerConnection.createDTMFSender(audioTracks[0]); + } + } + } + if (!this.dtmfSender) { + return false; + } + try { + this.dtmfSender.insertDTMF(tones, options.duration, options.interToneGap); + } + catch (e) { + if (e.type === "InvalidStateError" || e.type === "InvalidCharacterError") { + this.logger.error(e); + return false; + } + else { + throw e; + } + } + this.logger.log("DTMF sent via RTP: " + tones.toString()); + return true; + }; + /** + * Get the direction of the session description + * @returns {String} direction of the description + */ + SessionDescriptionHandler.prototype.getDirection = function () { + return this.direction; + }; + SessionDescriptionHandler.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + // Internal functions + SessionDescriptionHandler.prototype.createOfferOrAnswer = function (RTCOfferOptions, modifiers) { + var _this = this; + if (RTCOfferOptions === void 0) { RTCOfferOptions = {}; } + if (modifiers === void 0) { modifiers = []; } + var methodName = this.hasOffer("remote") ? "createAnswer" : "createOffer"; + var pc = this.peerConnection; + this.logger.log(methodName); + var method = this.hasOffer("remote") ? pc.createAnswer : pc.createOffer; + return method.apply(pc, RTCOfferOptions).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-" + methodName + "Failed"); + _this.emit("peerConnection-" + methodName + "Failed", error); + throw error; + }).then(function (sdp) { + return Utils_1.Utils.reducePromises(modifiers, _this.createRTCSessionDescriptionInit(sdp)); + }).then(function (sdp) { + _this.resetIceGatheringComplete(); + _this.logger.log("Setting local sdp."); + _this.logger.log("sdp is " + sdp.sdp || false); + return pc.setLocalDescription(sdp); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-SetLocalDescriptionFailed"); + _this.emit("peerConnection-SetLocalDescriptionFailed", error); + throw error; + }).then(function () { return _this.waitForIceGatheringComplete(); }) + .then(function () { + var localDescription = _this.createRTCSessionDescriptionInit(_this.peerConnection.localDescription); + return Utils_1.Utils.reducePromises(modifiers, localDescription); + }).then(function (localDescription) { + _this.setDirection(localDescription.sdp || ""); + return localDescription; + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e); + _this.logger.error(error.toString()); + throw error; + }); + }; + // Creates an RTCSessionDescriptionInit from an RTCSessionDescription + SessionDescriptionHandler.prototype.createRTCSessionDescriptionInit = function (RTCSessionDescription) { + return { + type: RTCSessionDescription.type, + sdp: RTCSessionDescription.sdp + }; + }; + SessionDescriptionHandler.prototype.addDefaultIceCheckingTimeout = function (peerConnectionOptions) { + if (peerConnectionOptions.iceCheckingTimeout === undefined) { + peerConnectionOptions.iceCheckingTimeout = 5000; + } + return peerConnectionOptions; + }; + SessionDescriptionHandler.prototype.addDefaultIceServers = function (rtcConfiguration) { + if (!rtcConfiguration.iceServers) { + rtcConfiguration.iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; + } + return rtcConfiguration; + }; + SessionDescriptionHandler.prototype.checkAndDefaultConstraints = function (constraints) { + var defaultConstraints = { audio: true, video: !this.options.alwaysAcquireMediaFirst }; + constraints = constraints || defaultConstraints; + // Empty object check + if (Object.keys(constraints).length === 0 && constraints.constructor === Object) { + return defaultConstraints; + } + return constraints; + }; + SessionDescriptionHandler.prototype.hasBrowserTrackSupport = function () { + return Boolean(this.peerConnection.addTrack); + }; + SessionDescriptionHandler.prototype.hasBrowserGetSenderSupport = function () { + return Boolean(this.peerConnection.getSenders); + }; + SessionDescriptionHandler.prototype.initPeerConnection = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + options = this.addDefaultIceCheckingTimeout(options); + options.rtcConfiguration = options.rtcConfiguration || {}; + options.rtcConfiguration = this.addDefaultIceServers(options.rtcConfiguration); + this.logger.log("initPeerConnection"); + if (this.peerConnection) { + this.logger.log("Already have a peer connection for this session. Tearing down."); + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + this.peerConnection = new this.WebRTC.RTCPeerConnection(options.rtcConfiguration); + this.logger.log("New peer connection created"); + if ("ontrack" in this.peerConnection) { + this.peerConnection.addEventListener("track", function (e) { + _this.logger.log("track added"); + _this.observer.trackAdded(); + _this.emit("addTrack", e); + }); + } + else { + this.logger.warn("Using onaddstream which is deprecated"); + this.peerConnection.onaddstream = function (e) { + _this.logger.log("stream added"); + _this.emit("addStream", e); + }; + } + this.peerConnection.onicecandidate = function (e) { + _this.emit("iceCandidate", e); + if (e.candidate) { + _this.logger.log("ICE candidate received: " + + (e.candidate.candidate === null ? null : e.candidate.candidate.trim())); + } + else if (e.candidate === null) { + // indicates the end of candidate gathering + _this.logger.log("ICE candidate gathering complete"); + _this.triggerIceGatheringComplete(); + } + }; + this.peerConnection.onicegatheringstatechange = function () { + _this.logger.log("RTCIceGatheringState changed: " + _this.peerConnection.iceGatheringState); + switch (_this.peerConnection.iceGatheringState) { + case "gathering": + _this.emit("iceGathering", _this); + if (!_this.iceGatheringTimer && options.iceCheckingTimeout) { + _this.iceGatheringTimeout = false; + _this.iceGatheringTimer = setTimeout(function () { + _this.logger.log("RTCIceChecking Timeout Triggered after " + options.iceCheckingTimeout + " milliseconds"); + _this.iceGatheringTimeout = true; + _this.triggerIceGatheringComplete(); + }, options.iceCheckingTimeout); + } + break; + case "complete": + _this.triggerIceGatheringComplete(); + break; + } + }; + this.peerConnection.oniceconnectionstatechange = function () { + var stateEvent; + switch (_this.peerConnection.iceConnectionState) { + case "new": + stateEvent = "iceConnection"; + break; + case "checking": + stateEvent = "iceConnectionChecking"; + break; + case "connected": + stateEvent = "iceConnectionConnected"; + break; + case "completed": + stateEvent = "iceConnectionCompleted"; + break; + case "failed": + stateEvent = "iceConnectionFailed"; + break; + case "disconnected": + stateEvent = "iceConnectionDisconnected"; + break; + case "closed": + stateEvent = "iceConnectionClosed"; + break; + default: + _this.logger.warn("Unknown iceConnection state: " + _this.peerConnection.iceConnectionState); + return; + } + _this.logger.log("ICE Connection State changed to " + stateEvent); + _this.emit(stateEvent, _this); + }; + }; + SessionDescriptionHandler.prototype.acquire = function (constraints) { + var _this = this; + // Default audio & video to true + constraints = this.checkAndDefaultConstraints(constraints); + return new Promise(function (resolve, reject) { + /* + * Make the call asynchronous, so that ICCs have a chance + * to define callbacks to `userMediaRequest` + */ + _this.logger.log("acquiring local media"); + _this.emit("userMediaRequest", constraints); + if (constraints.audio || constraints.video) { + _this.WebRTC.getUserMedia(constraints).then(function (streams) { + _this.observer.trackAdded(); + _this.emit("userMedia", streams); + resolve(streams); + }).catch(function (e) { + _this.emit("userMediaFailed", e); + reject(e); + }); + } + else { + // Local streams were explicitly excluded. + resolve([]); + } + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "unable to acquire streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + _this.logger.log("acquired local media streams"); + try { + // Remove old tracks + if (_this.peerConnection.removeTrack) { + _this.peerConnection.getSenders().forEach(function (sender) { + _this.peerConnection.removeTrack(sender); + }); + } + return streams; + } + catch (e) { + return Promise.reject(e); + } + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error removing streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + try { + streams = [].concat(streams); + streams.forEach(function (stream) { + if (_this.peerConnection.addTrack) { + stream.getTracks().forEach(function (track) { + _this.peerConnection.addTrack(track, stream); + }); + } + else { + // Chrome 59 does not support addTrack + _this.peerConnection.addStream(stream); + } + }); + } + catch (e) { + return Promise.reject(e); + } + return Promise.resolve(); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error adding stream"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }); + }; + SessionDescriptionHandler.prototype.hasOffer = function (where) { + var offerState = "have-" + where + "-offer"; + return this.peerConnection.signalingState === offerState; + }; + // ICE gathering state handling + SessionDescriptionHandler.prototype.isIceGatheringComplete = function () { + return this.peerConnection.iceGatheringState === "complete" || this.iceGatheringTimeout; + }; + SessionDescriptionHandler.prototype.resetIceGatheringComplete = function () { + this.iceGatheringTimeout = false; + this.logger.log("resetIceGatheringComplete"); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.reject(); + this.iceGatheringDeferred = undefined; + } + }; + SessionDescriptionHandler.prototype.setDirection = function (sdp) { + var match = sdp.match(/a=(sendrecv|sendonly|recvonly|inactive)/); + if (match === null) { + this.direction = this.C.DIRECTION.NULL; + this.observer.directionChanged(); + return; + } + var direction = match[1]; + switch (direction) { + case this.C.DIRECTION.SENDRECV: + case this.C.DIRECTION.SENDONLY: + case this.C.DIRECTION.RECVONLY: + case this.C.DIRECTION.INACTIVE: + this.direction = direction; + break; + default: + this.direction = this.C.DIRECTION.NULL; + break; + } + this.observer.directionChanged(); + }; + SessionDescriptionHandler.prototype.triggerIceGatheringComplete = function () { + if (this.isIceGatheringComplete()) { + this.emit("iceGatheringComplete", this); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.resolve(); + this.iceGatheringDeferred = undefined; + } + } + }; + SessionDescriptionHandler.prototype.waitForIceGatheringComplete = function () { + this.logger.log("waitForIceGatheringComplete"); + if (this.isIceGatheringComplete()) { + this.logger.log("ICE is already complete. Return resolved."); + return Promise.resolve(); + } + else if (!this.iceGatheringDeferred) { + this.iceGatheringDeferred = Utils_1.Utils.defer(); + } + this.logger.log("ICE is not complete. Returning promise"); + return this.iceGatheringDeferred ? this.iceGatheringDeferred.promise : Promise.resolve(); + }; + return SessionDescriptionHandler; +}(events_1.EventEmitter)); +exports.SessionDescriptionHandler = SessionDescriptionHandler; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }), +/* 95 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var stripPayload = function (sdp, payload) { + var mediaDescs = []; + var lines = sdp.split(/\r\n/); + var currentMediaDesc; + for (var i = 0; i < lines.length;) { + var line = lines[i]; + if (/^m=(?:audio|video)/.test(line)) { + currentMediaDesc = { + index: i, + stripped: [] + }; + mediaDescs.push(currentMediaDesc); + } + else if (currentMediaDesc) { + var rtpmap = /^a=rtpmap:(\d+) ([^/]+)\//.exec(line); + if (rtpmap && payload === rtpmap[2]) { + lines.splice(i, 1); + currentMediaDesc.stripped.push(rtpmap[1]); + continue; // Don't increment 'i' + } + } + i++; + } + for (var _i = 0, mediaDescs_1 = mediaDescs; _i < mediaDescs_1.length; _i++) { + var mediaDesc = mediaDescs_1[_i]; + var mline = lines[mediaDesc.index].split(" "); + // Ignore the first 3 parameters of the mline. The codec information is after that + for (var j = 3; j < mline.length;) { + if (mediaDesc.stripped.indexOf(mline[j]) !== -1) { + mline.splice(j, 1); + continue; + } + j++; + } + lines[mediaDesc.index] = mline.join(" "); + } + return lines.join("\r\n"); +}; +var stripMediaDescription = function (sdp, description) { + var descriptionRegExp = new RegExp("m=" + description + ".*$", "gm"); + var groupRegExp = new RegExp("^a=group:.*$", "gm"); + if (descriptionRegExp.test(sdp)) { + var midLineToRemove_1; + sdp = sdp.split(/^m=/gm).filter(function (section) { + if (section.substr(0, description.length) === description) { + midLineToRemove_1 = section.match(/^a=mid:.*$/gm); + if (midLineToRemove_1) { + var step = midLineToRemove_1[0].match(/:.+$/g); + if (step) { + midLineToRemove_1 = step[0].substr(1); + } + } + return false; + } + return true; + }).join("m="); + var groupLine = sdp.match(groupRegExp); + if (groupLine && groupLine.length === 1) { + var groupLinePortion = groupLine[0]; + var groupRegExpReplace = new RegExp("\ *" + midLineToRemove_1 + "[^\ ]*", "g"); + groupLinePortion = groupLinePortion.replace(groupRegExpReplace, ""); + sdp = sdp.split(groupRegExp).join(groupLinePortion); + } + } + return sdp; +}; +function stripTcpCandidates(description) { + description.sdp = (description.sdp || "").replace(/^a=candidate:\d+ \d+ tcp .*?\r\n/img, ""); + return Promise.resolve(description); +} +exports.stripTcpCandidates = stripTcpCandidates; +function stripTelephoneEvent(description) { + description.sdp = stripPayload(description.sdp || "", "telephone-event"); + return Promise.resolve(description); +} +exports.stripTelephoneEvent = stripTelephoneEvent; +function cleanJitsiSdpImageattr(description) { + description.sdp = (description.sdp || "").replace(/^(a=imageattr:.*?)(x|y)=\[0-/gm, "$1$2=[1:"); + return Promise.resolve(description); +} +exports.cleanJitsiSdpImageattr = cleanJitsiSdpImageattr; +function stripG722(description) { + description.sdp = stripPayload(description.sdp || "", "G722"); + return Promise.resolve(description); +} +exports.stripG722 = stripG722; +function stripRtpPayload(payload) { + return function (description) { + description.sdp = stripPayload(description.sdp || "", payload); + return Promise.resolve(description); + }; +} +exports.stripRtpPayload = stripRtpPayload; +function stripVideo(description) { + description.sdp = stripMediaDescription(description.sdp || "", "video"); + return Promise.resolve(description); +} +exports.stripVideo = stripVideo; +function addMidLines(description) { + var sdp = description.sdp || ""; + if (sdp.search(/^a=mid.*$/gm) === -1) { + var mlines_1 = sdp.match(/^m=.*$/gm); + var sdpArray_1 = sdp.split(/^m=.*$/gm); + if (mlines_1) { + mlines_1.forEach(function (elem, idx) { + mlines_1[idx] = elem + "\na=mid:" + idx; + }); + } + sdpArray_1.forEach(function (elem, idx) { + if (mlines_1 && mlines_1[idx]) { + sdpArray_1[idx] = elem + mlines_1[idx]; + } + }); + sdp = sdpArray_1.join(""); + description.sdp = sdp; + } + return Promise.resolve(description); +} +exports.addMidLines = addMidLines; + + +/***/ }), +/* 96 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var Enums_1 = __webpack_require__(81); +/* SessionDescriptionHandlerObserver + * @class SessionDescriptionHandler Observer Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandlerObserver = /** @class */ (function () { + function SessionDescriptionHandlerObserver(session, options) { + this.type = Enums_1.TypeStrings.SessionDescriptionHandlerObserver; + this.session = session; + this.options = options; + } + SessionDescriptionHandlerObserver.prototype.trackAdded = function () { + this.session.emit("trackAdded"); + }; + SessionDescriptionHandlerObserver.prototype.directionChanged = function () { + this.session.emit("directionChanged"); + }; + return SessionDescriptionHandlerObserver; +}()); +exports.SessionDescriptionHandlerObserver = SessionDescriptionHandlerObserver; + + +/***/ }), +/* 97 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +var TransportStatus; +(function (TransportStatus) { + TransportStatus[TransportStatus["STATUS_CONNECTING"] = 0] = "STATUS_CONNECTING"; + TransportStatus[TransportStatus["STATUS_OPEN"] = 1] = "STATUS_OPEN"; + TransportStatus[TransportStatus["STATUS_CLOSING"] = 2] = "STATUS_CLOSING"; + TransportStatus[TransportStatus["STATUS_CLOSED"] = 3] = "STATUS_CLOSED"; +})(TransportStatus = exports.TransportStatus || (exports.TransportStatus = {})); +/** + * Compute an amount of time in seconds to wait before sending another + * keep-alive. + * @returns {Number} + */ +var computeKeepAliveTimeout = function (upperBound) { + var lowerBound = upperBound * 0.8; + return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound); +}; +/** + * @class Transport + * @param {Object} options + */ +var Transport = /** @class */ (function (_super) { + tslib_1.__extends(Transport, _super); + function Transport(logger, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this, logger, options) || this; + _this.WebSocket = (global.window || global).WebSocket; + _this.type = Enums_1.TypeStrings.Transport; + _this.reconnectionAttempts = 0; + _this.status = TransportStatus.STATUS_CONNECTING; + _this.configuration = _this.loadConfig(options); + _this.server = _this.configuration.wsServers[0]; + return _this; + } + /** + * @returns {Boolean} + */ + Transport.prototype.isConnected = function () { + return this.status === TransportStatus.STATUS_OPEN; + }; + /** + * Send a message. + * @param {SIP.OutgoingRequest|String} msg + * @param {Object} [options] + * @returns {Promise} + */ + Transport.prototype.sendPromise = function (msg, options) { + if (options === void 0) { options = {}; } + if (!this.statusAssert(TransportStatus.STATUS_OPEN, options.force)) { + this.onError("unable to send message - WebSocket not open"); + return Promise.reject(); + } + var message = msg.toString(); + if (this.ws) { + if (this.configuration.traceSip === true) { + this.logger.log("sending WebSocket message:\n\n" + message + "\n"); + } + this.ws.send(message); + return Promise.resolve({ msg: message }); + } + else { + this.onError("unable to send message - WebSocket does not exist"); + return Promise.reject(); + } + }; + /** + * Disconnect socket. + */ + Transport.prototype.disconnectPromise = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.disconnectionPromise) { // Already disconnecting. Just return this. + return this.disconnectionPromise; + } + options.code = options.code || 1000; + if (!this.statusTransition(TransportStatus.STATUS_CLOSING, options.force)) { + if (this.status === TransportStatus.STATUS_CLOSED) { // Websocket is already closed + return Promise.resolve({ overrideEvent: true }); + } + else if (this.connectionPromise) { // Websocket is connecting, cannot move to disconneting yet + return this.connectionPromise.then(function () { return Promise.reject("The websocket did not disconnect"); }) + .catch(function () { return Promise.resolve({ overrideEvent: true }); }); + } + else { + // Cannot move to disconnecting, but not in connecting state. + return Promise.reject("The websocket did not disconnect"); + } + } + this.emit("disconnecting"); + this.disconnectionPromise = new Promise(function (resolve, reject) { + _this.disconnectDeferredResolve = resolve; + if (_this.reconnectTimer) { + clearTimeout(_this.reconnectTimer); + _this.reconnectTimer = undefined; + } + if (_this.ws) { + _this.stopSendingKeepAlives(); + _this.logger.log("closing WebSocket " + _this.server.wsUri); + _this.ws.close(options.code, options.reason); + } + else { + reject("Attempted to disconnect but the websocket doesn't exist"); + } + }); + return this.disconnectionPromise; + }; + /** + * Connect socket. + */ + Transport.prototype.connectPromise = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.status === TransportStatus.STATUS_CLOSING && !options.force) { + return Promise.reject("WebSocket " + this.server.wsUri + " is closing"); + } + if (this.connectionPromise) { + return this.connectionPromise; + } + this.server = this.server || this.getNextWsServer(options.force); + this.connectionPromise = new Promise(function (resolve, reject) { + if ((_this.status === TransportStatus.STATUS_OPEN || _this.status === TransportStatus.STATUS_CLOSING) + && !options.force) { + _this.logger.warn("WebSocket " + _this.server.wsUri + " is already connected"); + reject("Failed status check - attempted to open a connection but already open/closing"); + return; + } + _this.connectDeferredResolve = resolve; + _this.status = TransportStatus.STATUS_CONNECTING; + _this.emit("connecting"); + _this.logger.log("connecting to WebSocket " + _this.server.wsUri); + _this.disposeWs(); + try { + _this.ws = new WebSocket(_this.server.wsUri, "sip"); + } + catch (e) { + _this.ws = null; + _this.statusTransition(TransportStatus.STATUS_CLOSED, true); + _this.onError("error connecting to WebSocket " + _this.server.wsUri + ":" + e); + reject("Failed to create a websocket"); + return; + } + if (!_this.ws) { + reject("Unexpected instance websocket not set"); + return; + } + _this.connectionTimeout = setTimeout(function () { + _this.statusTransition(TransportStatus.STATUS_CLOSED); + _this.logger.warn("took too long to connect - exceeded time set in configuration.connectionTimeout: " + + _this.configuration.connectionTimeout + "s"); + _this.emit("disconnected", { code: 1000 }); + _this.connectionPromise = undefined; + reject("Connection timeout"); + }, _this.configuration.connectionTimeout * 1000); + _this.boundOnOpen = _this.onOpen.bind(_this); + _this.boundOnMessage = _this.onMessage.bind(_this); + _this.boundOnClose = _this.onClose.bind(_this); + _this.boundOnError = _this.onWebsocketError.bind(_this); + _this.ws.addEventListener("open", _this.boundOnOpen); + _this.ws.addEventListener("message", _this.boundOnMessage); + _this.ws.addEventListener("close", _this.boundOnClose); + _this.ws.addEventListener("error", _this.boundOnError); + }); + return this.connectionPromise; + }; + /** + * @event + * @param {event} e + */ + Transport.prototype.onMessage = function (e) { + var data = e.data; + var finishedData; + // CRLF Keep Alive response from server. Clear our keep alive timeout. + if (/^(\r\n)+$/.test(data)) { + this.clearKeepAliveTimeout(); + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket message with CRLF Keep Alive response"); + } + return; + } + else if (!data) { + this.logger.warn("received empty message, message discarded"); + return; + } + else if (typeof data !== "string") { // WebSocket binary message. + try { + // the UInt8Data was here prior to types, and doesn't check + finishedData = String.fromCharCode.apply(null, new Uint8Array(data)); + } + catch (err) { + this.logger.warn("received WebSocket binary message failed to be converted into string, message discarded"); + return; + } + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket binary message:\n\n" + data + "\n"); + } + } + else { // WebSocket text message. + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket text message:\n\n" + data + "\n"); + } + finishedData = data; + } + this.emit("message", finishedData); + }; + // Transport Event Handlers + /** + * @event + * @param {event} e + */ + Transport.prototype.onOpen = function () { + if (this.status === TransportStatus.STATUS_CLOSED) { // Indicated that the transport thinks the ws is dead already + var ws = this.ws; + this.disposeWs(); + ws.close(1000); + return; + } + this.statusTransition(TransportStatus.STATUS_OPEN, true); + this.emit("connected"); + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + this.connectionTimeout = undefined; + } + this.logger.log("WebSocket " + this.server.wsUri + " connected"); + // Clear reconnectTimer since we are not disconnected + if (this.reconnectTimer !== undefined) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + // Reset reconnectionAttempts + this.reconnectionAttempts = 0; + // Reset disconnection promise so we can disconnect from a fresh state + this.disconnectionPromise = undefined; + this.disconnectDeferredResolve = undefined; + // Start sending keep-alives + this.startSendingKeepAlives(); + if (this.connectDeferredResolve) { + this.connectDeferredResolve({ overrideEvent: true }); + } + else { + this.logger.warn("Unexpected websocket.onOpen with no connectDeferredResolve"); + } + }; + /** + * @event + * @param {event} e + */ + Transport.prototype.onClose = function (e) { + this.logger.log("WebSocket disconnected (code: " + e.code + (e.reason ? "| reason: " + e.reason : "") + ")"); + if (this.status !== TransportStatus.STATUS_CLOSING) { + this.logger.warn("WebSocket closed without SIP.js requesting it"); + this.emit("transportError"); + } + this.stopSendingKeepAlives(); + // Clean up connection variables so we can connect again from a fresh state + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + } + this.connectionTimeout = undefined; + this.connectionPromise = undefined; + this.connectDeferredResolve = undefined; + // Check whether the user requested to close. + if (this.disconnectDeferredResolve) { + this.disconnectDeferredResolve({ overrideEvent: true }); + this.statusTransition(TransportStatus.STATUS_CLOSED); + this.disconnectDeferredResolve = undefined; + return; + } + this.statusTransition(TransportStatus.STATUS_CLOSED, true); + this.emit("disconnected", { code: e.code, reason: e.reason }); + this.reconnect(); + }; + /** + * Removes event listeners and clears the instance ws + */ + Transport.prototype.disposeWs = function () { + if (this.ws) { + this.ws.removeEventListener("open", this.boundOnOpen); + this.ws.removeEventListener("message", this.boundOnMessage); + this.ws.removeEventListener("close", this.boundOnClose); + this.ws.removeEventListener("error", this.boundOnError); + this.ws = undefined; + } + }; + /** + * @event + * @param {string} e + */ + Transport.prototype.onError = function (e) { + this.logger.warn("Transport error: " + e); + this.emit("transportError"); + }; + /** + * @event + * @private + */ + Transport.prototype.onWebsocketError = function () { + this.onError("The Websocket had an error"); + }; + /** + * Reconnection attempt logic. + */ + Transport.prototype.reconnect = function () { + var _this = this; + if (this.reconnectionAttempts > 0) { + this.logger.log("Reconnection attempt " + this.reconnectionAttempts + " failed"); + } + if (this.noAvailableServers()) { + this.logger.warn("attempted to get next ws server but there are no available ws servers left"); + this.logger.warn("no available ws servers left - going to closed state"); + this.statusTransition(TransportStatus.STATUS_CLOSED, true); + this.emit("closed"); + this.resetServerErrorStatus(); + return; + } + if (this.isConnected()) { + this.logger.warn("attempted to reconnect while connected - forcing disconnect"); + this.disconnect({ force: true }); + } + this.reconnectionAttempts += 1; + if (this.reconnectionAttempts > this.configuration.maxReconnectionAttempts) { + this.logger.warn("maximum reconnection attempts for WebSocket " + this.server.wsUri); + this.logger.log("transport " + this.server.wsUri + " failed | connection state set to 'error'"); + this.server.isError = true; + this.emit("transportError"); + if (!this.noAvailableServers()) { + this.server = this.getNextWsServer(); + } + // When there are no available servers, the reconnect function ends on the next recursive call + // after checking for no available servers again. + this.reconnectionAttempts = 0; + this.reconnect(); + } + else { + this.logger.log("trying to reconnect to WebSocket " + + this.server.wsUri + " (reconnection attempt " + this.reconnectionAttempts + ")"); + this.reconnectTimer = setTimeout(function () { + _this.connect(); + _this.reconnectTimer = undefined; + }, (this.reconnectionAttempts === 1) ? 0 : this.configuration.reconnectionTimeout * 1000); + } + }; + /** + * Resets the error state of all servers in the configuration + */ + Transport.prototype.resetServerErrorStatus = function () { + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var websocket = _a[_i]; + websocket.isError = false; + } + }; + /** + * Retrieve the next server to which connect. + * @param {Boolean} force allows bypass of server error status checking + * @returns {Object} WsServer + */ + Transport.prototype.getNextWsServer = function (force) { + if (force === void 0) { force = false; } + if (this.noAvailableServers()) { + this.logger.warn("attempted to get next ws server but there are no available ws servers left"); + throw new Error("Attempted to get next ws server, but there are no available ws servers left."); + } + // Order servers by weight + var candidates = []; + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var wsServer = _a[_i]; + if (wsServer.isError && !force) { + continue; + } + else if (candidates.length === 0) { + candidates.push(wsServer); + } + else if (wsServer.weight > candidates[0].weight) { + candidates = [wsServer]; + } + else if (wsServer.weight === candidates[0].weight) { + candidates.push(wsServer); + } + } + var idx = Math.floor(Math.random() * candidates.length); + return candidates[idx]; + }; + /** + * Checks all configuration servers, returns true if all of them have isError: true and false otherwise + * @returns {Boolean} + */ + Transport.prototype.noAvailableServers = function () { + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var server = _a[_i]; + if (!server.isError) { + return false; + } + } + return true; + }; + // ============================== + // KeepAlive Stuff + // ============================== + /** + * Send a keep-alive (a double-CRLF sequence). + * @returns {Boolean} + */ + Transport.prototype.sendKeepAlive = function () { + var _this = this; + if (this.keepAliveDebounceTimeout) { + // We already have an outstanding keep alive, do not send another. + return; + } + this.keepAliveDebounceTimeout = setTimeout(function () { + _this.emit("keepAliveDebounceTimeout"); + _this.clearKeepAliveTimeout(); + }, this.configuration.keepAliveDebounce * 1000); + return this.send("\r\n\r\n"); + }; + Transport.prototype.clearKeepAliveTimeout = function () { + if (this.keepAliveDebounceTimeout) { + clearTimeout(this.keepAliveDebounceTimeout); + } + this.keepAliveDebounceTimeout = undefined; + }; + /** + * Start sending keep-alives. + */ + Transport.prototype.startSendingKeepAlives = function () { + var _this = this; + if (this.configuration.keepAliveInterval && !this.keepAliveInterval) { + this.keepAliveInterval = setInterval(function () { + _this.sendKeepAlive(); + _this.startSendingKeepAlives(); + }, computeKeepAliveTimeout(this.configuration.keepAliveInterval)); + } + }; + /** + * Stop sending keep-alives. + */ + Transport.prototype.stopSendingKeepAlives = function () { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + } + if (this.keepAliveDebounceTimeout) { + clearTimeout(this.keepAliveDebounceTimeout); + } + this.keepAliveInterval = undefined; + this.keepAliveDebounceTimeout = undefined; + }; + // ============================== + // Status Stuff + // ============================== + /** + * Checks given status against instance current status. Returns true if they match + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + Transport.prototype.statusAssert = function (status, force) { + if (status === this.status) { + return true; + } + else { + if (force) { + this.logger.warn("Attempted to assert " + + Object.keys(TransportStatus)[this.status] + " as " + + Object.keys(TransportStatus)[status] + "- continuing with option: 'force'"); + return true; + } + else { + this.logger.warn("Tried to assert " + + Object.keys(TransportStatus)[status] + " but is currently " + + Object.keys(TransportStatus)[this.status]); + return false; + } + } + }; + /** + * Transitions the status. Checks for legal transition via assertion beforehand + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + Transport.prototype.statusTransition = function (status, force) { + if (force === void 0) { force = false; } + this.logger.log("Attempting to transition status from " + + Object.keys(TransportStatus)[this.status] + " to " + + Object.keys(TransportStatus)[status]); + if ((status === TransportStatus.STATUS_CONNECTING && this.statusAssert(TransportStatus.STATUS_CLOSED, force)) || + (status === TransportStatus.STATUS_OPEN && this.statusAssert(TransportStatus.STATUS_CONNECTING, force)) || + (status === TransportStatus.STATUS_CLOSING && this.statusAssert(TransportStatus.STATUS_OPEN, force)) || + (status === TransportStatus.STATUS_CLOSED)) { + this.status = status; + return true; + } + else { + this.logger.warn("Status transition failed - result: no-op - reason:" + + " either gave an nonexistent status or attempted illegal transition"); + return false; + } + }; + // ============================== + // Configuration Handling + // ============================== + /** + * Configuration load. + * returns {Configuration} + */ + Transport.prototype.loadConfig = function (configuration) { + var settings = { + wsServers: [{ + scheme: "WSS", + sipUri: "", + weight: 0, + wsUri: "wss://edge.sip.onsip.com", + isError: false + }], + connectionTimeout: 5, + maxReconnectionAttempts: 3, + reconnectionTimeout: 4, + keepAliveInterval: 0, + keepAliveDebounce: 10, + // Logging + traceSip: false + }; + var configCheck = this.getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if ((value instanceof Array && value.length === 0) || + (value === null || value === "" || value === undefined) || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + var skeleton = {}; // Fill the value of the configuration_skeleton + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + skeleton[parameter] = { + value: settings[parameter], + }; + } + } + var returnConfiguration = Object.defineProperties({}, skeleton); + this.logger.log("configuration parameters after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + return returnConfiguration; + }; + /** + * Configuration checker. + * @return {Boolean} + */ + Transport.prototype.getConfigurationCheck = function () { + return { + mandatory: {}, + optional: { + // Note: this function used to call 'this.logger.error' but calling 'this' with anything here is invalid + wsServers: function (wsServers) { + /* Allow defining wsServers parameter as: + * String: "host" + * Array of Strings: ["host1", "host2"] + * Array of Objects: [{wsUri:"host1", weight:1}, {wsUri:"host2", weight:0}] + * Array of Objects and Strings: [{wsUri:"host1"}, "host2"] + */ + if (typeof wsServers === "string") { + wsServers = [{ wsUri: wsServers }]; + } + else if (wsServers instanceof Array) { + for (var idx = 0; idx < wsServers.length; idx++) { + if (typeof wsServers[idx] === "string") { + wsServers[idx] = { wsUri: wsServers[idx] }; + } + } + } + else { + return; + } + if (wsServers.length === 0) { + return false; + } + for (var _i = 0, wsServers_1 = wsServers; _i < wsServers_1.length; _i++) { + var wsServer = wsServers_1[_i]; + if (!wsServer.wsUri) { + return; + } + if (wsServer.weight && !Number(wsServer.weight)) { + return; + } + var url = core_1.Grammar.parse(wsServer.wsUri, "absoluteURI"); + if (url === -1) { + return; + } + else if (["wss", "ws", "udp"].indexOf(url.scheme) < 0) { + return; + } + else { + wsServer.sipUri = ""; + if (!wsServer.weight) { + wsServer.weight = 0; + } + wsServer.isError = false; + wsServer.scheme = url.scheme.toUpperCase(); + } + } + return wsServers; + }, + keepAliveInterval: function (keepAliveInterval) { + if (Utils_1.Utils.isDecimal(keepAliveInterval)) { + var value = Number(keepAliveInterval); + if (value > 0) { + return value; + } + } + }, + keepAliveDebounce: function (keepAliveDebounce) { + if (Utils_1.Utils.isDecimal(keepAliveDebounce)) { + var value = Number(keepAliveDebounce); + if (value > 0) { + return value; + } + } + }, + traceSip: function (traceSip) { + if (typeof traceSip === "boolean") { + return traceSip; + } + }, + connectionTimeout: function (connectionTimeout) { + if (Utils_1.Utils.isDecimal(connectionTimeout)) { + var value = Number(connectionTimeout); + if (value > 0) { + return value; + } + } + }, + maxReconnectionAttempts: function (maxReconnectionAttempts) { + if (Utils_1.Utils.isDecimal(maxReconnectionAttempts)) { + var value = Number(maxReconnectionAttempts); + if (value >= 0) { + return value; + } + } + }, + reconnectionTimeout: function (reconnectionTimeout) { + if (Utils_1.Utils.isDecimal(reconnectionTimeout)) { + var value = Number(reconnectionTimeout); + if (value > 0) { + return value; + } + } + } + } + }; + }; + Transport.C = TransportStatus; + return Transport; +}(core_1.Transport)); +exports.Transport = Transport; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }), +/* 98 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var Modifiers = tslib_1.__importStar(__webpack_require__(95)); +exports.Modifiers = Modifiers; +var Simple_1 = __webpack_require__(99); +exports.Simple = Simple_1.Simple; +var SessionDescriptionHandler_1 = __webpack_require__(94); +exports.SessionDescriptionHandler = SessionDescriptionHandler_1.SessionDescriptionHandler; +var Transport_1 = __webpack_require__(97); +exports.Transport = Transport_1.Transport; + + +/***/ }), +/* 99 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var UA_1 = __webpack_require__(92); +var Modifiers = tslib_1.__importStar(__webpack_require__(95)); +/* Simple + * @class Simple + */ +var SimpleStatus; +(function (SimpleStatus) { + SimpleStatus[SimpleStatus["STATUS_NULL"] = 0] = "STATUS_NULL"; + SimpleStatus[SimpleStatus["STATUS_NEW"] = 1] = "STATUS_NEW"; + SimpleStatus[SimpleStatus["STATUS_CONNECTING"] = 2] = "STATUS_CONNECTING"; + SimpleStatus[SimpleStatus["STATUS_CONNECTED"] = 3] = "STATUS_CONNECTED"; + SimpleStatus[SimpleStatus["STATUS_COMPLETED"] = 4] = "STATUS_COMPLETED"; +})(SimpleStatus = exports.SimpleStatus || (exports.SimpleStatus = {})); +var Simple = /** @class */ (function (_super) { + tslib_1.__extends(Simple, _super); + function Simple(options) { + var _this = _super.call(this) || this; + /* + * { + * media: { + * remote: { + * audio: , + * video: + * }, + * local: { + * video: + * } + * }, + * ua: { + * + * } + * } + */ + if (options.media.remote.video) { + _this.video = true; + } + else { + _this.video = false; + } + if (options.media.remote.audio) { + _this.audio = true; + } + else { + _this.audio = false; + } + if (!_this.audio && !_this.video) { + // Need to do at least audio or video + // Error + throw new Error("At least one remote audio or video element is required for Simple."); + } + _this.options = options; + // https://stackoverflow.com/questions/7944460/detect-safari-browser + var browserUa = global.navigator.userAgent.toLowerCase(); + var isSafari = false; + var isFirefox = false; + if (browserUa.indexOf("safari") > -1 && browserUa.indexOf("chrome") < 0) { + isSafari = true; + } + else if (browserUa.indexOf("firefox") > -1 && browserUa.indexOf("chrome") < 0) { + isFirefox = true; + } + var sessionDescriptionHandlerFactoryOptions = {}; + if (isSafari) { + sessionDescriptionHandlerFactoryOptions.modifiers = [Modifiers.stripG722]; + } + if (isFirefox) { + sessionDescriptionHandlerFactoryOptions.alwaysAcquireMediaFirst = true; + } + if (!_this.options.ua.uri) { + _this.anonymous = true; + } + else { + _this.anonymous = false; + } + _this.ua = new UA_1.UA({ + // User Configurable Options + uri: _this.options.ua.uri, + authorizationUser: _this.options.ua.authorizationUser, + password: _this.options.ua.password, + displayName: _this.options.ua.displayName, + // Undocumented "Advanced" Options + userAgentString: _this.options.ua.userAgentString, + // Fixed Options + register: true, + sessionDescriptionHandlerFactoryOptions: sessionDescriptionHandlerFactoryOptions, + transportOptions: { + traceSip: _this.options.ua.traceSip, + wsServers: _this.options.ua.wsServers + } + }); + _this.state = SimpleStatus.STATUS_NULL; + _this.logger = _this.ua.getLogger("sip.simple"); + _this.ua.on("registered", function () { + _this.emit("registered", _this.ua); + }); + _this.ua.on("unregistered", function () { + _this.emit("unregistered", _this.ua); + }); + _this.ua.on("registrationFailed", function () { + _this.emit("unregistered", _this.ua); + }); + _this.ua.on("invite", function (session) { + // If there is already an active session reject the incoming session + if (_this.state !== SimpleStatus.STATUS_NULL && _this.state !== SimpleStatus.STATUS_COMPLETED) { + _this.logger.warn("Rejecting incoming call. Simple only supports 1 call at a time"); + session.reject(); + return; + } + _this.session = session; + _this.setupSession(); + _this.emit("ringing", _this.session); + }); + _this.ua.on("message", function (message) { + _this.emit("message", message); + }); + return _this; + } + Simple.prototype.call = function (destination) { + if (!this.ua || !this.checkRegistration()) { + this.logger.warn("A registered UA is required for calling"); + return; + } + if (this.state !== SimpleStatus.STATUS_NULL && this.state !== SimpleStatus.STATUS_COMPLETED) { + this.logger.warn("Cannot make more than a single call with Simple"); + return; + } + // Safari hack, because you cannot call .play() from a non user action + if (this.options.media.remote.audio) { + this.options.media.remote.audio.autoplay = true; + } + if (this.options.media.remote.video) { + this.options.media.remote.video.autoplay = true; + } + if (this.options.media.local && this.options.media.local.video) { + this.options.media.local.video.autoplay = true; + this.options.media.local.video.volume = 0; + } + this.session = this.ua.invite(destination, { + sessionDescriptionHandlerOptions: { + constraints: { + audio: this.audio, + video: this.video + } + } + }); + this.setupSession(); + return this.session; + }; + Simple.prototype.answer = function () { + if (this.state !== SimpleStatus.STATUS_NEW && this.state !== SimpleStatus.STATUS_CONNECTING) { + this.logger.warn("No call to answer"); + return; + } + // Safari hack, because you cannot call .play() from a non user action + if (this.options.media.remote.audio) { + this.options.media.remote.audio.autoplay = true; + } + if (this.options.media.remote.video) { + this.options.media.remote.video.autoplay = true; + } + return this.session.accept({ + sessionDescriptionHandlerOptions: { + constraints: { + audio: this.audio, + video: this.video + } + } + }); + // emit call is active + }; + Simple.prototype.reject = function () { + if (this.state !== SimpleStatus.STATUS_NEW && this.state !== SimpleStatus.STATUS_CONNECTING) { + this.logger.warn("Call is already answered"); + return; + } + return this.session.reject(); + }; + Simple.prototype.hangup = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED && + this.state !== SimpleStatus.STATUS_CONNECTING && + this.state !== SimpleStatus.STATUS_NEW) { + this.logger.warn("No active call to hang up on"); + return; + } + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + return this.session.cancel(); + } + else if (this.session) { + return this.session.bye(); + } + }; + Simple.prototype.hold = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session || this.session.localHold) { + this.logger.warn("Cannot put call on hold"); + return; + } + this.mute(); + this.logger.log("Placing session on hold"); + return this.session.hold(); + }; + Simple.prototype.unhold = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session || !this.session.localHold) { + this.logger.warn("Cannot unhold a call that is not on hold"); + return; + } + this.unmute(); + this.logger.log("Placing call off hold"); + return this.session.unhold(); + }; + Simple.prototype.mute = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + this.logger.warn("An acitve call is required to mute audio"); + return; + } + this.logger.log("Muting Audio"); + this.toggleMute(true); + this.emit("mute", this); + }; + Simple.prototype.unmute = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + this.logger.warn("An active call is required to unmute audio"); + return; + } + this.logger.log("Unmuting Audio"); + this.toggleMute(false); + this.emit("unmute", this); + }; + Simple.prototype.sendDTMF = function (tone) { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session) { + this.logger.warn("An active call is required to send a DTMF tone"); + return; + } + this.logger.log("Sending DTMF tone: " + tone); + this.session.dtmf(tone); + }; + Simple.prototype.message = function (destination, message) { + if (!this.ua || !this.checkRegistration()) { + this.logger.warn("A registered UA is required to send a message"); + return; + } + if (!destination || !message) { + this.logger.warn("A destination and message are required to send a message"); + return; + } + this.ua.message(destination, message); + }; + // Private Helpers + Simple.prototype.checkRegistration = function () { + return (this.anonymous || (this.ua && this.ua.isRegistered())); + }; + Simple.prototype.setupRemoteMedia = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session to set remote media on"); + return; + } + // If there is a video track, it will attach the video and audio to the same element + var pc = this.session.sessionDescriptionHandler.peerConnection; + var remoteStream; + if (pc.getReceivers) { + remoteStream = new global.window.MediaStream(); + pc.getReceivers().forEach(function (receiver) { + var track = receiver.track; + if (track) { + remoteStream.addTrack(track); + } + }); + } + else { + remoteStream = pc.getRemoteStreams()[0]; + } + if (this.video) { + this.options.media.remote.video.srcObject = remoteStream; + this.options.media.remote.video.play().catch(function () { + _this.logger.log("play was rejected"); + }); + } + else if (this.audio) { + this.options.media.remote.audio.srcObject = remoteStream; + this.options.media.remote.audio.play().catch(function () { + _this.logger.log("play was rejected"); + }); + } + }; + Simple.prototype.setupLocalMedia = function () { + if (!this.session) { + this.logger.warn("No session to set local media on"); + return; + } + if (this.video && this.options.media.local && this.options.media.local.video) { + var pc = this.session.sessionDescriptionHandler.peerConnection; + var localStream_1; + if (pc.getSenders) { + localStream_1 = new global.window.MediaStream(); + pc.getSenders().forEach(function (sender) { + var track = sender.track; + if (track && track.kind === "video") { + localStream_1.addTrack(track); + } + }); + } + else { + localStream_1 = pc.getLocalStreams()[0]; + } + this.options.media.local.video.srcObject = localStream_1; + this.options.media.local.video.volume = 0; + this.options.media.local.video.play(); + } + }; + Simple.prototype.cleanupMedia = function () { + if (this.video) { + this.options.media.remote.video.srcObject = null; + this.options.media.remote.video.pause(); + if (this.options.media.local && this.options.media.local.video) { + this.options.media.local.video.srcObject = null; + this.options.media.local.video.pause(); + } + } + if (this.audio) { + this.options.media.remote.audio.srcObject = null; + this.options.media.remote.audio.pause(); + } + }; + Simple.prototype.setupSession = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session to set up"); + return; + } + this.state = SimpleStatus.STATUS_NEW; + this.emit("new", this.session); + this.session.on("progress", function () { return _this.onProgress(); }); + this.session.on("accepted", function () { return _this.onAccepted(); }); + this.session.on("rejected", function () { return _this.onEnded(); }); + this.session.on("failed", function () { return _this.onFailed(); }); + this.session.on("terminated", function () { return _this.onEnded(); }); + }; + Simple.prototype.destroyMedia = function () { + if (this.session && this.session.sessionDescriptionHandler) { + this.session.sessionDescriptionHandler.close(); + } + }; + Simple.prototype.toggleMute = function (mute) { + if (!this.session) { + this.logger.warn("No session to toggle mute"); + return; + } + var pc = this.session.sessionDescriptionHandler.peerConnection; + if (pc.getSenders) { + pc.getSenders().forEach(function (sender) { + if (sender.track) { + sender.track.enabled = !mute; + } + }); + } + else { + pc.getLocalStreams().forEach(function (stream) { + stream.getAudioTracks().forEach(function (track) { + track.enabled = !mute; + }); + stream.getVideoTracks().forEach(function (track) { + track.enabled = !mute; + }); + }); + } + }; + Simple.prototype.onAccepted = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session for accepting"); + return; + } + this.state = SimpleStatus.STATUS_CONNECTED; + this.emit("connected", this.session); + this.setupLocalMedia(); + this.setupRemoteMedia(); + if (this.session.sessionDescriptionHandler) { + this.session.sessionDescriptionHandler.on("addTrack", function () { + _this.logger.log("A track has been added, triggering new remoteMedia setup"); + _this.setupRemoteMedia(); + }); + this.session.sessionDescriptionHandler.on("addStream", function () { + _this.logger.log("A stream has been added, trigger new remoteMedia setup"); + _this.setupRemoteMedia(); + }); + } + this.session.on("dtmf", function (request, dtmf) { + _this.emit("dtmf", dtmf.tone); + }); + this.session.on("bye", function () { return _this.onEnded(); }); + }; + Simple.prototype.onProgress = function () { + this.state = SimpleStatus.STATUS_CONNECTING; + this.emit("connecting", this.session); + }; + Simple.prototype.onFailed = function () { + this.onEnded(); + }; + Simple.prototype.onEnded = function () { + this.state = SimpleStatus.STATUS_COMPLETED; + this.emit("ended", this.session); + this.cleanupMedia(); + }; + Simple.C = SimpleStatus; + return Simple; +}(events_1.EventEmitter)); +exports.Simple = Simple; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/dist/sip-0.14.2.min.js b/dist/sip-0.14.2.min.js new file mode 100644 index 000000000..04a944ef7 --- /dev/null +++ b/dist/sip-0.14.2.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SIP=t():e.SIP=t()}(this,function(){return function(e){var t={};function r(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(i,n,function(t){return e[t]}.bind(null,n));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=63)}([function(e,t,r){"use strict";r.r(t),r.d(t,"__extends",function(){return n}),r.d(t,"__assign",function(){return s}),r.d(t,"__rest",function(){return o}),r.d(t,"__decorate",function(){return a}),r.d(t,"__param",function(){return c}),r.d(t,"__metadata",function(){return u}),r.d(t,"__awaiter",function(){return d}),r.d(t,"__generator",function(){return p}),r.d(t,"__exportStar",function(){return h}),r.d(t,"__values",function(){return l}),r.d(t,"__read",function(){return g}),r.d(t,"__spread",function(){return f}),r.d(t,"__await",function(){return m}),r.d(t,"__asyncGenerator",function(){return v}),r.d(t,"__asyncDelegator",function(){return S}),r.d(t,"__asyncValues",function(){return T}),r.d(t,"__makeTemplateObject",function(){return y}),r.d(t,"__importStar",function(){return E}),r.d(t,"__importDefault",function(){return b});var i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function n(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}var s=function(){return(s=Object.assign||function(e){for(var t,r=1,i=arguments.length;r=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,r,o):n(t,r))||o);return s>3&&o&&Object.defineProperty(t,r,o),o}function c(e,t){return function(r,i){t(r,i,e)}}function u(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function d(e,t,r,i){return new(r||(r=Promise))(function(n,s){function o(e){try{c(i.next(e))}catch(e){s(e)}}function a(e){try{c(i.throw(e))}catch(e){s(e)}}function c(e){e.done?n(e.value):new r(function(t){t(e.value)}).then(o,a)}c((i=i.apply(e,t||[])).next())})}function p(e,t){var r,i,n,s,o={label:0,sent:function(){if(1&n[0])throw n[1];return n[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(r)throw new TypeError("Generator is already executing.");for(;o;)try{if(r=1,i&&(n=2&s[0]?i.return:s[0]?i.throw||((n=i.return)&&n.call(i),0):i.next)&&!(n=n.call(i,s[1])).done)return n;switch(i=0,n&&(s=[2&s[0],n.value]),s[0]){case 0:case 1:n=s;break;case 4:return o.label++,{value:s[1],done:!1};case 5:o.label++,i=s[1],s=[0];continue;case 7:s=o.ops.pop(),o.trys.pop();continue;default:if(!(n=(n=o.trys).length>0&&n[n.length-1])&&(6===s[0]||2===s[0])){o=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}function g(e,t){var r="function"==typeof Symbol&&e[Symbol.iterator];if(!r)return e;var i,n,s=r.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(i=s.next()).done;)o.push(i.value)}catch(e){n={error:e}}finally{try{i&&!i.done&&(r=s.return)&&r.call(s)}finally{if(n)throw n.error}}return o}function f(){for(var e=[],t=0;t1||a(e,t)})})}function a(e,t){try{(r=n[e](t)).value instanceof m?Promise.resolve(r.value.v).then(c,u):d(s[0][2],r)}catch(e){d(s[0][3],e)}var r}function c(e){a("next",e)}function u(e){a("throw",e)}function d(e,t){e(t),s.shift(),s.length&&a(s[0][0],s[0][1])}}function S(e){var t,r;return t={},i("next"),i("throw",function(e){throw e}),i("return"),t[Symbol.iterator]=function(){return this},t;function i(i,n){t[i]=e[i]?function(t){return(r=!r)?{value:m(e[i](t)),done:"return"===i}:n?n(t):t}:n}}function T(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,r=e[Symbol.asyncIterator];return r?r.call(e):(e=l(e),t={},i("next"),i("throw"),i("return"),t[Symbol.asyncIterator]=function(){return this},t);function i(r){t[r]=e[r]&&function(t){return new Promise(function(i,n){(function(e,t,r,i){Promise.resolve(i).then(function(t){e({value:t,done:r})},t)})(i,n,(t=e[r](t)).done,t.value)})}}}function y(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}function E(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}function b(e){return e&&e.__esModule?e:{default:e}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(27),t),i.__exportStar(r(39),t),i.__exportStar(r(76),t),i.__exportStar(r(77),t),i.__exportStar(r(78),t),i.__exportStar(r(39),t),i.__exportStar(r(30),t),i.__exportStar(r(14),t),i.__exportStar(r(28),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(64),t),i.__exportStar(r(66),t),i.__exportStar(r(68),t),i.__exportStar(r(22),t),i.__exportStar(r(21),t),i.__exportStar(r(36),t),i.__exportStar(r(37),t),i.__exportStar(r(23),t),i.__exportStar(r(38),t),i.__exportStar(r(71),t),i.__exportStar(r(24),t),i.__exportStar(r(25),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(2),n=r(1),s=function(){function e(e,t,r,i){this.transactionConstructor=e,this.core=t,this.message=r,this.delegate=i,this.challenged=!1,this.stale=!1,this.logger=this.loggerFactory.getLogger("sip.user-agent-client"),this.init()}return e.prototype.dispose=function(){this.transaction.dispose()},Object.defineProperty(e.prototype,"loggerFactory",{get:function(){return this.core.loggerFactory},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"transaction",{get:function(){if(!this._transaction)throw new Error("Transaction undefined.");return this._transaction},enumerable:!0,configurable:!0}),e.prototype.cancel=function(t,r){var s=this;if(void 0===r&&(r={}),!this.transaction)throw new Error("Transaction undefined.");if(!this.message.to)throw new Error("To undefined.");if(!this.message.from)throw new Error("From undefined.");var o=this.core.makeOutgoingRequestMessage(i.C.CANCEL,this.message.ruri,this.message.from.uri,this.message.to.uri,{toTag:this.message.toTag,fromTag:this.message.fromTag,callId:this.message.callId,cseq:this.message.cseq},r.extraHeaders);if(o.branch=this.message.branch,this.message.headers.Route&&(o.headers.Route=this.message.headers.Route),t&&o.setHeader("Reason",t),this.transaction.state===n.TransactionState.Proceeding)new e(n.NonInviteClientTransaction,this.core,o);else this.transaction.once("stateChanged",function(){if(s.transaction&&s.transaction.state===n.TransactionState.Proceeding)new e(n.NonInviteClientTransaction,s.core,o)});return o},e.prototype.authenticationGuard=function(e){var t,r,i=e.statusCode;if(!i)throw new Error("Response status code undefined.");if(401!==i&&407!==i)return!0;if(401===i?(t=e.parseHeader("www-authenticate"),r="authorization"):(t=e.parseHeader("proxy-authenticate"),r="proxy-authorization"),!t)return this.logger.warn(i+" with wrong or missing challenge, cannot authenticate"),!0;if(this.challenged&&(this.stale||!0!==t.stale))return this.logger.warn(i+" apparently in authentication loop, cannot authenticate"),!0;if(!this.credentials&&(this.credentials=this.core.configuration.authenticationFactory(),!this.credentials))return this.logger.warn("Unable to obtain credentials, cannot authenticate"),!0;if(!this.credentials.authenticate(this.message,t))return!0;this.challenged=!0,t.stale&&(this.stale=!0);var n=this.message.cseq+=1;return this.message.setHeader("cseq",n+" "+this.message.method),this.message.setHeader(r,this.credentials.toString()),this.init(),!1},e.prototype.receiveResponse=function(e){if(this.authenticationGuard(e)){var t=e.statusCode?e.statusCode.toString():"";if(!t)throw new Error("Response status code undefined.");switch(!0){case/^100$/.test(t):this.delegate&&this.delegate.onTrying&&this.delegate.onTrying({message:e});break;case/^1[0-9]{2}$/.test(t):this.delegate&&this.delegate.onProgress&&this.delegate.onProgress({message:e});break;case/^2[0-9]{2}$/.test(t):this.delegate&&this.delegate.onAccept&&this.delegate.onAccept({message:e});break;case/^3[0-9]{2}$/.test(t):this.delegate&&this.delegate.onRedirect&&this.delegate.onRedirect({message:e});break;case/^[4-6][0-9]{2}$/.test(t):this.delegate&&this.delegate.onReject&&this.delegate.onReject({message:e});break;default:throw new Error("Invalid status code "+t)}}},e.prototype.init=function(){var e=this,t={loggerFactory:this.loggerFactory,onRequestTimeout:function(){return e.onRequestTimeout()},onStateChange:function(t){t===n.TransactionState.Terminated&&(e.core.userAgentClients.delete(i),r===e._transaction&&e.dispose())},onTransportError:function(t){return e.onTransportError(t)},receiveResponse:function(t){return e.receiveResponse(t)}},r=new this.transactionConstructor(this.message,this.core.transport,t);this._transaction=r;var i=r.id+r.request.method;this.core.userAgentClients.set(i,this)},e.prototype.onRequestTimeout=function(){this.logger.warn("User agent client request timed out. Generating internal 408 Request Timeout.");var e=new i.IncomingResponseMessage;e.statusCode=408,e.reasonPhrase="Request Timeout",this.receiveResponse(e)},e.prototype.onTransportError=function(e){this.logger.error(e.message),this.logger.error("User agent client request transport error. Generating internal 503 Service Unavailable.");var t=new i.IncomingResponseMessage;t.statusCode=503,t.reasonPhrase="Service Unavailable",this.receiveResponse(t)},e}();t.UserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e[e.STATUS_EARLY=1]="STATUS_EARLY",e[e.STATUS_CONFIRMED=2]="STATUS_CONFIRMED"}(t.DialogStatus||(t.DialogStatus={})),function(e){e[e.STATUS_NULL=0]="STATUS_NULL",e[e.STATUS_INVITE_SENT=1]="STATUS_INVITE_SENT",e[e.STATUS_1XX_RECEIVED=2]="STATUS_1XX_RECEIVED",e[e.STATUS_INVITE_RECEIVED=3]="STATUS_INVITE_RECEIVED",e[e.STATUS_WAITING_FOR_ANSWER=4]="STATUS_WAITING_FOR_ANSWER",e[e.STATUS_ANSWERED=5]="STATUS_ANSWERED",e[e.STATUS_WAITING_FOR_PRACK=6]="STATUS_WAITING_FOR_PRACK",e[e.STATUS_WAITING_FOR_ACK=7]="STATUS_WAITING_FOR_ACK",e[e.STATUS_CANCELED=8]="STATUS_CANCELED",e[e.STATUS_TERMINATED=9]="STATUS_TERMINATED",e[e.STATUS_ANSWERED_WAITING_FOR_PRACK=10]="STATUS_ANSWERED_WAITING_FOR_PRACK",e[e.STATUS_EARLY_MEDIA=11]="STATUS_EARLY_MEDIA",e[e.STATUS_CONFIRMED=12]="STATUS_CONFIRMED"}(t.SessionStatus||(t.SessionStatus={})),function(e){e[e.ClientContext=0]="ClientContext",e[e.ConfigurationError=1]="ConfigurationError",e[e.Dialog=2]="Dialog",e[e.DigestAuthentication=3]="DigestAuthentication",e[e.DTMF=4]="DTMF",e[e.IncomingMessage=5]="IncomingMessage",e[e.IncomingRequest=6]="IncomingRequest",e[e.IncomingResponse=7]="IncomingResponse",e[e.InvalidStateError=8]="InvalidStateError",e[e.InviteClientContext=9]="InviteClientContext",e[e.InviteServerContext=10]="InviteServerContext",e[e.Logger=11]="Logger",e[e.LoggerFactory=12]="LoggerFactory",e[e.MethodParameterError=13]="MethodParameterError",e[e.NameAddrHeader=14]="NameAddrHeader",e[e.NotSupportedError=15]="NotSupportedError",e[e.OutgoingRequest=16]="OutgoingRequest",e[e.Parameters=17]="Parameters",e[e.PublishContext=18]="PublishContext",e[e.ReferClientContext=19]="ReferClientContext",e[e.ReferServerContext=20]="ReferServerContext",e[e.RegisterContext=21]="RegisterContext",e[e.RenegotiationError=22]="RenegotiationError",e[e.RequestSender=23]="RequestSender",e[e.ServerContext=24]="ServerContext",e[e.Session=25]="Session",e[e.SessionDescriptionHandler=26]="SessionDescriptionHandler",e[e.SessionDescriptionHandlerError=27]="SessionDescriptionHandlerError",e[e.SessionDescriptionHandlerObserver=28]="SessionDescriptionHandlerObserver",e[e.Subscription=29]="Subscription",e[e.Transport=30]="Transport",e[e.UA=31]="UA",e[e.URI=32]="URI"}(t.TypeStrings||(t.TypeStrings={})),function(e){e[e.STATUS_INIT=0]="STATUS_INIT",e[e.STATUS_STARTING=1]="STATUS_STARTING",e[e.STATUS_READY=2]="STATUS_READY",e[e.STATUS_USER_CLOSED=3]="STATUS_USER_CLOSED",e[e.STATUS_NOT_READY=4]="STATUS_NOT_READY"}(t.UAStatus||(t.UAStatus={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(19),t),i.__exportStar(r(15),t),i.__exportStar(r(81),t),i.__exportStar(r(2),t),i.__exportStar(r(26),t),i.__exportStar(r(32),t),i.__exportStar(r(1),t),i.__exportStar(r(83),t),i.__exportStar(r(53),t),i.__exportStar(r(11),t),i.__exportStar(r(95),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(15),n=r(2),s=r(13),o=r(1),a=function(){function e(e,t,r,i){this.transactionConstructor=e,this.core=t,this.message=r,this.delegate=i,this.logger=this.loggerFactory.getLogger("sip.user-agent-server"),this.toTag=r.toTag?r.toTag:s.newTag(),this.init()}return e.prototype.dispose=function(){this.transaction.dispose()},Object.defineProperty(e.prototype,"loggerFactory",{get:function(){return this.core.loggerFactory},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"transaction",{get:function(){if(!this._transaction)throw new Error("Transaction undefined.");return this._transaction},enumerable:!0,configurable:!0}),e.prototype.accept=function(e){if(void 0===e&&(e={statusCode:200}),!this.acceptable)throw new i.TransactionStateError(this.message.method+" not acceptable in state "+this.transaction.state+".");var t=e.statusCode;if(t<200||t>299)throw new TypeError("Invalid statusCode: "+t);return this.reply(e)},e.prototype.progress=function(e){if(void 0===e&&(e={statusCode:180}),!this.progressable)throw new i.TransactionStateError(this.message.method+" not progressable in state "+this.transaction.state+".");var t=e.statusCode;if(t<101||t>199)throw new TypeError("Invalid statusCode: "+t);return this.reply(e)},e.prototype.redirect=function(e,t){if(void 0===t&&(t={statusCode:302}),!this.redirectable)throw new i.TransactionStateError(this.message.method+" not redirectable in state "+this.transaction.state+".");var r=t.statusCode;if(r<300||r>399)throw new TypeError("Invalid statusCode: "+r);var n=new Array;return e.forEach(function(e){return n.push("Contact: "+e.toString())}),t.extraHeaders=(t.extraHeaders||[]).concat(n),this.reply(t)},e.prototype.reject=function(e){if(void 0===e&&(e={statusCode:480}),!this.rejectable)throw new i.TransactionStateError(this.message.method+" not rejectable in state "+this.transaction.state+".");var t=e.statusCode;if(t<400||t>699)throw new TypeError("Invalid statusCode: "+t);return this.reply(e)},e.prototype.trying=function(e){if(!this.tryingable)throw new i.TransactionStateError(this.message.method+" not tryingable in state "+this.transaction.state+".");return this.reply({statusCode:100})},e.prototype.receiveCancel=function(e){this.delegate&&this.delegate.onCancel&&this.delegate.onCancel(e)},Object.defineProperty(e.prototype,"acceptable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding||this.transaction.state===o.TransactionState.Accepted;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying||this.transaction.state===o.TransactionState.Proceeding;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"progressable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return!1;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"redirectable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying||this.transaction.state===o.TransactionState.Proceeding;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"rejectable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying||this.transaction.state===o.TransactionState.Proceeding;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"tryingable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),e.prototype.reply=function(e){e.toTag||100===e.statusCode||(e.toTag=this.toTag),e.userAgent=e.userAgent||this.core.configuration.userAgentHeaderFieldValue,e.supported=e.supported||this.core.configuration.supportedOptionTagsResponse;var t=n.constructOutgoingResponse(this.message,e);return this.transaction.receiveResponse(e.statusCode,t.message),t},e.prototype.init=function(){var e=this,t={loggerFactory:this.loggerFactory,onStateChange:function(t){t===o.TransactionState.Terminated&&(e.core.userAgentServers.delete(i),e.dispose())},onTransportError:function(t){e.logger.error(t.message),e.delegate&&e.delegate.onTransportError?e.delegate.onTransportError(t):e.logger.error("User agent server response transport error.")}},r=new this.transactionConstructor(this.message,this.core.transport,t);this._transaction=r;var i=r.id;this.core.userAgentServers.set(r.id,this)},e}();t.UserAgentServer=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(54);!function(e){e.USER_AGENT=i.title+"/"+i.version,e.SIP="sip",e.SIPS="sips",function(e){e.CONNECTION_ERROR="Connection Error",e.INTERNAL_ERROR="Internal Error",e.REQUEST_TIMEOUT="Request Timeout",e.SIP_FAILURE_CODE="SIP Failure Code",e.ADDRESS_INCOMPLETE="Address Incomplete",e.AUTHENTICATION_ERROR="Authentication Error",e.BUSY="Busy",e.DIALOG_ERROR="Dialog Error",e.INCOMPATIBLE_SDP="Incompatible SDP",e.NOT_FOUND="Not Found",e.REDIRECTED="Redirected",e.REJECTED="Rejected",e.UNAVAILABLE="Unavailable",e.BAD_MEDIA_DESCRIPTION="Bad Media Description",e.CANCELED="Canceled",e.EXPIRES="Expires",e.NO_ACK="No ACK",e.NO_ANSWER="No Answer",e.NO_PRACK="No PRACK",e.RTP_TIMEOUT="RTP Timeout",e.USER_DENIED_MEDIA_ACCESS="User Denied Media Access",e.WEBRTC_ERROR="WebRTC Error",e.WEBRTC_NOT_SUPPORTED="WebRTC Not Supported"}(e.causes||(e.causes={})),function(e){e.REQUIRED="required",e.SUPPORTED="supported",e.UNSUPPORTED="none"}(e.supported||(e.supported={})),e.SIP_ERROR_CAUSES={ADDRESS_INCOMPLETE:[484],AUTHENTICATION_ERROR:[401,407],BUSY:[486,600],INCOMPATIBLE_SDP:[488,606],NOT_FOUND:[404,604],REDIRECTED:[300,301,302,305,380],REJECTED:[403,603],UNAVAILABLE:[480,410,408,430]},e.ACK="ACK",e.BYE="BYE",e.CANCEL="CANCEL",e.INFO="INFO",e.INVITE="INVITE",e.MESSAGE="MESSAGE",e.NOTIFY="NOTIFY",e.OPTIONS="OPTIONS",e.REGISTER="REGISTER",e.UPDATE="UPDATE",e.SUBSCRIBE="SUBSCRIBE",e.PUBLISH="PUBLISH",e.REFER="REFER",e.PRACK="PRACK",e.REASON_PHRASE={100:"Trying",180:"Ringing",181:"Call Is Being Forwarded",182:"Queued",183:"Session Progress",199:"Early Dialog Terminated",200:"OK",202:"Accepted",204:"No Notification",300:"Multiple Choices",301:"Moved Permanently",302:"Moved Temporarily",305:"Use Proxy",380:"Alternative Service",400:"Bad Request",401:"Unauthorized",402:"Payment Required",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",406:"Not Acceptable",407:"Proxy Authentication Required",408:"Request Timeout",410:"Gone",412:"Conditional Request Failed",413:"Request Entity Too Large",414:"Request-URI Too Long",415:"Unsupported Media Type",416:"Unsupported URI Scheme",417:"Unknown Resource-Priority",420:"Bad Extension",421:"Extension Required",422:"Session Interval Too Small",423:"Interval Too Brief",428:"Use Identity Header",429:"Provide Referrer Identity",430:"Flow Failed",433:"Anonymity Disallowed",436:"Bad Identity-Info",437:"Unsupported Certificate",438:"Invalid Identity Header",439:"First Hop Lacks Outbound Support",440:"Max-Breadth Exceeded",469:"Bad Info Package",470:"Consent Needed",478:"Unresolvable Destination",480:"Temporarily Unavailable",481:"Call/Transaction Does Not Exist",482:"Loop Detected",483:"Too Many Hops",484:"Address Incomplete",485:"Ambiguous",486:"Busy Here",487:"Request Terminated",488:"Not Acceptable Here",489:"Bad Event",491:"Request Pending",493:"Undecipherable",494:"Security Agreement Required",500:"Internal Server Error",501:"Not Implemented",502:"Bad Gateway",503:"Service Unavailable",504:"Server Time-out",505:"Version Not Supported",513:"Message Too Large",580:"Precondition Failure",600:"Busy Everywhere",603:"Decline",604:"Does Not Exist Anywhere",606:"Not Acceptable"},e.OPTION_TAGS={"100rel":!0,199:!0,answermode:!0,"early-session":!0,eventlist:!0,explicitsub:!0,"from-change":!0,"geolocation-http":!0,"geolocation-sip":!0,gin:!0,gruu:!0,histinfo:!0,ice:!0,join:!0,"multiple-refer":!0,norefersub:!0,nosub:!0,outbound:!0,path:!0,policy:!0,precondition:!0,pref:!0,privacy:!0,"recipient-list-invite":!0,"recipient-list-message":!0,"recipient-list-subscribe":!0,replaces:!0,"resource-priority":!0,"sdp-anat":!0,"sec-agree":!0,tdialog:!0,timer:!0,uui:!0},function(e){e.INFO="info",e.RTP="rtp"}(e.dtmfType||(e.dtmfType={}))}(t.C||(t.C={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(7),n=r(22),s=r(25);!function(e){e.defer=function(){var e={};return e.promise=new Promise(function(t,r){e.resolve=t,e.reject=r}),e},e.reducePromises=function(e,t){return e.reduce(function(e,t){return e=e.then(t)},Promise.resolve(t))},e.str_utf8_length=function(e){return encodeURIComponent(e).replace(/%[A-F\d]{2}/g,"U").length},e.generateFakeSDP=function(e){if(e){var t=e.indexOf("o="),r=e.indexOf("\r\n",t);return"v=0\r\n"+e.slice(t,r)+"\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0"}},e.isDecimal=function(e){var t=parseInt(e,10);return!isNaN(t)&&parseFloat(e)===t},e.createRandomToken=function(e,t){void 0===t&&(t=32);for(var r="",i=0;i699)throw new TypeError("Invalid statusCode: "+t);if(t)return e.getReasonHeaderValue(t,r)},e.buildStatusLine=function(t,r){if(!t||t<100||t>699)throw new TypeError("Invalid statusCode: "+t);if(r&&"string"!=typeof r&&!(r instanceof String))throw new TypeError("Invalid reason: "+r);return"SIP/2.0 "+t+" "+(r=e.getReasonPhrase(t,r))+"\r\n"},e.fromBodyObj=function(e){var t=e.body,r=e.contentType;return{contentDisposition:function(e){return"application/sdp"===e?"session":"render"}(r),contentType:r,content:t}},e.toBodyObj=function(e){return{body:e.content,contentType:e.contentType}}}(t.Utils||(t.Utils={}))},function(e,t,r){"use strict";var i,n="object"==typeof Reflect?Reflect:null,s=n&&"function"==typeof n.apply?n.apply:function(e,t,r){return Function.prototype.apply.call(e,t,r)};i=n&&"function"==typeof n.ownKeys?n.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var o=Number.isNaN||function(e){return e!=e};function a(){a.init.call(this)}e.exports=a,a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var c=10;function u(e){return void 0===e._maxListeners?a.defaultMaxListeners:e._maxListeners}function d(e,t,r,i){var n,s,o,a;if("function"!=typeof r)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof r);if(void 0===(s=e._events)?(s=e._events=Object.create(null),e._eventsCount=0):(void 0!==s.newListener&&(e.emit("newListener",t,r.listener?r.listener:r),s=e._events),o=s[t]),void 0===o)o=s[t]=r,++e._eventsCount;else if("function"==typeof o?o=s[t]=i?[r,o]:[o,r]:i?o.unshift(r):o.push(r),(n=u(e))>0&&o.length>n&&!o.warned){o.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=o.length,a=c,console&&console.warn&&console.warn(a)}return e}function p(e,t,r){var i={fired:!1,wrapFn:void 0,target:e,type:t,listener:r},n=function(){for(var e=[],t=0;t0&&(o=t[0]),o instanceof Error)throw o;var a=new Error("Unhandled error."+(o?" ("+o.message+")":""));throw a.context=o,a}var c=n[e];if(void 0===c)return!1;if("function"==typeof c)s(c,this,t);else{var u=c.length,d=g(c,u);for(r=0;r=0;s--)if(r[s]===t||r[s].listener===t){o=r[s].listener,n=s;break}if(n<0)return this;0===n?r.shift():function(e,t){for(;t+1=0;i--)this.removeListener(e,t[i]);return this},a.prototype.listeners=function(e){return h(this,e,!0)},a.prototype.rawListeners=function(e){return h(this,e,!1)},a.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):l.call(e,t)},a.prototype.listenerCount=l,a.prototype.eventNames=function(){return this._eventsCount>0?i(this._events):[]}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(5),s=r(4);!function(e){var t=function(e){function t(){return e.call(this,"The session description handler has closed.")||this}return i.__extends(t,e),t}(n.Exception);e.ClosedSessionDescriptionHandlerError=t;var r=function(e){function t(){return e.call(this,"The session has terminated.")||this}return i.__extends(t,e),t}(n.Exception);e.TerminatedSessionError=r;var s=function(e){function t(t){return e.call(this,t||"Unsupported session description content type.")||this}return i.__extends(t,e),t}(n.Exception);e.UnsupportedSessionDescriptionContentTypeError=s}(t.Exceptions||(t.Exceptions={}));var o=function(e){function t(t,r,i){var n=e.call(this,i)||this;return n.code=t,n.name=r,n.message=i,n}return i.__extends(t,e),t}(n.Exception);!function(e){var t=function(e){function t(t,r){var i=e.call(this,1,"CONFIGURATION_ERROR",r?"Invalid value "+JSON.stringify(r)+" for parameter '"+t+"'":"Missing parameter: "+t)||this;return i.type=s.TypeStrings.ConfigurationError,i.parameter=t,i.value=r,i}return i.__extends(t,e),t}(o);e.ConfigurationError=t;var r=function(e){function t(t){var r=e.call(this,2,"INVALID_STATE_ERROR","Invalid status: "+t)||this;return r.type=s.TypeStrings.InvalidStateError,r.status=t,r}return i.__extends(t,e),t}(o);e.InvalidStateError=r;var n=function(e){function t(t){var r=e.call(this,3,"NOT_SUPPORTED_ERROR",t)||this;return r.type=s.TypeStrings.NotSupportedError,r}return i.__extends(t,e),t}(o);e.NotSupportedError=n;var a=function(e){function t(t){var r=e.call(this,5,"RENEGOTIATION_ERROR",t)||this;return r.type=s.TypeStrings.RenegotiationError,r}return i.__extends(t,e),t}(o);e.RenegotiationError=a;var c=function(e){function t(t,r,i){var n=e.call(this,6,"METHOD_PARAMETER_ERROR",i?"Invalid value "+JSON.stringify(i)+" for parameter '"+r+"'":"Missing parameter: "+r)||this;return n.type=s.TypeStrings.MethodParameterError,n.method=t,n.parameter=r,n.value=i,n}return i.__extends(t,e),t}(o);e.MethodParameterError=c;var u=function(e){function t(t,r,i){var n=e.call(this,8,"SESSION_DESCRIPTION_HANDLER_ERROR",i||"Error with Session Description Handler")||this;return n.type=s.TypeStrings.SessionDescriptionHandlerError,n.method=t,n.error=r,n}return i.__extends(t,e),t}(o);e.SessionDescriptionHandlerError=u}(t.Exceptions||(t.Exceptions={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=500;t.Timers={T1:i,T2:4e3,T4:5e3,TIMER_B:32e3,TIMER_D:0,TIMER_F:32e3,TIMER_H:32e3,TIMER_I:0,TIMER_J:0,TIMER_K:0,TIMER_L:32e3,TIMER_M:32e3,TIMER_N:32e3,PROVISIONAL_RESPONSE_INTERVAL:6e4}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(7),o=r(4),a=r(8),c=function(e){function t(r,i,n,s){var o=e.call(this)||this;return o.data={},t.initializer(o,r,i,n,s),o}return i.__extends(t,e),t.initializer=function(e,t,r,i,n){if(e.type=o.TypeStrings.ClientContext,void 0===i)throw new TypeError("Not enough arguments");e.ua=t,e.logger=t.getLogger("sip.clientcontext"),e.method=r;var s=t.normalizeTarget(i);if(!s)throw new TypeError("Invalid target: "+i);var c,u,d=((n=Object.create(n||Object.prototype)).extraHeaders||[]).slice(),p=n.params||{};n.body&&(c={body:n.body,contentType:n.contentType?n.contentType:"application/sdp"},e.body=c),c&&(u=a.Utils.fromBodyObj(c)),e.request=t.userAgentCore.makeOutgoingRequestMessage(r,s,p.fromUri?p.fromUri:t.userAgentCore.configuration.aor,p.toUri?p.toUri:s,p,d,u),e.request.from&&(e.localIdentity=e.request.from),e.request.to&&(e.remoteIdentity=e.request.to)},t.prototype.send=function(){var e=this;return this.ua.userAgentCore.request(this.request,{onAccept:function(t){return e.receiveResponse(t.message)},onProgress:function(t){return e.receiveResponse(t.message)},onRedirect:function(t){return e.receiveResponse(t.message)},onReject:function(t){return e.receiveResponse(t.message)},onTrying:function(t){return e.receiveResponse(t.message)}}),this},t.prototype.receiveResponse=function(e){var t=e.statusCode||0,r=a.Utils.getReasonPhrase(t);switch(!0){case/^1[0-9]{2}$/.test(t.toString()):this.emit("progress",e,r);break;case/^2[0-9]{2}$/.test(t.toString()):this.ua.applicants[this.toString()]&&delete this.ua.applicants[this.toString()],this.emit("accepted",e,r);break;default:this.ua.applicants[this.toString()]&&delete this.ua.applicants[this.toString()],this.emit("rejected",e,r),this.emit("failed",e,r)}},t.prototype.onRequestTimeout=function(){this.emit("failed",void 0,s.C.causes.REQUEST_TIMEOUT)},t.prototype.onTransportError=function(){this.emit("failed",void 0,s.C.causes.CONNECTION_ERROR)},t}(n.EventEmitter);t.ClientContext=c},function(e,t,r){"use strict";function i(e,t){void 0===t&&(t=32);for(var r="",i=0;in)throw new TypeError("Invalid statusCode: "+r);var p,h={statusCode:r,reasonPhrase:s,extraHeaders:a,body:u},l=r.toString();switch(!0){case/^100$/.test(l):p=this.incomingRequest.trying(h).message;break;case/^1[0-9]{2}$/.test(l):p=this.incomingRequest.progress(h).message;break;case/^2[0-9]{2}$/.test(l):p=this.incomingRequest.accept(h).message;break;case/^3[0-9]{2}$/.test(l):p=this.incomingRequest.redirect([],h).message;break;case/^[4-6][0-9]{2}$/.test(l):p=this.incomingRequest.reject(h).message;break;default:throw new Error("Invalid status code "+r)}return d.forEach(function(e){t.emit(e,p,s)}),this},t.prototype.onRequestTimeout=function(){this.emit("failed",void 0,s.C.causes.REQUEST_TIMEOUT)},t.prototype.onTransportError=function(){this.emit("failed",void 0,s.C.causes.CONNECTION_ERROR)},t}(n.EventEmitter);t.ServerContext=u},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(20),t),i.__exportStar(r(72),t),i.__exportStar(r(50),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(2),n=function(){function e(e,t){this.core=e,this.dialogState=t,this.core.dialogs.set(this.id,this)}return e.initialDialogStateForUserAgentClient=function(e,t){var r=t.getHeaders("record-route").reverse(),n=t.parseHeader("contact");if(!n)throw new Error("Contact undefined.");if(!(n instanceof i.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");var s=n.uri,o=e.cseq,a=e.callId,c=e.fromTag,u=t.toTag;if(!a)throw new Error("Call id undefined.");if(!c)throw new Error("From tag undefined.");if(!u)throw new Error("To tag undefined.");if(!e.from)throw new Error("From undefined.");if(!e.to)throw new Error("To undefined.");var d=e.from.uri,p=e.to.uri;if(!t.statusCode)throw new Error("Incoming response status code undefined.");return{id:a+c+u,early:t.statusCode<200,callId:a,localTag:c,remoteTag:u,localSequenceNumber:o,remoteSequenceNumber:void 0,localURI:d,remoteURI:p,remoteTarget:s,routeSet:r,secure:!1}},e.initialDialogStateForUserAgentServer=function(e,t,r){void 0===r&&(r=!1);var n=e.getHeaders("record-route"),s=e.parseHeader("contact");if(!s)throw new Error("Contact undefined.");if(!(s instanceof i.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");var o=s.uri,a=e.cseq,c=e.callId,u=t,d=e.fromTag,p=e.from.uri;return{id:c+u+d,early:r,callId:c,localTag:u,remoteTag:d,localSequenceNumber:void 0,remoteSequenceNumber:a,localURI:e.to.uri,remoteURI:p,remoteTarget:o,routeSet:n,secure:!1}},e.prototype.dispose=function(){this.core.dialogs.delete(this.id)},Object.defineProperty(e.prototype,"id",{get:function(){return this.dialogState.id},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"early",{get:function(){return this.dialogState.early},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"callId",{get:function(){return this.dialogState.callId},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"localTag",{get:function(){return this.dialogState.localTag},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteTag",{get:function(){return this.dialogState.remoteTag},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"localSequenceNumber",{get:function(){return this.dialogState.localSequenceNumber},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteSequenceNumber",{get:function(){return this.dialogState.remoteSequenceNumber},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"localURI",{get:function(){return this.dialogState.localURI},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteURI",{get:function(){return this.dialogState.remoteURI},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteTarget",{get:function(){return this.dialogState.remoteTarget},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"routeSet",{get:function(){return this.dialogState.routeSet},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"secure",{get:function(){return this.dialogState.secure},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"userAgentCore",{get:function(){return this.core},enumerable:!0,configurable:!0}),e.prototype.confirm=function(){this.dialogState.early=!1},e.prototype.receiveRequest=function(e){if(e.method!==i.C.ACK){if(this.remoteSequenceNumber){if(e.cseq<=this.remoteSequenceNumber)throw new Error("Out of sequence in dialog request. Did you forget to call sequenceGuard()?");this.dialogState.remoteSequenceNumber=e.cseq}this.remoteSequenceNumber||(this.dialogState.remoteSequenceNumber=e.cseq)}},e.prototype.recomputeRouteSet=function(e){this.dialogState.routeSet=e.getHeaders("record-route").reverse()},e.prototype.createOutgoingRequestMessage=function(e,t){var r,i=this.remoteURI,n=this.remoteTag,s=this.localURI,o=this.localTag,a=this.callId;r=t&&t.cseq?t.cseq:this.dialogState.localSequenceNumber?this.dialogState.localSequenceNumber+=1:this.dialogState.localSequenceNumber=1;var c=this.remoteTarget,u=this.routeSet,d=t&&t.extraHeaders,p=t&&t.body;return this.userAgentCore.makeOutgoingRequestMessage(e,c,s,i,{callId:a,cseq:r,fromTag:o,toTag:n,routeSet:u},d,p)},e.prototype.sequenceGuard=function(e){return e.method===i.C.ACK||(!(this.remoteSequenceNumber&&e.cseq<=this.remoteSequenceNumber)||(this.core.replyStateless(e,{statusCode:500}),!1))},e}();t.Dialog=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(22),n=r(13),s=function(){function e(){this.headers={}}return e.prototype.addHeader=function(e,t){var r={raw:t};e=n.headerize(e),this.headers[e]?this.headers[e].push(r):this.headers[e]=[r]},e.prototype.getHeader=function(e){var t=this.headers[n.headerize(e)];if(t)return t[0]?t[0].raw:void 0},e.prototype.getHeaders=function(e){var t=this.headers[n.headerize(e)],r=[];if(!t)return[];for(var i=0,s=t;i=this.headers[e].length)){var r=this.headers[e][t],s=r.raw;if(r.parsed)return r.parsed;var o=i.Grammar.parse(s,e.replace(/-/g,"_"));return-1===o?void this.headers[e].splice(t,1):(r.parsed=o,o)}},e.prototype.s=function(e,t){return void 0===t&&(t=0),this.parseHeader(e,t)},e.prototype.setHeader=function(e,t){this.headers[n.headerize(e)]=[{raw:t}]},e.prototype.toString=function(){return this.data},e}();t.IncomingMessage=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0).__importStar(r(67));!function(e){e.parse=function(e,t){var r={startRule:t};try{i.parse(e,r)}catch(e){r.data=-1}return r.data},e.nameAddrHeaderParse=function(t){var r=e.parse(t,"Name_Addr_Header");return-1!==r?r:void 0},e.URIParse=function(t){var r=e.parse(t,"SIP_URI");return-1!==r?r:void 0}}(t.Grammar||(t.Grammar={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t,r,i){var n=e.call(this,i)||this;return n.uri=t,n._displayName=r,n}return i.__extends(t,e),Object.defineProperty(t.prototype,"friendlyName",{get:function(){return this.displayName||this.uri.aor},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"displayName",{get:function(){return this._displayName},set:function(e){this._displayName=e},enumerable:!0,configurable:!0}),t.prototype.clone=function(){return new t(this.uri.clone(),this._displayName,JSON.parse(JSON.stringify(this.parameters)))},t.prototype.toString=function(){var e=this.displayName||"0"===this.displayName?'"'+this.displayName+'" ':"";for(var t in e+="<"+this.uri.toString()+">",this.parameters)this.parameters.hasOwnProperty(t)&&(e+=";"+t,null!==this.parameters[t]&&(e+="="+this.parameters[t]));return e},t}(r(24).Parameters);t.NameAddrHeader=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e){for(var t in this.parameters={},e)e.hasOwnProperty(t)&&this.setParam(t,e[t])}return e.prototype.setParam=function(e,t){e&&(this.parameters[e.toLowerCase()]=null==t?null:t.toString())},e.prototype.getParam=function(e){if(e)return this.parameters[e.toLowerCase()]},e.prototype.hasParam=function(e){return!!e&&!!this.parameters.hasOwnProperty(e.toLowerCase())},e.prototype.deleteParam=function(e){if(e=e.toLowerCase(),this.parameters.hasOwnProperty(e)){var t=this.parameters[e];return delete this.parameters[e],t}},e.prototype.clearParams=function(){this.parameters={}},e}();t.Parameters=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t,r,i,n,s,o){var a=e.call(this,s)||this;if(a.headers={},!i)throw new TypeError('missing or invalid "host" parameter');for(var c in t=t||"sip",o)o.hasOwnProperty(c)&&a.setHeader(c,o[c]);return a.raw={scheme:t,user:r,host:i,port:n},a.normal={scheme:t.toLowerCase(),user:r,host:i.toLowerCase(),port:n},a}return i.__extends(t,e),Object.defineProperty(t.prototype,"scheme",{get:function(){return this.normal.scheme},set:function(e){this.raw.scheme=e,this.normal.scheme=e.toLowerCase()},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"user",{get:function(){return this.normal.user},set:function(e){this.normal.user=this.raw.user=e},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"host",{get:function(){return this.normal.host},set:function(e){this.raw.host=e,this.normal.host=e.toLowerCase()},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"aor",{get:function(){return this.normal.user+"@"+this.normal.host},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"port",{get:function(){return this.normal.port},set:function(e){this.normal.port=this.raw.port=e},enumerable:!0,configurable:!0}),t.prototype.setHeader=function(e,t){this.headers[this.headerize(e)]=t instanceof Array?t:[t]},t.prototype.getHeader=function(e){if(e)return this.headers[this.headerize(e)]},t.prototype.hasHeader=function(e){return!!e&&!!this.headers.hasOwnProperty(this.headerize(e))},t.prototype.deleteHeader=function(e){if(e=this.headerize(e),this.headers.hasOwnProperty(e)){var t=this.headers[e];return delete this.headers[e],t}},t.prototype.clearHeaders=function(){this.headers={}},t.prototype.clone=function(){return new t(this._raw.scheme,this._raw.user||"",this._raw.host,this._raw.port,JSON.parse(JSON.stringify(this.parameters)),JSON.parse(JSON.stringify(this.headers)))},t.prototype.toRaw=function(){return this._toString(this._raw)},t.prototype.toString=function(){return this._toString(this._normal)},Object.defineProperty(t.prototype,"_normal",{get:function(){return this.normal},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"_raw",{get:function(){return this.raw},enumerable:!0,configurable:!0}),t.prototype._toString=function(e){var t=e.scheme+":";for(var r in e.scheme.toLowerCase().match("^sips?$")||(t+="//"),e.user&&(t+=this.escapeUser(e.user)+"@"),t+=e.host,(e.port||0===e.port)&&(t+=":"+e.port),this.parameters)this.parameters.hasOwnProperty(r)&&(t+=";"+r,null!==this.parameters[r]&&(t+="="+this.parameters[r]));var i=[];for(var n in this.headers)if(this.headers.hasOwnProperty(n))for(var s in this.headers[n])this.headers[n].hasOwnProperty(s)&&i.push(n+"="+this.headers[n][s]);return i.length>0&&(t+="?"+i.join("&")),t},t.prototype.escapeUser=function(e){return encodeURIComponent(decodeURIComponent(e)).replace(/%3A/gi,":").replace(/%2B/gi,"+").replace(/%3F/gi,"?").replace(/%2F/gi,"/")},t.prototype.headerize=function(e){for(var t={"Call-Id":"Call-ID",Cseq:"CSeq","Min-Se":"Min-SE",Rack:"RAck",Rseq:"RSeq","Www-Authenticate":"WWW-Authenticate"},r=e.toLowerCase().replace(/_/g,"-").split("-"),i=r.length,n="",s=0;s"),o.extraHeaders.push("Contact: "+r.contact),o.extraHeaders.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),o.extraHeaders.push("Refer-To: "+o.target),o.errorListener=o.onTransportError.bind(o),t.transport&&t.transport.on("transportError",o.errorListener),o}return i.__extends(t,e),t.prototype.refer=function(e){var t=this;void 0===e&&(e={});var r=(this.extraHeaders||[]).slice();return e.extraHeaders&&r.concat(e.extraHeaders),this.applicant.sendRequest(s.C.REFER,{extraHeaders:this.extraHeaders,receiveResponse:function(r){var i=r&&r.statusCode?r.statusCode.toString():"";/^1[0-9]{2}$/.test(i)?t.emit("referRequestProgress",t):/^2[0-9]{2}$/.test(i)?t.emit("referRequestAccepted",t):/^[4-6][0-9]{2}$/.test(i)&&t.emit("referRequestRejected",t),e.receiveResponse&&e.receiveResponse(r)}}),this},t.prototype.receiveNotify=function(e){var t=e.message.hasHeader("Content-Type")?e.message.getHeader("Content-Type"):void 0;if(t&&-1!==t.search(/^message\/sipfrag/)){var r=o.Grammar.parse(e.message.body,"sipfrag");if(-1===r)return void e.reject({statusCode:489,reasonPhrase:"Bad Event"});switch(!0){case/^1[0-9]{2}$/.test(r.status_code):this.emit("referProgress",this);break;case/^2[0-9]{2}$/.test(r.status_code):this.emit("referAccepted",this),!this.options.activeAfterTransfer&&this.applicant.terminate&&this.applicant.terminate();break;default:this.emit("referRejected",this)}return e.accept(),void this.emit("notify",e.message)}e.reject({statusCode:489,reasonPhrase:"Bad Event"})},t.prototype.initReferTo=function(e){var t;if("string"==typeof e){var r=o.Grammar.parse(e,"Refer_To");t=r&&r.uri?r.uri:e;var i=this.ua.normalizeTarget(e);if(!i)throw new TypeError("Invalid target: "+e);t=i}else{if(!e.session)throw new Error("Session undefined.");var n=e.remoteIdentity.friendlyName,s=e.session.remoteTarget.toString(),a=e.session.callId,c=e.session.remoteTag,u=e.session.localTag;t='"'+n+'" <'+s+"?Replaces="+encodeURIComponent(a+";to-tag="+c+";from-tag="+u)+">"}return t},t}(n.ClientContext);t.ReferClientContext=d;var p=function(e){function t(t,r,i){var n=e.call(this,t,r)||this;return n.session=i,n.type=a.TypeStrings.ReferServerContext,n.ua=t,n.status=a.SessionStatus.STATUS_INVITE_RECEIVED,n.fromTag=n.request.fromTag,n.id=n.request.callId+n.fromTag,n.contact=n.ua.contact.toString(),n.logger=t.getLogger("sip.referservercontext",n.id),n.cseq=Math.floor(1e4*Math.random()),n.callId=n.request.callId,n.fromUri=n.request.to.uri,n.fromTag=n.request.to.parameters.tag,n.remoteTarget=n.request.headers.Contact[0].parsed.uri,n.toUri=n.request.from.uri,n.toTag=n.request.fromTag,n.routeSet=n.request.getHeaders("record-route"),n.request.hasHeader("refer-to")?(n.referTo=n.request.parseHeader("refer-to"),n.referredSession=n.ua.findSession(n.request),n.request.hasHeader("referred-by")&&(n.referredBy=n.request.getHeader("referred-by")),n.referTo.uri.hasHeader("replaces")&&(n.replaces=n.referTo.uri.getHeader("replaces")),n.errorListener=n.onTransportError.bind(n),t.transport&&t.transport.on("transportError",n.errorListener),n.status=a.SessionStatus.STATUS_WAITING_FOR_ANSWER,n):(n.logger.warn("Invalid REFER packet. A refer-to header is required. Rejecting refer."),n.reject(),n)}return i.__extends(t,e),t.prototype.progress=function(){if(this.status!==a.SessionStatus.STATUS_WAITING_FOR_ANSWER)throw new c.Exceptions.InvalidStateError(this.status);this.incomingRequest.trying()},t.prototype.reject=function(t){if(void 0===t&&(t={}),this.status===a.SessionStatus.STATUS_TERMINATED)throw new c.Exceptions.InvalidStateError(this.status);this.logger.log("Rejecting refer"),this.status=a.SessionStatus.STATUS_TERMINATED,e.prototype.reject.call(this,t),this.emit("referRequestRejected",this)},t.prototype.accept=function(e,t){var r=this;if(void 0===e&&(e={}),this.status!==a.SessionStatus.STATUS_WAITING_FOR_ANSWER)throw new c.Exceptions.InvalidStateError(this.status);if(this.status=a.SessionStatus.STATUS_ANSWERED,this.incomingRequest.accept({statusCode:202,reasonPhrase:"Accepted"}),this.emit("referRequestAccepted",this),e.followRefer){this.logger.log("Accepted refer, attempting to automatically follow it");var i=this.referTo.uri;if(!i.scheme||!i.scheme.match("^sips?$"))return this.logger.error("SIP.js can only automatically follow SIP refer target"),void this.reject();var n=e.inviteOptions||{},s=(n.extraHeaders||[]).slice();if(this.replaces&&s.push("Replaces: "+decodeURIComponent(this.replaces)),this.referredBy&&s.push("Referred-By: "+this.referredBy),n.extraHeaders=s,i.clearHeaders(),this.targetSession=this.ua.invite(i.toString(),n,t),this.emit("referInviteSent",this),this.targetSession){this.targetSession.once("progress",function(e){var t=e.statusCode||100,i=e.reasonPhrase;r.sendNotify(("SIP/2.0 "+t+" "+i).trim()),r.emit("referProgress",r),r.referredSession&&r.referredSession.emit("referProgress",r)}),this.targetSession.once("accepted",function(){r.logger.log("Successfully followed the refer"),r.sendNotify("SIP/2.0 200 OK"),r.emit("referAccepted",r),r.referredSession&&r.referredSession.emit("referAccepted",r)});var o=function(e){if(r.status!==a.SessionStatus.STATUS_TERMINATED){if(r.logger.log("Refer was not successful. Resuming session"),e&&429===e.statusCode)return r.logger.log("Alerting referrer that identity is required."),void r.sendNotify("SIP/2.0 429 Provide Referrer Identity");r.sendNotify("SIP/2.0 603 Declined"),r.status=a.SessionStatus.STATUS_TERMINATED,r.emit("referRejected",r),r.referredSession&&r.referredSession.emit("referRejected")}};this.targetSession.once("rejected",o),this.targetSession.once("failed",o)}}else this.logger.log("Accepted refer, but did not automatically follow it"),this.sendNotify("SIP/2.0 200 OK"),this.emit("referAccepted",this),this.referredSession&&this.referredSession.emit("referAccepted",this)},t.prototype.sendNotify=function(e){if(this.status!==a.SessionStatus.STATUS_ANSWERED)throw new c.Exceptions.InvalidStateError(this.status);if(-1===o.Grammar.parse(e,"sipfrag"))throw new Error("sipfrag body is required to send notify for refer");var t={contentDisposition:"render",contentType:"message/sipfrag",content:e};if(this.session)this.session.notify(void 0,{extraHeaders:["Event: refer","Subscription-State: terminated"],body:t});else{var r=this.ua.userAgentCore.makeOutgoingRequestMessage(s.C.NOTIFY,this.remoteTarget,this.fromUri,this.toUri,{cseq:this.cseq+=1,callId:this.callId,fromTag:this.fromTag,toTag:this.toTag,routeSet:this.routeSet},["Event: refer","Subscription-State: terminated","Content-Type: message/sipfrag"],t),i=this.ua.transport;if(!i)throw new Error("Transport undefined.");var n={loggerFactory:this.ua.getLoggerFactory()};new o.NonInviteClientTransaction(r,i,n)}},t.prototype.on=function(t,r){return e.prototype.on.call(this,t,r)},t}(u.ServerContext);t.ReferServerContext=p},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=function(e,t){for(var r,i=[],n=e.split(/\r\n/),s=0;s699)throw new Error("Invalid status code "+r);switch(this.state){case o.TransactionState.Calling:if(r>=100&&r<=199)return this.stateTransition(o.TransactionState.Proceeding),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=200&&r<=299)return this.ackRetransmissionCache.set(e.toTag,void 0),this.stateTransition(o.TransactionState.Accepted),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=300&&r<=699)return this.stateTransition(o.TransactionState.Completed),this.ack(e),void(this.user.receiveResponse&&this.user.receiveResponse(e));break;case o.TransactionState.Proceeding:if(r>=100&&r<=199)return void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=200&&r<=299)return this.ackRetransmissionCache.set(e.toTag,void 0),this.stateTransition(o.TransactionState.Accepted),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=300&&r<=699)return this.stateTransition(o.TransactionState.Completed),this.ack(e),void(this.user.receiveResponse&&this.user.receiveResponse(e));break;case o.TransactionState.Accepted:if(r>=200&&r<=299){if(!this.ackRetransmissionCache.has(e.toTag))return this.ackRetransmissionCache.set(e.toTag,void 0),void(this.user.receiveResponse&&this.user.receiveResponse(e));var i=this.ackRetransmissionCache.get(e.toTag);return i?void this.send(i.toString()).catch(function(e){t.logTransportError(e,"Failed to send retransmission of ACK to 2xx response.")}):void 0}break;case o.TransactionState.Completed:if(r>=300&&r<=699)return void this.ack(e);break;case o.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}var n="Received unexpected "+r+" response while in state "+this.state+".";this.logger.warn(n)},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e),this.stateTransition(o.TransactionState.Terminated,!0)},t.prototype.typeToString=function(){return"INVITE client transaction"},t.prototype.ack=function(e){var t=this,r=this.request.ruri,i=this.request.callId,n=this.request.cseq,s=this.request.getHeader("from"),o=e.getHeader("to"),a=this.request.getHeader("via"),c=this.request.getHeader("route");if(!s)throw new Error("From undefined.");if(!o)throw new Error("To undefined.");if(!a)throw new Error("Via undefined.");var u="ACK "+r+" SIP/2.0\r\n";c&&(u+="Route: "+c+"\r\n"),u+="Via: "+a+"\r\n",u+="To: "+o+"\r\n",u+="From: "+s+"\r\n",u+="Call-ID: "+i+"\r\n",u+="CSeq: "+n+" ACK\r\n",u+="Max-Forwards: 70\r\n",u+="Content-Length: 0\r\n\r\n",this.send(u).catch(function(e){t.logTransportError(e,"Failed to send ACK to non-2xx response.")})},t.prototype.stateTransition=function(e,t){var r=this;void 0===t&&(t=!1);var i=function(){throw new Error("Invalid state transition from "+r.state+" to "+e)};switch(e){case o.TransactionState.Calling:i();break;case o.TransactionState.Proceeding:this.state!==o.TransactionState.Calling&&i();break;case o.TransactionState.Accepted:case o.TransactionState.Completed:this.state!==o.TransactionState.Calling&&this.state!==o.TransactionState.Proceeding&&i();break;case o.TransactionState.Terminated:this.state!==o.TransactionState.Calling&&this.state!==o.TransactionState.Accepted&&this.state!==o.TransactionState.Completed&&(t||i());break;default:i()}this.B&&(clearTimeout(this.B),this.B=void 0),o.TransactionState.Proceeding,e===o.TransactionState.Completed&&(this.D=setTimeout(function(){return r.timer_D()},n.Timers.TIMER_D)),e===o.TransactionState.Accepted&&(this.M=setTimeout(function(){return r.timer_M()},n.Timers.TIMER_M)),e===o.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.timer_A=function(){},t.prototype.timer_B=function(){this.logger.debug("Timer B expired for INVITE client transaction "+this.id+"."),this.state===o.TransactionState.Calling&&(this.onRequestTimeout(),this.stateTransition(o.TransactionState.Terminated))},t.prototype.timer_D=function(){this.logger.debug("Timer D expired for INVITE client transaction "+this.id+"."),this.state===o.TransactionState.Completed&&this.stateTransition(o.TransactionState.Terminated)},t.prototype.timer_M=function(){this.logger.debug("Timer M expired for INVITE client transaction "+this.id+"."),this.state===o.TransactionState.Accepted&&this.stateTransition(o.TransactionState.Terminated)},t}(s.ClientTransaction);t.InviteClientTransaction=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o,a=t.createOutgoingRequestMessage(n.C.BYE,i);return o=e.call(this,s.NonInviteClientTransaction,t.userAgentCore,a,r)||this,t.dispose(),o}return i.__extends(t,e),t}(r(3).UserAgentClient);t.ByeUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.ByeUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.InfoUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=t.createOutgoingRequestMessage(n.C.NOTIFY,i);return e.call(this,s.NonInviteClientTransaction,t.userAgentCore,o,r)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.NotifyUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o,a=t.createOutgoingRequestMessage(n.C.PRACK,i);return o=e.call(this,s.NonInviteClientTransaction,t.userAgentCore,a,r)||this,t.signalingStateTransition(a),o}return i.__extends(t,e),t}(r(3).UserAgentClient);t.PrackUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this;return t.signalingStateTransition(r),s.dialog=t,s}return i.__extends(t,e),t.prototype.accept=function(t){return void 0===t&&(t={statusCode:200}),t.body&&this.dialog.signalingStateTransition(t.body),e.prototype.accept.call(this,t)},t}(r(6).UserAgentServer);t.PrackUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=this,a=t.createOutgoingRequestMessage(n.C.INVITE,i);return(o=e.call(this,s.InviteClientTransaction,t.userAgentCore,a,r)||this).delegate=r,t.signalingStateTransition(a),t.reinviteUserAgentClient=o,o.dialog=t,o}return i.__extends(t,e),t.prototype.receiveResponse=function(e){var t=this,r=e.statusCode?e.statusCode.toString():"";if(!r)throw new Error("Response status code undefined.");switch(!0){case/^100$/.test(r):this.delegate&&this.delegate.onTrying&&this.delegate.onTrying({message:e});break;case/^1[0-9]{2}$/.test(r):this.delegate&&this.delegate.onProgress&&this.delegate.onProgress({message:e,session:this.dialog,prack:function(e){throw new Error("Unimplemented.")}});break;case/^2[0-9]{2}$/.test(r):this.dialog.signalingStateTransition(e),this.delegate&&this.delegate.onAccept&&this.delegate.onAccept({message:e,session:this.dialog,ack:function(e){return t.dialog.ack(e)}});break;case/^3[0-9]{2}$/.test(r):this.delegate&&this.delegate.onRedirect&&this.delegate.onRedirect({message:e});break;case/^[4-6][0-9]{2}$/.test(r):this.delegate&&this.delegate.onReject&&this.delegate.onReject({message:e});break;default:throw new Error("Invalid status code "+r)}},t}(r(3).UserAgentClient);t.ReInviteUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.InviteServerTransaction,t.userAgentCore,r,i)||this;return t.reinviteUserAgentServer=s,s.dialog=t,s}return i.__extends(t,e),t.prototype.accept=function(t){void 0===t&&(t={statusCode:200}),t.extraHeaders=t.extraHeaders||[],t.extraHeaders=t.extraHeaders.concat(this.dialog.routeSet.map(function(e){return"Record-Route: "+e}));var r=e.prototype.accept.call(this,t),n=this.dialog,s=i.__assign({},r,{session:n});return t.body&&this.dialog.signalingStateTransition(t.body),this.dialog.reConfirm(),s},t.prototype.progress=function(t){void 0===t&&(t={statusCode:180});var r=e.prototype.progress.call(this,t),n=this.dialog,s=i.__assign({},r,{session:n});return t.body&&this.dialog.signalingStateTransition(t.body),s},t}(r(6).UserAgentServer);t.ReInviteUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=t.createOutgoingRequestMessage(n.C.REFER,i);return e.call(this,s.NonInviteClientTransaction,t.userAgentCore,o,r)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.ReferUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=void 0!==t.userAgentCore?t.userAgentCore:t;return e.call(this,n.NonInviteServerTransaction,s,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.ReferUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(32),o=r(11),a=r(16),c=r(31),u=r(51),d=function(e){function t(t,r,i,n,s,o){var a=e.call(this,n,s)||this;return a.delegate=o,a._autoRefresh=!1,a._subscriptionEvent=t,a._subscriptionExpires=r,a._subscriptionExpiresInitial=r,a._subscriptionExpiresLastSet=Math.floor(Date.now()/1e3),a._subscriptionRefresh=void 0,a._subscriptionRefreshLastSet=void 0,a._subscriptionState=i,a.logger=n.loggerFactory.getLogger("sip.subscribe-dialog"),a.logger.log("SUBSCRIBE dialog "+a.id+" constructed"),a}return i.__extends(t,e),t.initialDialogStateForSubscription=function(e,t){var r=t.getHeaders("record-route"),i=t.parseHeader("contact");if(!i)throw new Error("Contact undefined.");if(!(i instanceof n.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");var s=i.uri,o=e.cseq,a=e.callId,c=e.fromTag,u=t.fromTag;if(!a)throw new Error("Call id undefined.");if(!c)throw new Error("From tag undefined.");if(!u)throw new Error("To tag undefined.");if(!e.from)throw new Error("From undefined.");if(!e.to)throw new Error("To undefined.");return{id:a+c+u,early:!1,callId:a,localTag:c,remoteTag:u,localSequenceNumber:o,remoteSequenceNumber:void 0,localURI:e.from.uri,remoteURI:e.to.uri,remoteTarget:s,routeSet:r,secure:!1}},t.prototype.dispose=function(){e.prototype.dispose.call(this),this.N&&(clearTimeout(this.N),this.N=void 0),this.refreshTimerClear(),this.logger.log("SUBSCRIBE dialog "+this.id+" destroyed")},Object.defineProperty(t.prototype,"autoRefresh",{get:function(){return this._autoRefresh},set:function(e){this._autoRefresh=!0,this.refreshTimerSet()},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionEvent",{get:function(){return this._subscriptionEvent},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionExpires",{get:function(){var e=Math.floor(Date.now()/1e3)-this._subscriptionExpiresLastSet,t=this._subscriptionExpires-e;return Math.max(t,0)},set:function(e){if(e<0)throw new Error("Expires must be greater than or equal to zero.");if(this._subscriptionExpires=e,this._subscriptionExpiresLastSet=Math.floor(Date.now()/1e3),this.autoRefresh){var t=this.subscriptionRefresh;(void 0===t||t>=e)&&this.refreshTimerSet()}},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionExpiresInitial",{get:function(){return this._subscriptionExpiresInitial},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionRefresh",{get:function(){if(void 0!==this._subscriptionRefresh&&void 0!==this._subscriptionRefreshLastSet){var e=Math.floor(Date.now()/1e3)-this._subscriptionRefreshLastSet,t=this._subscriptionRefresh-e;return Math.max(t,0)}},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionState",{get:function(){return this._subscriptionState},enumerable:!0,configurable:!0}),t.prototype.receiveRequest=function(t){if(this.logger.log("SUBSCRIBE dialog "+this.id+" received "+t.method+" request"),this.sequenceGuard(t))switch(e.prototype.receiveRequest.call(this,t),t.method){case n.C.NOTIFY:this.onNotify(t);break;default:this.logger.log("SUBSCRIBE dialog "+this.id+" received unimplemented "+t.method+" request"),this.core.replyStateless(t,{statusCode:501})}else this.logger.log("SUBSCRIBE dialog "+this.id+" rejected out of order "+t.method+" request.")},t.prototype.refresh=function(){var e="Allow: "+a.AllowedMethods.toString(),t={};return t.extraHeaders=(t.extraHeaders||[]).slice(),t.extraHeaders.push(e),t.extraHeaders.push("Event: "+this.subscriptionEvent),t.extraHeaders.push("Expires: "+this.subscriptionExpiresInitial),t.extraHeaders.push("Contact: "+this.core.configuration.contact.toString()),this.subscribe(void 0,t)},t.prototype.subscribe=function(e,t){var r=this;if(void 0===t&&(t={}),this.subscriptionState!==s.SubscriptionState.Pending&&this.subscriptionState!==s.SubscriptionState.Active)throw new Error("Invalid state "+this.subscriptionState+'. May only re-subscribe while in state "pending" or "active".');this.logger.log("SUBSCRIBE dialog "+this.id+" sending SUBSCRIBE request");var i=new u.ReSubscribeUserAgentClient(this,e,t);return this.N=setTimeout(function(){return r.timer_N()},o.Timers.TIMER_N),i},t.prototype.terminate=function(){this.stateTransition(s.SubscriptionState.Terminated),this.onTerminated()},t.prototype.unsubscribe=function(){var e="Allow: "+a.AllowedMethods.toString(),t={};return t.extraHeaders=(t.extraHeaders||[]).slice(),t.extraHeaders.push(e),t.extraHeaders.push("Event: "+this.subscriptionEvent),t.extraHeaders.push("Expires: 0"),t.extraHeaders.push("Contact: "+this.core.configuration.contact.toString()),this.subscribe(void 0,t)},t.prototype.onNotify=function(e){var t=e.parseHeader("Event").event;if(t&&t===this.subscriptionEvent){this.N&&(clearTimeout(this.N),this.N=void 0);var r=e.parseHeader("Subscription-State");if(r&&r.state){var i=r.state,n=r.expires?Math.max(r.expires,0):void 0;switch(i){case"pending":this.stateTransition(s.SubscriptionState.Pending,n);break;case"active":this.stateTransition(s.SubscriptionState.Active,n);break;case"terminated":this.stateTransition(s.SubscriptionState.Terminated,n);break;default:this.logger.warn("Unrecognized subscription state.")}var o=new c.NotifyUserAgentServer(this,e);this.delegate&&this.delegate.onNotify?this.delegate.onNotify(o):o.accept()}else this.core.replyStateless(e,{statusCode:489})}else this.core.replyStateless(e,{statusCode:489})},t.prototype.onRefresh=function(e){this.delegate&&this.delegate.onRefresh&&this.delegate.onRefresh(e)},t.prototype.onTerminated=function(){this.delegate&&this.delegate.onTerminated&&this.delegate.onTerminated()},t.prototype.refreshTimerClear=function(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=void 0)},t.prototype.refreshTimerSet=function(){var e=this;if(this.refreshTimerClear(),this.autoRefresh&&this.subscriptionExpires>0){var t=900*this.subscriptionExpires;this._subscriptionRefresh=Math.floor(t/1e3),this._subscriptionRefreshLastSet=Math.floor(Date.now()/1e3),this.refreshTimer=setTimeout(function(){e.refreshTimer=void 0,e._subscriptionRefresh=void 0,e._subscriptionRefreshLastSet=void 0,e.onRefresh(e.refresh())},t)}},t.prototype.stateTransition=function(e,t){var r=this,i=function(){r.logger.warn("Invalid subscription state transition from "+r.subscriptionState+" to "+e)};switch(e){case s.SubscriptionState.Initial:case s.SubscriptionState.NotifyWait:return void i();case s.SubscriptionState.Pending:if(this.subscriptionState!==s.SubscriptionState.NotifyWait&&this.subscriptionState!==s.SubscriptionState.Pending)return void i();break;case s.SubscriptionState.Active:case s.SubscriptionState.Terminated:if(this.subscriptionState!==s.SubscriptionState.NotifyWait&&this.subscriptionState!==s.SubscriptionState.Pending&&this.subscriptionState!==s.SubscriptionState.Active)return void i();break;default:return void i()}e===s.SubscriptionState.Pending&&t&&(this.subscriptionExpires=t),e===s.SubscriptionState.Active&&t&&(this.subscriptionExpires=t),e===s.SubscriptionState.Terminated&&this.dispose(),this._subscriptionState=e},t.prototype.timer_N=function(){this.subscriptionState!==s.SubscriptionState.Terminated&&(this.stateTransition(s.SubscriptionState.Terminated),this.onTerminated())},t}(r(20).Dialog);t.SubscriptionDialog=d},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=this,a=t.createOutgoingRequestMessage(n.C.SUBSCRIBE,i);return(o=e.call(this,s.NonInviteClientTransaction,t.userAgentCore,a,r)||this).dialog=t,o}return i.__extends(t,e),t.prototype.waitNotifyStop=function(){},t.prototype.receiveResponse=function(t){if(t.statusCode&&t.statusCode>=200&&t.statusCode<300){var r=t.getHeader("Expires");if(r){var i=Number(r);this.dialog.subscriptionExpires>i&&(this.dialog.subscriptionExpires=i)}else this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE")}if(t.statusCode&&t.statusCode>=400&&t.statusCode<700){[404,405,410,416,480,481,482,483,484,485,489,501,604].includes(t.statusCode)&&this.dialog.terminate()}e.prototype.receiveResponse.call(this,t)},t}(r(3).UserAgentClient);t.ReSubscribeUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(33),n=function(){function e(e,t,r){this.logger=e,this.category=t,this.label=r}return e.prototype.error=function(e){this.genericLog(i.Levels.error,e)},e.prototype.warn=function(e){this.genericLog(i.Levels.warn,e)},e.prototype.log=function(e){this.genericLog(i.Levels.log,e)},e.prototype.debug=function(e){this.genericLog(i.Levels.debug,e)},e.prototype.genericLog=function(e,t){this.logger.genericLog(e,this.category,this.label,t)},e}();t.Logger=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(40),t),i.__exportStar(r(41),t),i.__exportStar(r(85),t),i.__exportStar(r(42),t),i.__exportStar(r(86),t),i.__exportStar(r(87),t),i.__exportStar(r(88),t),i.__exportStar(r(89),t),i.__exportStar(r(43),t),i.__exportStar(r(31),t),i.__exportStar(r(90),t),i.__exportStar(r(44),t),i.__exportStar(r(45),t),i.__exportStar(r(46),t),i.__exportStar(r(47),t),i.__exportStar(r(51),t),i.__exportStar(r(91),t),i.__exportStar(r(48),t),i.__exportStar(r(49),t),i.__exportStar(r(92),t),i.__exportStar(r(93),t),i.__exportStar(r(94),t),i.__exportStar(r(3),t),i.__exportStar(r(6),t)},function(e){e.exports={name:"sip.js",title:"SIP.js",description:"A simple, intuitive, and powerful JavaScript signaling library",version:"0.14.2",license:"MIT",main:"./lib/index.js",types:"./lib/index.d.ts",homepage:"https://sipjs.com",author:"OnSIP (https://sipjs.com/aboutus/)",contributors:[{url:"https://github.com/onsip/SIP.js/blob/master/THANKS.md"}],repository:{type:"git",url:"https://github.com/onsip/SIP.js.git"},keywords:["sip","webrtc","library","websocket","javascript","typescript"],dependencies:{"crypto-js":"^3.1.9-1"},devDependencies:{"@types/crypto-js":"^3.1.43","@types/jasmine":"^3.3.13","@types/node":"^12.0.4","circular-dependency-plugin":"^5.0.2","jasmine-core":"^3.4.0",karma:"^4.1.0","karma-chrome-launcher":"^2.2.0","karma-cli":"^2.0.0","karma-jasmine":"^2.0.1","karma-jasmine-html-reporter":"^1.4.2","karma-mocha-reporter":"^2.2.5","karma-sourcemap-loader":"^0.3.7","karma-webpack":"^3.0.5",pegjs:"^0.10.0","ts-loader":"^6.0.2","ts-pegjs":"0.2.5",tslint:"^5.17.0",typescript:"^3.5.1",webpack:"^4.32.2","webpack-cli":"^3.3.2"},engines:{node:">=8.0"},scripts:{prebuild:"tslint -p tsconfig-base.json -c tslint.json","generate-grammar":"node build/grammarGenerator.js","build-reg-bundle":"webpack --progress --config build/webpack.config.js --env.buildType reg","build-min-bundle":"webpack --progress --config build/webpack.config.js --env.buildType min","build-bundles":"npm run build-reg-bundle && npm run build-min-bundle","build-lib":"tsc -p src","build-test":"tsc -p test","copy-dist-files":"cp dist/sip.js dist/sip-$npm_package_version.js && cp dist/sip.min.js dist/sip-$npm_package_version.min.js",build:"npm run generate-grammar && npm run build-lib && npm run build-reg-bundle && npm run build-min-bundle && npm run copy-dist-files",browserTest:"npm run build-test && sleep 2 && open http://0.0.0.0:9876/debug.html & karma start --reporters kjhtml --no-single-run",commandLineTest:"npm run build-test && karma start --reporters mocha --browsers ChromeHeadless --single-run",buildAndTest:"npm run build && npm run commandLineTest",buildAndBrowserTest:"npm run build && npm run browserTest"}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(5);!function(e){function t(e,t){var r=t,i=0,n=0;if(e.substring(r,r+2).match(/(^\r\n)/))return-2;for(;0===i;){if(-1===(n=e.indexOf("\r\n",r)))return n;!e.substring(n+2,n+4).match(/(^\r\n)/)&&e.charAt(n+2).match(/(^\s+)/)?r=n+2:i=n}return i}function r(e,t,r,n){var s,o=t.indexOf(":",r),a=t.substring(r,o).trim(),c=t.substring(o+1,n).trim();switch(a.toLowerCase()){case"via":case"v":e.addHeader("via",c),1===e.getHeaders("via").length?(s=e.parseHeader("Via"))&&(e.via=s,e.viaBranch=s.branch):s=0;break;case"from":case"f":e.setHeader("from",c),(s=e.parseHeader("from"))&&(e.from=s,e.fromTag=s.getParam("tag"));break;case"to":case"t":e.setHeader("to",c),(s=e.parseHeader("to"))&&(e.to=s,e.toTag=s.getParam("tag"));break;case"record-route":if(-1===(s=i.Grammar.parse(c,"Record_Route"))){s=void 0;break}for(var u in s)s[u]&&(e.addHeader("record-route",c.substring(s[u].position,s[u].offset)),e.headers["Record-Route"][e.getHeaders("record-route").length-1].parsed=s[u].parsed);break;case"call-id":case"i":e.setHeader("call-id",c),(s=e.parseHeader("call-id"))&&(e.callId=c);break;case"contact":case"m":if(-1===(s=i.Grammar.parse(c,"Contact"))){s=void 0;break}if(!(s instanceof Array)){s=void 0;break}s.forEach(function(t){e.addHeader("contact",c.substring(t.position,t.offset)),e.headers.Contact[e.getHeaders("contact").length-1].parsed=t.parsed});break;case"content-length":case"l":e.setHeader("content-length",c),s=e.parseHeader("content-length");break;case"content-type":case"c":e.setHeader("content-type",c),s=e.parseHeader("content-type");break;case"cseq":e.setHeader("cseq",c),(s=e.parseHeader("cseq"))&&(e.cseq=s.value),e instanceof i.IncomingResponseMessage&&(e.method=s.method);break;case"max-forwards":e.setHeader("max-forwards",c),s=e.parseHeader("max-forwards");break;case"www-authenticate":e.setHeader("www-authenticate",c),s=e.parseHeader("www-authenticate");break;case"proxy-authenticate":e.setHeader("proxy-authenticate",c),s=e.parseHeader("proxy-authenticate");break;case"refer-to":case"r":e.setHeader("refer-to",c),(s=e.parseHeader("refer-to"))&&(e.referTo=s);break;default:e.setHeader(a,c),s=0}return void 0!==s||{error:"error parsing header '"+a+"'"}}e.getHeader=t,e.parseHeader=r,e.parseMessage=function(e,n){var s=0,o=e.indexOf("\r\n");if(-1!==o){var a,c=e.substring(0,o),u=i.Grammar.parse(c,"Request_Response");if(-1!==u){var d;for(u.status_code?((a=new i.IncomingResponseMessage).statusCode=u.status_code,a.reasonPhrase=u.reason_phrase):((a=new i.IncomingRequestMessage).method=u.method,a.ruri=u.uri),a.data=e,s=o+2;;){if(-2===(o=t(e,s))){d=s+2;break}if(-1===o)return void n.error("malformed message");if(!0!==r(a,e,s,o))return void n.error(u.error);s=o+2}return a.hasHeader("content-length")?a.body=e.substr(d,Number(a.getHeader("content-length"))):a.body=e.substring(d),a}n.warn('error parsing first line of SIP message: "'+c+'"')}else n.warn("no CRLF found, not a SIP message, discarded")}}(t.Parser||(t.Parser={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(12),s=r(7),o=r(5),a=r(4),c=r(10),u=r(8),d=function(e){function t(t,r,i,n){void 0===n&&(n={});var o=this;if(n.extraHeaders=(n.extraHeaders||[]).slice(),n.contentType=n.contentType||"text/plain","number"!=typeof n.expires||n.expires%1!=0?n.expires=3600:n.expires=Number(n.expires),"boolean"!=typeof n.unpublishOnClose&&(n.unpublishOnClose=!0),null==r||""===r)throw new c.Exceptions.MethodParameterError("Publish","Target",r);if(void 0===(r=t.normalizeTarget(r)))throw new c.Exceptions.MethodParameterError("Publish","Target",r);if((o=e.call(this,t,s.C.PUBLISH,r,n)||this).type=a.TypeStrings.PublishContext,o.options=n,o.target=r,null==i||""===i)throw new c.Exceptions.MethodParameterError("Publish","Event",i);return o.event=i,o.logger=t.getLogger("sip.publish"),o.pubRequestExpires=o.options.expires,t.on("transportCreated",function(e){e.on("transportError",function(){return o.onTransportError()})}),o}return i.__extends(t,e),t.prototype.publish=function(e){this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.options.body=e,this.pubRequestBody=this.options.body,0===this.pubRequestExpires&&(this.pubRequestExpires=this.options.expires,this.pubRequestEtag=void 0),this.ua.publishers[this.target.toString()+":"+this.event]||(this.ua.publishers[this.target.toString()+":"+this.event]=this),this.sendPublishRequest()},t.prototype.unpublish=function(){this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,this.pubRequestExpires=0,void 0!==this.pubRequestEtag&&this.sendPublishRequest()},t.prototype.close=function(){this.options.unpublishOnClose?this.unpublish():(this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,this.pubRequestExpires=0,this.pubRequestEtag=void 0),this.ua.publishers[this.target.toString()+":"+this.event]&&delete this.ua.publishers[this.target.toString()+":"+this.event]},t.prototype.onRequestTimeout=function(){e.prototype.onRequestTimeout.call(this),this.emit("unpublished",void 0,s.C.causes.REQUEST_TIMEOUT)},t.prototype.onTransportError=function(){e.prototype.onTransportError.call(this),this.emit("unpublished",void 0,s.C.causes.CONNECTION_ERROR)},t.prototype.receiveResponse=function(e){var t=this,r=e.statusCode||0,i=u.Utils.getReasonPhrase(r);switch(!0){case/^1[0-9]{2}$/.test(r.toString()):this.emit("progress",e,i);break;case/^2[0-9]{2}$/.test(r.toString()):if(e.hasHeader("SIP-ETag")?this.pubRequestEtag=e.getHeader("SIP-ETag"):this.logger.warn("SIP-ETag header missing in a 200-class response to PUBLISH"),e.hasHeader("Expires")){var n=Number(e.getHeader("Expires"));"number"==typeof n&&n>=0&&n<=this.pubRequestExpires?this.pubRequestExpires=n:this.logger.warn("Bad Expires header in a 200-class response to PUBLISH")}else this.logger.warn("Expires header missing in a 200-class response to PUBLISH");0!==this.pubRequestExpires?(this.publishRefreshTimer=setTimeout(function(){return t.refreshRequest()},900*this.pubRequestExpires),this.emit("published",e,i)):this.emit("unpublished",e,i);break;case/^412$/.test(r.toString()):void 0!==this.pubRequestEtag&&0!==this.pubRequestExpires?(this.logger.warn("412 response to PUBLISH, recovering"),this.pubRequestEtag=void 0,this.emit("progress",e,i),this.publish(this.options.body)):(this.logger.warn("412 response to PUBLISH, recovery failed"),this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i));break;case/^423$/.test(r.toString()):if(0!==this.pubRequestExpires&&e.hasHeader("Min-Expires")){var s=Number(e.getHeader("Min-Expires"));"number"==typeof s||s>this.pubRequestExpires?(this.logger.warn("423 code in response to PUBLISH, adjusting the Expires value and trying to recover"),this.pubRequestExpires=s,this.emit("progress",e,i),this.publish(this.options.body)):(this.logger.warn("Bad 423 response Min-Expires header received for PUBLISH"),this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i))}else this.logger.warn("423 response to PUBLISH, recovery failed"),this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i);break;default:this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i)}0===this.pubRequestExpires&&(this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,this.pubRequestEtag=void 0)},t.prototype.send=function(){var e=this;return this.ua.userAgentCore.publish(this.request,{onAccept:function(t){return e.receiveResponse(t.message)},onProgress:function(t){return e.receiveResponse(t.message)},onRedirect:function(t){return e.receiveResponse(t.message)},onReject:function(t){return e.receiveResponse(t.message)},onTrying:function(t){return e.receiveResponse(t.message)}}),this},t.prototype.refreshRequest=function(){if(this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,void 0===this.pubRequestEtag)throw new c.Exceptions.MethodParameterError("Publish","Body",void 0);if(0===this.pubRequestExpires)throw new c.Exceptions.MethodParameterError("Publish","Expire",this.pubRequestExpires);this.sendPublishRequest()},t.prototype.sendPublishRequest=function(){var e=Object.create(this.options||Object.prototype);e.extraHeaders=(this.options.extraHeaders||[]).slice(),e.extraHeaders.push("Event: "+this.event),e.extraHeaders.push("Expires: "+this.pubRequestExpires),void 0!==this.pubRequestEtag&&e.extraHeaders.push("SIP-If-Match: "+this.pubRequestEtag);var t=this.target instanceof o.URI?this.target:this.ua.normalizeTarget(this.target);if(!t)throw new Error("ruri undefined.");var r,i,n=this.options.params||{};void 0!==this.pubRequestBody&&(r={body:this.pubRequestBody,contentType:this.options.contentType}),r&&(i=u.Utils.fromBodyObj(r)),this.request=this.ua.userAgentCore.makeOutgoingRequestMessage(s.C.PUBLISH,t,n.fromUri?n.fromUri:this.ua.userAgentCore.configuration.aor,n.toUri?n.toUri:this.target,n,e.extraHeaders,i),this.send()},t}(n.ClientContext);t.PublishContext=d},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(12),s=r(7),o=r(5),a=r(4),c=r(10),u=r(8);function d(e){var t={expires:600,extraContactHeaderParams:[],instanceId:void 0,params:{},regId:void 0,registrar:void 0},r={mandatory:{},optional:{expires:function(e){if(u.Utils.isDecimal(e)){var t=Number(e);if(t>=0)return t}},extraContactHeaderParams:function(e){if(e instanceof Array)return e.filter(function(e){return"string"==typeof e})},instanceId:function(e){if("string"==typeof e)return/^uuid:/i.test(e)&&(e=e.substr(5)),-1===o.Grammar.parse(e,"uuid")?void 0:e},params:function(e){if("object"==typeof e)return e},regId:function(e){if(u.Utils.isDecimal(e)){var t=Number(e);if(t>=0)return t}},registrar:function(e){if("string"==typeof e){/^sip:/i.test(e)||(e=s.C.SIP+":"+e);var t=o.Grammar.URIParse(e);return t?t.user?void 0:t:void 0}}}};for(var i in r.mandatory){if(!e.hasOwnProperty(i))throw new c.Exceptions.ConfigurationError(i);var n=e[i];if(void 0===(a=r.mandatory[i](n)))throw new c.Exceptions.ConfigurationError(i,n);t[i]=a}for(var i in r.optional)if(e.hasOwnProperty(i)){var a;if((n=e[i])instanceof Array&&0===n.length)continue;if(null===n||""===n||void 0===n||"number"==typeof n&&isNaN(n))continue;if(void 0===(a=r.optional[i](n)))throw new c.Exceptions.ConfigurationError(i,n);t[i]=a}return t}var p=function(e){function t(t,r){void 0===r&&(r={});var i=this,n=d(r);if(n.regId&&!n.instanceId?n.instanceId=u.Utils.newUUID():!n.regId&&n.instanceId&&(n.regId=1),n.params.toUri=n.params.toUri||t.configuration.uri,n.params.toDisplayName=n.params.toDisplayName||t.configuration.displayName,n.params.callId=n.params.callId||u.Utils.createRandomToken(22),n.params.cseq=n.params.cseq||Math.floor(1e4*Math.random()),!n.registrar){var o={};"object"==typeof t.configuration.uri?(o=t.configuration.uri.clone()).user=void 0:o=t.configuration.uri,n.registrar=o}for(var c in(i=e.call(this,t,s.C.REGISTER,n.registrar,n)||this).type=a.TypeStrings.RegisterContext,i.options=n,i.logger=t.getLogger("sip.registercontext"),i.logger.log("configuration parameters for RegisterContext after validation:"),n)n.hasOwnProperty(c)&&i.logger.log("\xb7 "+c+": "+JSON.stringify(n[c]));return i.expires=n.expires,i.contact=t.contact.toString(),i.registered=!1,t.on("transportCreated",function(e){e.on("disconnected",function(){return i.onTransportDisconnected()})}),i}return i.__extends(t,e),t.prototype.register=function(e){var t=this;void 0===e&&(e={}),this.options=i.__assign({},this.options,e);var r=(this.options.extraHeaders||[]).slice();r.push("Contact: "+this.generateContactHeader(this.expires)),r.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),this.closeHeaders=this.options.closeWithHeaders?(this.options.extraHeaders||[]).slice():[],this.receiveResponse=function(e){if(e.cseq===t.request.cseq){void 0!==t.registrationTimer&&(clearTimeout(t.registrationTimer),t.registrationTimer=void 0);var r=(e.statusCode||0).toString();switch(!0){case/^1[0-9]{2}$/.test(r):t.emit("progress",e);break;case/^2[0-9]{2}$/.test(r):t.emit("accepted",e);var i=void 0;e.hasHeader("expires")&&(i=Number(e.getHeader("expires"))),void 0!==t.registrationExpiredTimer&&(clearTimeout(t.registrationExpiredTimer),t.registrationExpiredTimer=void 0);var n=e.getHeaders("contact").length;if(!n){t.logger.warn("no Contact header in response to REGISTER, response ignored");break}for(var a=void 0;n--;){if((a=e.parseHeader("contact",n)).uri.user===t.ua.contact.uri.user){i=a.getParam("expires");break}a=void 0}if(!a){t.logger.warn("no Contact header pointing to us, response ignored");break}void 0===i&&(i=t.expires),t.registrationTimer=setTimeout(function(){t.registrationTimer=void 0,t.register(t.options)},1e3*i-3e3),t.registrationExpiredTimer=setTimeout(function(){t.logger.warn("registration expired"),t.registered&&t.unregistered(void 0,s.C.causes.EXPIRES)},1e3*i),a.hasParam("temp-gruu")&&(t.ua.contact.tempGruu=o.Grammar.URIParse(a.getParam("temp-gruu").replace(/"/g,""))),a.hasParam("pub-gruu")&&(t.ua.contact.pubGruu=o.Grammar.URIParse(a.getParam("pub-gruu").replace(/"/g,""))),t.registered=!0,t.emit("registered",e||void 0);break;case/^423$/.test(r):e.hasHeader("min-expires")?(t.expires=Number(e.getHeader("min-expires")),t.register(t.options)):(t.logger.warn("423 response received for REGISTER without Min-Expires"),t.registrationFailure(e,s.C.causes.SIP_FAILURE_CODE));break;default:t.registrationFailure(e,u.Utils.sipErrorCause(e.statusCode||0))}}},this.onRequestTimeout=function(){t.registrationFailure(void 0,s.C.causes.REQUEST_TIMEOUT)},this.onTransportError=function(){t.registrationFailure(void 0,s.C.causes.CONNECTION_ERROR)},this.request.cseq++,this.request.setHeader("cseq",this.request.cseq+" REGISTER"),this.request.extraHeaders=r,this.send()},t.prototype.close=function(){var e={all:!1,extraHeaders:this.closeHeaders};this.registeredBefore=this.registered,this.registered&&this.unregister(e)},t.prototype.unregister=function(e){var t=this;void 0===e&&(e={}),this.registered||e.all||this.logger.warn("Already unregistered, but sending an unregister anyways.");var r=(e.extraHeaders||[]).slice();this.registered=!1,void 0!==this.registrationTimer&&(clearTimeout(this.registrationTimer),this.registrationTimer=void 0),e.all?(r.push("Contact: *"),r.push("Expires: 0")):r.push("Contact: "+this.generateContactHeader(0)),this.receiveResponse=function(e){var r=e&&e.statusCode?e.statusCode.toString():"";switch(!0){case/^1[0-9]{2}$/.test(r):t.emit("progress",e);break;case/^2[0-9]{2}$/.test(r):t.emit("accepted",e),void 0!==t.registrationExpiredTimer&&(clearTimeout(t.registrationExpiredTimer),t.registrationExpiredTimer=void 0),t.unregistered(e);break;default:t.unregistered(e,u.Utils.sipErrorCause(e.statusCode||0))}},this.onRequestTimeout=function(){},this.request.cseq++,this.request.setHeader("cseq",this.request.cseq+" REGISTER"),this.request.extraHeaders=r,this.send()},t.prototype.unregistered=function(e,t){this.registered=!1,this.emit("unregistered",e||void 0,t||void 0)},t.prototype.send=function(){var e=this;return this.ua.userAgentCore.register(this.request,{onAccept:function(t){return e.receiveResponse(t.message)},onProgress:function(t){return e.receiveResponse(t.message)},onRedirect:function(t){return e.receiveResponse(t.message)},onReject:function(t){return e.receiveResponse(t.message)},onTrying:function(t){return e.receiveResponse(t.message)}}),this},t.prototype.registrationFailure=function(e,t){this.emit("failed",e||void 0,t||void 0)},t.prototype.onTransportDisconnected=function(){this.registeredBefore=this.registered,void 0!==this.registrationTimer&&(clearTimeout(this.registrationTimer),this.registrationTimer=void 0),void 0!==this.registrationExpiredTimer&&(clearTimeout(this.registrationExpiredTimer),this.registrationExpiredTimer=void 0),this.registered&&this.unregistered(void 0,s.C.causes.CONNECTION_ERROR)},t.prototype.generateContactHeader=function(e){void 0===e&&(e=0);var t=this.contact;return this.options.regId&&this.options.instanceId&&(t+=";reg-id="+this.options.regId,t+=';+sip.instance=""'),this.options.extraContactHeaderParams&&this.options.extraContactHeaderParams.forEach(function(e){t+=";"+e}),t+=";expires="+e},t}(n.ClientContext);t.RegisterContext=p},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(12),o=r(7),a=r(5),c=r(4),u=r(10),d=r(34),p=r(17),h=r(96),l=r(8),g=function(e){function t(r){var i=e.call(this)||this;if(i.data={},i.type=c.TypeStrings.Session,!r)throw new u.Exceptions.SessionDescriptionHandlerError("A session description handler is required for the session to function");return i.status=t.C.STATUS_NULL,i.pendingReinvite=!1,i.sessionDescriptionHandlerFactory=r,i.hasOffer=!1,i.hasAnswer=!1,i.timers={ackTimer:void 0,expiresTimer:void 0,invite2xxTimer:void 0,userNoAnswerTimer:void 0,rel1xxTimer:void 0,prackTimer:void 0},i.startTime=void 0,i.endTime=void 0,i.tones=void 0,i.localHold=!1,i.earlySdp=void 0,i.rel100=o.C.supported.UNSUPPORTED,i}return i.__extends(t,e),t.prototype.dtmf=function(e,t){var r=this;if(void 0===t&&(t={}),this.status!==c.SessionStatus.STATUS_CONFIRMED&&this.status!==c.SessionStatus.STATUS_WAITING_FOR_ACK)throw new u.Exceptions.InvalidStateError(this.status);if(!e||!e.toString().match(/^[0-9A-D#*,]+$/i))throw new TypeError("Invalid tones: "+e);var i=function(){if(r.status!==c.SessionStatus.STATUS_TERMINATED&&r.tones&&0!==r.tones.length){var e,n=r.tones.shift();","===n.tone?e=2e3:(n.on("failed",function(){r.tones=void 0}),n.send(t),e=n.duration+n.interToneGap),setTimeout(i,e)}else r.tones=void 0};e=e.toString();var n=this.ua.configuration.dtmfType;this.sessionDescriptionHandler&&n===o.C.dtmfType.RTP&&(this.sessionDescriptionHandler.sendDtmf(e,t)||(this.logger.warn("Attempt to use dtmfType 'RTP' has failed, falling back to INFO packet method"),n=o.C.dtmfType.INFO));if(n===o.C.dtmfType.INFO){for(var s=[],a=e.split("");a.length>0;)s.push(new h.DTMF(this,a.shift(),t));if(this.tones)return this.tones=this.tones.concat(s),this;this.tones=s,i()}return this},t.prototype.bye=function(e){if(void 0===e&&(e={}),this.status===c.SessionStatus.STATUS_TERMINATED)return this.logger.error("Error: Attempted to send BYE in a terminated session."),this;this.logger.log("terminating Session");var t=e.statusCode;if(t&&(t<200||t>=700))throw new TypeError("Invalid statusCode: "+t);return e.receiveResponse=function(){},this.sendRequest(o.C.BYE,e).terminated()},t.prototype.refer=function(e,t){if(void 0===t&&(t={}),this.status!==c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);return this.referContext=new d.ReferClientContext(this.ua,this,e,t),this.emit("referRequested",this.referContext),this.referContext.refer(t),this.referContext},t.prototype.sendRequest=function(e,t){if(void 0===t&&(t={}),!this.session)throw new Error("Session undefined.");var r;t.body&&(t.body=l.Utils.fromBodyObj(t.body));var i,n=t.receiveResponse;n&&(r={onAccept:function(e){return n(e.message)},onProgress:function(e){return n(e.message)},onRedirect:function(e){return n(e.message)},onReject:function(e){return n(e.message)},onTrying:function(e){return n(e.message)}});var s=t;switch(e){case o.C.BYE:i=this.session.bye(r,s);break;case o.C.INVITE:i=this.session.invite(r,s);break;case o.C.REFER:i=this.session.refer(r,s);break;default:throw new Error("Unexpected "+e+". Method not implemented by user agent core.")}return this.emit(e.toLowerCase(),i.message),this},t.prototype.close=function(){if(this.status===c.SessionStatus.STATUS_TERMINATED)return this;for(var e in this.logger.log("closing INVITE session "+this.id),this.sessionDescriptionHandler&&this.sessionDescriptionHandler.close(),this.timers)this.timers[e]&&clearTimeout(this.timers[e]);return this.status=c.SessionStatus.STATUS_TERMINATED,this.ua.transport&&this.ua.transport.removeListener("transportError",this.errorListener),delete this.ua.sessions[this.id],this},t.prototype.hold=function(e,t){if(void 0===e&&(e={}),void 0===t&&(t=[]),this.status!==c.SessionStatus.STATUS_WAITING_FOR_ACK&&this.status!==c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);this.localHold?this.logger.log("Session is already on hold, cannot put it on hold again"):(e.modifiers=t,this.sessionDescriptionHandler&&e.modifiers.push(this.sessionDescriptionHandler.holdModifier),this.localHold=!0,this.sendReinvite(e))},t.prototype.unhold=function(e,t){if(void 0===e&&(e={}),void 0===t&&(t=[]),this.status!==c.SessionStatus.STATUS_WAITING_FOR_ACK&&this.status!==c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);this.localHold?(e.modifiers=t,this.localHold=!1,this.sendReinvite(e)):this.logger.log("Session is not on hold, cannot unhold it")},t.prototype.reinvite=function(e,t){return void 0===e&&(e={}),void 0===t&&(t=[]),e.modifiers=t,this.sendReinvite(e)},t.prototype.terminate=function(e){return this},t.prototype.onTransportError=function(){this.status!==c.SessionStatus.STATUS_CONFIRMED&&this.status!==c.SessionStatus.STATUS_TERMINATED&&this.failed(void 0,o.C.causes.CONNECTION_ERROR)},t.prototype.onRequestTimeout=function(){this.status===c.SessionStatus.STATUS_CONFIRMED?this.terminated(void 0,o.C.causes.REQUEST_TIMEOUT):this.status!==c.SessionStatus.STATUS_TERMINATED&&(this.failed(void 0,o.C.causes.REQUEST_TIMEOUT),this.terminated(void 0,o.C.causes.REQUEST_TIMEOUT))},t.prototype.onDialogError=function(e){this.status===c.SessionStatus.STATUS_CONFIRMED?this.terminated(e,o.C.causes.DIALOG_ERROR):this.status!==c.SessionStatus.STATUS_TERMINATED&&(this.failed(e,o.C.causes.DIALOG_ERROR),this.terminated(e,o.C.causes.DIALOG_ERROR))},t.prototype.on=function(t,r){return e.prototype.on.call(this,t,r)},t.prototype.onAck=function(e){var t=this,r=function(){clearTimeout(t.timers.ackTimer),clearTimeout(t.timers.invite2xxTimer),t.status=c.SessionStatus.STATUS_CONFIRMED;var r=e.message.getHeader("Content-Disposition");r&&"render"===r.type&&(t.renderbody=e.message.body,t.rendertype=e.message.getHeader("Content-Type")),t.emit("confirmed",e.message)};this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK&&(this.sessionDescriptionHandler&&this.sessionDescriptionHandler.hasDescription(e.message.getHeader("Content-Type")||"")?(this.hasAnswer=!0,this.sessionDescriptionHandler.setDescription(e.message.body,this.sessionDescriptionHandlerOptions,this.modifiers).catch(function(r){throw t.logger.warn(r),t.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),t.failed(e.message,o.C.causes.BAD_MEDIA_DESCRIPTION),t.terminated(e.message,o.C.causes.BAD_MEDIA_DESCRIPTION),r}).then(function(){return r()})):r())},t.prototype.receiveRequest=function(e){switch(e.message.method){case o.C.BYE:e.accept(),this.status===c.SessionStatus.STATUS_CONFIRMED&&(this.emit("bye",e.message),this.terminated(e.message,o.C.BYE));break;case o.C.INVITE:this.status===c.SessionStatus.STATUS_CONFIRMED&&(this.logger.log("re-INVITE received"),this.receiveReinvite(e));break;case o.C.INFO:if(this.status===c.SessionStatus.STATUS_CONFIRMED||this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK){if(this.onInfo)return this.onInfo(e.message);var t=e.message.getHeader("content-type");if(t)if(t.match(/^application\/dtmf-relay/i)){if(e.message.body){var r=e.message.body.split("\r\n",2);if(2===r.length){var i=void 0,n=void 0,s=/^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/;s.test(r[0])&&(i=r[0].replace(s,"$2"));var a=/^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/;a.test(r[1])&&(n=parseInt(r[1].replace(a,"$2"),10)),i&&n&&new h.DTMF(this,i,{duration:n}).init_incoming(e)}}}else e.reject({statusCode:415,extraHeaders:["Accept: application/dtmf-relay"]})}break;case o.C.REFER:if(this.status===c.SessionStatus.STATUS_CONFIRMED)if(this.logger.log("REFER received"),this.referContext=new d.ReferServerContext(this.ua,e,this.session),this.listeners("referRequested").length)this.emit("referRequested",this.referContext);else{this.logger.log("No referRequested listeners, automatically accepting and following the refer");var u={followRefer:!0};this.passedOptions&&(u.inviteOptions=this.passedOptions),this.referContext.accept(u,this.modifiers)}break;case o.C.NOTIFY:if(this.referContext&&this.referContext.type===c.TypeStrings.ReferClientContext&&e.message.hasHeader("event")&&/^refer(;.*)?$/.test(e.message.getHeader("event")))return void this.referContext.receiveNotify(e);e.accept(),this.emit("notify",e.message)}},t.prototype.receiveReinvite=function(e){var t,r=this;if(this.emit("reinvite",this,e.message),e.message.hasHeader("P-Asserted-Identity")&&(this.assertedIdentity=a.Grammar.nameAddrHeaderParse(e.message.getHeader("P-Asserted-Identity"))),this.sessionDescriptionHandler){if("0"!==e.message.getHeader("Content-Length")||e.message.getHeader("Content-Type")){if(!this.sessionDescriptionHandler.hasDescription(e.message.getHeader("Content-Type")||""))return e.reject({statusCode:415}),void this.emit("reinviteFailed",this);t=this.sessionDescriptionHandler.setDescription(e.message.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(this.sessionDescriptionHandler.getDescription.bind(this.sessionDescriptionHandler,this.sessionDescriptionHandlerOptions,this.modifiers))}else t=this.sessionDescriptionHandler.getDescription(this.sessionDescriptionHandlerOptions,this.modifiers);t.catch(function(t){var i;throw t.type===c.TypeStrings.SessionDescriptionHandlerError?i=500:t.type===c.TypeStrings.RenegotiationError?(r.emit("renegotiationError",t),r.logger.warn(t.toString()),i=488):(r.logger.error(t),i=488),e.reject({statusCode:i}),r.emit("reinviteFailed",r),t}).then(function(t){var i=["Contact: "+r.contact];e.accept({statusCode:200,extraHeaders:i,body:l.Utils.fromBodyObj(t)}),r.status=c.SessionStatus.STATUS_WAITING_FOR_ACK,r.emit("reinviteAccepted",r)})}else this.logger.warn("No SessionDescriptionHandler to reinvite")},t.prototype.sendReinvite=function(e){var t=this;if(void 0===e&&(e={}),this.pendingReinvite)this.logger.warn("Reinvite in progress. Please wait until complete, then try again.");else if(this.sessionDescriptionHandler){this.pendingReinvite=!0,e.modifiers=e.modifiers||[];var r=(e.extraHeaders||[]).slice();r.push("Contact: "+this.contact),r.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),this.sessionDescriptionHandler.getDescription(e.sessionDescriptionHandlerOptions,e.modifiers).then(function(e){if(!t.session)throw new Error("Session undefined.");var i={onAccept:function(e){if(t.status!==c.SessionStatus.STATUS_TERMINATED)if(t.pendingReinvite){if(t.status=c.SessionStatus.STATUS_CONFIRMED,t.emit("ack",e.ack()),t.pendingReinvite=!1,clearTimeout(t.timers.invite2xxTimer),!t.sessionDescriptionHandler||!t.sessionDescriptionHandler.hasDescription(e.message.getHeader("Content-Type")||""))return t.logger.error("2XX response received to re-invite but did not have a description"),t.emit("reinviteFailed",t),void t.emit("renegotiationError",new u.Exceptions.RenegotiationError("2XX response received to re-invite but did not have a description"));t.sessionDescriptionHandler.setDescription(e.message.body,t.sessionDescriptionHandlerOptions,t.modifiers).catch(function(e){throw t.logger.error("Could not set the description in 2XX response"),t.logger.error(e),t.emit("reinviteFailed",t),t.emit("renegotiationError",e),t.sendRequest(o.C.BYE,{extraHeaders:["Reason: "+l.Utils.getReasonHeaderValue(488,"Not Acceptable Here")]}),t.terminated(void 0,o.C.causes.INCOMPATIBLE_SDP),e}).then(function(){t.emit("reinviteAccepted",t)})}else t.logger.error("Received reinvite response, but have no pending reinvite");else t.logger.error("Received reinvite response, but in STATUS_TERMINATED")},onProgress:function(e){},onRedirect:function(e){t.pendingReinvite=!1,t.logger.log("Received a non 1XX or 2XX response to a re-invite"),t.emit("reinviteFailed",t),t.emit("renegotiationError",new u.Exceptions.RenegotiationError("Invalid response to a re-invite"))},onReject:function(e){t.pendingReinvite=!1,t.logger.log("Received a non 1XX or 2XX response to a re-invite"),t.emit("reinviteFailed",t),t.emit("renegotiationError",new u.Exceptions.RenegotiationError("Invalid response to a re-invite"))},onTrying:function(e){}},n={extraHeaders:r,body:l.Utils.fromBodyObj(e)};t.session.invite(i,n)}).catch(function(e){if(e.type===c.TypeStrings.RenegotiationError)throw t.pendingReinvite=!1,t.emit("renegotiationError",e),t.logger.warn("Renegotiation Error"),t.logger.warn(e.toString()),e;throw t.logger.error("sessionDescriptionHandler error"),t.logger.error(e),e})}else this.logger.warn("No SessionDescriptionHandler, can't reinvite..")},t.prototype.failed=function(e,t){return this.status===c.SessionStatus.STATUS_TERMINATED?this:(this.emit("failed",e,t),this)},t.prototype.rejected=function(e,t){return this.emit("rejected",e,t),this},t.prototype.canceled=function(){return this.sessionDescriptionHandler&&this.sessionDescriptionHandler.close(),this.emit("cancel"),this},t.prototype.accepted=function(e,t){return e instanceof String||(t=l.Utils.getReasonPhrase(e&&e.statusCode||0,t)),this.startTime=new Date,this.replacee&&(this.replacee.emit("replaced",this),this.replacee.terminate()),this.emit("accepted",e,t),this},t.prototype.terminated=function(e,t){return this.status===c.SessionStatus.STATUS_TERMINATED?this:(this.endTime=new Date,this.close(),this.emit("terminated",e,t),this)},t.prototype.connecting=function(e){return this.emit("connecting",{request:e}),this},t.C=c.SessionStatus,t}(n.EventEmitter);t.Session=g;var f=function(e){function t(t,r){var i=this;if(!t.configuration.sessionDescriptionHandlerFactory)throw t.logger.warn("Can't build ISC without SDH Factory"),new Error("ISC Constructor Failed");(i=e.call(this,t.configuration.sessionDescriptionHandlerFactory)||this)._canceled=!1,i.rseq=Math.floor(1e4*Math.random()),i.incomingRequest=r;var n=r.message;p.ServerContext.initializer(i,t,r),i.type=c.TypeStrings.InviteServerContext;var s=n.parseHeader("Content-Disposition");s&&"render"===s.type&&(i.renderbody=n.body,i.rendertype=n.getHeader("Content-Type")),i.status=c.SessionStatus.STATUS_INVITE_RECEIVED,i.fromTag=n.fromTag,i.id=n.callId+i.fromTag,i.request=n,i.contact=i.ua.contact.toString(),i.logger=t.getLogger("sip.inviteservercontext",i.id),i.ua.sessions[i.id]=i;var a=function(e,t){n.hasHeader(e)&&n.getHeader(e).toLowerCase().indexOf("100rel")>=0&&(i.rel100=t)};if(a("require",o.C.supported.REQUIRED),a("supported",o.C.supported.SUPPORTED),i.request.toTag=r.toTag,i.status=c.SessionStatus.STATUS_WAITING_FOR_ANSWER,i.timers.userNoAnswerTimer=setTimeout(function(){r.reject({statusCode:408}),i.failed(n,o.C.causes.NO_ANSWER),i.terminated(n,o.C.causes.NO_ANSWER)},i.ua.configuration.noAnswerTimeout||60),n.hasHeader("expires")){var u=1e3*Number(n.getHeader("expires")||0);i.timers.expiresTimer=setTimeout(function(){i.status===c.SessionStatus.STATUS_WAITING_FOR_ANSWER&&(r.reject({statusCode:487}),i.failed(n,o.C.causes.EXPIRES),i.terminated(n,o.C.causes.EXPIRES))},u)}return i.errorListener=i.onTransportError.bind(i),t.transport&&t.transport.on("transportError",i.errorListener),i}return i.__extends(t,e),Object.defineProperty(t.prototype,"autoSendAnInitialProvisionalResponse",{get:function(){return this.rel100!==o.C.supported.REQUIRED},enumerable:!0,configurable:!0}),t.prototype.reply=function(e){return void 0===e&&(e={}),this},t.prototype.reject=function(e){var t=this;if(void 0===e&&(e={}),this.status===c.SessionStatus.STATUS_TERMINATED)throw new u.Exceptions.InvalidStateError(this.status);this.logger.log("rejecting RTCSession");var r=e.statusCode||480,i=l.Utils.getReasonPhrase(r,e.reasonPhrase),n=e.extraHeaders||[];if(r<300||r>699)throw new TypeError("Invalid statusCode: "+r);var s=e.body?a.fromBodyLegacy(e.body):void 0,o=r<400?this.incomingRequest.redirect([],{statusCode:r,reasonPhrase:i,extraHeaders:n,body:s}):this.incomingRequest.reject({statusCode:r,reasonPhrase:i,extraHeaders:n,body:s});return["rejected","failed"].forEach(function(e){t.emit(e,o.message,i)}),this.terminated()},t.prototype.accept=function(e){var t=this;return void 0===e&&(e={}),this._accept(e).then(function(e){var r=e.message,i=e.session;i.delegate={onAck:function(e){return t.onAck(e)},onAckTimeout:function(){return t.onAckTimeout()},onBye:function(e){return t.receiveRequest(e)},onInfo:function(e){return t.receiveRequest(e)},onInvite:function(e){return t.receiveRequest(e)},onNotify:function(e){return t.receiveRequest(e)},onPrack:function(e){return t.receiveRequest(e)},onRefer:function(e){return t.receiveRequest(e)}},t.session=i,t.status=c.SessionStatus.STATUS_WAITING_FOR_ACK,t.accepted(r,l.Utils.getReasonPhrase(200))}).catch(function(e){if(t.onContextError(e),!t._canceled)throw e}),this},t.prototype.progress=function(e){var t=this;void 0===e&&(e={});var r=e.statusCode||180;if(r<100||r>199)throw new TypeError("Invalid statusCode: "+r);if(this.status===c.SessionStatus.STATUS_TERMINATED)return this.logger.warn("Unexpected call for progress while terminated, ignoring"),this;if(this.status===c.SessionStatus.STATUS_ANSWERED)return this.logger.warn("Unexpected call for progress while answered, ignoring"),this;if(this.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK)return this.logger.warn("Unexpected call for progress while answered (waiting for prack), ignoring"),this;if(this.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK)return this.logger.warn("Unexpected call for progress while waiting for prack, ignoring"),this;if(100===e.statusCode){try{this.incomingRequest.trying()}catch(e){if(this.onContextError(e),!this._canceled)throw e}return this}return this.rel100===o.C.supported.REQUIRED||this.rel100===o.C.supported.SUPPORTED&&e.rel100||this.rel100===o.C.supported.SUPPORTED&&this.ua.configuration.rel100===o.C.supported.REQUIRED?(this._reliableProgressWaitForPrack(e).catch(function(e){if(t.onContextError(e),!t._canceled)throw e}),this):(this._progress(e).catch(function(e){if(t.onContextError(e),!t._canceled)throw e}),this)},t.prototype.terminate=function(e){var t=this;if(void 0===e&&(e={}),!this.session)return this.reject(e),this;switch(this.session.sessionState){case a.SessionState.Initial:case a.SessionState.Early:return this.reject(e),this;case a.SessionState.AckWait:return this.session.delegate={onAck:function(){t.sendRequest(o.C.BYE,e)},onAckTimeout:function(){t.sendRequest(o.C.BYE,e)}},this.emit("bye",this.request),this.terminated(),this;case a.SessionState.Confirmed:return this.bye(e),this;case a.SessionState.Terminated:default:return this}},t.prototype.onCancel=function(e){this.status!==c.SessionStatus.STATUS_WAITING_FOR_ANSWER&&this.status!==c.SessionStatus.STATUS_WAITING_FOR_PRACK&&this.status!==c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK&&this.status!==c.SessionStatus.STATUS_EARLY_MEDIA&&this.status!==c.SessionStatus.STATUS_ANSWERED||(this.status=c.SessionStatus.STATUS_CANCELED,this.incomingRequest.reject({statusCode:487}),this.canceled(),this.rejected(e,o.C.causes.CANCELED),this.failed(e,o.C.causes.CANCELED),this.terminated(e,o.C.causes.CANCELED))},t.prototype.receiveRequest=function(t){var r=this;switch(t.message.method){case o.C.PRACK:this.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK||this.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK?this.hasAnswer?(clearTimeout(this.timers.rel1xxTimer),clearTimeout(this.timers.prackTimer),t.accept(),this.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK&&(this.status=c.SessionStatus.STATUS_EARLY_MEDIA,this.accept()),this.status=c.SessionStatus.STATUS_EARLY_MEDIA):(this.sessionDescriptionHandler=this.setupSessionDescriptionHandler(),this.emit("SessionDescriptionHandler-created",this.sessionDescriptionHandler),this.sessionDescriptionHandler.hasDescription(t.message.getHeader("Content-Type")||"")?(this.hasAnswer=!0,this.sessionDescriptionHandler.setDescription(t.message.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){clearTimeout(r.timers.rel1xxTimer),clearTimeout(r.timers.prackTimer),t.accept(),r.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK&&(r.status=c.SessionStatus.STATUS_EARLY_MEDIA,r.accept()),r.status=c.SessionStatus.STATUS_EARLY_MEDIA},function(e){r.logger.warn(e),r.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),r.failed(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION),r.terminated(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION)})):(this.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),this.failed(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION),this.terminated(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION))):this.status===c.SessionStatus.STATUS_EARLY_MEDIA&&t.accept();break;default:e.prototype.receiveRequest.call(this,t)}},t.prototype.setupSessionDescriptionHandler=function(){return this.sessionDescriptionHandler?this.sessionDescriptionHandler:this.sessionDescriptionHandlerFactory(this,this.ua.configuration.sessionDescriptionHandlerFactoryOptions)},t.prototype.generateResponseOfferAnswer=function(e){if(!this.session){var t=a.getBody(this.incomingRequest.message);return t&&"session"===t.contentDisposition?this.setOfferAndGetAnswer(t,e):this.getOffer(e)}switch(this.session.signalingState){case a.SignalingState.Initial:return this.getOffer(e);case a.SignalingState.Stable:case a.SignalingState.HaveLocalOffer:return Promise.resolve(void 0);case a.SignalingState.HaveRemoteOffer:if(!this.session.offer)throw new Error("Session offer undefined");return this.setOfferAndGetAnswer(this.session.offer,e);case a.SignalingState.Closed:default:throw new Error("Invalid signaling state "+this.session.signalingState+".")}},t.prototype.handlePrackOfferAnswer=function(e,t){if(!this.session)throw new Error("Session undefined.");var r=a.getBody(e.message);if(!r||"session"!==r.contentDisposition)return Promise.resolve(void 0);switch(this.session.signalingState){case a.SignalingState.Initial:throw new Error("Invalid signaling state "+this.session.signalingState+".");case a.SignalingState.Stable:return this.setAnswer(r,t).then(function(){});case a.SignalingState.HaveLocalOffer:throw new Error("Invalid signaling state "+this.session.signalingState+".");case a.SignalingState.HaveRemoteOffer:return this.setOfferAndGetAnswer(r,t);case a.SignalingState.Closed:default:throw new Error("Invalid signaling state "+this.session.signalingState+".")}},t.prototype.canceled=function(){return this._canceled=!0,e.prototype.canceled.call(this)},t.prototype.terminated=function(t,r){return this.prackNeverArrived(),e.prototype.terminated.call(this,t,r)},t.prototype._accept=function(e){var t=this;return void 0===e&&(e={}),this.onInfo=e.onInfo,this.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK?(this.status=c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK,this.waitForArrivalOfPrack().then(function(){t.status=c.SessionStatus.STATUS_ANSWERED,clearTimeout(t.timers.userNoAnswerTimer)}).then(function(){return t.generateResponseOfferAnswer(e)}).then(function(e){return t.incomingRequest.accept({statusCode:200,body:e})})):this.status!==c.SessionStatus.STATUS_WAITING_FOR_ANSWER?Promise.reject(new u.Exceptions.InvalidStateError(this.status)):(this.status=c.SessionStatus.STATUS_ANSWERED,this.status=c.SessionStatus.STATUS_ANSWERED,clearTimeout(this.timers.userNoAnswerTimer),this.generateResponseOfferAnswer(e).then(function(e){return t.incomingRequest.accept({statusCode:200,body:e})}))},t.prototype._progress=function(e){void 0===e&&(e={});var t=e.statusCode||180,r=e.reasonPhrase,i=(e.extraHeaders||[]).slice(),n=e.body?a.fromBodyLegacy(e.body):void 0;try{var s=this.incomingRequest.progress({statusCode:t,reasonPhrase:r,extraHeaders:i,body:n});return this.emit("progress",s.message,r),this.session=s.session,Promise.resolve(s)}catch(e){return Promise.reject(e)}},t.prototype._reliableProgress=function(e){var t=this;void 0===e&&(e={});var r=e.statusCode||183,i=e.reasonPhrase,n=(e.extraHeaders||[]).slice();return n.push("Require: 100rel"),n.push("RSeq: "+Math.floor(1e4*Math.random())),this.generateResponseOfferAnswer(e).then(function(e){return t.incomingRequest.progress({statusCode:r,reasonPhrase:i,extraHeaders:n,body:e})}).then(function(e){return t.emit("progress",e.message,i),t.session=e.session,e})},t.prototype._reliableProgressWaitForPrack=function(e){var t=this;void 0===e&&(e={});var r,i=e.statusCode||183,n=e.reasonPhrase,s=(e.extraHeaders||[]).slice();return s.push("Require: 100rel"),s.push("RSeq: "+this.rseq++),this.status=c.SessionStatus.STATUS_WAITING_FOR_PRACK,new Promise(function(d,p){var h=!0;return t.generateResponseOfferAnswer(e).then(function(e){return r=e,t.incomingRequest.progress({statusCode:i,reasonPhrase:n,extraHeaders:s,body:r})}).then(function(l){var g,f;t.emit("progress",l.message,n),t.session=l.session,l.session.delegate={onPrack:function(r){g=r,clearTimeout(m),clearTimeout(T),h&&(h=!1,t.handlePrackOfferAnswer(g,e).then(function(e){try{f=g.accept({statusCode:200,body:e}),t.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK&&(t.status=c.SessionStatus.STATUS_WAITING_FOR_ANSWER),t.prackArrived(),d({prackRequest:g,prackResponse:f,progressResponse:l})}catch(e){p(e)}}))}};var m=setTimeout(function(){if(h){h=!1,t.logger.warn("No PRACK received, rejecting INVITE."),clearTimeout(T);try{t.incomingRequest.reject({statusCode:504}),t.terminated(void 0,o.C.causes.NO_PRACK),p(new u.Exceptions.TerminatedSessionError)}catch(e){p(e)}}},64*a.Timers.T1),v=function(){try{t.incomingRequest.progress({statusCode:i,reasonPhrase:n,extraHeaders:s,body:r})}catch(e){return h=!1,void p(e)}T=setTimeout(v,S*=2)},S=a.Timers.T1,T=setTimeout(v,S)})})},t.prototype.onAckTimeout=function(){if(this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK){if(this.logger.log("no ACK received for an extended period of time, terminating the call"),!this.session)throw new Error("Session undefined.");this.session.bye(),this.terminated(void 0,o.C.causes.NO_ACK)}},t.prototype.onContextError=function(e){var t=480;e instanceof a.Exception?e instanceof u.Exceptions.SessionDescriptionHandlerError?(this.logger.error(e.message),e.error&&this.logger.error(e.error)):e instanceof u.Exceptions.TerminatedSessionError?this.logger.warn("Incoming session terminated while waiting for PRACK."):e instanceof u.Exceptions.UnsupportedSessionDescriptionContentTypeError?t=415:e instanceof a.Exception&&this.logger.error(e.message):e instanceof Error?this.logger.error(e.message):(this.logger.error("An error occurred in the session description handler."),this.logger.error(e));try{this.incomingRequest.reject({statusCode:t}),this.failed(this.incomingRequest.message,e.message),this.terminated(this.incomingRequest.message,e.message)}catch(e){return}},t.prototype.prackArrived=function(){this.waitingForPrackResolve&&this.waitingForPrackResolve(),this.waitingForPrackPromise=void 0,this.waitingForPrackResolve=void 0,this.waitingForPrackReject=void 0},t.prototype.prackNeverArrived=function(){this.waitingForPrackReject&&this.waitingForPrackReject(new u.Exceptions.TerminatedSessionError),this.waitingForPrackPromise=void 0,this.waitingForPrackResolve=void 0,this.waitingForPrackReject=void 0},t.prototype.waitForArrivalOfPrack=function(){var e=this;if(this.waitingForPrackPromise)throw new Error("Already waiting for PRACK");return this.waitingForPrackPromise=new Promise(function(t,r){e.waitingForPrackResolve=t,e.waitingForPrackReject=r}),this.waitingForPrackPromise},t.prototype.getOffer=function(e){return this.hasOffer=!0,this.getSessionDescriptionHandler().getDescription(e.sessionDescriptionHandlerOptions,e.modifiers).then(function(e){return l.Utils.fromBodyObj(e)})},t.prototype.setAnswer=function(e,t){this.hasAnswer=!0;var r=this.getSessionDescriptionHandler();return r.hasDescription(e.contentType)?r.setDescription(e.content,t.sessionDescriptionHandlerOptions,t.modifiers):Promise.reject(new u.Exceptions.UnsupportedSessionDescriptionContentTypeError)},t.prototype.setOfferAndGetAnswer=function(e,t){this.hasOffer=!0,this.hasAnswer=!0;var r=this.getSessionDescriptionHandler();return r.hasDescription(e.contentType)?r.setDescription(e.content,t.sessionDescriptionHandlerOptions,t.modifiers).then(function(){return r.getDescription(t.sessionDescriptionHandlerOptions,t.modifiers)}).then(function(e){return l.Utils.fromBodyObj(e)}):Promise.reject(new u.Exceptions.UnsupportedSessionDescriptionContentTypeError)},t.prototype.getSessionDescriptionHandler=function(){var e=this.sessionDescriptionHandler=this.setupSessionDescriptionHandler();return this.emit("SessionDescriptionHandler-created",this.sessionDescriptionHandler),e},t}(g);t.InviteServerContext=f;var m=function(e){function t(t,r,i,n){void 0===i&&(i={}),void 0===n&&(n=[]);var a=this;if(!t.configuration.sessionDescriptionHandlerFactory)throw t.logger.warn("Can't build ISC without SDH Factory"),new Error("ICC Constructor Failed");i.params=i.params||{};var d=i.anonymous||!1,p=l.Utils.newTag();i.params.fromTag=p;var h=t.contact.toString({anonymous:d,outbound:d?!t.contact.tempGruu:!t.contact.pubGruu}),g=(i.extraHeaders||[]).slice();if(d&&t.configuration.uri&&(i.params.from_displayName="Anonymous",i.params.from_uri="sip:anonymous@anonymous.invalid",g.push("P-Preferred-Identity: "+t.configuration.uri.toString()),g.push("Privacy: id")),g.push("Contact: "+h),g.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),t.configuration.rel100===o.C.supported.REQUIRED&&g.push("Require: 100rel"),t.configuration.replaces===o.C.supported.REQUIRED&&g.push("Require: replaces"),i.extraHeaders=g,a=e.call(this,t.configuration.sessionDescriptionHandlerFactory)||this,s.ClientContext.initializer(a,t,o.C.INVITE,r,i),a.earlyMediaSessionDescriptionHandlers=new Map,a.type=c.TypeStrings.InviteClientContext,a.passedOptions=i,a.sessionDescriptionHandlerOptions=i.sessionDescriptionHandlerOptions||{},a.modifiers=n,a.inviteWithoutSdp=i.inviteWithoutSdp||!1,a.anonymous=i.anonymous||!1,a.renderbody=i.renderbody||void 0,a.rendertype=i.rendertype||"text/plain",a.fromTag=p,a.contact=h,a.status!==c.SessionStatus.STATUS_NULL)throw new u.Exceptions.InvalidStateError(a.status);return a.isCanceled=!1,a.received100=!1,a.method=o.C.INVITE,a.logger=t.getLogger("sip.inviteclientcontext"),t.applicants[a.toString()]=a,a.id=a.request.callId+a.fromTag,a.onInfo=i.onInfo,a.errorListener=a.onTransportError.bind(a),t.transport&&t.transport.on("transportError",a.errorListener),a}return i.__extends(t,e),t.prototype.receiveResponse=function(e){throw new Error("Unimplemented.")},t.prototype.send=function(){return this.sendInvite(),this},t.prototype.invite=function(){var e=this;return this.ua.sessions[this.id]=this,Promise.resolve().then(function(){e.isCanceled||e.status===c.SessionStatus.STATUS_TERMINATED||(e.inviteWithoutSdp?(e.renderbody&&e.rendertype&&(e.request.body={body:e.renderbody,contentType:e.rendertype}),e.status=c.SessionStatus.STATUS_INVITE_SENT,e.send()):(e.sessionDescriptionHandler=e.sessionDescriptionHandlerFactory(e,e.ua.configuration.sessionDescriptionHandlerFactoryOptions||{}),e.emit("SessionDescriptionHandler-created",e.sessionDescriptionHandler),e.sessionDescriptionHandler.getDescription(e.sessionDescriptionHandlerOptions,e.modifiers).then(function(t){e.isCanceled||e.status===c.SessionStatus.STATUS_TERMINATED||(e.hasOffer=!0,e.request.body=t,e.status=c.SessionStatus.STATUS_INVITE_SENT,e.send())},function(t){t.type===c.TypeStrings.SessionDescriptionHandlerError&&(e.logger.log(t.message),t.error&&e.logger.log(t.error)),e.status!==c.SessionStatus.STATUS_TERMINATED&&(e.failed(void 0,o.C.causes.WEBRTC_ERROR),e.terminated(void 0,o.C.causes.WEBRTC_ERROR))})))}),this},t.prototype.cancel=function(e){if(void 0===e&&(e={}),this.status===c.SessionStatus.STATUS_TERMINATED||this.status===c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);if(this.isCanceled)throw new u.Exceptions.InvalidStateError(c.SessionStatus.STATUS_CANCELED);this.isCanceled=!0,this.logger.log("Canceling session");var t=l.Utils.getCancelReason(e.statusCode,e.reasonPhrase);return e.extraHeaders=(e.extraHeaders||[]).slice(),this.outgoingInviteRequest&&(this.logger.warn("Canceling session before it was created"),this.outgoingInviteRequest.cancel(t,e)),this.canceled()},t.prototype.terminate=function(e){return this.status===c.SessionStatus.STATUS_TERMINATED?this:(this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK||this.status===c.SessionStatus.STATUS_CONFIRMED?this.bye(e):this.cancel(e),this)},t.prototype.sendInvite=function(){var e=this;this.outgoingInviteRequest=this.ua.userAgentCore.invite(this.request,{onAccept:function(t){return e.onAccept(t)},onProgress:function(t){return e.onProgress(t)},onRedirect:function(t){return e.onRedirect(t)},onReject:function(t){return e.onReject(t)},onTrying:function(t){return e.onTrying(t)}})},t.prototype.ackAndBye=function(e,t,r,i){if(!this.ua.userAgentCore)throw new Error("Method requires user agent core.");var n=[];r&&n.push("Reason: "+l.Utils.getReasonHeaderValue(r,i));var s=e.ack();this.emit("ack",s.message);var o=t.bye(void 0,{extraHeaders:n});this.emit("bye",o.message)},t.prototype.disposeEarlyMedia=function(){if(!this.earlyMediaSessionDescriptionHandlers)throw new Error("Early media session description handlers undefined.");this.earlyMediaSessionDescriptionHandlers.forEach(function(e){e.close()})},t.prototype.onAccept=function(e){var t=this;if(!this.earlyMediaSessionDescriptionHandlers)throw new Error("Early media session description handlers undefined.");var r=e.message,i=e.session;if(this.session)this.ackAndBye(e,i);else{if(this.isCanceled)return this.ackAndBye(e,i),void this.emit("bye",this.request);switch(r.hasHeader("P-Asserted-Identity")&&(this.assertedIdentity=a.Grammar.nameAddrHeaderParse(r.getHeader("P-Asserted-Identity"))),this.session=i,this.session.delegate={onAck:function(e){return t.onAck(e)},onBye:function(e){return t.receiveRequest(e)},onInfo:function(e){return t.receiveRequest(e)},onInvite:function(e){return t.receiveRequest(e)},onNotify:function(e){return t.receiveRequest(e)},onPrack:function(e){return t.receiveRequest(e)},onRefer:function(e){return t.receiveRequest(e)}},i.signalingState){case a.SignalingState.Initial:case a.SignalingState.HaveLocalOffer:this.ackAndBye(e,i,400,"Missing session description"),this.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION);break;case a.SignalingState.HaveRemoteOffer:var n=this.sessionDescriptionHandlerFactory(this,this.ua.configuration.sessionDescriptionHandlerFactoryOptions||{});if(this.sessionDescriptionHandler=n,this.emit("SessionDescriptionHandler-created",this.sessionDescriptionHandler),!n.hasDescription(r.getHeader("Content-Type")||"")){this.ackAndBye(e,i,400,"Missing session description"),this.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION);break}this.hasOffer=!0,n.setDescription(r.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){return n.getDescription(t.sessionDescriptionHandlerOptions,t.modifiers)}).then(function(i){if(!t.isCanceled&&t.status!==c.SessionStatus.STATUS_TERMINATED){t.status=c.SessionStatus.STATUS_CONFIRMED,t.hasAnswer=!0;var n={contentDisposition:"session",contentType:i.contentType,content:i.body},s=e.ack({body:n});t.emit("ack",s.message),t.accepted(r)}}).catch(function(n){if(n.type!==c.TypeStrings.SessionDescriptionHandlerError)throw n;t.logger.warn("invalid description"),t.logger.warn(n.toString()),t.ackAndBye(e,i,488,"Invalid session description"),t.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION)});break;case a.SignalingState.Stable:var s;if(this.renderbody&&this.rendertype&&(s={body:{contentDisposition:"render",contentType:this.rendertype,content:this.renderbody}}),this.hasOffer&&!this.hasAnswer){if(!this.sessionDescriptionHandler)throw new Error("Session description handler undefined.");var u=i.answer;if(!u)throw new Error("Answer is undefined.");this.sessionDescriptionHandler.setDescription(u.content,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){t.hasAnswer=!0,t.status=c.SessionStatus.STATUS_CONFIRMED;var i=e.ack(s);t.emit("ack",i.message),t.accepted(r)}).catch(function(n){t.logger.error(n),t.ackAndBye(e,i,488,"Not Acceptable Here"),t.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION)})}else{if(this.sessionDescriptionHandler=this.earlyMediaSessionDescriptionHandlers.get(i.id),!this.sessionDescriptionHandler)throw new Error("Session description handler undefined.");this.earlyMediaSessionDescriptionHandlers.delete(i.id),this.hasOffer=!0,this.hasAnswer=!0,this.status=c.SessionStatus.STATUS_CONFIRMED;var d=e.ack();this.emit("ack",d.message),this.accepted(r)}break;case a.SignalingState.Closed:break;default:throw new Error("Unknown session signaling state.")}this.disposeEarlyMedia()}},t.prototype.onProgress=function(e){var t=this;if(!this.isCanceled){if(!this.outgoingInviteRequest)throw new Error("Outgoing INVITE request undefined.");if(!this.earlyMediaSessionDescriptionHandlers)throw new Error("Early media session description handlers undefined.");var r=e.message,i=e.session;if(this.status=c.SessionStatus.STATUS_1XX_RECEIVED,r.hasHeader("P-Asserted-Identity")&&(this.assertedIdentity=a.Grammar.nameAddrHeaderParse(r.getHeader("P-Asserted-Identity"))),!i)throw new Error("Session undefined.");var n=r.getHeader("require"),s=r.getHeader("rseq"),u=!!(n&&n.includes("100rel")&&s?Number(s):void 0),d=[];if(u&&d.push("RAck: "+r.getHeader("rseq")+" "+r.getHeader("cseq")),i.signalingState===a.SignalingState.Initial)return u&&(this.logger.warn("First reliable provisional response received MUST contain an offer when INVITE does not contain an offer."),e.prack({extraHeaders:d})),void this.emit("progress",r);if(i.signalingState===a.SignalingState.HaveLocalOffer)return u&&e.prack({extraHeaders:d}),void this.emit("progress",r);if(i.signalingState===a.SignalingState.HaveRemoteOffer){if(!u)return void this.logger.warn("Non-reliable provisional response MUST NOT contain an initial offer, discarding response.");var p=this.sessionDescriptionHandlerFactory(this,this.ua.configuration.sessionDescriptionHandlerFactoryOptions||{});return this.emit("SessionDescriptionHandler-created",p),this.earlyMediaSessionDescriptionHandlers.set(i.id,p),void p.setDescription(r.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){return p.getDescription(t.sessionDescriptionHandlerOptions,t.modifiers)}).then(function(i){var n={contentDisposition:"session",contentType:i.contentType,content:i.body};e.prack({extraHeaders:d,body:n}),t.status=c.SessionStatus.STATUS_EARLY_MEDIA,t.emit("progress",r)}).catch(function(e){t.status!==c.SessionStatus.STATUS_TERMINATED&&(t.failed(void 0,o.C.causes.WEBRTC_ERROR),t.terminated(void 0,o.C.causes.WEBRTC_ERROR))})}return i.signalingState===a.SignalingState.Stable?(u&&e.prack({extraHeaders:d}),void this.emit("progress",r)):void 0}},t.prototype.onRedirect=function(e){this.disposeEarlyMedia();var t=e.message,r=t.statusCode,i=l.Utils.sipErrorCause(r||0);this.rejected(t,i),this.failed(t,i),this.terminated(t,i)},t.prototype.onReject=function(e){this.disposeEarlyMedia();var t=e.message,r=t.statusCode,i=l.Utils.sipErrorCause(r||0);this.rejected(t,i),this.failed(t,i),this.terminated(t,i)},t.prototype.onTrying=function(e){this.received100=!0,this.emit("progress",e.message)},t}(g);t.InviteClientContext=m},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(7),o=r(5),a=r(16),c=r(4),u=r(8),d=function(e){function t(t,r,i,n){void 0===n&&(n={});var o=e.call(this)||this;o.data={},o.method=s.C.SUBSCRIBE,o.body=void 0,o.type=c.TypeStrings.Subscription,o.ua=t,o.logger=t.getLogger("sip.subscription"),n.body&&(o.body={body:n.body,contentType:n.contentType?n.contentType:"application/sdp"});var a=t.normalizeTarget(r);if(!a)throw new TypeError("Invalid target: "+r);if(o.uri=a,o.event=i,void 0===n.expires?o.expires=3600:"number"!=typeof n.expires?(t.logger.warn('Option "expires" must be a number. Using default of 3600.'),o.expires=3600):o.expires=n.expires,o.extraHeaders=(n.extraHeaders||[]).slice(),o.context=o.initContext(),o.disposed=!1,o.request=o.context.message,!o.request.from)throw new Error("From undefined.");if(!o.request.to)throw new Error("From undefined.");return o.localIdentity=o.request.from,o.remoteIdentity=o.request.to,o.id=o.request.callId+o.request.from.parameters.tag+o.event,o.ua.subscriptions[o.id]=o,o}return i.__extends(t,e),t.prototype.dispose=function(){this.disposed||(this.retryAfterTimer&&(clearTimeout(this.retryAfterTimer),this.retryAfterTimer=void 0),this.context.dispose(),this.disposed=!0,delete this.ua.subscriptions[this.id])},t.prototype.on=function(t,r){return e.prototype.on.call(this,t,r)},t.prototype.emit=function(t){for(var r=[],i=1;i0?(e.accept(),i.emit("notify",{request:e.message})):e.reject({statusCode:481})},onRefer:function(e){i.logger.log("Received an out of dialog refer"),i.configuration.allowOutOfDialogRefers||e.reject({statusCode:405}),i.logger.log("Allow out of dialog refers is enabled on the UA");var t=new h.ReferServerContext(i,e);i.listeners("outOfDialogReferRequested").length?i.emit("outOfDialogReferRequested",t):(i.logger.log("No outOfDialogReferRequest listeners, automatically accepting and following the out of dialog refer"),t.accept({followRefer:!0}))},onSubscribe:function(e){i.emit("subscribe",e)}};return i.userAgentCore=new a.UserAgentCore(u,d),i.registerContext=new l.RegisterContext(i,e.registerOptions),i.registerContext.on("failed",i.emit.bind(i,"registrationFailed")),i.registerContext.on("registered",i.emit.bind(i,"registered")),i.registerContext.on("unregistered",i.emit.bind(i,"unregistered")),i.configuration.autostart&&i.start(),i}return i.__extends(r,t),r.prototype.register=function(e){return void 0===e&&(e={}),e.register&&(this.configuration.register=!0),this.registerContext.register(e),this},r.prototype.unregister=function(e){var t=this;return this.configuration.register=!1,this.transport&&this.transport.afterConnected(function(){t.registerContext.unregister(e)}),this},r.prototype.isRegistered=function(){return this.registerContext.registered},r.prototype.invite=function(e,t,r){var i=this,n=new f.InviteClientContext(this,e,t,r);return this.transport&&this.transport.afterConnected(function(){n.invite(),i.emit("inviteSent",n)}),n},r.prototype.subscribe=function(e,t,r){var i=new m.Subscription(this,e,t,r);return this.transport&&this.transport.afterConnected(function(){return i.subscribe()}),i},r.prototype.publish=function(e,t,r,i){var n=new p.PublishContext(this,e,t,i);return this.transport&&this.transport.afterConnected(function(){n.publish(r)}),n},r.prototype.message=function(e,t,r){if(void 0===r&&(r={}),void 0===t)throw new TypeError("Not enough arguments");return r.contentType=r.contentType||"text/plain",r.body=t,this.request(o.C.MESSAGE,e,r)},r.prototype.request=function(e,t,r){var i=new s.ClientContext(this,e,t,r);return this.transport&&this.transport.afterConnected(function(){return i.send()}),i},r.prototype.stop=function(){if(this.logger.log("user requested closure..."),this.status===c.UAStatus.STATUS_USER_CLOSED)return this.logger.warn("UA already closed"),this;for(var t in this.logger.log("closing registerContext"),this.registerContext.close(),this.sessions)this.sessions[t]&&(this.logger.log("closing session "+t),this.sessions[t].terminate());for(var r in this.subscriptions)this.subscriptions[r]&&(this.logger.log("unsubscribe "+r),this.subscriptions[r].unsubscribe());for(var i in this.publishers)this.publishers[i]&&(this.logger.log("unpublish "+i),this.publishers[i].close());for(var n in this.applicants)this.applicants[n]&&this.applicants[n].close();return this.status=c.UAStatus.STATUS_USER_CLOSED,this.transport&&this.transport.disconnect(),this.userAgentCore.reset(),"function"==typeof y.removeEventListener&&(e.chrome&&e.chrome.app&&e.chrome.app.runtime||y.removeEventListener("unload",this.environListener)),this},r.prototype.start=function(){var t=this;if(this.logger.log("user requested startup..."),this.status===c.UAStatus.STATUS_INIT){if(this.status=c.UAStatus.STATUS_STARTING,!this.configuration.transportConstructor)throw new a.TransportError("Transport constructor not set");this.transport=new this.configuration.transportConstructor(this.getLogger("sip.transport"),this.configuration.transportOptions),this.setTransportListeners(),this.emit("transportCreated",this.transport),this.transport.connect()}else this.status===c.UAStatus.STATUS_USER_CLOSED?(this.logger.log("resuming"),this.status=c.UAStatus.STATUS_READY,this.transport&&this.transport.connect()):this.status===c.UAStatus.STATUS_STARTING?this.logger.log("UA is in STARTING status, not opening new connection"):this.status===c.UAStatus.STATUS_READY?this.logger.log("UA is in READY status, not resuming"):this.logger.error("Connection is down. Auto-Recovery system is trying to connect");return this.configuration.autostop&&"function"==typeof y.addEventListener&&(e.chrome&&e.chrome.app&&e.chrome.app.runtime||(this.environListener=this.stop,y.addEventListener("unload",function(){return t.environListener()}))),this},r.prototype.normalizeTarget=function(e){return v.Utils.normalizeTarget(e,this.configuration.hostportParams)},r.prototype.getLogger=function(e,t){return this.log.getLogger(e,t)},r.prototype.getLoggerFactory=function(){return this.log},r.prototype.getSupportedResponseOptions=function(){var e=[];(this.contact.pubGruu||this.contact.tempGruu)&&e.push("gruu"),this.configuration.rel100===o.C.supported.SUPPORTED&&e.push("100rel"),this.configuration.replaces===o.C.supported.SUPPORTED&&e.push("replaces"),e.push("outbound"),e=e.concat(this.configuration.extraSupported||[]);var t=this.configuration.hackAllowUnregisteredOptionTags||!1,r={};return e=e.filter(function(e){var i=o.C.OPTION_TAGS[e],n=!r[e];return r[e]=!0,(i||t)&&n})},r.prototype.findSession=function(e){return this.sessions[e.callId+e.fromTag]||this.sessions[e.callId+e.toTag]||void 0},r.prototype.on=function(e,r){return t.prototype.on.call(this,e,r)},r.prototype.onTransportError=function(){this.status!==c.UAStatus.STATUS_USER_CLOSED&&(this.error&&this.error===r.C.NETWORK_ERROR||(this.status=c.UAStatus.STATUS_NOT_READY,this.error=r.C.NETWORK_ERROR))},r.prototype.setTransportListeners=function(){var e=this;this.transport&&(this.transport.on("connected",function(){return e.onTransportConnected()}),this.transport.on("message",function(t){return e.onTransportReceiveMsg(t)}),this.transport.on("transportError",function(){return e.onTransportError()}))},r.prototype.onTransportConnected=function(){var e=this;this.configuration.register&&Promise.resolve().then(function(){return e.registerContext.register()})},r.prototype.onTransportReceiveMsg=function(e){var t=this,r=d.Parser.parseMessage(e,this.getLogger("sip.parser"));if(r)if(this.status===c.UAStatus.STATUS_USER_CLOSED&&r instanceof a.IncomingRequestMessage)this.logger.warn("UA received message when status = USER_CLOSED - aborting");else{var i=function(){for(var e=0,i=["from","to","call_id","cseq","via"];e1)return void this.logger.warn("More than one Via header field present in the response. Dropping.");if(r.via.host!==this.configuration.viaHost||void 0!==r.via.port)return void this.logger.warn("Via sent-by in the response does not match UA Via host value. Dropping.");var s;n=v.Utils.str_utf8_length(r.body);if((s=r.getHeader("content-length"))&&n"}};var h={};for(var n in r)r.hasOwnProperty(n)&&(h[n]=r[n]);for(var n in Object.assign(this.configuration,h),this.logger.log("configuration parameters after validation:"),r)if(r.hasOwnProperty(n))switch(n){case"uri":case"sessionDescriptionHandlerFactory":this.logger.log("\xb7 "+n+": "+r[n]);break;case"password":this.logger.log("\xb7 "+n+": NOT SHOWN");break;case"transportConstructor":this.logger.log("\xb7 "+n+": "+r[n].name);break;default:this.logger.log("\xb7 "+n+": "+JSON.stringify(r[n]))}},r.prototype.getConfigurationCheck=function(){return{mandatory:{},optional:{uri:function(e){/^sip:/i.test(e)||(e=o.C.SIP+":"+e);var t=a.Grammar.URIParse(e);return t&&t.user?t:void 0},transportConstructor:function(e){if(e instanceof Function)return e},transportOptions:function(e){if("object"==typeof e)return e},authorizationUser:function(e){return-1===a.Grammar.parse('"'+e+'"',"quoted_string")?void 0:e},displayName:function(e){return-1===a.Grammar.parse('"'+e+'"',"displayName")?void 0:e},dtmfType:function(e){switch(e){case o.C.dtmfType.RTP:return o.C.dtmfType.RTP;case o.C.dtmfType.INFO:default:return o.C.dtmfType.INFO}},hackViaTcp:function(e){if("boolean"==typeof e)return e},hackIpInContact:function(e){return"boolean"==typeof e?e:"string"==typeof e&&-1!==a.Grammar.parse(e,"host")?e:void 0},hackWssInTransport:function(e){if("boolean"==typeof e)return e},hackAllowUnregisteredOptionTags:function(e){if("boolean"==typeof e)return e},contactTransport:function(e){if("string"==typeof e)return e},extraSupported:function(e){if(e instanceof Array){for(var t=0,r=e;t0)return t}},password:function(e){return String(e)},rel100:function(e){return e===o.C.supported.REQUIRED?o.C.supported.REQUIRED:e===o.C.supported.SUPPORTED?o.C.supported.SUPPORTED:o.C.supported.UNSUPPORTED},replaces:function(e){return e===o.C.supported.REQUIRED?o.C.supported.REQUIRED:e===o.C.supported.SUPPORTED?o.C.supported.SUPPORTED:o.C.supported.UNSUPPORTED},register:function(e){if("boolean"==typeof e)return e},registerOptions:function(e){if("object"==typeof e)return e},userAgentString:function(e){if("string"==typeof e)return e},autostart:function(e){if("boolean"==typeof e)return e},autostop:function(e){if("boolean"==typeof e)return e},sessionDescriptionHandlerFactory:function(e){if(e instanceof Function)return e},sessionDescriptionHandlerFactoryOptions:function(e){if("object"==typeof e)return e},authenticationFactory:this.checkAuthenticationFactory,allowLegacyNotifications:function(e){if("boolean"==typeof e)return e},custom:function(e){if("object"==typeof e)return e},contactName:function(e){if("string"==typeof e)return e},experimentalFeatures:function(e){if("boolean"==typeof e)return e}}}},r.C={STATUS_INIT:0,STATUS_STARTING:1,STATUS_READY:2,STATUS_USER_CLOSED:3,STATUS_NOT_READY:4,CONFIGURATION_ERROR:1,NETWORK_ERROR:2,ALLOWED_METHODS:["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"],ACCEPTED_BODY_TYPES:["application/sdp","application/dtmf-relay"],MAX_FORWARDS:70,TAG_LENGTH:10},r}(n.EventEmitter);function b(e){if(!(e.configuration.uri instanceof a.URI))throw new Error("Configuration URI not instance of URI.");var t=e.configuration.uri,r=e.contact,i=e.configuration.displayName?e.configuration.displayName:"",n=!!e.configuration.hackViaTcp,s=e.configuration.usePreloadedRoute&&e.transport?[e.transport.server.sipUri]:[],c=e.configuration.sipjsId||v.Utils.createRandomToken(5),u=[];u.push("outbound"),e.configuration.rel100===o.C.supported.SUPPORTED&&u.push("100rel"),e.configuration.replaces===o.C.supported.SUPPORTED&&u.push("replaces"),e.configuration.extraSupported&&u.push.apply(u,e.configuration.extraSupported),e.configuration.hackAllowUnregisteredOptionTags||(u=u.filter(function(e){return o.C.OPTION_TAGS[e]})),u=Array.from(new Set(u));var d=e.getSupportedResponseOptions(),p=e.configuration.userAgentString||"sipjs";if(!e.configuration.viaHost)throw new Error("Configuration via host undefined");var h=!!e.configuration.forceRport,l=e.configuration.viaHost;return{aor:t,contact:r,displayName:i,hackViaTcp:n,loggerFactory:e.getLoggerFactory(),routeSet:s,sipjsId:c,supportedOptionTags:u,supportedOptionTagsResponse:d,userAgentHeaderFieldValue:p,viaForceRport:h,viaHost:l,authenticationFactory:function(){if(e.configuration.authenticationFactory)return e.configuration.authenticationFactory(e)},transportAccessor:function(){return e.transport}}}t.UA=E,function(e){!function(e){e.RTP="rtp",e.INFO="info"}(e.DtmfType||(e.DtmfType={}))}(E=t.UA||(t.UA={})),t.UA=E,t.makeUserAgentCoreConfigurationFromUA=b}).call(this,r(18))},function(e,t,r){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(4),o=r(10),a=r(8),c=i.__importStar(r(35)),u=r(97),d=function(t){function r(r,i,n){var o=t.call(this)||this;o.type=s.TypeStrings.SessionDescriptionHandler,o.options=n||{},o.logger=r,o.observer=i,o.dtmfSender=void 0,o.shouldAcquireMedia=!0,o.CONTENT_TYPE="application/sdp",o.C={DIRECTION:{NULL:null,SENDRECV:"sendrecv",SENDONLY:"sendonly",RECVONLY:"recvonly",INACTIVE:"inactive"}},o.logger.log("SessionDescriptionHandlerOptions: "+JSON.stringify(o.options)),o.direction=o.C.DIRECTION.NULL,o.modifiers=o.options.modifiers||[],Array.isArray(o.modifiers)||(o.modifiers=[o.modifiers]);var a=e.window||e;return o.WebRTC={MediaStream:a.MediaStream,getUserMedia:a.navigator.mediaDevices.getUserMedia.bind(a.navigator.mediaDevices),RTCPeerConnection:a.RTCPeerConnection},o.iceGatheringTimeout=!1,o.initPeerConnection(o.options.peerConnectionOptions),o.constraints=o.checkAndDefaultConstraints(o.options.constraints),o}return i.__extends(r,t),r.defaultFactory=function(e,t){return new r(e.ua.getLogger("sip.invitecontext.sessionDescriptionHandler",e.id),new u.SessionDescriptionHandlerObserver(e,t),t)},r.prototype.close=function(){this.logger.log("closing PeerConnection"),this.peerConnection&&"closed"!==this.peerConnection.signalingState&&(this.peerConnection.getSenders?this.peerConnection.getSenders().forEach(function(e){e.track&&e.track.stop()}):(this.logger.warn("Using getLocalStreams which is deprecated"),this.peerConnection.getLocalStreams().forEach(function(e){e.getTracks().forEach(function(e){e.stop()})})),this.peerConnection.getReceivers?this.peerConnection.getReceivers().forEach(function(e){e.track&&e.track.stop()}):(this.logger.warn("Using getRemoteStreams which is deprecated"),this.peerConnection.getRemoteStreams().forEach(function(e){e.getTracks().forEach(function(e){e.stop()})})),this.resetIceGatheringComplete(),this.peerConnection.close())},r.prototype.getDescription=function(e,t){var r=this;void 0===e&&(e={}),void 0===t&&(t=[]),e.peerConnectionOptions&&this.initPeerConnection(e.peerConnectionOptions);var i=Object.assign({},this.constraints,e.constraints);return i=this.checkAndDefaultConstraints(i),JSON.stringify(i)!==JSON.stringify(this.constraints)&&(this.constraints=i,this.shouldAcquireMedia=!0),Array.isArray(t)||(t=[t]),t=t.concat(this.modifiers),Promise.resolve().then(function(){if(r.shouldAcquireMedia)return r.acquire(r.constraints).then(function(){r.shouldAcquireMedia=!1})}).then(function(){return r.createOfferOrAnswer(e.RTCOfferOptions,t)}).then(function(e){if(void 0===e.sdp)throw new o.Exceptions.SessionDescriptionHandlerError("getDescription",void 0,"SDP undefined");return r.emit("getDescription",e),{body:e.sdp,contentType:r.CONTENT_TYPE}})},r.prototype.hasDescription=function(e){return e===this.CONTENT_TYPE},r.prototype.holdModifier=function(e){return e.sdp?(/a=(sendrecv|sendonly|recvonly|inactive)/.test(e.sdp)?(e.sdp=e.sdp.replace(/a=sendrecv\r\n/g,"a=sendonly\r\n"),e.sdp=e.sdp.replace(/a=recvonly\r\n/g,"a=inactive\r\n")):e.sdp=e.sdp.replace(/(m=[^\r]*\r\n)/g,"$1a=sendonly\r\n"),Promise.resolve(e)):Promise.resolve(e)},r.prototype.setDescription=function(e,t,r){var i=this;void 0===t&&(t={}),void 0===r&&(r=[]),t.peerConnectionOptions&&this.initPeerConnection(t.peerConnectionOptions),Array.isArray(r)||(r=[r]),r=r.concat(this.modifiers);var n={type:this.hasOffer("local")?"answer":"offer",sdp:e};return Promise.resolve().then(function(){if(i.shouldAcquireMedia&&i.options.alwaysAcquireMediaFirst)return i.acquire(i.constraints).then(function(){i.shouldAcquireMedia=!1})}).then(function(){return a.Utils.reducePromises(r,n)}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("setDescription",e,"The modifiers did not resolve successfully");throw i.logger.error(t.message),i.emit("peerConnection-setRemoteDescriptionFailed",t),t}).then(function(e){return i.emit("setDescription",e),i.peerConnection.setRemoteDescription(e)}).catch(function(n){if(n.type===s.TypeStrings.SessionDescriptionHandlerError)throw n;if(/^m=video.+$/gm.test(e)&&!t.disableAudioFallback)return t.disableAudioFallback=!0,i.setDescription(e,t,[c.stripVideo].concat(r));var a=new o.Exceptions.SessionDescriptionHandlerError("setDescription",n);throw a.error&&i.logger.error(a.error),i.emit("peerConnection-setRemoteDescriptionFailed",a),a}).then(function(){i.peerConnection.getReceivers?i.emit("setRemoteDescription",i.peerConnection.getReceivers()):i.emit("setRemoteDescription",i.peerConnection.getRemoteStreams()),i.emit("confirmed",i)})},r.prototype.sendDtmf=function(e,t){if(void 0===t&&(t={}),!this.dtmfSender&&this.hasBrowserGetSenderSupport()){var r=this.peerConnection.getSenders();r.length>0&&(this.dtmfSender=r[0].dtmf)}if(!this.dtmfSender&&this.hasBrowserTrackSupport()){var i=this.peerConnection.getLocalStreams();if(i.length>0){var n=i[0].getAudioTracks();n.length>0&&(this.dtmfSender=this.peerConnection.createDTMFSender(n[0]))}}if(!this.dtmfSender)return!1;try{this.dtmfSender.insertDTMF(e,t.duration,t.interToneGap)}catch(e){if("InvalidStateError"===e.type||"InvalidCharacterError"===e.type)return this.logger.error(e),!1;throw e}return this.logger.log("DTMF sent via RTP: "+e.toString()),!0},r.prototype.getDirection=function(){return this.direction},r.prototype.on=function(e,r){return t.prototype.on.call(this,e,r)},r.prototype.createOfferOrAnswer=function(e,t){var r=this;void 0===e&&(e={}),void 0===t&&(t=[]);var i=this.hasOffer("remote")?"createAnswer":"createOffer",n=this.peerConnection;return this.logger.log(i),(this.hasOffer("remote")?n.createAnswer:n.createOffer).apply(n,e).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer",e,"peerConnection-"+i+"Failed");throw r.emit("peerConnection-"+i+"Failed",t),t}).then(function(e){return a.Utils.reducePromises(t,r.createRTCSessionDescriptionInit(e))}).then(function(e){return r.resetIceGatheringComplete(),r.logger.log("Setting local sdp."),r.logger.log("sdp is "+e.sdp||!1),n.setLocalDescription(e)}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer",e,"peerConnection-SetLocalDescriptionFailed");throw r.emit("peerConnection-SetLocalDescriptionFailed",t),t}).then(function(){return r.waitForIceGatheringComplete()}).then(function(){var e=r.createRTCSessionDescriptionInit(r.peerConnection.localDescription);return a.Utils.reducePromises(t,e)}).then(function(e){return r.setDirection(e.sdp||""),e}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer",e);throw r.logger.error(t.toString()),t})},r.prototype.createRTCSessionDescriptionInit=function(e){return{type:e.type,sdp:e.sdp}},r.prototype.addDefaultIceCheckingTimeout=function(e){return void 0===e.iceCheckingTimeout&&(e.iceCheckingTimeout=5e3),e},r.prototype.addDefaultIceServers=function(e){return e.iceServers||(e.iceServers=[{urls:"stun:stun.l.google.com:19302"}]),e},r.prototype.checkAndDefaultConstraints=function(e){var t={audio:!0,video:!this.options.alwaysAcquireMediaFirst};return e=e||t,0===Object.keys(e).length&&e.constructor===Object?t:e},r.prototype.hasBrowserTrackSupport=function(){return Boolean(this.peerConnection.addTrack)},r.prototype.hasBrowserGetSenderSupport=function(){return Boolean(this.peerConnection.getSenders)},r.prototype.initPeerConnection=function(e){var t=this;void 0===e&&(e={}),(e=this.addDefaultIceCheckingTimeout(e)).rtcConfiguration=e.rtcConfiguration||{},e.rtcConfiguration=this.addDefaultIceServers(e.rtcConfiguration),this.logger.log("initPeerConnection"),this.peerConnection&&(this.logger.log("Already have a peer connection for this session. Tearing down."),this.resetIceGatheringComplete(),this.peerConnection.close()),this.peerConnection=new this.WebRTC.RTCPeerConnection(e.rtcConfiguration),this.logger.log("New peer connection created"),"ontrack"in this.peerConnection?this.peerConnection.addEventListener("track",function(e){t.logger.log("track added"),t.observer.trackAdded(),t.emit("addTrack",e)}):(this.logger.warn("Using onaddstream which is deprecated"),this.peerConnection.onaddstream=function(e){t.logger.log("stream added"),t.emit("addStream",e)}),this.peerConnection.onicecandidate=function(e){t.emit("iceCandidate",e),e.candidate?t.logger.log("ICE candidate received: "+(null===e.candidate.candidate?null:e.candidate.candidate.trim())):null===e.candidate&&(t.logger.log("ICE candidate gathering complete"),t.triggerIceGatheringComplete())},this.peerConnection.onicegatheringstatechange=function(){switch(t.logger.log("RTCIceGatheringState changed: "+t.peerConnection.iceGatheringState),t.peerConnection.iceGatheringState){case"gathering":t.emit("iceGathering",t),!t.iceGatheringTimer&&e.iceCheckingTimeout&&(t.iceGatheringTimeout=!1,t.iceGatheringTimer=setTimeout(function(){t.logger.log("RTCIceChecking Timeout Triggered after "+e.iceCheckingTimeout+" milliseconds"),t.iceGatheringTimeout=!0,t.triggerIceGatheringComplete()},e.iceCheckingTimeout));break;case"complete":t.triggerIceGatheringComplete()}},this.peerConnection.oniceconnectionstatechange=function(){var e;switch(t.peerConnection.iceConnectionState){case"new":e="iceConnection";break;case"checking":e="iceConnectionChecking";break;case"connected":e="iceConnectionConnected";break;case"completed":e="iceConnectionCompleted";break;case"failed":e="iceConnectionFailed";break;case"disconnected":e="iceConnectionDisconnected";break;case"closed":e="iceConnectionClosed";break;default:return void t.logger.warn("Unknown iceConnection state: "+t.peerConnection.iceConnectionState)}t.logger.log("ICE Connection State changed to "+e),t.emit(e,t)}},r.prototype.acquire=function(e){var t=this;return e=this.checkAndDefaultConstraints(e),new Promise(function(r,i){t.logger.log("acquiring local media"),t.emit("userMediaRequest",e),e.audio||e.video?t.WebRTC.getUserMedia(e).then(function(e){t.observer.trackAdded(),t.emit("userMedia",e),r(e)}).catch(function(e){t.emit("userMediaFailed",e),i(e)}):r([])}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var r=new o.Exceptions.SessionDescriptionHandlerError("acquire",e,"unable to acquire streams");throw t.logger.error(r.message),r.error&&t.logger.error(r.error),r}).then(function(e){t.logger.log("acquired local media streams");try{return t.peerConnection.removeTrack&&t.peerConnection.getSenders().forEach(function(e){t.peerConnection.removeTrack(e)}),e}catch(e){return Promise.reject(e)}}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var r=new o.Exceptions.SessionDescriptionHandlerError("acquire",e,"error removing streams");throw t.logger.error(r.message),r.error&&t.logger.error(r.error),r}).then(function(e){try{(e=[].concat(e)).forEach(function(e){t.peerConnection.addTrack?e.getTracks().forEach(function(r){t.peerConnection.addTrack(r,e)}):t.peerConnection.addStream(e)})}catch(e){return Promise.reject(e)}return Promise.resolve()}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var r=new o.Exceptions.SessionDescriptionHandlerError("acquire",e,"error adding stream");throw t.logger.error(r.message),r.error&&t.logger.error(r.error),r})},r.prototype.hasOffer=function(e){var t="have-"+e+"-offer";return this.peerConnection.signalingState===t},r.prototype.isIceGatheringComplete=function(){return"complete"===this.peerConnection.iceGatheringState||this.iceGatheringTimeout},r.prototype.resetIceGatheringComplete=function(){this.iceGatheringTimeout=!1,this.logger.log("resetIceGatheringComplete"),this.iceGatheringTimer&&(clearTimeout(this.iceGatheringTimer),this.iceGatheringTimer=void 0),this.iceGatheringDeferred&&(this.iceGatheringDeferred.reject(),this.iceGatheringDeferred=void 0)},r.prototype.setDirection=function(e){var t=e.match(/a=(sendrecv|sendonly|recvonly|inactive)/);if(null===t)return this.direction=this.C.DIRECTION.NULL,void this.observer.directionChanged();var r=t[1];switch(r){case this.C.DIRECTION.SENDRECV:case this.C.DIRECTION.SENDONLY:case this.C.DIRECTION.RECVONLY:case this.C.DIRECTION.INACTIVE:this.direction=r;break;default:this.direction=this.C.DIRECTION.NULL}this.observer.directionChanged()},r.prototype.triggerIceGatheringComplete=function(){this.isIceGatheringComplete()&&(this.emit("iceGatheringComplete",this),this.iceGatheringTimer&&(clearTimeout(this.iceGatheringTimer),this.iceGatheringTimer=void 0),this.iceGatheringDeferred&&(this.iceGatheringDeferred.resolve(),this.iceGatheringDeferred=void 0))},r.prototype.waitForIceGatheringComplete=function(){return this.logger.log("waitForIceGatheringComplete"),this.isIceGatheringComplete()?(this.logger.log("ICE is already complete. Return resolved."),Promise.resolve()):(this.iceGatheringDeferred||(this.iceGatheringDeferred=a.Utils.defer()),this.logger.log("ICE is not complete. Returning promise"),this.iceGatheringDeferred?this.iceGatheringDeferred.promise:Promise.resolve())},r}(n.EventEmitter);t.SessionDescriptionHandler=d}).call(this,r(18))},function(e,t,r){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var i,n=r(0),s=r(5),o=r(4),a=r(10),c=r(8);!function(e){e[e.STATUS_CONNECTING=0]="STATUS_CONNECTING",e[e.STATUS_OPEN=1]="STATUS_OPEN",e[e.STATUS_CLOSING=2]="STATUS_CLOSING",e[e.STATUS_CLOSED=3]="STATUS_CLOSED"}(i=t.TransportStatus||(t.TransportStatus={}));var u=function(t){function r(r,n){void 0===n&&(n={});var s=t.call(this,r,n)||this;return s.WebSocket=(e.window||e).WebSocket,s.type=o.TypeStrings.Transport,s.reconnectionAttempts=0,s.status=i.STATUS_CONNECTING,s.configuration=s.loadConfig(n),s.server=s.configuration.wsServers[0],s}return n.__extends(r,t),r.prototype.isConnected=function(){return this.status===i.STATUS_OPEN},r.prototype.sendPromise=function(e,t){if(void 0===t&&(t={}),!this.statusAssert(i.STATUS_OPEN,t.force))return this.onError("unable to send message - WebSocket not open"),Promise.reject();var r=e.toString();return this.ws?(!0===this.configuration.traceSip&&this.logger.log("sending WebSocket message:\n\n"+r+"\n"),this.ws.send(r),Promise.resolve({msg:r})):(this.onError("unable to send message - WebSocket does not exist"),Promise.reject())},r.prototype.disconnectPromise=function(e){var t=this;return void 0===e&&(e={}),this.disconnectionPromise?this.disconnectionPromise:(e.code=e.code||1e3,this.statusTransition(i.STATUS_CLOSING,e.force)?(this.emit("disconnecting"),this.disconnectionPromise=new Promise(function(r,i){t.disconnectDeferredResolve=r,t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.ws?(t.stopSendingKeepAlives(),t.logger.log("closing WebSocket "+t.server.wsUri),t.ws.close(e.code,e.reason)):i("Attempted to disconnect but the websocket doesn't exist")}),this.disconnectionPromise):this.status===i.STATUS_CLOSED?Promise.resolve({overrideEvent:!0}):this.connectionPromise?this.connectionPromise.then(function(){return Promise.reject("The websocket did not disconnect")}).catch(function(){return Promise.resolve({overrideEvent:!0})}):Promise.reject("The websocket did not disconnect"))},r.prototype.connectPromise=function(e){var t=this;return void 0===e&&(e={}),this.status!==i.STATUS_CLOSING||e.force?this.connectionPromise?this.connectionPromise:(this.server=this.server||this.getNextWsServer(e.force),this.connectionPromise=new Promise(function(r,n){if((t.status===i.STATUS_OPEN||t.status===i.STATUS_CLOSING)&&!e.force)return t.logger.warn("WebSocket "+t.server.wsUri+" is already connected"),void n("Failed status check - attempted to open a connection but already open/closing");t.connectDeferredResolve=r,t.status=i.STATUS_CONNECTING,t.emit("connecting"),t.logger.log("connecting to WebSocket "+t.server.wsUri),t.disposeWs();try{t.ws=new WebSocket(t.server.wsUri,"sip")}catch(e){return t.ws=null,t.statusTransition(i.STATUS_CLOSED,!0),t.onError("error connecting to WebSocket "+t.server.wsUri+":"+e),void n("Failed to create a websocket")}t.ws?(t.connectionTimeout=setTimeout(function(){t.statusTransition(i.STATUS_CLOSED),t.logger.warn("took too long to connect - exceeded time set in configuration.connectionTimeout: "+t.configuration.connectionTimeout+"s"),t.emit("disconnected",{code:1e3}),t.connectionPromise=void 0,n("Connection timeout")},1e3*t.configuration.connectionTimeout),t.boundOnOpen=t.onOpen.bind(t),t.boundOnMessage=t.onMessage.bind(t),t.boundOnClose=t.onClose.bind(t),t.boundOnError=t.onWebsocketError.bind(t),t.ws.addEventListener("open",t.boundOnOpen),t.ws.addEventListener("message",t.boundOnMessage),t.ws.addEventListener("close",t.boundOnClose),t.ws.addEventListener("error",t.boundOnError)):n("Unexpected instance websocket not set")}),this.connectionPromise):Promise.reject("WebSocket "+this.server.wsUri+" is closing")},r.prototype.onMessage=function(e){var t,r=e.data;if(/^(\r\n)+$/.test(r))return this.clearKeepAliveTimeout(),void(!0===this.configuration.traceSip&&this.logger.log("received WebSocket message with CRLF Keep Alive response"));if(r){if("string"!=typeof r){try{t=String.fromCharCode.apply(null,new Uint8Array(r))}catch(e){return void this.logger.warn("received WebSocket binary message failed to be converted into string, message discarded")}!0===this.configuration.traceSip&&this.logger.log("received WebSocket binary message:\n\n"+r+"\n")}else!0===this.configuration.traceSip&&this.logger.log("received WebSocket text message:\n\n"+r+"\n"),t=r;this.emit("message",t)}else this.logger.warn("received empty message, message discarded")},r.prototype.onOpen=function(){if(this.status===i.STATUS_CLOSED){var e=this.ws;return this.disposeWs(),void e.close(1e3)}this.statusTransition(i.STATUS_OPEN,!0),this.emit("connected"),this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.logger.log("WebSocket "+this.server.wsUri+" connected"),void 0!==this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0),this.reconnectionAttempts=0,this.disconnectionPromise=void 0,this.disconnectDeferredResolve=void 0,this.startSendingKeepAlives(),this.connectDeferredResolve?this.connectDeferredResolve({overrideEvent:!0}):this.logger.warn("Unexpected websocket.onOpen with no connectDeferredResolve")},r.prototype.onClose=function(e){if(this.logger.log("WebSocket disconnected (code: "+e.code+(e.reason?"| reason: "+e.reason:"")+")"),this.status!==i.STATUS_CLOSING&&(this.logger.warn("WebSocket closed without SIP.js requesting it"),this.emit("transportError")),this.stopSendingKeepAlives(),this.connectionTimeout&&clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0,this.connectionPromise=void 0,this.connectDeferredResolve=void 0,this.disconnectDeferredResolve)return this.disconnectDeferredResolve({overrideEvent:!0}),this.statusTransition(i.STATUS_CLOSED),void(this.disconnectDeferredResolve=void 0);this.statusTransition(i.STATUS_CLOSED,!0),this.emit("disconnected",{code:e.code,reason:e.reason}),this.reconnect()},r.prototype.disposeWs=function(){this.ws&&(this.ws.removeEventListener("open",this.boundOnOpen),this.ws.removeEventListener("message",this.boundOnMessage),this.ws.removeEventListener("close",this.boundOnClose),this.ws.removeEventListener("error",this.boundOnError),this.ws=void 0)},r.prototype.onError=function(e){this.logger.warn("Transport error: "+e),this.emit("transportError")},r.prototype.onWebsocketError=function(){this.onError("The Websocket had an error")},r.prototype.reconnect=function(){var e=this;if(this.reconnectionAttempts>0&&this.logger.log("Reconnection attempt "+this.reconnectionAttempts+" failed"),this.noAvailableServers())return this.logger.warn("attempted to get next ws server but there are no available ws servers left"),this.logger.warn("no available ws servers left - going to closed state"),this.statusTransition(i.STATUS_CLOSED,!0),this.emit("closed"),void this.resetServerErrorStatus();this.isConnected()&&(this.logger.warn("attempted to reconnect while connected - forcing disconnect"),this.disconnect({force:!0})),this.reconnectionAttempts+=1,this.reconnectionAttempts>this.configuration.maxReconnectionAttempts?(this.logger.warn("maximum reconnection attempts for WebSocket "+this.server.wsUri),this.logger.log("transport "+this.server.wsUri+" failed | connection state set to 'error'"),this.server.isError=!0,this.emit("transportError"),this.noAvailableServers()||(this.server=this.getNextWsServer()),this.reconnectionAttempts=0,this.reconnect()):(this.logger.log("trying to reconnect to WebSocket "+this.server.wsUri+" (reconnection attempt "+this.reconnectionAttempts+")"),this.reconnectTimer=setTimeout(function(){e.connect(),e.reconnectTimer=void 0},1===this.reconnectionAttempts?0:1e3*this.configuration.reconnectionTimeout))},r.prototype.resetServerErrorStatus=function(){for(var e=0,t=this.configuration.wsServers;et[0].weight?t=[n]:n.weight===t[0].weight&&t.push(n))}return t[Math.floor(Math.random()*t.length)]},r.prototype.noAvailableServers=function(){for(var e=0,t=this.configuration.wsServers;e",weight:0,wsUri:"wss://edge.sip.onsip.com",isError:!1}],connectionTimeout:5,maxReconnectionAttempts:3,reconnectionTimeout:4,keepAliveInterval:0,keepAliveDebounce:10,traceSip:!1},r=this.getConfigurationCheck();for(var i in r.mandatory){if(!e.hasOwnProperty(i))throw new a.Exceptions.ConfigurationError(i);var n=e[i];if(void 0===(s=r.mandatory[i](n)))throw new a.Exceptions.ConfigurationError(i,n);t[i]=s}for(var i in r.optional)if(e.hasOwnProperty(i)){var s;if((n=e[i])instanceof Array&&0===n.length||null===n||""===n||void 0===n||"number"==typeof n&&isNaN(n))continue;if(void 0===(s=r.optional[i](n)))throw new a.Exceptions.ConfigurationError(i,n);t[i]=s}var o={};for(var i in t)t.hasOwnProperty(i)&&(o[i]={value:t[i]});var c=Object.defineProperties({},o);for(var i in this.logger.log("configuration parameters after validation:"),t)t.hasOwnProperty(i)&&this.logger.log("\xb7 "+i+": "+JSON.stringify(t[i]));return c},r.prototype.getConfigurationCheck=function(){return{mandatory:{},optional:{wsServers:function(e){if("string"==typeof e)e=[{wsUri:e}];else{if(!(e instanceof Array))return;for(var t=0;t",n.weight||(n.weight=0),n.isError=!1,n.scheme=o.scheme.toUpperCase()}return e},keepAliveInterval:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}},keepAliveDebounce:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}},traceSip:function(e){if("boolean"==typeof e)return e},connectionTimeout:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}},maxReconnectionAttempts:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>=0)return t}},reconnectionTimeout:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}}}}},r.C=i,r}(s.Transport);t.Transport=u}).call(this,r(18))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(5);t.DigestAuthentication=n.DigestAuthentication,t.Grammar=n.Grammar,t.IncomingRequest=n.IncomingRequestMessage,t.IncomingResponse=n.IncomingResponseMessage,t.LoggerFactory=n.LoggerFactory,t.NameAddrHeader=n.NameAddrHeader,t.OutgoingRequest=n.OutgoingRequestMessage,t.Timers=n.Timers,t.Transport=n.Transport,t.URI=n.URI;var s=r(12);t.ClientContext=s.ClientContext;var o=r(7);t.C=o.C;var a=r(4);t.DialogStatus=a.DialogStatus,t.SessionStatus=a.SessionStatus,t.TypeStrings=a.TypeStrings,t.UAStatus=a.UAStatus;var c=r(10);t.Exceptions=c.Exceptions;var u=r(55);t.Parser=u.Parser;var d=r(56);t.PublishContext=d.PublishContext;var p=r(34);t.ReferClientContext=p.ReferClientContext,t.ReferServerContext=p.ReferServerContext;var h=r(57);t.RegisterContext=h.RegisterContext;var l=r(17);t.ServerContext=l.ServerContext;var g=r(58);t.InviteClientContext=g.InviteClientContext,t.InviteServerContext=g.InviteServerContext,t.Session=g.Session;var f=r(59);t.Subscription=f.Subscription;var m=r(1),v={InviteClientTransaction:m.InviteClientTransaction,InviteServerTransaction:m.InviteServerTransaction,NonInviteClientTransaction:m.NonInviteClientTransaction,NonInviteServerTransaction:m.NonInviteServerTransaction};t.Transactions=v;var S=r(60);t.makeUserAgentCoreConfigurationFromUA=S.makeUserAgentCoreConfigurationFromUA,t.UA=S.UA;var T=r(8);t.Utils=T.Utils;var y=i.__importStar(r(98));t.Web=y;var E=r(54),b=E.title;t.name=b;var C=E.version;t.version=C;var _=i.__importStar(r(5));t.Core=_},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(0).__exportStar(r(65),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.ACK="ACK",e.BYE="BYE",e.CANCEL="CANCEL",e.INFO="INFO",e.INVITE="INVITE",e.MESSAGE="MESSAGE",e.NOTIFY="NOTIFY",e.OPTIONS="OPTIONS",e.REGISTER="REGISTER",e.UPDATE="UPDATE",e.SUBSCRIBE="SUBSCRIBE",e.PUBLISH="PUBLISH",e.REFER="REFER",e.PRACK="PRACK"}(t.C||(t.C={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(36),n=r(37),s=r(38);function o(e){return!(!e||"string"!=typeof e.content||"string"!=typeof e.contentType||void 0!==e.contentDisposition)||"string"==typeof e.contentDisposition}function a(e){return"application/sdp"===e?"session":"render"}t.fromBodyLegacy=function(e){var t="string"==typeof e?e:e.body,r="string"==typeof e?"application/sdp":e.contentType;return{contentDisposition:a(r),contentType:r,content:t}},t.getBody=function(e){var t,r,c,u;if(e instanceof i.IncomingRequestMessage&&e.body&&(t=(u=e.parseHeader("Content-Disposition"))?u.type:void 0,r=e.parseHeader("Content-Type"),c=e.body),e instanceof n.IncomingResponseMessage&&e.body&&(t=(u=e.parseHeader("Content-Disposition"))?u.type:void 0,r=e.parseHeader("Content-Type"),c=e.body),e instanceof s.OutgoingRequestMessage&&e.body)if(t=e.getHeader("Content-Disposition"),r=e.getHeader("Content-Type"),"string"==typeof e.body){if(!r)throw new Error("Header content type header does not equal body content type.");c=e.body}else{if(r&&r!==e.body.contentType)throw new Error("Header content type header does not equal body content type.");r=e.body.contentType,c=e.body.body}if(o(e)&&(t=e.contentDisposition,r=e.contentType,c=e.content),c){if(r&&!t&&(t=a(r)),!t)throw new Error("Content disposition undefined.");if(!r)throw new Error("Content type undefined.");return{contentDisposition:t,contentType:r,content:c}}},t.isBody=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(23),s=r(25),o=function(e){function t(r,i,n,s){var o=e.call(this)||this;return o.message=r,o.expected=i,o.found=n,o.location=s,o.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(o,t),o}return i.__extends(t,e),t.buildMessage=function(e,t){function r(e){return e.charCodeAt(0).toString(16).toUpperCase()}function i(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,function(e){return"\\x0"+r(e)}).replace(/[\x10-\x1F\x7F-\x9F]/g,function(e){return"\\x"+r(e)})}function n(e){return e.replace(/\\/g,"\\\\").replace(/\]/g,"\\]").replace(/\^/g,"\\^").replace(/-/g,"\\-").replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,function(e){return"\\x0"+r(e)}).replace(/[\x10-\x1F\x7F-\x9F]/g,function(e){return"\\x"+r(e)})}function s(e){switch(e.type){case"literal":return'"'+i(e.text)+'"';case"class":var t=e.parts.map(function(e){return Array.isArray(e)?n(e[0])+"-"+n(e[1]):n(e)});return"["+(e.inverted?"^":"")+t+"]";case"any":return"any character";case"end":return"end of input";case"other":return e.description}}return"Expected "+function(e){var t,r,i=e.map(s);if(i.sort(),i.length>0){for(t=1,r=1;t",T(">",!1),"\\",T("\\",!1),"[",T("[",!1),"]",T("]",!1),"{",T("{",!1),"}",T("}",!1),function(){return"*"},function(){return"/"},function(){return"="},function(){return"("},function(){return")"},function(){return">"},function(){return"<"},function(){return","},function(){return";"},function(){return":"},function(){return'"'},/^[!-']/,y([["!","'"]],!1,!1),/^[*-[]/,y([["*","["]],!1,!1),/^[\]-~]/,y([["]","~"]],!1,!1),function(e){return e},/^[#-[]/,y([["#","["]],!1,!1),/^[\0-\t]/,y([["\0","\t"]],!1,!1),/^[\x0B-\f]/,y([["\v","\f"]],!1,!1),/^[\x0E-\x7F]/,y([["\x0e","\x7f"]],!1,!1),function(){(t=t||{data:{}}).data.uri=new s.URI(t.data.scheme,t.data.user,t.data.host,t.data.port),delete t.data.scheme,delete t.data.user,delete t.data.host,delete t.data.host_type,delete t.data.port},function(){(t=t||{data:{}}).data.uri=new s.URI(t.data.scheme,t.data.user,t.data.host,t.data.port,t.data.uri_params,t.data.uri_headers),delete t.data.scheme,delete t.data.user,delete t.data.host,delete t.data.host_type,delete t.data.port,delete t.data.uri_params,"SIP_URI"===t.startRule&&(t.data=t.data.uri)},"sips",T("sips",!0),"sip",T("sip",!0),function(e){(t=t||{data:{}}).data.scheme=e},function(){(t=t||{data:{}}).data.user=decodeURIComponent(v().slice(0,-1))},function(){(t=t||{data:{}}).data.password=v()},function(){return(t=t||{data:{}}).data.host=v(),t.data.host},function(){return(t=t||{data:{}}).data.host_type="domain",v()},/^[a-zA-Z0-9_\-]/,y([["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),/^[a-zA-Z0-9\-]/,y([["a","z"],["A","Z"],["0","9"],"-"],!1,!1),function(){return(t=t||{data:{}}).data.host_type="IPv6",v()},"::",T("::",!1),function(){return(t=t||{data:{}}).data.host_type="IPv6",v()},function(){return(t=t||{data:{}}).data.host_type="IPv4",v()},"25",T("25",!1),/^[0-5]/,y([["0","5"]],!1,!1),"2",T("2",!1),/^[0-4]/,y([["0","4"]],!1,!1),"1",T("1",!1),/^[1-9]/,y([["1","9"]],!1,!1),function(e){return t=t||{data:{}},e=parseInt(e.join("")),t.data.port=e,e},"transport=",T("transport=",!0),"udp",T("udp",!0),"tcp",T("tcp",!0),"sctp",T("sctp",!0),"tls",T("tls",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.transport=e.toLowerCase()},"user=",T("user=",!0),"phone",T("phone",!0),"ip",T("ip",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.user=e.toLowerCase()},"method=",T("method=",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.method=e},"ttl=",T("ttl=",!0),function(e){(t=t||{data:{}}).data.params||(t.data.params={}),t.data.params.ttl=e},"maddr=",T("maddr=",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.maddr=e},"lr",T("lr",!0),function(){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.lr=void 0},function(e,r){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),r=null===r?void 0:r[1],t.data.uri_params[e.toLowerCase()]=r},function(e,r){e=e.join("").toLowerCase(),r=r.join(""),(t=t||{data:{}}).data.uri_headers||(t.data.uri_headers={}),t.data.uri_headers[e]?t.data.uri_headers[e].push(r):t.data.uri_headers[e]=[r]},function(){"Refer_To"===(t=t||{data:{}}).startRule&&(t.data.uri=new s.URI(t.data.scheme,t.data.user,t.data.host,t.data.port,t.data.uri_params,t.data.uri_headers),delete t.data.scheme,delete t.data.user,delete t.data.host,delete t.data.host_type,delete t.data.port,delete t.data.uri_params)},"//",T("//",!1),function(){(t=t||{data:{}}).data.scheme=v()},T("SIP",!0),function(){(t=t||{data:{}}).data.sip_version=v()},"INVITE",T("INVITE",!1),"ACK",T("ACK",!1),"VXACH",T("VXACH",!1),"OPTIONS",T("OPTIONS",!1),"BYE",T("BYE",!1),"CANCEL",T("CANCEL",!1),"REGISTER",T("REGISTER",!1),"SUBSCRIBE",T("SUBSCRIBE",!1),"NOTIFY",T("NOTIFY",!1),"REFER",T("REFER",!1),"PUBLISH",T("PUBLISH",!1),function(){return(t=t||{data:{}}).data.method=v(),t.data.method},function(e){(t=t||{data:{}}).data.status_code=parseInt(e.join(""))},function(){(t=t||{data:{}}).data.reason_phrase=v()},function(){(t=t||{data:{}}).data=v()},function(){var e,r;for(r=(t=t||{data:{}}).data.multi_header.length,e=0;e""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E'),R(";).# &;,"),R('2F""6F7G.} &2H""6H7I.q &2J""6J7K.e &2L""6L7M.Y &2N""6N7O.M &2P""6P7Q.A &2R""6R7S.5 &2T""6T7U.) &2V""6V7W'),R('%%2X""6X7Y/5#;#/,$;#/#$+#)(#\'#("\'#&\'#/"!&,)'),R('%%$;$0#*;$&/,#; /#$+")("\'#&\'#." &"/=#$;$/�#*;$&&&#/\'$8":Z" )("\'#&\'#'),R(';.." &"'),R("%$;'.# &;(0)*;'.# &;(&/?#28\"\"6879/0$;//'$8#:[# )(#'#(\"'#&'#"),R('%%$;2/�#*;2&&&#/g#$%$;.0#*;.&/,#;2/#$+")("\'#&\'#0=*%$;.0#*;.&/,#;2/#$+")("\'#&\'#&/#$+")("\'#&\'#/"!&,)'),R('4\\""5!7].# &;3'),R('4^""5!7_'),R('4`""5!7a'),R(';!.) &4b""5!7c'),R('%$;).\x95 &2F""6F7G.\x89 &2J""6J7K.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O/\x9e#0\x9b*;).\x95 &2F""6F7G.\x89 &2J""6J7K.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O&&&#/"!&,)'),R('%$;).\x89 &2F""6F7G.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O/\x92#0\x8f*;).\x89 &2F""6F7G.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O&&&#/"!&,)'),R('2T""6T7U.\xe3 &2V""6V7W.\xd7 &2f""6f7g.\xcb &2h""6h7i.\xbf &2:""6:7;.\xb3 &2D""6D7E.\xa7 &22""6273.\x9b &28""6879.\x8f &2j""6j7k.\x83 &;&.} &24""6475.q &2l""6l7m.e &2n""6n7o.Y &26""6677.M &2>""6>7?.A &2p""6p7q.5 &2r""6r7s.) &;\'.# &;('),R('%$;).\u012b &2F""6F7G.\u011f &2J""6J7K.\u0113 &2L""6L7M.\u0107 &2X""6X7Y.\xfb &2P""6P7Q.\xef &2H""6H7I.\xe3 &2@""6@7A.\xd7 &2d""6d7e.\xcb &2R""6R7S.\xbf &2N""6N7O.\xb3 &2T""6T7U.\xa7 &2V""6V7W.\x9b &2f""6f7g.\x8f &2h""6h7i.\x83 &28""6879.w &2j""6j7k.k &;&.e &24""6475.Y &2l""6l7m.M &2n""6n7o.A &26""6677.5 &2p""6p7q.) &2r""6r7s/\u0134#0\u0131*;).\u012b &2F""6F7G.\u011f &2J""6J7K.\u0113 &2L""6L7M.\u0107 &2X""6X7Y.\xfb &2P""6P7Q.\xef &2H""6H7I.\xe3 &2@""6@7A.\xd7 &2d""6d7e.\xcb &2R""6R7S.\xbf &2N""6N7O.\xb3 &2T""6T7U.\xa7 &2V""6V7W.\x9b &2f""6f7g.\x8f &2h""6h7i.\x83 &28""6879.w &2j""6j7k.k &;&.e &24""6475.Y &2l""6l7m.M &2n""6n7o.A &26""6677.5 &2p""6p7q.) &2r""6r7s&&&#/"!&,)'),R("%;//?#2P\"\"6P7Q/0$;//'$8#:t# )(#'#(\"'#&'#"),R("%;//?#24\"\"6475/0$;//'$8#:u# )(#'#(\"'#&'#"),R("%;//?#2>\"\"6>7?/0$;//'$8#:v# )(#'#(\"'#&'#"),R("%;//?#2T\"\"6T7U/0$;//'$8#:w# )(#'#(\"'#&'#"),R("%;//?#2V\"\"6V7W/0$;//'$8#:x# )(#'#(\"'#&'#"),R('%2h""6h7i/0#;//\'$8":y" )("\'#&\'#'),R('%;//6#2f""6f7g/\'$8":z" )("\'#&\'#'),R("%;//?#2D\"\"6D7E/0$;//'$8#:{# )(#'#(\"'#&'#"),R("%;//?#22\"\"6273/0$;//'$8#:|# )(#'#(\"'#&'#"),R("%;//?#28\"\"6879/0$;//'$8#:}# )(#'#(\"'#&'#"),R("%;//0#;&/'$8\":~\" )(\"'#&'#"),R("%;&/0#;//'$8\":~\" )(\"'#&'#"),R("%;=/T#$;G.) &;K.# &;F0/*;G.) &;K.# &;F&/,$;>/#$+#)(#'#(\"'#&'#"),R('4\x7f""5!7\x80.A &4\x81""5!7\x82.5 &4\x83""5!7\x84.) &;3.# &;.'),R("%%;//Q#;&/H$$;J.# &;K0)*;J.# &;K&/,$;&/#$+$)($'#(#'#(\"'#&'#/\"!&,)"),R("%;//]#;&/T$%$;J.# &;K0)*;J.# &;K&/\"!&,)/1$;&/($8$:\x85$!!)($'#(#'#(\"'#&'#"),R(';..G &2L""6L7M.; &4\x86""5!7\x87./ &4\x83""5!7\x84.# &;3'),R('%2j""6j7k/J#4\x88""5!7\x89.5 &4\x8a""5!7\x8b.) &4\x8c""5!7\x8d/#$+")("\'#&\'#'),R("%;N/M#28\"\"6879/>$;O.\" &\"/0$;S/'$8$:\x8e$ )($'#(#'#(\"'#&'#"),R("%;N/d#28\"\"6879/U$;O.\" &\"/G$;S/>$;_/5$;l.\" &\"/'$8&:\x8f& )(&'#(%'#($'#(#'#(\"'#&'#"),R('%3\x90""5$7\x91.) &3\x92""5#7\x93/\' 8!:\x94!! )'),R('%;P/]#%28""6879/,#;R/#$+")("\'#&\'#." &"/6$2:""6:7;/\'$8#:\x95# )(#\'#("\'#&\'#'),R("$;+.) &;-.# &;Q/2#0/*;+.) &;-.# &;Q&&&#"),R('2<""6<7=.q &2>""6>7?.e &2@""6@7A.Y &2B""6B7C.M &2D""6D7E.A &22""6273.5 &26""6677.) &24""6475'),R('%$;+._ &;-.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E0e*;+._ &;-.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E&/& 8!:\x96! )'),R('%;T/J#%28""6879/,#;^/#$+")("\'#&\'#." &"/#$+")("\'#&\'#'),R("%;U.) &;\\.# &;X/& 8!:\x97! )"),R('%$%;V/2#2J""6J7K/#$+")("\'#&\'#0<*%;V/2#2J""6J7K/#$+")("\'#&\'#&/D#;W/;$2J""6J7K." &"/\'$8#:\x98# )(#\'#("\'#&\'#'),R('$4\x99""5!7\x9a/,#0)*4\x99""5!7\x9a&&&#'),R('%4$""5!7%/?#$4\x9b""5!7\x9c0)*4\x9b""5!7\x9c&/#$+")("\'#&\'#'),R('%2l""6l7m/?#;Y/6$2n""6n7o/\'$8#:\x9d# )(#\'#("\'#&\'#'),R('%%;Z/\xb3#28""6879/\xa4$;Z/\x9b$28""6879/\x8c$;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+-)(-\'#(,\'#(+\'#(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0790 &%2\x9e""6\x9e7\x9f/\xa4#;Z/\x9b$28""6879/\x8c$;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+,)(,\'#(+\'#(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u06f9 &%2\x9e""6\x9e7\x9f/\x8c#;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+*)(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u067a &%2\x9e""6\x9e7\x9f/t#;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0613 &%2\x9e""6\x9e7\x9f/\\#;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+&)(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u05c4 &%2\x9e""6\x9e7\x9f/D#;Z/;$28""6879/,$;[/#$+$)($\'#(#\'#("\'#&\'#.\u058d &%2\x9e""6\x9e7\x9f/,#;[/#$+")("\'#&\'#.\u056e &%2\x9e""6\x9e7\x9f/,#;Z/#$+")("\'#&\'#.\u054f &%;Z/\x9b#2\x9e""6\x9e7\x9f/\x8c$;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$++)(+\'#(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u04c7 &%;Z/\xaa#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x83$2\x9e""6\x9e7\x9f/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+*)(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0430 &%;Z/\xb9#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x92$%28""6879/,#;Z/#$+")("\'#&\'#." &"/k$2\x9e""6\x9e7\x9f/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+))()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u038a &%;Z/\xc8#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xa1$%28""6879/,#;Z/#$+")("\'#&\'#." &"/z$%28""6879/,#;Z/#$+")("\'#&\'#." &"/S$2\x9e""6\x9e7\x9f/D$;Z/;$28""6879/,$;[/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u02d5 &%;Z/\xd7#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xb0$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x89$%28""6879/,#;Z/#$+")("\'#&\'#." &"/b$%28""6879/,#;Z/#$+")("\'#&\'#." &"/;$2\x9e""6\x9e7\x9f/,$;[/#$+\')(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0211 &%;Z/\xfe#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xd7$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xb0$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x89$%28""6879/,#;Z/#$+")("\'#&\'#." &"/b$%28""6879/,#;Z/#$+")("\'#&\'#." &"/;$2\x9e""6\x9e7\x9f/,$;Z/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0126 &%;Z/\u011c#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xf5$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xce$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xa7$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x80$%28""6879/,#;Z/#$+")("\'#&\'#." &"/Y$%28""6879/,#;Z/#$+")("\'#&\'#." &"/2$2\x9e""6\x9e7\x9f/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#/& 8!:\xa0! )'),R('%;#/M#;#." &"/?$;#." &"/1$;#." &"/#$+$)($\'#(#\'#("\'#&\'#'),R("%;Z/;#28\"\"6879/,$;Z/#$+#)(#'#(\"'#&'#.# &;\\"),R("%;]/o#2J\"\"6J7K/`$;]/W$2J\"\"6J7K/H$;]/?$2J\"\"6J7K/0$;]/'$8':\xa1' )(''#(&'#(%'#($'#(#'#(\"'#&'#"),R('%2\xa2""6\xa27\xa3/2#4\xa4""5!7\xa5/#$+")("\'#&\'#.\x98 &%2\xa6""6\xa67\xa7/;#4\xa8""5!7\xa9/,$;!/#$+#)(#\'#("\'#&\'#.j &%2\xaa""6\xaa7\xab/5#;!/,$;!/#$+#)(#\'#("\'#&\'#.B &%4\xac""5!7\xad/,#;!/#$+")("\'#&\'#.# &;!'),R('%%;!." &"/[#;!." &"/M$;!." &"/?$;!." &"/1$;!." &"/#$+%)(%\'#($\'#(#\'#("\'#&\'#/\' 8!:\xae!! )'),R('$%22""6273/,#;`/#$+")("\'#&\'#0<*%22""6273/,#;`/#$+")("\'#&\'#&'),R(";a.A &;b.; &;c.5 &;d./ &;e.) &;f.# &;g"),R('%3\xaf""5*7\xb0/a#3\xb1""5#7\xb2.G &3\xb3""5#7\xb4.; &3\xb5""5$7\xb6./ &3\xb7""5#7\xb8.# &;6/($8":\xb9"! )("\'#&\'#'),R('%3\xba""5%7\xbb/I#3\xbc""5%7\xbd./ &3\xbe""5"7\xbf.# &;6/($8":\xc0"! )("\'#&\'#'),R('%3\xc1""5\'7\xc2/1#;\x90/($8":\xc3"! )("\'#&\'#'),R('%3\xc4""5$7\xc5/1#;\xf0/($8":\xc6"! )("\'#&\'#'),R('%3\xc7""5&7\xc8/1#;T/($8":\xc9"! )("\'#&\'#'),R('%3\xca""5"7\xcb/N#%2>""6>7?/,#;6/#$+")("\'#&\'#." &"/\'$8":\xcc" )("\'#&\'#'),R('%;h/P#%2>""6>7?/,#;i/#$+")("\'#&\'#." &"/)$8":\xcd""! )("\'#&\'#'),R('%$;j/�#*;j&&&#/"!&,)'),R('%$;j/�#*;j&&&#/"!&,)'),R(";k.) &;+.# &;-"),R('2l""6l7m.e &2n""6n7o.Y &24""6475.M &28""6879.A &2<""6<7=.5 &2@""6@7A.) &2B""6B7C'),R('%26""6677/n#;m/e$$%2<""6<7=/,#;m/#$+")("\'#&\'#0<*%2<""6<7=/,#;m/#$+")("\'#&\'#&/#$+#)(#\'#("\'#&\'#'),R('%;n/A#2>""6>7?/2$;o/)$8#:\xce#"" )(#\'#("\'#&\'#'),R("$;p.) &;+.# &;-/2#0/*;p.) &;+.# &;-&&&#"),R("$;p.) &;+.# &;-0/*;p.) &;+.# &;-&"),R('2l""6l7m.e &2n""6n7o.Y &24""6475.M &26""6677.A &28""6879.5 &2@""6@7A.) &2B""6B7C'),R(";\x91.# &;r"),R("%;\x90/G#;'/>$;s/5$;'/,$;\x84/#$+%)(%'#($'#(#'#(\"'#&'#"),R(";M.# &;t"),R("%;\x7f/E#28\"\"6879/6$;u.# &;x/'$8#:\xcf# )(#'#(\"'#&'#"),R('%;v.# &;w/J#%26""6677/,#;\x83/#$+")("\'#&\'#." &"/#$+")("\'#&\'#'),R('%2\xd0""6\xd07\xd1/:#;\x80/1$;w." &"/#$+#)(#\'#("\'#&\'#'),R('%24""6475/,#;{/#$+")("\'#&\'#'),R("%;z/3#$;y0#*;y&/#$+\")(\"'#&'#"),R(";*.) &;+.# &;-"),R(';+.\x8f &;-.\x89 &22""6273.} &26""6677.q &28""6879.e &2:""6:7;.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E'),R('%;|/e#$%24""6475/,#;|/#$+")("\'#&\'#0<*%24""6475/,#;|/#$+")("\'#&\'#&/#$+")("\'#&\'#'),R('%$;~0#*;~&/e#$%22""6273/,#;}/#$+")("\'#&\'#0<*%22""6273/,#;}/#$+")("\'#&\'#&/#$+")("\'#&\'#'),R("$;~0#*;~&"),R(';+.w &;-.q &28""6879.e &2:""6:7;.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E'),R('%%;"/\x87#$;".G &;!.A &2@""6@7A.5 &2F""6F7G.) &2J""6J7K0M*;".G &;!.A &2@""6@7A.5 &2F""6F7G.) &2J""6J7K&/#$+")("\'#&\'#/& 8!:\xd2! )'),R(";\x81.# &;\x82"),R('%%;O/2#2:""6:7;/#$+")("\'#&\'#." &"/,#;S/#$+")("\'#&\'#." &"'),R('$;+.\x83 &;-.} &2B""6B7C.q &2D""6D7E.e &22""6273.Y &28""6879.M &2:""6:7;.A &2<""6<7=.5 &2>""6>7?.) &2@""6@7A/\x8c#0\x89*;+.\x83 &;-.} &2B""6B7C.q &2D""6D7E.e &22""6273.Y &28""6879.M &2:""6:7;.A &2<""6<7=.5 &2>""6>7?.) &2@""6@7A&&&#'),R("$;y0#*;y&"),R('%3\x92""5#7\xd3/q#24""6475/b$$;!/�#*;!&&&#/L$2J""6J7K/=$$;!/�#*;!&&&#/\'$8%:\xd4% )(%\'#($\'#(#\'#("\'#&\'#'),R('2\xd5""6\xd57\xd6'),R('2\xd7""6\xd77\xd8'),R('2\xd9""6\xd97\xda'),R('2\xdb""6\xdb7\xdc'),R('2\xdd""6\xdd7\xde'),R('2\xdf""6\xdf7\xe0'),R('2\xe1""6\xe17\xe2'),R('2\xe3""6\xe37\xe4'),R('2\xe5""6\xe57\xe6'),R('2\xe7""6\xe77\xe8'),R('2\xe9""6\xe97\xea'),R("%;\x85.Y &;\x86.S &;\x88.M &;\x89.G &;\x8a.A &;\x8b.; &;\x8c.5 &;\x8f./ &;\x8d.) &;\x8e.# &;6/& 8!:\xeb! )"),R("%;\x84/G#;'/>$;\x92/5$;'/,$;\x94/#$+%)(%'#($'#(#'#(\"'#&'#"),R("%;\x93/' 8!:\xec!! )"),R("%;!/5#;!/,$;!/#$+#)(#'#(\"'#&'#"),R("%$;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(0G*;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(&/& 8!:\xed! )"),R("%;\xb6/Y#$%;A/,#;\xb6/#$+\")(\"'#&'#06*%;A/,#;\xb6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),R('%;9/N#%2:""6:7;/,#;9/#$+")("\'#&\'#." &"/\'$8":\xee" )("\'#&\'#'),R("%;:.c &%;\x98/Y#$%;A/,#;\x98/#$+\")(\"'#&'#06*%;A/,#;\x98/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/& 8!:\xef! )"),R("%;L.# &;\x99/]#$%;B/,#;\x9b/#$+\")(\"'#&'#06*%;B/,#;\x9b/#$+\")(\"'#&'#&/'$8\":\xf0\" )(\"'#&'#"),R("%;\x9a.\" &\"/>#;@/5$;M/,$;?/#$+$)($'#(#'#(\"'#&'#"),R("%%;6/Y#$%;./,#;6/#$+\")(\"'#&'#06*%;./,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#.# &;H/' 8!:\xf1!! )"),R(";\x9c.) &;\x9d.# &;\xa0"),R("%3\xf2\"\"5!7\xf3/:#;$;\xcf/5$;./,$;\x90/#$+%)(%'#($'#(#'#(\"'#&'#"),R("%$;!/�#*;!&&&#/' 8!:\u014b!! )"),R("%;\xd1/]#$%;A/,#;\xd1/#$+\")(\"'#&'#06*%;A/,#;\xd1/#$+\")(\"'#&'#&/'$8\":\u014c\" )(\"'#&'#"),R("%;\x99/]#$%;B/,#;\xa0/#$+\")(\"'#&'#06*%;B/,#;\xa0/#$+\")(\"'#&'#&/'$8\":\u014d\" )(\"'#&'#"),R('%;L.O &;\x99.I &%;@." &"/:#;t/1$;?." &"/#$+#)(#\'#("\'#&\'#/]#$%;B/,#;\xa0/#$+")("\'#&\'#06*%;B/,#;\xa0/#$+")("\'#&\'#&/\'$8":\u014e" )("\'#&\'#'),R("%;\xd4/]#$%;B/,#;\xd5/#$+\")(\"'#&'#06*%;B/,#;\xd5/#$+\")(\"'#&'#&/'$8\":\u014f\" )(\"'#&'#"),R("%;\x96/& 8!:\u0150! )"),R('%3\u0151""5(7\u0152/:#;$;6/5$;;/,$;\xec/#$+%)(%'#($'#(#'#(\"'#&'#"),R('%3\x92""5#7\xd3.# &;6/\' 8!:\u018b!! )'),R('%3\xb1""5#7\u018c.G &3\xb3""5#7\u018d.; &3\xb7""5#7\u018e./ &3\xb5""5$7\u018f.# &;6/\' 8!:\u0190!! )'),R('%;\xee/D#%;C/,#;\xef/#$+")("\'#&\'#." &"/#$+")("\'#&\'#'),R("%;U.) &;\\.# &;X/& 8!:\u0191! )"),R('%%;!." &"/[#;!." &"/M$;!." &"/?$;!." &"/1$;!." &"/#$+%)(%\'#($\'#(#\'#("\'#&\'#/\' 8!:\u0192!! )'),R('%%;!/?#;!." &"/1$;!." &"/#$+#)(#\'#("\'#&\'#/\' 8!:\u0193!! )'),R(";\xbe"),R('%;\x9e/^#$%;B/,#;\xf3/#$+")("\'#&\'#06*%;B/,#;\xf3/#$+")("\'#&\'#&/($8":\u0194"!!)("\'#&\'#'),R(";\xf4.# &;\xa0"),R('%2\u0195""6\u01957\u0196/L#;""6>7?'),R('%;\u0100/b#28""6879/S$;\xfb/J$%2\u01a3""6\u01a37\u01a4/,#;\xec/#$+")("\'#&\'#." &"/#$+$)($\'#(#\'#("\'#&\'#'),R('%3\u01a5""5%7\u01a6.) &3\u01a7""5$7\u01a8/\' 8!:\u01a1!! )'),R('%3\xb1""5#7\xb2.6 &3\xb3""5#7\xb4.* &$;+0#*;+&/\' 8!:\u01a9!! )'),R("%;\u0104/\x87#2F\"\"6F7G/x$;\u0103/o$2F\"\"6F7G/`$;\u0103/W$2F\"\"6F7G/H$;\u0103/?$2F\"\"6F7G/0$;\u0105/'$8):\u01aa) )()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#"),R("%;#/>#;#/5$;#/,$;#/#$+$)($'#(#'#(\"'#&'#"),R("%;\u0103/,#;\u0103/#$+\")(\"'#&'#"),R("%;\u0103/5#;\u0103/,$;\u0103/#$+#)(#'#(\"'#&'#"),R("%;q/T#$;m0#*;m&/D$%; /,#;\xf8/#$+\")(\"'#&'#.\" &\"/#$+#)(#'#(\"'#&'#"),R('%2\u01ab""6\u01ab7\u01ac.) &2\u01ad""6\u01ad7\u01ae/w#;0/n$;\u0108/e$$%;B/2#;\u0109.# &;\xa0/#$+")("\'#&\'#0<*%;B/2#;\u0109.# &;\xa0/#$+")("\'#&\'#&/#$+$)($\'#(#\'#("\'#&\'#'),R(";\x99.# &;L"),R("%2\u01af\"\"6\u01af7\u01b0/5#;g&&(g=p,f=[]),f.push(e))}function _(e,t,r){return new o(o.buildMessage(e,t),e,t,r)}function R(e){return e.split("").map(function(e){return e.charCodeAt(0)-32})}if(t.data={},(r=function t(r){for(var n,s=d[r],o=0,a=[],c=s.length,l=[],g=[];;){for(;op?(c=o+3+s[o+1],o+=3):(c=o+3+s[o+1]+s[o+2],o+=3+s[o+1]);break;case 18:l.push(c),a.push(o+4+s[o+2]+s[o+3]),e.substr(p,u[s[o+1]].length)===u[s[o+1]]?(c=o+4+s[o+2],o+=4):(c=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 19:l.push(c),a.push(o+4+s[o+2]+s[o+3]),e.substr(p,u[s[o+1]].length).toLowerCase()===u[s[o+1]]?(c=o+4+s[o+2],o+=4):(c=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 20:l.push(c),a.push(o+4+s[o+2]+s[o+3]),u[s[o+1]].test(e.charAt(p))?(c=o+4+s[o+2],o+=4):(c=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 21:g.push(e.substr(p,s[o+1])),p+=s[o+1],o+=2;break;case 22:g.push(u[s[o+1]]),p+=u[s[o+1]].length,o+=2;break;case 23:g.push(i),0===m&&C(u[s[o+1]]),o+=2;break;case 24:h=g[g.length-1-s[o+1]],o+=2;break;case 25:h=p,o++;break;case 26:n=s.slice(o+4,o+4+s[o+3]).map(function(e){return g[g.length-1-e]}),g.splice(g.length-s[o+2],s[o+2],u[s[o+1]].apply(null,n)),o+=4+s[o+3];break;case 27:g.push(t(s[o+1])),o+=2;break;case 28:m++,o++;break;case 29:m--,o++;break;default:throw new Error("Invalid opcode: "+s[o]+".")}if(!(l.length>0))break;c=l.pop(),o=a.pop()}return g[0]}(c))!==i&&p===e.length)return r;throw r!==i&&p-1)this.qop="auth";else{if(!(t.qop.indexOf("auth-int")>-1))return this.logger.warn("challenge without Digest qop different than 'auth' or 'auth-int', authentication aborted"),!1;this.qop="auth-int"}else this.qop=void 0;return this.method=e.method,this.uri=e.ruri,this.cnonce=n.createRandomToken(12),this.nc+=1,this.updateNcHex(),4294967296===this.nc&&(this.nc=1,this.ncHex="00000001"),this.calculateResponse(r),!0},e.prototype.toString=function(){var e=[];if(!this.response)throw new Error("response field does not exist, cannot generate Authorization header");return e.push("algorithm="+this.algorithm),e.push('username="'+this.username+'"'),e.push('realm="'+this.realm+'"'),e.push('nonce="'+this.nonce+'"'),e.push('uri="'+this.uri+'"'),e.push('response="'+this.response+'"'),this.opaque&&e.push('opaque="'+this.opaque+'"'),this.qop&&(e.push("qop="+this.qop),e.push('cnonce="'+this.cnonce+'"'),e.push("nc="+this.ncHex)),"Digest "+e.join(", ")},e.prototype.updateNcHex=function(){var e=Number(this.nc).toString(16);this.ncHex="00000000".substr(0,8-e.length)+e},e.prototype.calculateResponse=function(e){var t,r=i.default(this.username+":"+this.realm+":"+this.password);"auth"===this.qop?(t=i.default(this.method+":"+this.uri),this.response=i.default(r+":"+this.nonce+":"+this.ncHex+":"+this.cnonce+":auth:"+t)):"auth-int"===this.qop?(t=i.default(this.method+":"+this.uri+":"+i.default(e||"")),this.response=i.default(r+":"+this.nonce+":"+this.ncHex+":"+this.cnonce+":auth-int:"+t)):void 0===this.qop&&(t=i.default(this.method+":"+this.uri),this.response=i.default(r+":"+this.nonce+":"+t))},e}();t.DigestAuthentication=s},function(e,t,r){var i;e.exports=(i=r(70),function(e){var t=i,r=t.lib,n=r.WordArray,s=r.Hasher,o=t.algo,a=[];!function(){for(var t=0;t<64;t++)a[t]=4294967296*e.abs(e.sin(t+1))|0}();var c=o.MD5=s.extend({_doReset:function(){this._hash=new n.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(e,t){for(var r=0;r<16;r++){var i=t+r,n=e[i];e[i]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8)}var s=this._hash.words,o=e[t+0],c=e[t+1],l=e[t+2],g=e[t+3],f=e[t+4],m=e[t+5],v=e[t+6],S=e[t+7],T=e[t+8],y=e[t+9],E=e[t+10],b=e[t+11],C=e[t+12],_=e[t+13],R=e[t+14],w=e[t+15],A=s[0],I=s[1],x=s[2],N=s[3];A=u(A,I,x,N,o,7,a[0]),N=u(N,A,I,x,c,12,a[1]),x=u(x,N,A,I,l,17,a[2]),I=u(I,x,N,A,g,22,a[3]),A=u(A,I,x,N,f,7,a[4]),N=u(N,A,I,x,m,12,a[5]),x=u(x,N,A,I,v,17,a[6]),I=u(I,x,N,A,S,22,a[7]),A=u(A,I,x,N,T,7,a[8]),N=u(N,A,I,x,y,12,a[9]),x=u(x,N,A,I,E,17,a[10]),I=u(I,x,N,A,b,22,a[11]),A=u(A,I,x,N,C,7,a[12]),N=u(N,A,I,x,_,12,a[13]),x=u(x,N,A,I,R,17,a[14]),A=d(A,I=u(I,x,N,A,w,22,a[15]),x,N,c,5,a[16]),N=d(N,A,I,x,v,9,a[17]),x=d(x,N,A,I,b,14,a[18]),I=d(I,x,N,A,o,20,a[19]),A=d(A,I,x,N,m,5,a[20]),N=d(N,A,I,x,E,9,a[21]),x=d(x,N,A,I,w,14,a[22]),I=d(I,x,N,A,f,20,a[23]),A=d(A,I,x,N,y,5,a[24]),N=d(N,A,I,x,R,9,a[25]),x=d(x,N,A,I,g,14,a[26]),I=d(I,x,N,A,T,20,a[27]),A=d(A,I,x,N,_,5,a[28]),N=d(N,A,I,x,l,9,a[29]),x=d(x,N,A,I,S,14,a[30]),A=p(A,I=d(I,x,N,A,C,20,a[31]),x,N,m,4,a[32]),N=p(N,A,I,x,T,11,a[33]),x=p(x,N,A,I,b,16,a[34]),I=p(I,x,N,A,R,23,a[35]),A=p(A,I,x,N,c,4,a[36]),N=p(N,A,I,x,f,11,a[37]),x=p(x,N,A,I,S,16,a[38]),I=p(I,x,N,A,E,23,a[39]),A=p(A,I,x,N,_,4,a[40]),N=p(N,A,I,x,o,11,a[41]),x=p(x,N,A,I,g,16,a[42]),I=p(I,x,N,A,v,23,a[43]),A=p(A,I,x,N,y,4,a[44]),N=p(N,A,I,x,C,11,a[45]),x=p(x,N,A,I,w,16,a[46]),A=h(A,I=p(I,x,N,A,l,23,a[47]),x,N,o,6,a[48]),N=h(N,A,I,x,S,10,a[49]),x=h(x,N,A,I,R,15,a[50]),I=h(I,x,N,A,m,21,a[51]),A=h(A,I,x,N,C,6,a[52]),N=h(N,A,I,x,g,10,a[53]),x=h(x,N,A,I,E,15,a[54]),I=h(I,x,N,A,c,21,a[55]),A=h(A,I,x,N,T,6,a[56]),N=h(N,A,I,x,w,10,a[57]),x=h(x,N,A,I,v,15,a[58]),I=h(I,x,N,A,_,21,a[59]),A=h(A,I,x,N,f,6,a[60]),N=h(N,A,I,x,b,10,a[61]),x=h(x,N,A,I,l,15,a[62]),I=h(I,x,N,A,y,21,a[63]),s[0]=s[0]+A|0,s[1]=s[1]+I|0,s[2]=s[2]+x|0,s[3]=s[3]+N|0},_doFinalize:function(){var t=this._data,r=t.words,i=8*this._nDataBytes,n=8*t.sigBytes;r[n>>>5]|=128<<24-n%32;var s=e.floor(i/4294967296),o=i;r[15+(n+64>>>9<<4)]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),r[14+(n+64>>>9<<4)]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),t.sigBytes=4*(r.length+1),this._process();for(var a=this._hash,c=a.words,u=0;u<4;u++){var d=c[u];c[u]=16711935&(d<<8|d>>>24)|4278255360&(d<<24|d>>>8)}return a},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}});function u(e,t,r,i,n,s,o){var a=e+(t&r|~t&i)+n+o;return(a<>>32-s)+t}function d(e,t,r,i,n,s,o){var a=e+(t&i|r&~i)+n+o;return(a<>>32-s)+t}function p(e,t,r,i,n,s,o){var a=e+(t^r^i)+n+o;return(a<>>32-s)+t}function h(e,t,r,i,n,s,o){var a=e+(r^(t|~i))+n+o;return(a<>>32-s)+t}t.MD5=s._createHelper(c),t.HmacMD5=s._createHmacHelper(c)}(Math),i.MD5)},function(e,t,r){var i;e.exports=(i=i||function(e,t){var r=Object.create||function(){function e(){}return function(t){var r;return e.prototype=t,r=new e,e.prototype=null,r}}(),i={},n=i.lib={},s=n.Base={extend:function(e){var t=r(this);return e&&t.mixIn(e),t.hasOwnProperty("init")&&this.init!==t.init||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},o=n.WordArray=s.extend({init:function(e,t){e=this.words=e||[],this.sigBytes=null!=t?t:4*e.length},toString:function(e){return(e||c).stringify(this)},concat:function(e){var t=this.words,r=e.words,i=this.sigBytes,n=e.sigBytes;if(this.clamp(),i%4)for(var s=0;s>>2]>>>24-s%4*8&255;t[i+s>>>2]|=o<<24-(i+s)%4*8}else for(var s=0;s>>2]=r[s>>>2];return this.sigBytes+=n,this},clamp:function(){var t=this.words,r=this.sigBytes;t[r>>>2]&=4294967295<<32-r%4*8,t.length=e.ceil(r/4)},clone:function(){var e=s.clone.call(this);return e.words=this.words.slice(0),e},random:function(t){for(var r,i=[],n=function(t){var t=t,r=987654321,i=4294967295;return function(){var n=((r=36969*(65535&r)+(r>>16)&i)<<16)+(t=18e3*(65535&t)+(t>>16)&i)&i;return n/=4294967296,(n+=.5)*(e.random()>.5?1:-1)}},s=0;s>>2]>>>24-n%4*8&255;i.push((s>>>4).toString(16)),i.push((15&s).toString(16))}return i.join("")},parse:function(e){for(var t=e.length,r=[],i=0;i>>3]|=parseInt(e.substr(i,2),16)<<24-i%8*4;return new o.init(r,t/2)}},u=a.Latin1={stringify:function(e){for(var t=e.words,r=e.sigBytes,i=[],n=0;n>>2]>>>24-n%4*8&255;i.push(String.fromCharCode(s))}return i.join("")},parse:function(e){for(var t=e.length,r=[],i=0;i>>2]|=(255&e.charCodeAt(i))<<24-i%4*8;return new o.init(r,t)}},d=a.Utf8={stringify:function(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function(e){return u.parse(unescape(encodeURIComponent(e)))}},p=n.BufferedBlockAlgorithm=s.extend({reset:function(){this._data=new o.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=d.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var r=this._data,i=r.words,n=r.sigBytes,s=this.blockSize,a=4*s,c=n/a,u=(c=t?e.ceil(c):e.max((0|c)-this._minBufferSize,0))*s,d=e.min(4*u,n);if(u){for(var p=0;p699)throw new TypeError("Invalid statusCode: "+t.statusCode);var n=t.reasonPhrase?t.reasonPhrase:i.getReasonPhrase(t.statusCode),s="SIP/2.0 "+t.statusCode+" "+n+r;t.statusCode>=100&&t.statusCode,t.statusCode;var o="From: "+e.getHeader("From")+r,a="Call-ID: "+e.callId+r,c="CSeq: "+e.cseq+" "+e.method+r,u=e.getHeaders("via").reduce(function(e,t){return e+"Via: "+t+r},""),d="To: "+e.getHeader("to");if(t.statusCode>100&&!e.parseHeader("to").hasParam("tag")){var p=t.toTag;p||(p=i.newTag()),d+=";tag="+p}d+=r;var h="";t.supported&&(h="Supported: "+t.supported.join(", ")+r);var l="";t.userAgent&&(l="User-Agent: "+t.userAgent+r);var g="";return t.extraHeaders&&(g=t.extraHeaders.reduce(function(e,t){return e+t.trim()+r},"")),s+=u,s+=o,s+=d,s+=c,s+=a,s+=h,s+=l,s+=g,t.body?(s+="Content-Type: "+t.body.contentType+r,s+="Content-Length: "+i.str_utf8_length(t.body.content)+r+r,s+=t.body.content):s+="Content-Length: 0\r\n\r\n",{message:s}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(26),o=r(11),a=r(1),c=r(40),u=r(41),d=r(79),p=r(42),h=r(43),l=r(31),g=r(44),f=r(45),m=r(46),v=r(47),S=r(48),T=r(49),y=function(e){function t(t,r,i,n){var o=e.call(this,r,i)||this;return o.initialTransaction=t,o._signalingState=s.SignalingState.Initial,o.ackWait=!1,o.delegate=n,t instanceof a.InviteServerTransaction&&(o.ackWait=!0),o.early||o.start2xxRetransmissionTimer(),o.signalingStateTransition(t.request),o.logger=r.loggerFactory.getLogger("sip.invite-dialog"),o.logger.log("INVITE dialog "+o.id+" constructed"),o}return i.__extends(t,e),t.prototype.dispose=function(){e.prototype.dispose.call(this),this._signalingState=s.SignalingState.Closed,this._offer=void 0,this._answer=void 0,this.invite2xxTimer&&(clearTimeout(this.invite2xxTimer),this.invite2xxTimer=void 0),this.logger.log("INVITE dialog "+this.id+" destroyed")},Object.defineProperty(t.prototype,"sessionState",{get:function(){return this.early?s.SessionState.Early:this.ackWait?s.SessionState.AckWait:this._signalingState===s.SignalingState.Closed?s.SessionState.Terminated:s.SessionState.Confirmed},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"signalingState",{get:function(){return this._signalingState},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"offer",{get:function(){return this._offer},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"answer",{get:function(){return this._answer},enumerable:!0,configurable:!0}),t.prototype.confirm=function(){this.early&&this.start2xxRetransmissionTimer(),e.prototype.confirm.call(this)},t.prototype.reConfirm=function(){this.reinviteUserAgentServer&&this.startReInvite2xxRetransmissionTimer()},t.prototype.ack=function(e){var t;if(void 0===e&&(e={}),this.logger.log("INVITE dialog "+this.id+" sending ACK request"),this.reinviteUserAgentClient){if(!(this.reinviteUserAgentClient.transaction instanceof a.InviteClientTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");t=this.reinviteUserAgentClient.transaction,this.reinviteUserAgentClient=void 0}else{if(!(this.initialTransaction instanceof a.InviteClientTransaction))throw new Error("Initial transaction not instance of InviteClientTransaction.");t=this.initialTransaction}e.cseq=t.request.cseq;var r=this.createOutgoingRequestMessage(n.C.ACK,e);return t.ackResponse(r),this.signalingStateTransition(r),{message:r}},t.prototype.bye=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending BYE request"),this.initialTransaction instanceof a.InviteServerTransaction){if(this.early)throw new Error("UAS MUST NOT send a BYE on early dialogs.");if(this.ackWait&&this.initialTransaction.state!==a.TransactionState.Terminated)throw new Error("UAS MUST NOT send a BYE on a confirmed dialog until it has received an ACK for its 2xx response or until the server transaction times out.")}return new c.ByeUserAgentClient(this,e,t)},t.prototype.info=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending INFO request"),this.early)throw new Error("Dialog not confirmed.");return new d.InfoUserAgentClient(this,e,t)},t.prototype.invite=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending INVITE request"),this.early)throw new Error("Dialog not confirmed.");if(this.reinviteUserAgentClient)throw new Error("There is an ongoing re-INVITE client transaction.");if(this.reinviteUserAgentServer)throw new Error("There is an ongoing re-INVITE server transaction.");return new m.ReInviteUserAgentClient(this,e,t)},t.prototype.notify=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending NOTIFY request"),this.early)throw new Error("Dialog not confirmed.");return new h.NotifyUserAgentClient(this,e,t)},t.prototype.prack=function(e,t){return this.logger.log("INVITE dialog "+this.id+" sending PRACK request"),new g.PrackUserAgentClient(this,e,t)},t.prototype.refer=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending REFER request"),this.early)throw new Error("Dialog not confirmed.");return new S.ReferUserAgentClient(this,e,t)},t.prototype.receiveRequest=function(t){if(this.logger.log("INVITE dialog "+this.id+" received "+t.method+" request"),t.method===n.C.ACK){if(this.ackWait){if(this.initialTransaction instanceof a.InviteClientTransaction)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");if(this.initialTransaction.request.cseq!==t.cseq)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");this.ackWait=!1}else{if(!this.reinviteUserAgentServer)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");if(this.reinviteUserAgentServer.transaction.request.cseq!==t.cseq)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");this.reinviteUserAgentServer=void 0}return this.signalingStateTransition(t),void(this.delegate&&this.delegate.onAck&&this.delegate.onAck({message:t}))}if(this.sequenceGuard(t)){if(t.method===n.C.INVITE){if(this.reinviteUserAgentServer){var r=["Retry-After: "+(Math.floor(10*Math.random())+1)];return void this.core.replyStateless(t,{statusCode:500,extraHeaders:r})}if(this.reinviteUserAgentClient)return void this.core.replyStateless(t,{statusCode:491})}if(e.prototype.receiveRequest.call(this,t),t.method===n.C.INVITE){var i=t.parseHeader("contact");if(!i)throw new Error("Contact undefined.");if(!(i instanceof n.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");this.dialogState.remoteTarget=i.uri}switch(t.method){case n.C.BYE:var s=new u.ByeUserAgentServer(this,t);this.delegate&&this.delegate.onBye?this.delegate.onBye(s):s.accept(),this.dispose();break;case n.C.INFO:s=new p.InfoUserAgentServer(this,t);this.delegate&&this.delegate.onInfo?this.delegate.onInfo(s):s.reject({statusCode:469,extraHeaders:["Recv-Info :"]});break;case n.C.INVITE:s=new v.ReInviteUserAgentServer(this,t);this.delegate&&this.delegate.onInvite?this.delegate.onInvite(s):s.reject({statusCode:488});break;case n.C.NOTIFY:s=new l.NotifyUserAgentServer(this,t);this.delegate&&this.delegate.onNotify?this.delegate.onNotify(s):s.accept();break;case n.C.PRACK:s=new f.PrackUserAgentServer(this,t);this.delegate&&this.delegate.onPrack?this.delegate.onPrack(s):s.accept();break;case n.C.REFER:s=new T.ReferUserAgentServer(this,t);this.delegate&&this.delegate.onRefer?this.delegate.onRefer(s):s.reject();break;default:this.logger.log("INVITE dialog "+this.id+" received unimplemented "+t.method+" request"),this.core.replyStateless(t,{statusCode:501})}}else this.logger.log("INVITE dialog "+this.id+" rejected out of order "+t.method+" request.")},t.prototype.reliableSequenceGuard=function(e){var t=e.statusCode;if(!t)throw new Error("Status code undefined");if(t>100&&t<200){var r=e.getHeader("require"),i=e.getHeader("rseq"),n=r&&r.includes("100rel")&&i?Number(i):void 0;if(n){if(this.rseq&&this.rseq+1!==n)return!1;this.rseq||(this.rseq=n)}}return!0},t.prototype.signalingStateTransition=function(e){var t=n.getBody(e);if(t&&"session"===t.contentDisposition){if(e instanceof n.IncomingRequestMessage)switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveRemoteOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.HaveRemoteOffer:case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}if(e instanceof n.IncomingResponseMessage)switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveRemoteOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.HaveRemoteOffer:case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}if(e instanceof n.OutgoingRequestMessage)switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveLocalOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:break;case s.SignalingState.HaveRemoteOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}if(n.isBody(e))switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveLocalOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:break;case s.SignalingState.HaveRemoteOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}}},t.prototype.start2xxRetransmissionTimer=function(){var e=this;if(this.initialTransaction instanceof a.InviteServerTransaction){var t=this.initialTransaction,r=o.Timers.T1,i=function(){e.ackWait?(e.logger.log("No ACK for 2xx response received, attempting retransmission"),t.retransmitAcceptedResponse(),r=Math.min(2*r,o.Timers.T2),e.invite2xxTimer=setTimeout(i,r)):e.invite2xxTimer=void 0};this.invite2xxTimer=setTimeout(i,r);var n=function(){t.state===a.TransactionState.Terminated&&(t.removeListener("stateChanged",n),e.invite2xxTimer&&(clearTimeout(e.invite2xxTimer),e.invite2xxTimer=void 0),e.ackWait&&(e.delegate&&e.delegate.onAckTimeout?e.delegate.onAckTimeout():e.bye()))};t.addListener("stateChanged",n)}},t.prototype.startReInvite2xxRetransmissionTimer=function(){var e=this;if(this.reinviteUserAgentServer&&this.reinviteUserAgentServer.transaction instanceof a.InviteServerTransaction){var t=this.reinviteUserAgentServer.transaction,r=o.Timers.T1,i=function(){e.reinviteUserAgentServer?(e.logger.log("No ACK for 2xx response received, attempting retransmission"),t.retransmitAcceptedResponse(),r=Math.min(2*r,o.Timers.T2),e.invite2xxTimer=setTimeout(i,r)):e.invite2xxTimer=void 0};this.invite2xxTimer=setTimeout(i,r);var n=function(){t.state===a.TransactionState.Terminated&&(t.removeListener("stateChanged",n),e.invite2xxTimer&&(clearTimeout(e.invite2xxTimer),e.invite2xxTimer=void 0),e.reinviteUserAgentServer)};t.addListener("stateChanged",n)}},t}(r(20).Dialog);t.SessionDialog=y},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Initial="Initial",e.Early="Early",e.AckWait="AckWait",e.Confirmed="Confirmed",e.Terminated="Terminated"}(t.SessionState||(t.SessionState={})),function(e){e.Initial="Initial",e.HaveLocalOffer="HaveLocalOffer",e.HaveRemoteOffer="HaveRemoteOffer",e.Stable="Stable",e.Closed="Closed"}(t.SignalingState||(t.SignalingState={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t){return e.call(this,t||"Transaction state error.")||this}return i.__extends(t,e),t}(r(29).Exception);t.TransactionStateError=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t){return e.call(this,t||"Unspecified transport error.")||this}return i.__extends(t,e),t}(r(29).Exception);t.TransportError=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(11),o=r(30),a=r(14),c=function(e){function t(t,r,i){return e.call(this,t,r,i,a.TransactionState.Proceeding,"sip.transaction.ist")||this}return i.__extends(t,e),t.prototype.dispose=function(){this.stopProgressExtensionTimer(),this.H&&(clearTimeout(this.H),this.H=void 0),this.I&&(clearTimeout(this.I),this.I=void 0),this.L&&(clearTimeout(this.L),this.L=void 0),e.prototype.dispose.call(this)},Object.defineProperty(t.prototype,"kind",{get:function(){return"ist"},enumerable:!0,configurable:!0}),t.prototype.receiveRequest=function(e){var t=this;switch(this.state){case a.TransactionState.Proceeding:if(e.method===n.C.INVITE)return void(this.lastProvisionalResponse&&this.send(this.lastProvisionalResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of provisional response.")}));break;case a.TransactionState.Accepted:if(e.method===n.C.INVITE)return;break;case a.TransactionState.Completed:if(e.method===n.C.INVITE){if(!this.lastFinalResponse)throw new Error("Last final response undefined.");return void this.send(this.lastFinalResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of final response.")})}if(e.method===n.C.ACK)return void this.stateTransition(a.TransactionState.Confirmed);break;case a.TransactionState.Confirmed:case a.TransactionState.Terminated:if(e.method===n.C.INVITE||e.method===n.C.ACK)return;break;default:throw new Error("Invalid state "+this.state)}var r="INVITE server transaction received unexpected "+e.method+" request while in state "+this.state+".";this.logger.warn(r)},t.prototype.receiveResponse=function(e,t){var r=this;if(e<100||e>699)throw new Error("Invalid status code "+e);switch(this.state){case a.TransactionState.Proceeding:if(e>=100&&e<=199)return this.lastProvisionalResponse=t,e>100&&this.startProgressExtensionTimer(),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send 1xx response.")});if(e>=200&&e<=299)return this.lastFinalResponse=t,this.stateTransition(a.TransactionState.Accepted),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send 2xx response.")});if(e>=300&&e<=699)return this.lastFinalResponse=t,this.stateTransition(a.TransactionState.Completed),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send non-2xx final response.")});break;case a.TransactionState.Accepted:if(e>=200&&e<=299)return void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send 2xx response.")});break;case a.TransactionState.Completed:case a.TransactionState.Confirmed:case a.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}var i="INVITE server transaction received unexpected "+e+" response from TU while in state "+this.state+".";throw this.logger.error(i),new Error(i)},t.prototype.retransmitAcceptedResponse=function(){var e=this;this.state===a.TransactionState.Accepted&&this.lastFinalResponse&&this.send(this.lastFinalResponse).catch(function(t){e.logTransportError(t,"Failed to send 2xx response.")})},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e)},t.prototype.typeToString=function(){return"INVITE server transaction"},t.prototype.stateTransition=function(e){var t=this,r=function(){throw new Error("Invalid state transition from "+t.state+" to "+e)};switch(e){case a.TransactionState.Proceeding:r();break;case a.TransactionState.Accepted:case a.TransactionState.Completed:this.state!==a.TransactionState.Proceeding&&r();break;case a.TransactionState.Confirmed:this.state!==a.TransactionState.Completed&&r();break;case a.TransactionState.Terminated:this.state!==a.TransactionState.Accepted&&this.state!==a.TransactionState.Completed&&this.state!==a.TransactionState.Confirmed&&r();break;default:r()}this.stopProgressExtensionTimer(),e===a.TransactionState.Accepted&&(this.L=setTimeout(function(){return t.timer_L()},s.Timers.TIMER_L)),e===a.TransactionState.Completed&&(this.H=setTimeout(function(){return t.timer_H()},s.Timers.TIMER_H)),e===a.TransactionState.Confirmed&&(this.I=setTimeout(function(){return t.timer_I()},s.Timers.TIMER_I)),e===a.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.startProgressExtensionTimer=function(){var e=this;void 0===this.progressExtensionTimer&&(this.progressExtensionTimer=setInterval(function(){if(e.logger.debug("Progress extension timer expired for INVITE server transaction "+e.id+"."),!e.lastProvisionalResponse)throw new Error("Last provisional response undefined.");e.send(e.lastProvisionalResponse).catch(function(t){e.logTransportError(t,"Failed to send retransmission of provisional response.")})},s.Timers.PROVISIONAL_RESPONSE_INTERVAL))},t.prototype.stopProgressExtensionTimer=function(){void 0!==this.progressExtensionTimer&&(clearInterval(this.progressExtensionTimer),this.progressExtensionTimer=void 0)},t.prototype.timer_G=function(){},t.prototype.timer_H=function(){this.logger.debug("Timer H expired for INVITE server transaction "+this.id+"."),this.state===a.TransactionState.Completed&&(this.logger.warn("ACK to negative final response was never received, terminating transaction."),this.stateTransition(a.TransactionState.Terminated))},t.prototype.timer_I=function(){this.logger.debug("Timer I expired for INVITE server transaction "+this.id+"."),this.stateTransition(a.TransactionState.Terminated)},t.prototype.timer_L=function(){this.logger.debug("Timer L expired for INVITE server transaction "+this.id+"."),this.state===a.TransactionState.Accepted&&this.stateTransition(a.TransactionState.Terminated)},t}(o.ServerTransaction);t.InviteServerTransaction=c},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(11),s=r(27),o=r(14),a=function(e){function t(t,r,i){var s=e.call(this,t,r,i,o.TransactionState.Trying,"sip.transaction.nict")||this;return s.F=setTimeout(function(){return s.timer_F()},n.Timers.TIMER_F),s.send(t.toString()).catch(function(e){s.logTransportError(e,"Failed to send initial outgoing request.")}),s}return i.__extends(t,e),t.prototype.dispose=function(){this.F&&(clearTimeout(this.F),this.F=void 0),this.K&&(clearTimeout(this.K),this.K=void 0),e.prototype.dispose.call(this)},Object.defineProperty(t.prototype,"kind",{get:function(){return"nict"},enumerable:!0,configurable:!0}),t.prototype.receiveResponse=function(e){var t=e.statusCode;if(!t||t<100||t>699)throw new Error("Invalid status code "+t);switch(this.state){case o.TransactionState.Trying:if(t>=100&&t<=199)return this.stateTransition(o.TransactionState.Proceeding),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(t>=200&&t<=699)return this.stateTransition(o.TransactionState.Completed),408===t?void this.onRequestTimeout():void(this.user.receiveResponse&&this.user.receiveResponse(e));break;case o.TransactionState.Proceeding:if(t>=100&&t<=199&&this.user.receiveResponse)return this.user.receiveResponse(e);if(t>=200&&t<=699)return this.stateTransition(o.TransactionState.Completed),408===t?void this.onRequestTimeout():void(this.user.receiveResponse&&this.user.receiveResponse(e));case o.TransactionState.Completed:case o.TransactionState.Terminated:return;default:throw new Error("Invalid state "+this.state)}var r="Non-INVITE client transaction received unexpected "+t+" response while in state "+this.state+".";this.logger.warn(r)},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e),this.stateTransition(o.TransactionState.Terminated,!0)},t.prototype.typeToString=function(){return"non-INVITE client transaction"},t.prototype.stateTransition=function(e,t){var r=this;void 0===t&&(t=!1);var i=function(){throw new Error("Invalid state transition from "+r.state+" to "+e)};switch(e){case o.TransactionState.Trying:i();break;case o.TransactionState.Proceeding:this.state!==o.TransactionState.Trying&&i();break;case o.TransactionState.Completed:this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding&&i();break;case o.TransactionState.Terminated:this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding&&this.state!==o.TransactionState.Completed&&(t||i());break;default:i()}e===o.TransactionState.Completed&&(this.F&&(clearTimeout(this.F),this.F=void 0),this.K=setTimeout(function(){return r.timer_K()},n.Timers.TIMER_K)),e===o.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.timer_F=function(){this.logger.debug("Timer F expired for non-INVITE client transaction "+this.id+"."),this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding||(this.onRequestTimeout(),this.stateTransition(o.TransactionState.Terminated))},t.prototype.timer_K=function(){this.state===o.TransactionState.Completed&&this.stateTransition(o.TransactionState.Terminated)},t}(s.ClientTransaction);t.NonInviteClientTransaction=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(11),s=r(30),o=r(14),a=function(e){function t(t,r,i){return e.call(this,t,r,i,o.TransactionState.Trying,"sip.transaction.nist")||this}return i.__extends(t,e),t.prototype.dispose=function(){this.J&&(clearTimeout(this.J),this.J=void 0),e.prototype.dispose.call(this)},Object.defineProperty(t.prototype,"kind",{get:function(){return"nist"},enumerable:!0,configurable:!0}),t.prototype.receiveRequest=function(e){var t=this;switch(this.state){case o.TransactionState.Trying:break;case o.TransactionState.Proceeding:if(!this.lastResponse)throw new Error("Last response undefined.");this.send(this.lastResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of provisional response.")});break;case o.TransactionState.Completed:if(!this.lastResponse)throw new Error("Last response undefined.");this.send(this.lastResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of final response.")});break;case o.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}},t.prototype.receiveResponse=function(e,t){var r=this;if(e<100||e>699)throw new Error("Invalid status code "+e);if(e>100&&e<=199)throw new Error("Provisional response other than 100 not allowed.");switch(this.state){case o.TransactionState.Trying:if(this.lastResponse=t,e>=100&&e<200)return this.stateTransition(o.TransactionState.Proceeding),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send provisional response.")});if(e>=200&&e<=699)return this.stateTransition(o.TransactionState.Completed),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send final response.")});break;case o.TransactionState.Proceeding:if(this.lastResponse=t,e>=200&&e<=699)return this.stateTransition(o.TransactionState.Completed),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send final response.")});break;case o.TransactionState.Completed:return;case o.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}var i="Non-INVITE server transaction received unexpected "+e+" response from TU while in state "+this.state+".";throw this.logger.error(i),new Error(i)},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e),this.stateTransition(o.TransactionState.Terminated,!0)},t.prototype.typeToString=function(){return"non-INVITE server transaction"},t.prototype.stateTransition=function(e,t){var r=this;void 0===t&&(t=!1);var i=function(){throw new Error("Invalid state transition from "+r.state+" to "+e)};switch(e){case o.TransactionState.Trying:i();break;case o.TransactionState.Proceeding:this.state!==o.TransactionState.Trying&&i();break;case o.TransactionState.Completed:this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding&&i();break;case o.TransactionState.Terminated:this.state!==o.TransactionState.Proceeding&&this.state!==o.TransactionState.Completed&&(t||i());break;default:i()}e===o.TransactionState.Completed&&(this.J=setTimeout(function(){return r.timer_J()},n.Timers.TIMER_J)),e===o.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.timer_J=function(){this.logger.debug("Timer J expired for NON-INVITE server transaction "+this.id+"."),this.state===o.TransactionState.Completed&&this.stateTransition(o.TransactionState.Terminated)},t}(s.ServerTransaction);t.NonInviteServerTransaction=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=t.createOutgoingRequestMessage(n.C.INFO,i);return e.call(this,s.NonInviteClientTransaction,t.userAgentCore,o,r)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.InfoUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Initial="Initial",e.NotifyWait="NotifyWait",e.Pending="Pending",e.Active="Active",e.Terminated="Terminated"}(t.SubscriptionState||(t.SubscriptionState={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(33),t),i.__exportStar(r(82),t),i.__exportStar(r(52),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(33),n=r(52),s=function(){function e(){this.builtinEnabled=!0,this._level=i.Levels.log,this.loggers={},this.logger=this.getLogger("sip:loggerfactory")}return Object.defineProperty(e.prototype,"level",{get:function(){return this._level},set:function(e){e>=0&&e<=3?this._level=e:e>3?this._level=3:i.Levels.hasOwnProperty(e)?this._level=e:this.logger.error("invalid 'level' parameter value: "+JSON.stringify(e))},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"connector",{get:function(){return this._connector},set:function(e){e?"function"==typeof e?this._connector=e:this.logger.error("invalid 'connector' parameter value: "+JSON.stringify(e)):this._connector=void 0},enumerable:!0,configurable:!0}),e.prototype.getLogger=function(e,t){if(t&&3===this.level)return new n.Logger(this,e,t);if(this.loggers[e])return this.loggers[e];var r=new n.Logger(this,e);return this.loggers[e]=r,r},e.prototype.genericLog=function(e,t,r,n){this.level>=e&&this.builtinEnabled&&this.print(e,t,r,n),this.connector&&this.connector(i.Levels[e],t,r,n)},e.prototype.print=function(e,t,r,n){if("string"==typeof n){var s=[new Date,t];r&&s.push(r),n=s.concat(n).join(" | ")}switch(e){case i.Levels.error:console.error(n);break;case i.Levels.warn:console.warn(n);break;case i.Levels.log:console.log(n);break;case i.Levels.debug:console.debug(n)}},e}();t.LoggerFactory=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(0).__exportStar(r(84),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=r(53),a=r(16),c=["application/sdp","application/dtmf-relay"],u=function(){function e(e,t){void 0===t&&(t={}),this.userAgentClients=new Map,this.userAgentServers=new Map,this.configuration=e,this.delegate=t,this.dialogs=new Map,this.subscribers=new Map,this.logger=e.loggerFactory.getLogger("sip.user-agent-core")}return e.prototype.dispose=function(){this.reset()},e.prototype.reset=function(){this.dialogs.forEach(function(e){return e.dispose()}),this.dialogs.clear(),this.subscribers.forEach(function(e){return e.dispose()}),this.subscribers.clear(),this.userAgentClients.forEach(function(e){return e.dispose()}),this.userAgentClients.clear(),this.userAgentServers.forEach(function(e){return e.dispose()}),this.userAgentServers.clear()},Object.defineProperty(e.prototype,"loggerFactory",{get:function(){return this.configuration.loggerFactory},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"transport",{get:function(){var e=this.configuration.transportAccessor();if(!e)throw new Error("Transport undefined.");return e},enumerable:!0,configurable:!0}),e.prototype.invite=function(e,t){return new o.InviteUserAgentClient(this,e,t)},e.prototype.message=function(e,t){return new o.MessageUserAgentClient(this,e,t)},e.prototype.publish=function(e,t){return new o.PublishUserAgentClient(this,e,t)},e.prototype.register=function(e,t){return new o.RegisterUserAgentClient(this,e,t)},e.prototype.subscribe=function(e,t){return new o.SubscribeUserAgentClient(this,e,t)},e.prototype.request=function(e,t){return new o.UserAgentClient(s.NonInviteClientTransaction,this,e,t)},e.prototype.makeOutgoingRequestMessage=function(e,t,r,s,o,a,c){var u=this.configuration.sipjsId,d=this.configuration.displayName,p=this.configuration.viaForceRport,h=this.configuration.hackViaTcp,l=this.configuration.supportedOptionTags.slice();e===n.C.REGISTER&&l.push("path","gruu"),e===n.C.INVITE&&(this.configuration.contact.pubGruu||this.configuration.contact.tempGruu)&&l.push("gruu");var g={callIdPrefix:u,forceRport:p,fromDisplayName:d,hackViaTcp:h,optionTags:l,routeSet:this.configuration.routeSet,userAgentString:this.configuration.userAgentHeaderFieldValue,viaHost:this.configuration.viaHost},f=i.__assign({},g,o);return new n.OutgoingRequestMessage(e,t,r,s,f,a,c)},e.prototype.receiveIncomingRequestFromTransport=function(e){this.receiveRequestFromTransport(e)},e.prototype.receiveIncomingResponseFromTransport=function(e){this.receiveResponseFromTransport(e)},e.prototype.replyStateless=function(e,t){var r=this.configuration.userAgentHeaderFieldValue,s=this.configuration.supportedOptionTagsResponse;t=i.__assign({},t,{userAgent:r,supported:s});var o=n.constructOutgoingResponse(e,t);return this.transport.send(o.message),o},e.prototype.receiveRequestFromTransport=function(e){var t=e.viaBranch,r=this.userAgentServers.get(t);e.method===n.C.ACK&&r&&r.transaction.state===s.TransactionState.Accepted&&r instanceof o.InviteUserAgentServer?this.logger.warn("Discarding out of dialog ACK after 2xx response sent on transaction "+t+"."):e.method!==n.C.CANCEL?r?r.transaction.receiveRequest(e):this.receiveRequest(e):r?(this.replyStateless(e,{statusCode:200}),r.transaction instanceof s.InviteServerTransaction&&r.transaction.state===s.TransactionState.Proceeding&&r instanceof o.InviteUserAgentServer&&r.receiveCancel(e)):this.replyStateless(e,{statusCode:481})},e.prototype.receiveRequest=function(e){if(a.AllowedMethods.includes(e.method)){if(!e.ruri)throw new Error("Request-URI undefined.");if("sip"===e.ruri.scheme){var t=e.ruri,r=function(e){return!!e&&e.user===t.user};if(!r(this.configuration.aor)&&!(r(this.configuration.contact.uri)||r(this.configuration.contact.pubGruu)||r(this.configuration.contact.tempGruu)))return this.logger.warn("Request-URI does not point to us."),void(e.method!==n.C.ACK&&this.replyStateless(e,{statusCode:404}));if(e.method!==n.C.INVITE||e.hasHeader("Contact")){if(!e.toTag){var i=e.viaBranch;if(!this.userAgentServers.has(i))if(Array.from(this.userAgentServers.values()).some(function(t){return t.transaction.request.fromTag===e.fromTag&&t.transaction.request.callId===e.callId&&t.transaction.request.cseq===e.cseq}))return void this.replyStateless(e,{statusCode:482})}e.toTag?this.receiveInsideDialogRequest(e):this.receiveOutsideDialogRequest(e)}else this.replyStateless(e,{statusCode:400,reasonPhrase:"Missing Contact Header"})}else this.replyStateless(e,{statusCode:416})}else{var s="Allow: "+a.AllowedMethods.toString();this.replyStateless(e,{statusCode:405,extraHeaders:[s]})}},e.prototype.receiveInsideDialogRequest=function(e){if(e.method===n.C.NOTIFY){var t=e.parseHeader("Event");if(!t||!t.event)return void this.replyStateless(e,{statusCode:489});var r=e.callId+e.toTag+t.event,i=this.subscribers.get(r);if(i){var s=new o.NotifyUserAgentServer(this,e);return void i.onNotify(s)}}var u=e.callId+e.toTag+e.fromTag,d=this.dialogs.get(u);if(d){if(e.method===n.C.OPTIONS){var p="Allow: "+a.AllowedMethods.toString(),h="Accept: "+c.toString();return void this.replyStateless(e,{statusCode:200,extraHeaders:[p,h]})}d.receiveRequest(e)}else e.method!==n.C.ACK&&this.replyStateless(e,{statusCode:481})},e.prototype.receiveOutsideDialogRequest=function(e){switch(e.method){case n.C.ACK:break;case n.C.BYE:this.replyStateless(e,{statusCode:481});break;case n.C.CANCEL:throw new Error("Unexpected out of dialog request method "+e.method+".");case n.C.INFO:this.replyStateless(e,{statusCode:405});break;case n.C.INVITE:var t=new o.InviteUserAgentServer(this,e);this.delegate.onInvite?this.delegate.onInvite(t):t.reject();break;case n.C.MESSAGE:t=new o.MessageUserAgentServer(this,e);this.delegate.onMessage?this.delegate.onMessage(t):t.accept();break;case n.C.NOTIFY:t=new o.NotifyUserAgentServer(this,e);this.delegate.onNotify?this.delegate.onNotify(t):this.replyStateless(e,{statusCode:405});break;case n.C.OPTIONS:var r="Allow: "+a.AllowedMethods.toString(),i="Accept: "+c.toString();this.replyStateless(e,{statusCode:200,extraHeaders:[r,i]});break;case n.C.REFER:t=new o.ReferUserAgentServer(this,e);this.delegate.onRefer?this.delegate.onRefer(t):this.replyStateless(e,{statusCode:405});break;case n.C.SUBSCRIBE:t=new o.SubscribeUserAgentServer(this,e);this.delegate.onSubscribe?this.delegate.onSubscribe(t):t.reject();break;default:throw new Error("Unexpected out of dialog request method "+e.method+".")}},e.prototype.receiveResponseFromTransport=function(e){if(e.getHeaders("via").length>1)this.logger.warn("More than one Via header field present in the response, dropping");else{var t=e.viaBranch+e.method,r=this.userAgentClients.get(t);r?r.transaction.receiveResponse(e):this.logger.warn("Discarding unmatched "+e.statusCode+" response to "+e.method+" "+t+".")}},e}();t.UserAgentCore=u},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.CancelUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(19),s=r(1),o=function(e){function t(t,r,i){var n=e.call(this,s.InviteClientTransaction,t,r,i)||this;return n.confirmedDialogAcks=new Map,n.confirmedDialogs=new Map,n.earlyDialogs=new Map,n.delegate=i,n}return i.__extends(t,e),t.prototype.dispose=function(){this.earlyDialogs.forEach(function(e){return e.dispose()}),this.earlyDialogs.clear(),e.prototype.dispose.call(this)},t.prototype.receiveResponse=function(e){var t=this;if(this.authenticationGuard(e)){var r=e.statusCode?e.statusCode.toString():"";if(!r)throw new Error("Response status code undefined.");switch(!0){case/^100$/.test(r):return void(this.delegate&&this.delegate.onTrying&&this.delegate.onTrying({message:e}));case/^1[0-9]{2}$/.test(r):if(!e.toTag)return void this.logger.warn("Non-100 1xx INVITE response received without a to tag, dropping.");var i=n.Dialog.initialDialogStateForUserAgentClient(this.message,e),o=this.earlyDialogs.get(i.id);if(!o){if(!((u=this.transaction)instanceof s.InviteClientTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");o=new n.SessionDialog(u,this.core,i),this.earlyDialogs.set(o.id,o)}if(!o.reliableSequenceGuard(e))return void this.logger.warn("1xx INVITE reliable response received out of order, dropping.");o.signalingStateTransition(e);var a=o;return void(this.delegate&&this.delegate.onProgress&&this.delegate.onProgress({message:e,session:a,prack:function(e){return a.prack(void 0,e)}}));case/^2[0-9]{2}$/.test(r):i=n.Dialog.initialDialogStateForUserAgentClient(this.message,e);var c=this.confirmedDialogs.get(i.id);if(c){if(p=this.confirmedDialogAcks.get(i.id)){if(!((u=this.transaction)instanceof s.InviteClientTransaction))throw new Error("Client transaction not instance of InviteClientTransaction.");u.ackResponse(p.message)}return}if(c=this.earlyDialogs.get(i.id))c.confirm(),c.recomputeRouteSet(e),this.earlyDialogs.delete(c.id),this.confirmedDialogs.set(c.id,c);else{var u;if(!((u=this.transaction)instanceof s.InviteClientTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");c=new n.SessionDialog(u,this.core,i),this.confirmedDialogs.set(c.id,c)}c.signalingStateTransition(e);var d=c;if(this.delegate&&this.delegate.onAccept)this.delegate.onAccept({message:e,session:d,ack:function(e){var r=d.ack(e);return t.confirmedDialogAcks.set(d.id,r),r}});else{var p=d.ack();this.confirmedDialogAcks.set(d.id,p)}return;case/^3[0-9]{2}$/.test(r):return this.earlyDialogs.forEach(function(e){return e.dispose()}),this.earlyDialogs.clear(),void(this.delegate&&this.delegate.onRedirect&&this.delegate.onRedirect({message:e}));case/^[4-6][0-9]{2}$/.test(r):return this.earlyDialogs.forEach(function(e){return e.dispose()}),this.earlyDialogs.clear(),void(this.delegate&&this.delegate.onReject&&this.delegate.onReject({message:e}));default:throw new Error("Invalid status code "+r)}throw new Error("Executing what should be an unreachable code path receiving "+r+" response.")}},t}(r(3).UserAgentClient);t.InviteUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(19),s=r(15),o=r(26),a=r(1),c=r(16),u=function(e){function t(t,r,i){var n=e.call(this,a.InviteServerTransaction,t,r,i)||this;return n.core=t,n}return i.__extends(t,e),t.prototype.dispose=function(){this.earlyDialog&&this.earlyDialog.dispose(),e.prototype.dispose.call(this)},t.prototype.accept=function(t){if(void 0===t&&(t={statusCode:200}),!this.acceptable)throw new s.TransactionStateError(this.message.method+" not acceptable in state "+this.transaction.state+".");if(!this.confirmedDialog)if(this.earlyDialog)this.earlyDialog.confirm(),this.confirmedDialog=this.earlyDialog,this.earlyDialog=void 0;else{var r=this.transaction;if(!(r instanceof a.InviteServerTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");var u=n.Dialog.initialDialogStateForUserAgentServer(this.message,this.toTag);this.confirmedDialog=new n.SessionDialog(r,this.core,u)}var d=this.message.getHeaders("record-route").map(function(e){return"Record-Route: "+e}),p="Contact: "+this.core.configuration.contact.toString(),h="Allow: "+c.AllowedMethods.toString();if(!t.body&&(this.confirmedDialog.signalingState===o.SignalingState.Initial||this.confirmedDialog.signalingState===o.SignalingState.HaveRemoteOffer))throw new Error("Response must have a body.");t.statusCode=t.statusCode||200,t.extraHeaders=t.extraHeaders||[],t.extraHeaders=t.extraHeaders.concat(d),t.extraHeaders.push(h),t.extraHeaders.push(p);var l=e.prototype.accept.call(this,t),g=this.confirmedDialog,f=i.__assign({},l,{session:g});return t.body&&this.confirmedDialog.signalingStateTransition(t.body),f},t.prototype.progress=function(t){if(void 0===t&&(t={statusCode:180}),!this.progressable)throw new s.TransactionStateError(this.message.method+" not progressable in state "+this.transaction.state+".");if(!this.earlyDialog){var r=this.transaction;if(!(r instanceof a.InviteServerTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");var o=n.Dialog.initialDialogStateForUserAgentServer(this.message,this.toTag,!0);this.earlyDialog=new n.SessionDialog(r,this.core,o)}var c=this.message.getHeaders("record-route").map(function(e){return"Record-Route: "+e}),u="Contact: "+this.core.configuration.contact;t.extraHeaders=t.extraHeaders||[],t.extraHeaders=t.extraHeaders.concat(c),t.extraHeaders.push(u);var d=e.prototype.progress.call(this,t),p=this.earlyDialog,h=i.__assign({},d,{session:p});return t.body&&this.earlyDialog.signalingStateTransition(t.body),h},t.prototype.redirect=function(t,r){return void 0===r&&(r={statusCode:302}),e.prototype.redirect.call(this,t,r)},t.prototype.reject=function(t){return void 0===t&&(t={statusCode:486}),e.prototype.reject.call(this,t)},t}(r(6).UserAgentServer);t.InviteUserAgentServer=u},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.MessageUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.NonInviteServerTransaction,t,r,i)||this;return s.core=t,s}return i.__extends(t,e),t}(r(6).UserAgentServer);t.MessageUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.PublishUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.ReSubscribeUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.RegisterUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(50),s=r(32),o=r(11),a=r(1),c=function(e){function t(t,r,i){var n=this,o=r.getHeader("Event");if(!o)throw new Error("Event undefined");var c=r.getHeader("Expires");if(!c)throw new Error("Expires undefined");return(n=e.call(this,a.NonInviteClientTransaction,t,r,i)||this).delegate=i,n.subscriberId=r.callId+r.fromTag+o,n.subscriptionExpiresRequested=n.subscriptionExpires=Number(c),n.subscriptionEvent=o,n.subscriptionState=s.SubscriptionState.NotifyWait,n.waitNotifyStart(),n}return i.__extends(t,e),t.prototype.dispose=function(){e.prototype.dispose.call(this)},t.prototype.onNotify=function(e){var t=e.message.parseHeader("Event").event;if(!t||t!==this.subscriptionEvent)return this.logger.warn("Failed to parse event."),void e.reject({statusCode:489});var r=e.message.parseHeader("Subscription-State");if(!r||!r.state)return this.logger.warn("Failed to parse subscription state."),void e.reject({statusCode:489});var i=r.state;switch(i){case"pending":case"active":case"terminated":break;default:return this.logger.warn("Invalid subscription state "+i),void e.reject({statusCode:489})}if("terminated"!==i&&!e.message.parseHeader("contact"))return this.logger.warn("Failed to parse contact."),void e.reject({statusCode:489});if(this.dialog)throw new Error("Dialog already created. This implementation only supports install of single subscriptions.");switch(this.waitNotifyStop(),this.subscriptionExpires=r.expires?Math.min(this.subscriptionExpires,Math.max(r.expires,0)):this.subscriptionExpires,i){case"pending":this.subscriptionState=s.SubscriptionState.Pending;break;case"active":this.subscriptionState=s.SubscriptionState.Active;break;case"terminated":this.subscriptionState=s.SubscriptionState.Terminated;break;default:throw new Error("Unrecognized state "+i+".")}if(this.subscriptionState!==s.SubscriptionState.Terminated){var o=n.SubscriptionDialog.initialDialogStateForSubscription(this.message,e.message);this.dialog=new n.SubscriptionDialog(this.subscriptionEvent,this.subscriptionExpires,this.subscriptionState,this.core,o)}if(this.delegate&&this.delegate.onNotify){var a=e,c=this.dialog;this.delegate.onNotify({request:a,subscription:c})}else e.accept()},t.prototype.waitNotifyStart=function(){var e=this;this.N||(this.core.subscribers.set(this.subscriberId,this),this.N=setTimeout(function(){return e.timer_N()},o.Timers.TIMER_N))},t.prototype.waitNotifyStop=function(){this.N&&(this.core.subscribers.delete(this.subscriberId),clearTimeout(this.N),this.N=void 0)},t.prototype.receiveResponse=function(t){if(this.authenticationGuard(t)){if(t.statusCode&&t.statusCode>=200&&t.statusCode<300){var r=t.getHeader("Expires");if(r){var i=Number(r);i>this.subscriptionExpiresRequested&&this.logger.warn("Expires header in a 200-class response to SUBSCRIBE with a higher value than the one in the request"),ithis.subscriptionExpires&&(this.dialog.subscriptionExpires=this.subscriptionExpires)}t.statusCode&&t.statusCode>=300&&t.statusCode<700&&this.waitNotifyStop(),e.prototype.receiveResponse.call(this,t)}},t.prototype.timer_N=function(){this.logger.warn("Timer N expired for SUBSCRIBE user agent client. Timed out waiting for NOTIFY."),this.waitNotifyStop(),this.delegate&&this.delegate.onNotifyTimeout&&this.delegate.onNotifyTimeout()},t}(r(3).UserAgentClient);t.SubscribeUserAgentClient=c},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.NonInviteServerTransaction,t,r,i)||this;return s.core=t,s}return i.__extends(t,e),t}(r(6).UserAgentServer);t.SubscribeUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t,r){var i=e.call(this)||this;return i.logger=t,i}return i.__extends(t,e),t.prototype.connect=function(e){var t=this;return void 0===e&&(e={}),this.connectPromise(e).then(function(e){e.overrideEvent||t.emit("connected")})},t.prototype.send=function(e,t){var r=this;return void 0===t&&(t={}),this.sendPromise(e).then(function(e){e.overrideEvent||r.emit("messageSent",e.msg)})},t.prototype.disconnect=function(e){var t=this;return void 0===e&&(e={}),this.disconnectPromise(e).then(function(e){e.overrideEvent||t.emit("disconnected")})},t.prototype.afterConnected=function(e){this.isConnected()?e():this.once("connected",e)},t.prototype.waitForConnected=function(){var e=this;return console.warn("DEPRECATION WARNING Transport.waitForConnected(): use afterConnected() instead"),new Promise(function(t){e.afterConnected(t)})},t}(r(9).EventEmitter);t.Transport=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(7),o=r(4),a=r(10),c=r(8),u=function(e){function t(t,r,i){void 0===i&&(i={});var n=e.call(this)||this;if(n.C={MIN_DURATION:70,MAX_DURATION:6e3,DEFAULT_DURATION:100,MIN_INTER_TONE_GAP:50,DEFAULT_INTER_TONE_GAP:500},n.type=o.TypeStrings.DTMF,void 0===r)throw new TypeError("Not enough arguments");if(n.logger=t.ua.getLogger("sip.invitecontext.dtmf",t.id),n.owner=t,"string"==typeof r)r=r.toUpperCase();else{if("number"!=typeof r)throw new TypeError("Invalid tone: "+r);r=r.toString()}if(!r.match(/^[0-9A-D#*]$/))throw new TypeError("Invalid tone: "+r);n.tone=r;var s=i.duration,a=i.interToneGap;if(s&&!c.Utils.isDecimal(s))throw new TypeError("Invalid tone duration: "+s);if(s?sn.C.MAX_DURATION?(n.logger.warn("'duration' value is greater than the maximum allowed, setting it to "+n.C.MAX_DURATION+" milliseconds"),s=n.C.MAX_DURATION):s=Math.abs(s):s=n.C.DEFAULT_DURATION,n.duration=s,a&&!c.Utils.isDecimal(a))throw new TypeError("Invalid interToneGap: "+a);return a?a-1&&s.indexOf("chrome")<0?c=!0:s.indexOf("firefox")>-1&&s.indexOf("chrome")<0&&(u=!0);var d={};return c&&(d.modifiers=[a.stripG722]),u&&(d.alwaysAcquireMediaFirst=!0),n.options.ua.uri?n.anonymous=!1:n.anonymous=!0,n.ua=new o.UA({uri:n.options.ua.uri,authorizationUser:n.options.ua.authorizationUser,password:n.options.ua.password,displayName:n.options.ua.displayName,userAgentString:n.options.ua.userAgentString,register:!0,sessionDescriptionHandlerFactoryOptions:d,transportOptions:{traceSip:n.options.ua.traceSip,wsServers:n.options.ua.wsServers}}),n.state=i.STATUS_NULL,n.logger=n.ua.getLogger("sip.simple"),n.ua.on("registered",function(){n.emit("registered",n.ua)}),n.ua.on("unregistered",function(){n.emit("unregistered",n.ua)}),n.ua.on("registrationFailed",function(){n.emit("unregistered",n.ua)}),n.ua.on("invite",function(e){if(n.state!==i.STATUS_NULL&&n.state!==i.STATUS_COMPLETED)return n.logger.warn("Rejecting incoming call. Simple only supports 1 call at a time"),void e.reject();n.session=e,n.setupSession(),n.emit("ringing",n.session)}),n.ua.on("message",function(e){n.emit("message",e)}),n}return n.__extends(r,t),r.prototype.call=function(e){if(this.ua&&this.checkRegistration()){if(this.state===i.STATUS_NULL||this.state===i.STATUS_COMPLETED)return this.options.media.remote.audio&&(this.options.media.remote.audio.autoplay=!0),this.options.media.remote.video&&(this.options.media.remote.video.autoplay=!0),this.options.media.local&&this.options.media.local.video&&(this.options.media.local.video.autoplay=!0,this.options.media.local.video.volume=0),this.session=this.ua.invite(e,{sessionDescriptionHandlerOptions:{constraints:{audio:this.audio,video:this.video}}}),this.setupSession(),this.session;this.logger.warn("Cannot make more than a single call with Simple")}else this.logger.warn("A registered UA is required for calling")},r.prototype.answer=function(){if(this.state===i.STATUS_NEW||this.state===i.STATUS_CONNECTING)return this.options.media.remote.audio&&(this.options.media.remote.audio.autoplay=!0),this.options.media.remote.video&&(this.options.media.remote.video.autoplay=!0),this.session.accept({sessionDescriptionHandlerOptions:{constraints:{audio:this.audio,video:this.video}}});this.logger.warn("No call to answer")},r.prototype.reject=function(){if(this.state===i.STATUS_NEW||this.state===i.STATUS_CONNECTING)return this.session.reject();this.logger.warn("Call is already answered")},r.prototype.hangup=function(){if(this.state===i.STATUS_CONNECTED||this.state===i.STATUS_CONNECTING||this.state===i.STATUS_NEW)return this.state!==i.STATUS_CONNECTED?this.session.cancel():this.session?this.session.bye():void 0;this.logger.warn("No active call to hang up on")},r.prototype.hold=function(){if(this.state===i.STATUS_CONNECTED&&this.session&&!this.session.localHold)return this.mute(),this.logger.log("Placing session on hold"),this.session.hold();this.logger.warn("Cannot put call on hold")},r.prototype.unhold=function(){if(this.state===i.STATUS_CONNECTED&&this.session&&this.session.localHold)return this.unmute(),this.logger.log("Placing call off hold"),this.session.unhold();this.logger.warn("Cannot unhold a call that is not on hold")},r.prototype.mute=function(){this.state===i.STATUS_CONNECTED?(this.logger.log("Muting Audio"),this.toggleMute(!0),this.emit("mute",this)):this.logger.warn("An acitve call is required to mute audio")},r.prototype.unmute=function(){this.state===i.STATUS_CONNECTED?(this.logger.log("Unmuting Audio"),this.toggleMute(!1),this.emit("unmute",this)):this.logger.warn("An active call is required to unmute audio")},r.prototype.sendDTMF=function(e){this.state===i.STATUS_CONNECTED&&this.session?(this.logger.log("Sending DTMF tone: "+e),this.session.dtmf(e)):this.logger.warn("An active call is required to send a DTMF tone")},r.prototype.message=function(e,t){this.ua&&this.checkRegistration()?e&&t?this.ua.message(e,t):this.logger.warn("A destination and message are required to send a message"):this.logger.warn("A registered UA is required to send a message")},r.prototype.checkRegistration=function(){return this.anonymous||this.ua&&this.ua.isRegistered()},r.prototype.setupRemoteMedia=function(){var t=this;if(this.session){var r,i=this.session.sessionDescriptionHandler.peerConnection;i.getReceivers?(r=new e.window.MediaStream,i.getReceivers().forEach(function(e){var t=e.track;t&&r.addTrack(t)})):r=i.getRemoteStreams()[0],this.video?(this.options.media.remote.video.srcObject=r,this.options.media.remote.video.play().catch(function(){t.logger.log("play was rejected")})):this.audio&&(this.options.media.remote.audio.srcObject=r,this.options.media.remote.audio.play().catch(function(){t.logger.log("play was rejected")}))}else this.logger.warn("No session to set remote media on")},r.prototype.setupLocalMedia=function(){if(this.session){if(this.video&&this.options.media.local&&this.options.media.local.video){var t,r=this.session.sessionDescriptionHandler.peerConnection;r.getSenders?(t=new e.window.MediaStream,r.getSenders().forEach(function(e){var r=e.track;r&&"video"===r.kind&&t.addTrack(r)})):t=r.getLocalStreams()[0],this.options.media.local.video.srcObject=t,this.options.media.local.video.volume=0,this.options.media.local.video.play()}}else this.logger.warn("No session to set local media on")},r.prototype.cleanupMedia=function(){this.video&&(this.options.media.remote.video.srcObject=null,this.options.media.remote.video.pause(),this.options.media.local&&this.options.media.local.video&&(this.options.media.local.video.srcObject=null,this.options.media.local.video.pause())),this.audio&&(this.options.media.remote.audio.srcObject=null,this.options.media.remote.audio.pause())},r.prototype.setupSession=function(){var e=this;this.session?(this.state=i.STATUS_NEW,this.emit("new",this.session),this.session.on("progress",function(){return e.onProgress()}),this.session.on("accepted",function(){return e.onAccepted()}),this.session.on("rejected",function(){return e.onEnded()}),this.session.on("failed",function(){return e.onFailed()}),this.session.on("terminated",function(){return e.onEnded()})):this.logger.warn("No session to set up")},r.prototype.destroyMedia=function(){this.session&&this.session.sessionDescriptionHandler&&this.session.sessionDescriptionHandler.close()},r.prototype.toggleMute=function(e){if(this.session){var t=this.session.sessionDescriptionHandler.peerConnection;t.getSenders?t.getSenders().forEach(function(t){t.track&&(t.track.enabled=!e)}):t.getLocalStreams().forEach(function(t){t.getAudioTracks().forEach(function(t){t.enabled=!e}),t.getVideoTracks().forEach(function(t){t.enabled=!e})})}else this.logger.warn("No session to toggle mute")},r.prototype.onAccepted=function(){var e=this;this.session?(this.state=i.STATUS_CONNECTED,this.emit("connected",this.session),this.setupLocalMedia(),this.setupRemoteMedia(),this.session.sessionDescriptionHandler&&(this.session.sessionDescriptionHandler.on("addTrack",function(){e.logger.log("A track has been added, triggering new remoteMedia setup"),e.setupRemoteMedia()}),this.session.sessionDescriptionHandler.on("addStream",function(){e.logger.log("A stream has been added, trigger new remoteMedia setup"),e.setupRemoteMedia()})),this.session.on("dtmf",function(t,r){e.emit("dtmf",r.tone)}),this.session.on("bye",function(){return e.onEnded()})):this.logger.warn("No session for accepting")},r.prototype.onProgress=function(){this.state=i.STATUS_CONNECTING,this.emit("connecting",this.session)},r.prototype.onFailed=function(){this.onEnded()},r.prototype.onEnded=function(){this.state=i.STATUS_COMPLETED,this.emit("ended",this.session),this.cleanupMedia()},r.C=i,r}(s.EventEmitter);t.Simple=c}).call(this,r(18))}])}); \ No newline at end of file diff --git a/dist/sip.js b/dist/sip.js new file mode 100644 index 000000000..b8a544976 --- /dev/null +++ b/dist/sip.js @@ -0,0 +1,19888 @@ +/*! + * + * SIP version 0.14.2 + * Copyright (c) 2014-2019 Junction Networks, Inc + * Homepage: https://sipjs.com + * License: https://sipjs.com/license/ + * + * + * ~~~SIP.js contains substantial portions of JsSIP under the following license~~~ + * Homepage: http://jssip.net + * Copyright (c) 2012-2013 José Luis Millán - Versatica + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * ~~~ end JsSIP license ~~~ + * + * + * + * + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["SIP"] = factory(); + else + root["SIP"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var core_1 = __webpack_require__(2); +exports.DigestAuthentication = core_1.DigestAuthentication; +exports.Grammar = core_1.Grammar; +exports.IncomingRequest = core_1.IncomingRequestMessage; +exports.IncomingResponse = core_1.IncomingResponseMessage; +exports.LoggerFactory = core_1.LoggerFactory; +exports.NameAddrHeader = core_1.NameAddrHeader; +exports.OutgoingRequest = core_1.OutgoingRequestMessage; +exports.Timers = core_1.Timers; +exports.Transport = core_1.Transport; +exports.URI = core_1.URI; +var ClientContext_1 = __webpack_require__(78); +exports.ClientContext = ClientContext_1.ClientContext; +var Constants_1 = __webpack_require__(79); +exports.C = Constants_1.C; +var Enums_1 = __webpack_require__(81); +exports.DialogStatus = Enums_1.DialogStatus; +exports.SessionStatus = Enums_1.SessionStatus; +exports.TypeStrings = Enums_1.TypeStrings; +exports.UAStatus = Enums_1.UAStatus; +var Exceptions_1 = __webpack_require__(83); +exports.Exceptions = Exceptions_1.Exceptions; +var Parser_1 = __webpack_require__(84); +exports.Parser = Parser_1.Parser; +var PublishContext_1 = __webpack_require__(85); +exports.PublishContext = PublishContext_1.PublishContext; +var ReferContext_1 = __webpack_require__(86); +exports.ReferClientContext = ReferContext_1.ReferClientContext; +exports.ReferServerContext = ReferContext_1.ReferServerContext; +var RegisterContext_1 = __webpack_require__(88); +exports.RegisterContext = RegisterContext_1.RegisterContext; +var ServerContext_1 = __webpack_require__(87); +exports.ServerContext = ServerContext_1.ServerContext; +var Session_1 = __webpack_require__(89); +exports.InviteClientContext = Session_1.InviteClientContext; +exports.InviteServerContext = Session_1.InviteServerContext; +exports.Session = Session_1.Session; +var Subscription_1 = __webpack_require__(91); +exports.Subscription = Subscription_1.Subscription; +var transactions_1 = __webpack_require__(27); +var Transactions = { + InviteClientTransaction: transactions_1.InviteClientTransaction, + InviteServerTransaction: transactions_1.InviteServerTransaction, + NonInviteClientTransaction: transactions_1.NonInviteClientTransaction, + NonInviteServerTransaction: transactions_1.NonInviteServerTransaction +}; +exports.Transactions = Transactions; +var UA_1 = __webpack_require__(92); +exports.makeUserAgentCoreConfigurationFromUA = UA_1.makeUserAgentCoreConfigurationFromUA; +exports.UA = UA_1.UA; +var Utils_1 = __webpack_require__(82); +exports.Utils = Utils_1.Utils; +var Web = tslib_1.__importStar(__webpack_require__(98)); +exports.Web = Web; +// tslint:disable-next-line:no-var-requires +var pkg = __webpack_require__(80); +var name = pkg.title; +exports.name = name; +var version = pkg.version; +exports.version = version; +var Core = tslib_1.__importStar(__webpack_require__(2)); +exports.Core = Core; + + +/***/ }), +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__extends", function() { return __extends; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__assign", function() { return __assign; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__rest", function() { return __rest; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__decorate", function() { return __decorate; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__param", function() { return __param; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__metadata", function() { return __metadata; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__awaiter", function() { return __awaiter; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__generator", function() { return __generator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__exportStar", function() { return __exportStar; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__values", function() { return __values; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__read", function() { return __read; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spread", function() { return __spread; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__await", function() { return __await; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncGenerator", function() { return __asyncGenerator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncDelegator", function() { return __asyncDelegator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncValues", function() { return __asyncValues; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__makeTemplateObject", function() { return __makeTemplateObject; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importStar", function() { return __importStar; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importDefault", function() { return __importDefault; }); +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + } + return __assign.apply(this, arguments); +} + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __param(paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +} + +function __metadata(metadataKey, metadataValue) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); +} + +function __awaiter(thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __exportStar(m, exports) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} + +function __values(o) { + var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; + if (m) return m.call(o); + return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; +} + +function __read(o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +} + +function __spread() { + for (var ar = [], i = 0; i < arguments.length; i++) + ar = ar.concat(__read(arguments[i])); + return ar; +} + +function __await(v) { + return this instanceof __await ? (this.v = v, this) : new __await(v); +} + +function __asyncGenerator(thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +} + +function __asyncDelegator(o) { + var i, p; + return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; + function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } +} + +function __asyncValues(o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +} + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; + +function __importStar(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result.default = mod; + return result; +} + +function __importDefault(mod) { + return (mod && mod.__esModule) ? mod : { default: mod }; +} + + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +// Directories +tslib_1.__exportStar(__webpack_require__(3), exports); +tslib_1.__exportStar(__webpack_require__(31), exports); +tslib_1.__exportStar(__webpack_require__(60), exports); +tslib_1.__exportStar(__webpack_require__(5), exports); +tslib_1.__exportStar(__webpack_require__(24), exports); +tslib_1.__exportStar(__webpack_require__(56), exports); +tslib_1.__exportStar(__webpack_require__(27), exports); +tslib_1.__exportStar(__webpack_require__(64), exports); +tslib_1.__exportStar(__webpack_require__(66), exports); +// Files +tslib_1.__exportStar(__webpack_require__(26), exports); +tslib_1.__exportStar(__webpack_require__(77), exports); + + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(4), exports); +tslib_1.__exportStar(__webpack_require__(23), exports); +tslib_1.__exportStar(__webpack_require__(55), exports); + + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = __webpack_require__(5); +/** + * A key concept for a user agent is that of a dialog. A dialog + * represents a peer-to-peer SIP relationship between two user agents + * that persists for some time. The dialog facilitates sequencing of + * messages between the user agents and proper routing of requests + * between both of them. The dialog represents a context in which to + * interpret SIP messages. + * https://tools.ietf.org/html/rfc3261#section-12 + */ +var Dialog = /** @class */ (function () { + /** + * Dialog constructor. + * @param core User agent core. + * @param dialogState Initial dialog state. + */ + function Dialog(core, dialogState) { + this.core = core; + this.dialogState = dialogState; + this.core.dialogs.set(this.id, this); + } + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + Dialog.initialDialogStateForUserAgentClient = function (outgoingRequestMessage, incomingResponseMessage) { + // If the request was sent over TLS, and the Request-URI contained a + // SIPS URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the response, taken in reverse order and preserving + // all URI parameters. If no Record-Route header field is present in + // the response, the route set MUST be set to the empty set. This route + // set, even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the response. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var routeSet = incomingResponseMessage.getHeaders("record-route").reverse(); + var contact = incomingResponseMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The local sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The remote sequence + // number MUST be empty (it is established when the remote UA sends a + // request within the dialog). The call identifier component of the + // dialog ID MUST be set to the value of the Call-ID in the request. + // The local tag component of the dialog ID MUST be set to the tag in + // the From field in the request, and the remote tag component of the + // dialog ID MUST be set to the tag in the To field of the response. A + // UAC MUST be prepared to receive a response without a tag in the To + // field, in which case the tag is considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate To tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var localSequenceNumber = outgoingRequestMessage.cseq; + var remoteSequenceNumber = undefined; + var callId = outgoingRequestMessage.callId; + var localTag = outgoingRequestMessage.fromTag; + var remoteTag = incomingResponseMessage.toTag; + if (!callId) { // TODO: Review to make sure this will never happen + throw new Error("Call id undefined."); + } + if (!localTag) { // TODO: Review to make sure this will never happen + throw new Error("From tag undefined."); + } + if (!remoteTag) { // TODO: Review to make sure this will never happen + throw new Error("To tag undefined."); // FIXME: No backwards compatibility with RFC 2543 + } + // The remote URI MUST be set to the URI in the To field, and the local + // URI MUST be set to the URI in the From field. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + if (!outgoingRequestMessage.from) { // TODO: Review to make sure this will never happen + throw new Error("From undefined."); + } + if (!outgoingRequestMessage.to) { // TODO: Review to make sure this will never happen + throw new Error("To undefined."); + } + var localURI = outgoingRequestMessage.from.uri; + var remoteURI = outgoingRequestMessage.to.uri; + // A dialog can also be in the "early" state, which occurs when it is + // created with a provisional response, and then transition to the + // "confirmed" state when a 2xx final response arrives. + // https://tools.ietf.org/html/rfc3261#section-12 + if (!incomingResponseMessage.statusCode) { + throw new Error("Incoming response status code undefined."); + } + var early = incomingResponseMessage.statusCode < 200 ? true : false; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + /** + * The UAS then constructs the state of the dialog. This state MUST be + * maintained for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.1 + * @param incomingRequestMessage Incoming request message creating dialog. + * @param toTag Tag in the To field in the response to the incoming request. + */ + Dialog.initialDialogStateForUserAgentServer = function (incomingRequestMessage, toTag, early) { + if (early === void 0) { early = false; } + // If the request arrived over TLS, and the Request-URI contained a SIPS + // URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the request, taken in order and preserving all URI + // parameters. If no Record-Route header field is present in the + // request, the route set MUST be set to the empty set. This route set, + // even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the request. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var routeSet = incomingRequestMessage.getHeaders("record-route"); + var contact = incomingRequestMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The remote sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The local sequence + // number MUST be empty. The call identifier component of the dialog ID + // MUST be set to the value of the Call-ID in the request. The local + // tag component of the dialog ID MUST be set to the tag in the To field + // in the response to the request (which always includes a tag), and the + // remote tag component of the dialog ID MUST be set to the tag from the + // From field in the request. A UAS MUST be prepared to receive a + // request without a tag in the From field, in which case the tag is + // considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate From tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var remoteSequenceNumber = incomingRequestMessage.cseq; + var localSequenceNumber = undefined; + var callId = incomingRequestMessage.callId; + var localTag = toTag; + var remoteTag = incomingRequestMessage.fromTag; + // The remote URI MUST be set to the URI in the From field, and the + // local URI MUST be set to the URI in the To field. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var remoteURI = incomingRequestMessage.from.uri; + var localURI = incomingRequestMessage.to.uri; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + /** Destructor. */ + Dialog.prototype.dispose = function () { + this.core.dialogs.delete(this.id); + }; + Object.defineProperty(Dialog.prototype, "id", { + /** + * A dialog is identified at each UA with a dialog ID, which consists of + * a Call-ID value, a local tag and a remote tag. The dialog ID at each + * UA involved in the dialog is not the same. Specifically, the local + * tag at one UA is identical to the remote tag at the peer UA. The + * tags are opaque tokens that facilitate the generation of unique + * dialog IDs. + * https://tools.ietf.org/html/rfc3261#section-12 + */ + get: function () { + return this.dialogState.id; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "early", { + /** + * A dialog can also be in the "early" state, which occurs when it is + * created with a provisional response, and then it transition to the + * "confirmed" state when a 2xx final response received or is sent. + * + * Note: RFC 3261 is concise on when a dialog is "confirmed", but it + * can be a point of confusion if an INVITE dialog is "confirmed" after + * a 2xx is sent or after receiving the ACK for the 2xx response. + * With careful reading it can be inferred a dialog is always is + * "confirmed" when the 2xx is sent (regardless of type of dialog). + * However a INVITE dialog does have additional considerations + * when it is confirmed but an ACK has not yet been received (in + * particular with regard to a callee sending BYE requests). + */ + get: function () { + return this.dialogState.early; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "callId", { + /** Call identifier component of the dialog id. */ + get: function () { + return this.dialogState.callId; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localTag", { + /** Local tag component of the dialog id. */ + get: function () { + return this.dialogState.localTag; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteTag", { + /** Remote tag component of the dialog id. */ + get: function () { + return this.dialogState.remoteTag; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localSequenceNumber", { + /** Local sequence number (used to order requests from the UA to its peer). */ + get: function () { + return this.dialogState.localSequenceNumber; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteSequenceNumber", { + /** Remote sequence number (used to order requests from its peer to the UA). */ + get: function () { + return this.dialogState.remoteSequenceNumber; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localURI", { + /** Local URI. */ + get: function () { + return this.dialogState.localURI; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteURI", { + /** Remote URI. */ + get: function () { + return this.dialogState.remoteURI; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteTarget", { + /** Remote target. */ + get: function () { + return this.dialogState.remoteTarget; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "routeSet", { + /** + * Route set, which is an ordered list of URIs. The route set is the + * list of servers that need to be traversed to send a request to the peer. + */ + get: function () { + return this.dialogState.routeSet; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "secure", { + /** + * If the request was sent over TLS, and the Request-URI contained + * a SIPS URI, the "secure" flag is set to true. *NOT IMPLEMENTED* + */ + get: function () { + return this.dialogState.secure; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "userAgentCore", { + /** The user agent core servicing this dialog. */ + get: function () { + return this.core; + }, + enumerable: true, + configurable: true + }); + /** Confirm the dialog. Only matters if dialog is currently early. */ + Dialog.prototype.confirm = function () { + this.dialogState.early = false; + }; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * + * Note that some requests, such as INVITEs, affect several pieces of + * state. + * + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + Dialog.prototype.receiveRequest = function (message) { + // ACK guard. + // By convention, the handling of ACKs is the responsibility + // the particular dialog implementation. For example, see SessionDialog. + // Furthermore, ACKs have same sequence number as the associated INVITE. + if (message.method === messages_1.C.ACK) { + return; + } + // If the remote sequence number was not empty, but the sequence number + // of the request is lower than the remote sequence number, the request + // is out of order and MUST be rejected with a 500 (Server Internal + // Error) response. If the remote sequence number was not empty, and + // the sequence number of the request is greater than the remote + // sequence number, the request is in order. It is possible for the + // CSeq sequence number to be higher than the remote sequence number by + // more than one. This is not an error condition, and a UAS SHOULD be + // prepared to receive and process requests with CSeq values more than + // one higher than the previous received request. The UAS MUST then set + // the remote sequence number to the value of the sequence number in the + // CSeq header field value in the request. + // + // If a proxy challenges a request generated by the UAC, the UAC has + // to resubmit the request with credentials. The resubmitted request + // will have a new CSeq number. The UAS will never see the first + // request, and thus, it will notice a gap in the CSeq number space. + // Such a gap does not represent any error condition. + // + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (this.remoteSequenceNumber) { + if (message.cseq <= this.remoteSequenceNumber) { + throw new Error("Out of sequence in dialog request. Did you forget to call sequenceGuard()?"); + } + this.dialogState.remoteSequenceNumber = message.cseq; + } + // If the remote sequence number is empty, it MUST be set to the value + // of the sequence number in the CSeq header field value in the request. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.remoteSequenceNumber) { + this.dialogState.remoteSequenceNumber = message.cseq; + } + // When a UAS receives a target refresh request, it MUST replace the + // dialog's remote target URI with the URI from the Contact header field + // in that request, if present. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + // Note: "target refresh request" processing delegated to sub-class. + }; + /** + * If the dialog identifier in the 2xx response matches the dialog + * identifier of an existing dialog, the dialog MUST be transitioned to + * the "confirmed" state, and the route set for the dialog MUST be + * recomputed based on the 2xx response using the procedures of Section + * 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + * constructed using the procedures of Section 12.1.2. + * + * Note that the only piece of state that is recomputed is the route + * set. Other pieces of state such as the highest sequence numbers + * (remote and local) sent within the dialog are not recomputed. The + * route set only is recomputed for backwards compatibility. RFC + * 2543 did not mandate mirroring of the Record-Route header field in + * a 1xx, only 2xx. However, we cannot update the entire state of + * the dialog, since mid-dialog requests may have been sent within + * the early dialog, modifying the sequence numbers, for example. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + */ + Dialog.prototype.recomputeRouteSet = function (message) { + this.dialogState.routeSet = message.getHeaders("record-route").reverse(); + }; + /** + * A request within a dialog is constructed by using many of the + * components of the state stored as part of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + * @param method Outgoing request method. + */ + Dialog.prototype.createOutgoingRequestMessage = function (method, options) { + // The URI in the To field of the request MUST be set to the remote URI + // from the dialog state. The tag in the To header field of the request + // MUST be set to the remote tag of the dialog ID. The From URI of the + // request MUST be set to the local URI from the dialog state. The tag + // in the From header field of the request MUST be set to the local tag + // of the dialog ID. If the value of the remote or local tags is null, + // the tag parameter MUST be omitted from the To or From header fields, + // respectively. + // + // Usage of the URI from the To and From fields in the original + // request within subsequent requests is done for backwards + // compatibility with RFC 2543, which used the URI for dialog + // identification. In this specification, only the tags are used for + // dialog identification. It is expected that mandatory reflection + // of the original To and From URI in mid-dialog requests will be + // deprecated in a subsequent revision of this specification. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + var toUri = this.remoteURI; + var toTag = this.remoteTag; + var fromUri = this.localURI; + var fromTag = this.localTag; + // The Call-ID of the request MUST be set to the Call-ID of the dialog. + // Requests within a dialog MUST contain strictly monotonically + // increasing and contiguous CSeq sequence numbers (increasing-by-one) + // in each direction (excepting ACK and CANCEL of course, whose numbers + // equal the requests being acknowledged or cancelled). Therefore, if + // the local sequence number is not empty, the value of the local + // sequence number MUST be incremented by one, and this value MUST be + // placed into the CSeq header field. If the local sequence number is + // empty, an initial value MUST be chosen using the guidelines of + // Section 8.1.1.5. The method field in the CSeq header field value + // MUST match the method of the request. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + var callId = this.callId; + var cseq; + if (options && options.cseq) { + cseq = options.cseq; + } + else if (!this.dialogState.localSequenceNumber) { + cseq = this.dialogState.localSequenceNumber = 1; // https://tools.ietf.org/html/rfc3261#section-8.1.1.5 + } + else { + cseq = this.dialogState.localSequenceNumber += 1; + } + // The UAC uses the remote target and route set to build the Request-URI + // and Route header field of the request. + // + // If the route set is empty, the UAC MUST place the remote target URI + // into the Request-URI. The UAC MUST NOT add a Route header field to + // the request. + // + // If the route set is not empty, and the first URI in the route set + // contains the lr parameter (see Section 19.1.1), the UAC MUST place + // the remote target URI into the Request-URI and MUST include a Route + // header field containing the route set values in order, including all + // parameters. + // + // If the route set is not empty, and its first URI does not contain the + // lr parameter, the UAC MUST place the first URI from the route set + // into the Request-URI, stripping any parameters that are not allowed + // in a Request-URI. The UAC MUST add a Route header field containing + // the remainder of the route set values in order, including all + // parameters. The UAC MUST then place the remote target URI into the + // Route header field as the last value. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + // The lr parameter, when present, indicates that the element + // responsible for this resource implements the routing mechanisms + // specified in this document. This parameter will be used in the + // URIs proxies place into Record-Route header field values, and + // may appear in the URIs in a pre-existing route set. + // + // This parameter is used to achieve backwards compatibility with + // systems implementing the strict-routing mechanisms of RFC 2543 + // and the rfc2543bis drafts up to bis-05. An element preparing + // to send a request based on a URI not containing this parameter + // can assume the receiving element implements strict-routing and + // reformat the message to preserve the information in the + // Request-URI. + // https://tools.ietf.org/html/rfc3261#section-19.1.1 + // NOTE: Not backwards compatible with RFC 2543 (no support for strict-routing). + var ruri = this.remoteTarget; + var routeSet = this.routeSet; + var extraHeaders = options && options.extraHeaders; + var body = options && options.body; + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + var message = this.userAgentCore.makeOutgoingRequestMessage(method, ruri, fromUri, toUri, { + callId: callId, + cseq: cseq, + fromTag: fromTag, + toTag: toTag, + routeSet: routeSet + }, extraHeaders, body); + return message; + }; + /** + * If the remote sequence number was not empty, but the sequence number + * of the request is lower than the remote sequence number, the request + * is out of order and MUST be rejected with a 500 (Server Internal + * Error) response. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param request Incoming request to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise a 500 Server Internal Error was stateless sent and request processing must stop. + */ + Dialog.prototype.sequenceGuard = function (message) { + // ACK guard. + // By convention, handling of unexpected ACKs is responsibility + // the particular dialog implementation. For example, see SessionDialog. + // Furthermore, we cannot reply to an "out of sequence" ACK. + if (message.method === messages_1.C.ACK) { + return true; + } + // Note: We are rejecting on "less than or equal to" the remote + // sequence number (excepting ACK whose numbers equal the requests + // being acknowledged or cancelled), which is the correct thing to + // do in our case. The only time a request with the same sequence number + // will show up here if is a) it is a very late retransmission of a + // request we already handled or b) it is a different request with the + // same sequence number which would be violation of the standard. + // Request retransmissions are absorbed by the transaction layer, + // so any request with a duplicate sequence number getting here + // would have to be a retransmission after the transaction terminated + // or a broken request (with unique via branch value). + // Requests within a dialog MUST contain strictly monotonically + // increasing and contiguous CSeq sequence numbers (increasing-by-one) + // in each direction (excepting ACK and CANCEL of course, whose numbers + // equal the requests being acknowledged or cancelled). Therefore, if + // the local sequence number is not empty, the value of the local + // sequence number MUST be incremented by one, and this value MUST be + // placed into the CSeq header field. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + if (this.remoteSequenceNumber && message.cseq <= this.remoteSequenceNumber) { + this.core.replyStateless(message, { statusCode: 500 }); + return false; + } + return true; + }; + return Dialog; +}()); +exports.Dialog = Dialog; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +// Directories +tslib_1.__exportStar(__webpack_require__(6), exports); +// Files +tslib_1.__exportStar(__webpack_require__(8), exports); +tslib_1.__exportStar(__webpack_require__(19), exports); +tslib_1.__exportStar(__webpack_require__(11), exports); +tslib_1.__exportStar(__webpack_require__(10), exports); +tslib_1.__exportStar(__webpack_require__(9), exports); +tslib_1.__exportStar(__webpack_require__(17), exports); +tslib_1.__exportStar(__webpack_require__(13), exports); +tslib_1.__exportStar(__webpack_require__(18), exports); +tslib_1.__exportStar(__webpack_require__(22), exports); +tslib_1.__exportStar(__webpack_require__(14), exports); +tslib_1.__exportStar(__webpack_require__(15), exports); + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(7), exports); + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * SIP Methods + * @internal + */ +var C; +(function (C) { + C.ACK = "ACK"; + C.BYE = "BYE"; + C.CANCEL = "CANCEL"; + C.INFO = "INFO"; + C.INVITE = "INVITE"; + C.MESSAGE = "MESSAGE"; + C.NOTIFY = "NOTIFY"; + C.OPTIONS = "OPTIONS"; + C.REGISTER = "REGISTER"; + C.UPDATE = "UPDATE"; + C.SUBSCRIBE = "SUBSCRIBE"; + C.PUBLISH = "PUBLISH"; + C.REFER = "REFER"; + C.PRACK = "PRACK"; +})(C = exports.C || (exports.C = {})); + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var incoming_request_message_1 = __webpack_require__(9); +var incoming_response_message_1 = __webpack_require__(17); +var outgoing_request_message_1 = __webpack_require__(18); +/** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ +function fromBodyLegacy(bodyLegacy) { + var content = (typeof bodyLegacy === "string") ? bodyLegacy : bodyLegacy.body; + var contentType = (typeof bodyLegacy === "string") ? "application/sdp" : bodyLegacy.contentType; + var contentDisposition = contentTypeToContentDisposition(contentType); + var body = { contentDisposition: contentDisposition, contentType: contentType, content: content }; + return body; +} +exports.fromBodyLegacy = fromBodyLegacy; +/** + * Given a message, get a normalized body. + * The content disposition is inferred if not set. + * @param message The message. + */ +function getBody(message) { + var contentDisposition; + var contentType; + var content; + // We're in UAS role, receiving incoming request + if (message instanceof incoming_request_message_1.IncomingRequestMessage) { + if (message.body) { + // FIXME: Parsing needs typing + var parse = message.parseHeader("Content-Disposition"); + contentDisposition = parse ? parse.type : undefined; + contentType = message.parseHeader("Content-Type"); + content = message.body; + } + } + // We're in UAC role, receiving incoming response + if (message instanceof incoming_response_message_1.IncomingResponseMessage) { + if (message.body) { + // FIXME: Parsing needs typing + var parse = message.parseHeader("Content-Disposition"); + contentDisposition = parse ? parse.type : undefined; + contentType = message.parseHeader("Content-Type"); + content = message.body; + } + } + // We're in UAC role, sending outgoing request + if (message instanceof outgoing_request_message_1.OutgoingRequestMessage) { + if (message.body) { + contentDisposition = message.getHeader("Content-Disposition"); + contentType = message.getHeader("Content-Type"); + if (typeof message.body === "string") { + // FIXME: OutgoingRequest should not allow a "string" body without a "Content-Type" header. + if (!contentType) { + throw new Error("Header content type header does not equal body content type."); + } + content = message.body; + } + else { + // FIXME: OutgoingRequest should not allow the "Content-Type" header not to match th body content type + if (contentType && contentType !== message.body.contentType) { + throw new Error("Header content type header does not equal body content type."); + } + contentType = message.body.contentType; + content = message.body.body; + } + } + } + // We're in UAS role, sending outgoing response + if (isBody(message)) { + contentDisposition = message.contentDisposition; + contentType = message.contentType; + content = message.content; + } + // No content, no body. + if (!content) { + return undefined; + } + if (contentType && !contentDisposition) { + contentDisposition = contentTypeToContentDisposition(contentType); + } + if (!contentDisposition) { + throw new Error("Content disposition undefined."); + } + if (!contentType) { + throw new Error("Content type undefined."); + } + return { + contentDisposition: contentDisposition, + contentType: contentType, + content: content + }; +} +exports.getBody = getBody; +/** + * User-Defined Type Guard for Body. + * @param body Body to check. + */ +function isBody(body) { + return body && + typeof body.content === "string" && + typeof body.contentType === "string" && + body.contentDisposition === undefined ? true : typeof body.contentDisposition === "string"; +} +exports.isBody = isBody; +// If the Content-Disposition header field is missing, bodies of +// Content-Type application/sdp imply the disposition "session", while +// other content types imply "render". +// https://tools.ietf.org/html/rfc3261#section-13.2.1 +function contentTypeToContentDisposition(contentType) { + if (contentType === "application/sdp") { + return "session"; + } + else { + return "render"; + } +} + + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var incoming_message_1 = __webpack_require__(10); +/** + * Incoming SIP request message. + */ +var IncomingRequestMessage = /** @class */ (function (_super) { + tslib_1.__extends(IncomingRequestMessage, _super); + function IncomingRequestMessage() { + return _super.call(this) || this; + } + return IncomingRequestMessage; +}(incoming_message_1.IncomingMessage)); +exports.IncomingRequestMessage = IncomingRequestMessage; + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var grammar_1 = __webpack_require__(11); +var utils_1 = __webpack_require__(16); +/** + * Incoming SIP message. + * @public + */ +var IncomingMessage = /** @class */ (function () { + function IncomingMessage() { + this.headers = {}; + } + /** + * Insert a header of the given name and value into the last position of the + * header array. + * @param name - header name + * @param value - header value + */ + IncomingMessage.prototype.addHeader = function (name, value) { + var header = { raw: value }; + name = utils_1.headerize(name); + if (this.headers[name]) { + this.headers[name].push(header); + } + else { + this.headers[name] = [header]; + } + }; + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + IncomingMessage.prototype.getHeader = function (name) { + var header = this.headers[utils_1.headerize(name)]; + if (header) { + if (header[0]) { + return header[0].raw; + } + } + else { + return; + } + }; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array - with all the headers of the specified name. + */ + IncomingMessage.prototype.getHeaders = function (name) { + var header = this.headers[utils_1.headerize(name)]; + var result = []; + if (!header) { + return []; + } + for (var _i = 0, header_1 = header; _i < header_1.length; _i++) { + var headerPart = header_1[_i]; + result.push(headerPart.raw); + } + return result; + }; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + IncomingMessage.prototype.hasHeader = function (name) { + return !!this.headers[utils_1.headerize(name)]; + }; + /** + * Parse the given header on the given index. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + */ + IncomingMessage.prototype.parseHeader = function (name, idx) { + if (idx === void 0) { idx = 0; } + name = utils_1.headerize(name); + if (!this.headers[name]) { + // this.logger.log("header '" + name + "' not present"); + return; + } + else if (idx >= this.headers[name].length) { + // this.logger.log("not so many '" + name + "' headers present"); + return; + } + var header = this.headers[name][idx]; + var value = header.raw; + if (header.parsed) { + return header.parsed; + } + // substitute '-' by '_' for grammar rule matching. + var parsed = grammar_1.Grammar.parse(value, name.replace(/-/g, "_")); + if (parsed === -1) { + this.headers[name].splice(idx, 1); // delete from headers + // this.logger.warn('error parsing "' + name + '" header field with value "' + value + '"'); + return; + } + else { + header.parsed = parsed; + return parsed; + } + }; + /** + * Message Header attribute selector. Alias of parseHeader. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + * + * @example + * message.s('via',3).port + */ + IncomingMessage.prototype.s = function (name, idx) { + if (idx === void 0) { idx = 0; } + return this.parseHeader(name, idx); + }; + /** + * Replace the value of the given header by the value. + * @param name - header name + * @param value - header value + */ + IncomingMessage.prototype.setHeader = function (name, value) { + this.headers[utils_1.headerize(name)] = [{ raw: value }]; + }; + IncomingMessage.prototype.toString = function () { + return this.data; + }; + return IncomingMessage; +}()); +exports.IncomingMessage = IncomingMessage; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var pegGrammar = tslib_1.__importStar(__webpack_require__(12)); +/** + * Grammar. + * @internal + */ +var Grammar; +(function (Grammar) { + /** + * Parse. + * @param input - + * @param startRule - + */ + function parse(input, startRule) { + var options = { startRule: startRule }; + try { + pegGrammar.parse(input, options); + } + catch (e) { + options.data = -1; + } + return options.data; + } + Grammar.parse = parse; + /** + * Parse the given string and returns a SIP.NameAddrHeader instance or undefined if + * it is an invalid NameAddrHeader. + * @param name_addr_header - + */ + function nameAddrHeaderParse(nameAddrHeader) { + var parsedNameAddrHeader = Grammar.parse(nameAddrHeader, "Name_Addr_Header"); + return parsedNameAddrHeader !== -1 ? parsedNameAddrHeader : undefined; + } + Grammar.nameAddrHeaderParse = nameAddrHeaderParse; + /** + * Parse the given string and returns a SIP.URI instance or undefined if + * it is an invalid URI. + * @param uri - + */ + function URIParse(uri) { + var parsedUri = Grammar.parse(uri, "SIP_URI"); + return parsedUri !== -1 ? parsedUri : undefined; + } + Grammar.URIParse = URIParse; +})(Grammar = exports.Grammar || (exports.Grammar = {})); + + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +// tslint:disable:interface-name +// tslint:disable: trailing-comma +// tslint:disable: object-literal-sort-keys +// tslint:disable: max-line-length +// tslint:disable: only-arrow-functions +// tslint:disable: one-variable-per-declaration +// tslint:disable: no-consecutive-blank-lines +// tslint:disable: align +// tslint:disable: radix +// tslint:disable: quotemark +// tslint:disable: semicolon +// tslint:disable: object-literal-shorthand +// tslint:disable: variable-name +// tslint:disable: no-var-keyword +// tslint:disable: whitespace +// tslint:disable: curly +// tslint:disable: prefer-const +// tslint:disable: object-literal-key-quotes +// tslint:disable: no-string-literal +// tslint:disable: one-line +// tslint:disable: no-unused-expression +// tslint:disable: space-before-function-paren +// tslint:disable: arrow-return-shorthand +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +// Generated by PEG.js v. 0.10.0 (ts-pegjs plugin v. 0.2.5 ) +// +// https://pegjs.org/ https://github.com/metadevpro/ts-pegjs +var name_addr_header_1 = __webpack_require__(13); +var uri_1 = __webpack_require__(15); +var SyntaxError = /** @class */ (function (_super) { + tslib_1.__extends(SyntaxError, _super); + function SyntaxError(message, expected, found, location) { + var _this = _super.call(this) || this; + _this.message = message; + _this.expected = expected; + _this.found = found; + _this.location = location; + _this.name = "SyntaxError"; + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(_this, SyntaxError); + } + return _this; + } + SyntaxError.buildMessage = function (expected, found) { + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); + } + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); + } + function describeExpectation(expectation) { + switch (expectation.type) { + case "literal": + return "\"" + literalEscape(expectation.text) + "\""; + case "class": + var escapedParts = expectation.parts.map(function (part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + case "any": + return "any character"; + case "end": + return "end of input"; + case "other": + return expectation.description; + } + } + function describeExpected(expected1) { + var descriptions = expected1.map(describeExpectation); + var i; + var j; + descriptions.sort(); + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + switch (descriptions.length) { + case 1: + return descriptions[0]; + case 2: + return descriptions[0] + " or " + descriptions[1]; + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + function describeFound(found1) { + return found1 ? "\"" + literalEscape(found1) + "\"" : "end of input"; + } + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; + }; + return SyntaxError; +}(Error)); +exports.SyntaxError = SyntaxError; +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + var peg$FAILED = {}; + var peg$startRuleIndices = { Contact: 119, Name_Addr_Header: 156, Record_Route: 176, Request_Response: 81, SIP_URI: 45, Subscription_State: 186, Supported: 191, Require: 182, Via: 194, absoluteURI: 84, Call_ID: 118, Content_Disposition: 130, Content_Length: 135, Content_Type: 136, CSeq: 146, displayName: 122, Event: 149, From: 151, host: 52, Max_Forwards: 154, Min_SE: 213, Proxy_Authenticate: 157, quoted_string: 40, Refer_To: 178, Replaces: 179, Session_Expires: 210, stun_URI: 217, To: 192, turn_URI: 223, uuid: 226, WWW_Authenticate: 209, challenge: 158, sipfrag: 230, Referred_By: 231 }; + var peg$startRuleIndex = 119; + var peg$consts = [ + "\r\n", + peg$literalExpectation("\r\n", false), + /^[0-9]/, + peg$classExpectation([["0", "9"]], false, false), + /^[a-zA-Z]/, + peg$classExpectation([["a", "z"], ["A", "Z"]], false, false), + /^[0-9a-fA-F]/, + peg$classExpectation([["0", "9"], ["a", "f"], ["A", "F"]], false, false), + /^[\0-\xFF]/, + peg$classExpectation([["\0", "\xFF"]], false, false), + /^["]/, + peg$classExpectation(["\""], false, false), + " ", + peg$literalExpectation(" ", false), + "\t", + peg$literalExpectation("\t", false), + /^[a-zA-Z0-9]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false), + ";", + peg$literalExpectation(";", false), + "/", + peg$literalExpectation("/", false), + "?", + peg$literalExpectation("?", false), + ":", + peg$literalExpectation(":", false), + "@", + peg$literalExpectation("@", false), + "&", + peg$literalExpectation("&", false), + "=", + peg$literalExpectation("=", false), + "+", + peg$literalExpectation("+", false), + "$", + peg$literalExpectation("$", false), + ",", + peg$literalExpectation(",", false), + "-", + peg$literalExpectation("-", false), + "_", + peg$literalExpectation("_", false), + ".", + peg$literalExpectation(".", false), + "!", + peg$literalExpectation("!", false), + "~", + peg$literalExpectation("~", false), + "*", + peg$literalExpectation("*", false), + "'", + peg$literalExpectation("'", false), + "(", + peg$literalExpectation("(", false), + ")", + peg$literalExpectation(")", false), + "%", + peg$literalExpectation("%", false), + function () { return " "; }, + function () { return ':'; }, + /^[!-~]/, + peg$classExpectation([["!", "~"]], false, false), + /^[\x80-\uFFFF]/, + peg$classExpectation([["\x80", "\uFFFF"]], false, false), + /^[\x80-\xBF]/, + peg$classExpectation([["\x80", "\xBF"]], false, false), + /^[a-f]/, + peg$classExpectation([["a", "f"]], false, false), + "`", + peg$literalExpectation("`", false), + "<", + peg$literalExpectation("<", false), + ">", + peg$literalExpectation(">", false), + "\\", + peg$literalExpectation("\\", false), + "[", + peg$literalExpectation("[", false), + "]", + peg$literalExpectation("]", false), + "{", + peg$literalExpectation("{", false), + "}", + peg$literalExpectation("}", false), + function () { return "*"; }, + function () { return "/"; }, + function () { return "="; }, + function () { return "("; }, + function () { return ")"; }, + function () { return ">"; }, + function () { return "<"; }, + function () { return ","; }, + function () { return ";"; }, + function () { return ":"; }, + function () { return "\""; }, + /^[!-']/, + peg$classExpectation([["!", "'"]], false, false), + /^[*-[]/, + peg$classExpectation([["*", "["]], false, false), + /^[\]-~]/, + peg$classExpectation([["]", "~"]], false, false), + function (contents) { + return contents; + }, + /^[#-[]/, + peg$classExpectation([["#", "["]], false, false), + /^[\0-\t]/, + peg$classExpectation([["\0", "\t"]], false, false), + /^[\x0B-\f]/, + peg$classExpectation([["\x0B", "\f"]], false, false), + /^[\x0E-\x7F]/, + peg$classExpectation([["\x0E", "\x7F"]], false, false), + function () { + options = options || { data: {} }; + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + }, + function () { + options = options || { data: {} }; + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + delete options.data.uri_params; + if (options.startRule === 'SIP_URI') { + options.data = options.data.uri; + } + }, + "sips", + peg$literalExpectation("sips", true), + "sip", + peg$literalExpectation("sip", true), + function (uri_scheme) { + options = options || { data: {} }; + options.data.scheme = uri_scheme; + }, + function () { + options = options || { data: {} }; + options.data.user = decodeURIComponent(text().slice(0, -1)); + }, + function () { + options = options || { data: {} }; + options.data.password = text(); + }, + function () { + options = options || { data: {} }; + options.data.host = text(); + return options.data.host; + }, + function () { + options = options || { data: {} }; + options.data.host_type = 'domain'; + return text(); + }, + /^[a-zA-Z0-9_\-]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false), + /^[a-zA-Z0-9\-]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "-"], false, false), + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv6'; + return text(); + }, + "::", + peg$literalExpectation("::", false), + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv6'; + return text(); + }, + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv4'; + return text(); + }, + "25", + peg$literalExpectation("25", false), + /^[0-5]/, + peg$classExpectation([["0", "5"]], false, false), + "2", + peg$literalExpectation("2", false), + /^[0-4]/, + peg$classExpectation([["0", "4"]], false, false), + "1", + peg$literalExpectation("1", false), + /^[1-9]/, + peg$classExpectation([["1", "9"]], false, false), + function (port) { + options = options || { data: {} }; + port = parseInt(port.join('')); + options.data.port = port; + return port; + }, + "transport=", + peg$literalExpectation("transport=", true), + "udp", + peg$literalExpectation("udp", true), + "tcp", + peg$literalExpectation("tcp", true), + "sctp", + peg$literalExpectation("sctp", true), + "tls", + peg$literalExpectation("tls", true), + function (transport) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['transport'] = transport.toLowerCase(); + }, + "user=", + peg$literalExpectation("user=", true), + "phone", + peg$literalExpectation("phone", true), + "ip", + peg$literalExpectation("ip", true), + function (user) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['user'] = user.toLowerCase(); + }, + "method=", + peg$literalExpectation("method=", true), + function (method) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['method'] = method; + }, + "ttl=", + peg$literalExpectation("ttl=", true), + function (ttl) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['ttl'] = ttl; + }, + "maddr=", + peg$literalExpectation("maddr=", true), + function (maddr) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['maddr'] = maddr; + }, + "lr", + peg$literalExpectation("lr", true), + function () { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['lr'] = undefined; + }, + function (param, value) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + if (value === null) { + value = undefined; + } + else { + value = value[1]; + } + options.data.uri_params[param.toLowerCase()] = value; + }, + function (hname, hvalue) { + hname = hname.join('').toLowerCase(); + hvalue = hvalue.join(''); + options = options || { data: {} }; + if (!options.data.uri_headers) + options.data.uri_headers = {}; + if (!options.data.uri_headers[hname]) { + options.data.uri_headers[hname] = [hvalue]; + } + else { + options.data.uri_headers[hname].push(hvalue); + } + }, + function () { + options = options || { data: {} }; + // lots of tests fail if this isn't guarded... + if (options.startRule === 'Refer_To') { + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + delete options.data.uri_params; + } + }, + "//", + peg$literalExpectation("//", false), + function () { + options = options || { data: {} }; + options.data.scheme = text(); + }, + peg$literalExpectation("SIP", true), + function () { + options = options || { data: {} }; + options.data.sip_version = text(); + }, + "INVITE", + peg$literalExpectation("INVITE", false), + "ACK", + peg$literalExpectation("ACK", false), + "VXACH", + peg$literalExpectation("VXACH", false), + "OPTIONS", + peg$literalExpectation("OPTIONS", false), + "BYE", + peg$literalExpectation("BYE", false), + "CANCEL", + peg$literalExpectation("CANCEL", false), + "REGISTER", + peg$literalExpectation("REGISTER", false), + "SUBSCRIBE", + peg$literalExpectation("SUBSCRIBE", false), + "NOTIFY", + peg$literalExpectation("NOTIFY", false), + "REFER", + peg$literalExpectation("REFER", false), + "PUBLISH", + peg$literalExpectation("PUBLISH", false), + function () { + options = options || { data: {} }; + options.data.method = text(); + return options.data.method; + }, + function (status_code) { + options = options || { data: {} }; + options.data.status_code = parseInt(status_code.join('')); + }, + function () { + options = options || { data: {} }; + options.data.reason_phrase = text(); + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + function () { + var idx, length; + options = options || { data: {} }; + length = options.data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (options.data.multi_header[idx].parsed === null) { + options.data = null; + break; + } + } + if (options.data !== null) { + options.data = options.data.multi_header; + } + else { + options.data = -1; + } + }, + function () { + var header; + options = options || { data: {} }; + if (!options.data.multi_header) + options.data.multi_header = []; + try { + header = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + delete options.data.uri; + delete options.data.displayName; + delete options.data.params; + } + catch (e) { + header = null; + } + options.data.multi_header.push({ 'position': peg$currPos, + 'offset': location().start.offset, + 'parsed': header + }); + }, + function (displayName) { + displayName = text().trim(); + if (displayName[0] === '\"') { + displayName = displayName.substring(1, displayName.length - 1); + } + options = options || { data: {} }; + options.data.displayName = displayName; + }, + "q", + peg$literalExpectation("q", true), + function (q) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['q'] = q; + }, + "expires", + peg$literalExpectation("expires", true), + function (expires) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['expires'] = expires; + }, + function (delta_seconds) { + return parseInt(delta_seconds.join('')); + }, + "0", + peg$literalExpectation("0", false), + function () { + return parseFloat(text()); + }, + function (param, value) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + if (value === null) { + value = undefined; + } + else { + value = value[1]; + } + options.data.params[param.toLowerCase()] = value; + }, + "render", + peg$literalExpectation("render", true), + "session", + peg$literalExpectation("session", true), + "icon", + peg$literalExpectation("icon", true), + "alert", + peg$literalExpectation("alert", true), + function () { + options = options || { data: {} }; + if (options.startRule === 'Content_Disposition') { + options.data.type = text().toLowerCase(); + } + }, + "handling", + peg$literalExpectation("handling", true), + "optional", + peg$literalExpectation("optional", true), + "required", + peg$literalExpectation("required", true), + function (length) { + options = options || { data: {} }; + options.data = parseInt(length.join('')); + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + "text", + peg$literalExpectation("text", true), + "image", + peg$literalExpectation("image", true), + "audio", + peg$literalExpectation("audio", true), + "video", + peg$literalExpectation("video", true), + "application", + peg$literalExpectation("application", true), + "message", + peg$literalExpectation("message", true), + "multipart", + peg$literalExpectation("multipart", true), + "x-", + peg$literalExpectation("x-", true), + function (cseq_value) { + options = options || { data: {} }; + options.data.value = parseInt(cseq_value.join('')); + }, + function (expires) { options = options || { data: {} }; options.data = expires; }, + function (event_type) { + options = options || { data: {} }; + options.data.event = event_type.toLowerCase(); + }, + function () { + options = options || { data: {} }; + var tag = options.data.tag; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + if (tag) { + options.data.setParam('tag', tag); + } + }, + "tag", + peg$literalExpectation("tag", true), + function (tag) { options = options || { data: {} }; options.data.tag = tag; }, + function (forwards) { + options = options || { data: {} }; + options.data = parseInt(forwards.join('')); + }, + function (min_expires) { options = options || { data: {} }; options.data = min_expires; }, + function () { + options = options || { data: {} }; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + }, + "digest", + peg$literalExpectation("Digest", true), + "realm", + peg$literalExpectation("realm", true), + function (realm) { options = options || { data: {} }; options.data.realm = realm; }, + "domain", + peg$literalExpectation("domain", true), + "nonce", + peg$literalExpectation("nonce", true), + function (nonce) { options = options || { data: {} }; options.data.nonce = nonce; }, + "opaque", + peg$literalExpectation("opaque", true), + function (opaque) { options = options || { data: {} }; options.data.opaque = opaque; }, + "stale", + peg$literalExpectation("stale", true), + "true", + peg$literalExpectation("true", true), + function () { options = options || { data: {} }; options.data.stale = true; }, + "false", + peg$literalExpectation("false", true), + function () { options = options || { data: {} }; options.data.stale = false; }, + "algorithm", + peg$literalExpectation("algorithm", true), + "md5", + peg$literalExpectation("MD5", true), + "md5-sess", + peg$literalExpectation("MD5-sess", true), + function (algorithm) { + options = options || { data: {} }; + options.data.algorithm = algorithm.toUpperCase(); + }, + "qop", + peg$literalExpectation("qop", true), + "auth-int", + peg$literalExpectation("auth-int", true), + "auth", + peg$literalExpectation("auth", true), + function (qop_value) { + options = options || { data: {} }; + options.data.qop || (options.data.qop = []); + options.data.qop.push(qop_value.toLowerCase()); + }, + function (rack_value) { + options = options || { data: {} }; + options.data.value = parseInt(rack_value.join('')); + }, + function () { + var idx, length; + options = options || { data: {} }; + length = options.data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (options.data.multi_header[idx].parsed === null) { + options.data = null; + break; + } + } + if (options.data !== null) { + options.data = options.data.multi_header; + } + else { + options.data = -1; + } + }, + function () { + var header; + options = options || { data: {} }; + if (!options.data.multi_header) + options.data.multi_header = []; + try { + header = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + delete options.data.uri; + delete options.data.displayName; + delete options.data.params; + } + catch (e) { + header = null; + } + options.data.multi_header.push({ 'position': peg$currPos, + 'offset': location().start.offset, + 'parsed': header + }); + }, + function () { + options = options || { data: {} }; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + }, + function () { + options = options || { data: {} }; + if (!(options.data.replaces_from_tag && options.data.replaces_to_tag)) { + options.data = -1; + } + }, + function () { + options = options || { data: {} }; + options.data = { + call_id: options.data + }; + }, + "from-tag", + peg$literalExpectation("from-tag", true), + function (from_tag) { + options = options || { data: {} }; + options.data.replaces_from_tag = from_tag; + }, + "to-tag", + peg$literalExpectation("to-tag", true), + function (to_tag) { + options = options || { data: {} }; + options.data.replaces_to_tag = to_tag; + }, + "early-only", + peg$literalExpectation("early-only", true), + function () { + options = options || { data: {} }; + options.data.early_only = true; + }, + function (head, r) { return r; }, + function (head, tail) { return list(head, tail); }, + function (value) { + options = options || { data: {} }; + if (options.startRule === 'Require') { + options.data = value || []; + } + }, + function (rseq_value) { + options = options || { data: {} }; + options.data.value = parseInt(rseq_value.join('')); + }, + "active", + peg$literalExpectation("active", true), + "pending", + peg$literalExpectation("pending", true), + "terminated", + peg$literalExpectation("terminated", true), + function () { + options = options || { data: {} }; + options.data.state = text(); + }, + "reason", + peg$literalExpectation("reason", true), + function (reason) { + options = options || { data: {} }; + if (typeof reason !== 'undefined') + options.data.reason = reason; + }, + function (expires) { + options = options || { data: {} }; + if (typeof expires !== 'undefined') + options.data.expires = expires; + }, + "retry_after", + peg$literalExpectation("retry_after", true), + function (retry_after) { + options = options || { data: {} }; + if (typeof retry_after !== 'undefined') + options.data.retry_after = retry_after; + }, + "deactivated", + peg$literalExpectation("deactivated", true), + "probation", + peg$literalExpectation("probation", true), + "rejected", + peg$literalExpectation("rejected", true), + "timeout", + peg$literalExpectation("timeout", true), + "giveup", + peg$literalExpectation("giveup", true), + "noresource", + peg$literalExpectation("noresource", true), + "invariant", + peg$literalExpectation("invariant", true), + function (value) { + options = options || { data: {} }; + if (options.startRule === 'Supported') { + options.data = value || []; + } + }, + function () { + options = options || { data: {} }; + var tag = options.data.tag; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + if (tag) { + options.data.setParam('tag', tag); + } + }, + "ttl", + peg$literalExpectation("ttl", true), + function (via_ttl_value) { + options = options || { data: {} }; + options.data.ttl = via_ttl_value; + }, + "maddr", + peg$literalExpectation("maddr", true), + function (via_maddr) { + options = options || { data: {} }; + options.data.maddr = via_maddr; + }, + "received", + peg$literalExpectation("received", true), + function (via_received) { + options = options || { data: {} }; + options.data.received = via_received; + }, + "branch", + peg$literalExpectation("branch", true), + function (via_branch) { + options = options || { data: {} }; + options.data.branch = via_branch; + }, + "rport", + peg$literalExpectation("rport", true), + function (response_port) { + options = options || { data: {} }; + if (typeof response_port !== 'undefined') + options.data.rport = response_port.join(''); + }, + function (via_protocol) { + options = options || { data: {} }; + options.data.protocol = via_protocol; + }, + peg$literalExpectation("UDP", true), + peg$literalExpectation("TCP", true), + peg$literalExpectation("TLS", true), + peg$literalExpectation("SCTP", true), + function (via_transport) { + options = options || { data: {} }; + options.data.transport = via_transport; + }, + function () { + options = options || { data: {} }; + options.data.host = text(); + }, + function (via_sent_by_port) { + options = options || { data: {} }; + options.data.port = parseInt(via_sent_by_port.join('')); + }, + function (ttl) { + return parseInt(ttl.join('')); + }, + function (deltaSeconds) { + options = options || { data: {} }; + if (options.startRule === 'Session_Expires') { + options.data.deltaSeconds = deltaSeconds; + } + }, + "refresher", + peg$literalExpectation("refresher", false), + "uas", + peg$literalExpectation("uas", false), + "uac", + peg$literalExpectation("uac", false), + function (endpoint) { + options = options || { data: {} }; + if (options.startRule === 'Session_Expires') { + options.data.refresher = endpoint; + } + }, + function (deltaSeconds) { + options = options || { data: {} }; + if (options.startRule === 'Min_SE') { + options.data = deltaSeconds; + } + }, + "stuns", + peg$literalExpectation("stuns", true), + "stun", + peg$literalExpectation("stun", true), + function (scheme) { + options = options || { data: {} }; + options.data.scheme = scheme; + }, + function (host) { + options = options || { data: {} }; + options.data.host = host; + }, + "?transport=", + peg$literalExpectation("?transport=", false), + "turns", + peg$literalExpectation("turns", true), + "turn", + peg$literalExpectation("turn", true), + function (transport) { + options = options || { data: {} }; + options.data.transport = transport; + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + "Referred-By", + peg$literalExpectation("Referred-By", false), + "b", + peg$literalExpectation("b", false), + "cid", + peg$literalExpectation("cid", false) + ]; + var peg$bytecode = [ + peg$decode("2 \"\"6 7!"), + peg$decode("4\"\"\"5!7#"), + peg$decode("4$\"\"5!7%"), + peg$decode("4&\"\"5!7'"), + peg$decode(";'.# &;("), + peg$decode("4(\"\"5!7)"), + peg$decode("4*\"\"5!7+"), + peg$decode("2,\"\"6,7-"), + peg$decode("2.\"\"6.7/"), + peg$decode("40\"\"5!71"), + peg$decode("22\"\"6273.\x89 &24\"\"6475.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode(";).# &;,"), + peg$decode("2F\"\"6F7G.} &2H\"\"6H7I.q &2J\"\"6J7K.e &2L\"\"6L7M.Y &2N\"\"6N7O.M &2P\"\"6P7Q.A &2R\"\"6R7S.5 &2T\"\"6T7U.) &2V\"\"6V7W"), + peg$decode("%%2X\"\"6X7Y/5#;#/,$;#/#$+#)(#'#(\"'#&'#/\"!&,)"), + peg$decode("%%$;$0#*;$&/,#; /#$+\")(\"'#&'#.\" &\"/=#$;$/�#*;$&&&#/'$8\":Z\" )(\"'#&'#"), + peg$decode(";..\" &\""), + peg$decode("%$;'.# &;(0)*;'.# &;(&/?#28\"\"6879/0$;//'$8#:[# )(#'#(\"'#&'#"), + peg$decode("%%$;2/�#*;2&&&#/g#$%$;.0#*;.&/,#;2/#$+\")(\"'#&'#0=*%$;.0#*;.&/,#;2/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/\"!&,)"), + peg$decode("4\\\"\"5!7].# &;3"), + peg$decode("4^\"\"5!7_"), + peg$decode("4`\"\"5!7a"), + peg$decode(";!.) &4b\"\"5!7c"), + peg$decode("%$;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x9E#0\x9B*;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"), + peg$decode("%$;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x92#0\x8F*;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"), + peg$decode("2T\"\"6T7U.\xE3 &2V\"\"6V7W.\xD7 &2f\"\"6f7g.\xCB &2h\"\"6h7i.\xBF &2:\"\"6:7;.\xB3 &2D\"\"6D7E.\xA7 &22\"\"6273.\x9B &28\"\"6879.\x8F &2j\"\"6j7k.\x83 &;&.} &24\"\"6475.q &2l\"\"6l7m.e &2n\"\"6n7o.Y &26\"\"6677.M &2>\"\"6>7?.A &2p\"\"6p7q.5 &2r\"\"6r7s.) &;'.# &;("), + peg$decode("%$;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s/\u0134#0\u0131*;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s&&&#/\"!&,)"), + peg$decode("%;//?#2P\"\"6P7Q/0$;//'$8#:t# )(#'#(\"'#&'#"), + peg$decode("%;//?#24\"\"6475/0$;//'$8#:u# )(#'#(\"'#&'#"), + peg$decode("%;//?#2>\"\"6>7?/0$;//'$8#:v# )(#'#(\"'#&'#"), + peg$decode("%;//?#2T\"\"6T7U/0$;//'$8#:w# )(#'#(\"'#&'#"), + peg$decode("%;//?#2V\"\"6V7W/0$;//'$8#:x# )(#'#(\"'#&'#"), + peg$decode("%2h\"\"6h7i/0#;//'$8\":y\" )(\"'#&'#"), + peg$decode("%;//6#2f\"\"6f7g/'$8\":z\" )(\"'#&'#"), + peg$decode("%;//?#2D\"\"6D7E/0$;//'$8#:{# )(#'#(\"'#&'#"), + peg$decode("%;//?#22\"\"6273/0$;//'$8#:|# )(#'#(\"'#&'#"), + peg$decode("%;//?#28\"\"6879/0$;//'$8#:}# )(#'#(\"'#&'#"), + peg$decode("%;//0#;&/'$8\":~\" )(\"'#&'#"), + peg$decode("%;&/0#;//'$8\":~\" )(\"'#&'#"), + peg$decode("%;=/T#$;G.) &;K.# &;F0/*;G.) &;K.# &;F&/,$;>/#$+#)(#'#(\"'#&'#"), + peg$decode("4\x7F\"\"5!7\x80.A &4\x81\"\"5!7\x82.5 &4\x83\"\"5!7\x84.) &;3.# &;."), + peg$decode("%%;//Q#;&/H$$;J.# &;K0)*;J.# &;K&/,$;&/#$+$)($'#(#'#(\"'#&'#/\"!&,)"), + peg$decode("%;//]#;&/T$%$;J.# &;K0)*;J.# &;K&/\"!&,)/1$;&/($8$:\x85$!!)($'#(#'#(\"'#&'#"), + peg$decode(";..G &2L\"\"6L7M.; &4\x86\"\"5!7\x87./ &4\x83\"\"5!7\x84.# &;3"), + peg$decode("%2j\"\"6j7k/J#4\x88\"\"5!7\x89.5 &4\x8A\"\"5!7\x8B.) &4\x8C\"\"5!7\x8D/#$+\")(\"'#&'#"), + peg$decode("%;N/M#28\"\"6879/>$;O.\" &\"/0$;S/'$8$:\x8E$ )($'#(#'#(\"'#&'#"), + peg$decode("%;N/d#28\"\"6879/U$;O.\" &\"/G$;S/>$;_/5$;l.\" &\"/'$8&:\x8F& )(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%3\x90\"\"5$7\x91.) &3\x92\"\"5#7\x93/' 8!:\x94!! )"), + peg$decode("%;P/]#%28\"\"6879/,#;R/#$+\")(\"'#&'#.\" &\"/6$2:\"\"6:7;/'$8#:\x95# )(#'#(\"'#&'#"), + peg$decode("$;+.) &;-.# &;Q/2#0/*;+.) &;-.# &;Q&&&#"), + peg$decode("2<\"\"6<7=.q &2>\"\"6>7?.e &2@\"\"6@7A.Y &2B\"\"6B7C.M &2D\"\"6D7E.A &22\"\"6273.5 &26\"\"6677.) &24\"\"6475"), + peg$decode("%$;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E0e*;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E&/& 8!:\x96! )"), + peg$decode("%;T/J#%28\"\"6879/,#;^/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%;U.) &;\\.# &;X/& 8!:\x97! )"), + peg$decode("%$%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#0<*%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#&/D#;W/;$2J\"\"6J7K.\" &\"/'$8#:\x98# )(#'#(\"'#&'#"), + peg$decode("$4\x99\"\"5!7\x9A/,#0)*4\x99\"\"5!7\x9A&&&#"), + peg$decode("%4$\"\"5!7%/?#$4\x9B\"\"5!7\x9C0)*4\x9B\"\"5!7\x9C&/#$+\")(\"'#&'#"), + peg$decode("%2l\"\"6l7m/?#;Y/6$2n\"\"6n7o/'$8#:\x9D# )(#'#(\"'#&'#"), + peg$decode("%%;Z/\xB3#28\"\"6879/\xA4$;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+-)(-'#(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0790 &%2\x9E\"\"6\x9E7\x9F/\xA4#;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+,)(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u06F9 &%2\x9E\"\"6\x9E7\x9F/\x8C#;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u067A &%2\x9E\"\"6\x9E7\x9F/t#;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0613 &%2\x9E\"\"6\x9E7\x9F/\\#;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+&)(&'#(%'#($'#(#'#(\"'#&'#.\u05C4 &%2\x9E\"\"6\x9E7\x9F/D#;Z/;$28\"\"6879/,$;[/#$+$)($'#(#'#(\"'#&'#.\u058D &%2\x9E\"\"6\x9E7\x9F/,#;[/#$+\")(\"'#&'#.\u056E &%2\x9E\"\"6\x9E7\x9F/,#;Z/#$+\")(\"'#&'#.\u054F &%;Z/\x9B#2\x9E\"\"6\x9E7\x9F/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$++)(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u04C7 &%;Z/\xAA#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x83$2\x9E\"\"6\x9E7\x9F/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0430 &%;Z/\xB9#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x92$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/k$2\x9E\"\"6\x9E7\x9F/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+))()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u038A &%;Z/\xC8#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA1$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/z$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/S$2\x9E\"\"6\x9E7\x9F/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u02D5 &%;Z/\xD7#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;[/#$+')(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0211 &%;Z/\xFE#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xD7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;Z/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0126 &%;Z/\u011C#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xF5$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xCE$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x80$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/Y$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/2$2\x9E\"\"6\x9E7\x9F/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#/& 8!:\xA0! )"), + peg$decode("%;#/M#;#.\" &\"/?$;#.\" &\"/1$;#.\" &\"/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%;Z/;#28\"\"6879/,$;Z/#$+#)(#'#(\"'#&'#.# &;\\"), + peg$decode("%;]/o#2J\"\"6J7K/`$;]/W$2J\"\"6J7K/H$;]/?$2J\"\"6J7K/0$;]/'$8':\xA1' )(''#(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%2\xA2\"\"6\xA27\xA3/2#4\xA4\"\"5!7\xA5/#$+\")(\"'#&'#.\x98 &%2\xA6\"\"6\xA67\xA7/;#4\xA8\"\"5!7\xA9/,$;!/#$+#)(#'#(\"'#&'#.j &%2\xAA\"\"6\xAA7\xAB/5#;!/,$;!/#$+#)(#'#(\"'#&'#.B &%4\xAC\"\"5!7\xAD/,#;!/#$+\")(\"'#&'#.# &;!"), + peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\xAE!! )"), + peg$decode("$%22\"\"6273/,#;`/#$+\")(\"'#&'#0<*%22\"\"6273/,#;`/#$+\")(\"'#&'#&"), + peg$decode(";a.A &;b.; &;c.5 &;d./ &;e.) &;f.# &;g"), + peg$decode("%3\xAF\"\"5*7\xB0/a#3\xB1\"\"5#7\xB2.G &3\xB3\"\"5#7\xB4.; &3\xB5\"\"5$7\xB6./ &3\xB7\"\"5#7\xB8.# &;6/($8\":\xB9\"! )(\"'#&'#"), + peg$decode("%3\xBA\"\"5%7\xBB/I#3\xBC\"\"5%7\xBD./ &3\xBE\"\"5\"7\xBF.# &;6/($8\":\xC0\"! )(\"'#&'#"), + peg$decode("%3\xC1\"\"5'7\xC2/1#;\x90/($8\":\xC3\"! )(\"'#&'#"), + peg$decode("%3\xC4\"\"5$7\xC5/1#;\xF0/($8\":\xC6\"! )(\"'#&'#"), + peg$decode("%3\xC7\"\"5&7\xC8/1#;T/($8\":\xC9\"! )(\"'#&'#"), + peg$decode("%3\xCA\"\"5\"7\xCB/N#%2>\"\"6>7?/,#;6/#$+\")(\"'#&'#.\" &\"/'$8\":\xCC\" )(\"'#&'#"), + peg$decode("%;h/P#%2>\"\"6>7?/,#;i/#$+\")(\"'#&'#.\" &\"/)$8\":\xCD\"\"! )(\"'#&'#"), + peg$decode("%$;j/�#*;j&&&#/\"!&,)"), + peg$decode("%$;j/�#*;j&&&#/\"!&,)"), + peg$decode(";k.) &;+.# &;-"), + peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &28\"\"6879.A &2<\"\"6<7=.5 &2@\"\"6@7A.) &2B\"\"6B7C"), + peg$decode("%26\"\"6677/n#;m/e$$%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#0<*%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#&/#$+#)(#'#(\"'#&'#"), + peg$decode("%;n/A#2>\"\"6>7?/2$;o/)$8#:\xCE#\"\" )(#'#(\"'#&'#"), + peg$decode("$;p.) &;+.# &;-/2#0/*;p.) &;+.# &;-&&&#"), + peg$decode("$;p.) &;+.# &;-0/*;p.) &;+.# &;-&"), + peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &26\"\"6677.A &28\"\"6879.5 &2@\"\"6@7A.) &2B\"\"6B7C"), + peg$decode(";\x91.# &;r"), + peg$decode("%;\x90/G#;'/>$;s/5$;'/,$;\x84/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode(";M.# &;t"), + peg$decode("%;\x7F/E#28\"\"6879/6$;u.# &;x/'$8#:\xCF# )(#'#(\"'#&'#"), + peg$decode("%;v.# &;w/J#%26\"\"6677/,#;\x83/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%2\xD0\"\"6\xD07\xD1/:#;\x80/1$;w.\" &\"/#$+#)(#'#(\"'#&'#"), + peg$decode("%24\"\"6475/,#;{/#$+\")(\"'#&'#"), + peg$decode("%;z/3#$;y0#*;y&/#$+\")(\"'#&'#"), + peg$decode(";*.) &;+.# &;-"), + peg$decode(";+.\x8F &;-.\x89 &22\"\"6273.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode("%;|/e#$%24\"\"6475/,#;|/#$+\")(\"'#&'#0<*%24\"\"6475/,#;|/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("%$;~0#*;~&/e#$%22\"\"6273/,#;}/#$+\")(\"'#&'#0<*%22\"\"6273/,#;}/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("$;~0#*;~&"), + peg$decode(";+.w &;-.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode("%%;\"/\x87#$;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K0M*;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K&/#$+\")(\"'#&'#/& 8!:\xD2! )"), + peg$decode(";\x81.# &;\x82"), + peg$decode("%%;O/2#2:\"\"6:7;/#$+\")(\"'#&'#.\" &\"/,#;S/#$+\")(\"'#&'#.\" &\""), + peg$decode("$;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A/\x8C#0\x89*;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A&&&#"), + peg$decode("$;y0#*;y&"), + peg$decode("%3\x92\"\"5#7\xD3/q#24\"\"6475/b$$;!/�#*;!&&&#/L$2J\"\"6J7K/=$$;!/�#*;!&&&#/'$8%:\xD4% )(%'#($'#(#'#(\"'#&'#"), + peg$decode("2\xD5\"\"6\xD57\xD6"), + peg$decode("2\xD7\"\"6\xD77\xD8"), + peg$decode("2\xD9\"\"6\xD97\xDA"), + peg$decode("2\xDB\"\"6\xDB7\xDC"), + peg$decode("2\xDD\"\"6\xDD7\xDE"), + peg$decode("2\xDF\"\"6\xDF7\xE0"), + peg$decode("2\xE1\"\"6\xE17\xE2"), + peg$decode("2\xE3\"\"6\xE37\xE4"), + peg$decode("2\xE5\"\"6\xE57\xE6"), + peg$decode("2\xE7\"\"6\xE77\xE8"), + peg$decode("2\xE9\"\"6\xE97\xEA"), + peg$decode("%;\x85.Y &;\x86.S &;\x88.M &;\x89.G &;\x8A.A &;\x8B.; &;\x8C.5 &;\x8F./ &;\x8D.) &;\x8E.# &;6/& 8!:\xEB! )"), + peg$decode("%;\x84/G#;'/>$;\x92/5$;'/,$;\x94/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%;\x93/' 8!:\xEC!! )"), + peg$decode("%;!/5#;!/,$;!/#$+#)(#'#(\"'#&'#"), + peg$decode("%$;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(0G*;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(&/& 8!:\xED! )"), + peg$decode("%;\xB6/Y#$%;A/,#;\xB6/#$+\")(\"'#&'#06*%;A/,#;\xB6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("%;9/N#%2:\"\"6:7;/,#;9/#$+\")(\"'#&'#.\" &\"/'$8\":\xEE\" )(\"'#&'#"), + peg$decode("%;:.c &%;\x98/Y#$%;A/,#;\x98/#$+\")(\"'#&'#06*%;A/,#;\x98/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/& 8!:\xEF! )"), + peg$decode("%;L.# &;\x99/]#$%;B/,#;\x9B/#$+\")(\"'#&'#06*%;B/,#;\x9B/#$+\")(\"'#&'#&/'$8\":\xF0\" )(\"'#&'#"), + peg$decode("%;\x9A.\" &\"/>#;@/5$;M/,$;?/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%%;6/Y#$%;./,#;6/#$+\")(\"'#&'#06*%;./,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#.# &;H/' 8!:\xF1!! )"), + peg$decode(";\x9C.) &;\x9D.# &;\xA0"), + peg$decode("%3\xF2\"\"5!7\xF3/:#;$;\xCF/5$;./,$;\x90/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%$;!/�#*;!&&&#/' 8!:\u014B!! )"), + peg$decode("%;\xD1/]#$%;A/,#;\xD1/#$+\")(\"'#&'#06*%;A/,#;\xD1/#$+\")(\"'#&'#&/'$8\":\u014C\" )(\"'#&'#"), + peg$decode("%;\x99/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014D\" )(\"'#&'#"), + peg$decode("%;L.O &;\x99.I &%;@.\" &\"/:#;t/1$;?.\" &\"/#$+#)(#'#(\"'#&'#/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014E\" )(\"'#&'#"), + peg$decode("%;\xD4/]#$%;B/,#;\xD5/#$+\")(\"'#&'#06*%;B/,#;\xD5/#$+\")(\"'#&'#&/'$8\":\u014F\" )(\"'#&'#"), + peg$decode("%;\x96/& 8!:\u0150! )"), + peg$decode("%3\u0151\"\"5(7\u0152/:#;$;6/5$;;/,$;\xEC/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%3\x92\"\"5#7\xD3.# &;6/' 8!:\u018B!! )"), + peg$decode("%3\xB1\"\"5#7\u018C.G &3\xB3\"\"5#7\u018D.; &3\xB7\"\"5#7\u018E./ &3\xB5\"\"5$7\u018F.# &;6/' 8!:\u0190!! )"), + peg$decode("%;\xEE/D#%;C/,#;\xEF/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%;U.) &;\\.# &;X/& 8!:\u0191! )"), + peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\u0192!! )"), + peg$decode("%%;!/?#;!.\" &\"/1$;!.\" &\"/#$+#)(#'#(\"'#&'#/' 8!:\u0193!! )"), + peg$decode(";\xBE"), + peg$decode("%;\x9E/^#$%;B/,#;\xF3/#$+\")(\"'#&'#06*%;B/,#;\xF3/#$+\")(\"'#&'#&/($8\":\u0194\"!!)(\"'#&'#"), + peg$decode(";\xF4.# &;\xA0"), + peg$decode("%2\u0195\"\"6\u01957\u0196/L#;\"\"6>7?"), + peg$decode("%;\u0100/b#28\"\"6879/S$;\xFB/J$%2\u01A3\"\"6\u01A37\u01A4/,#;\xEC/#$+\")(\"'#&'#.\" &\"/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%3\u01A5\"\"5%7\u01A6.) &3\u01A7\"\"5$7\u01A8/' 8!:\u01A1!! )"), + peg$decode("%3\xB1\"\"5#7\xB2.6 &3\xB3\"\"5#7\xB4.* &$;+0#*;+&/' 8!:\u01A9!! )"), + peg$decode("%;\u0104/\x87#2F\"\"6F7G/x$;\u0103/o$2F\"\"6F7G/`$;\u0103/W$2F\"\"6F7G/H$;\u0103/?$2F\"\"6F7G/0$;\u0105/'$8):\u01AA) )()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%;#/>#;#/5$;#/,$;#/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%;\u0103/,#;\u0103/#$+\")(\"'#&'#"), + peg$decode("%;\u0103/5#;\u0103/,$;\u0103/#$+#)(#'#(\"'#&'#"), + peg$decode("%;q/T#$;m0#*;m&/D$%; /,#;\xF8/#$+\")(\"'#&'#.\" &\"/#$+#)(#'#(\"'#&'#"), + peg$decode("%2\u01AB\"\"6\u01AB7\u01AC.) &2\u01AD\"\"6\u01AD7\u01AE/w#;0/n$;\u0108/e$$%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#0<*%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#"), + peg$decode(";\x99.# &;L"), + peg$decode("%2\u01AF\"\"6\u01AF7\u01B0/5#; peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + peg$maxFailExpected.push(expected1); + } + function peg$buildSimpleError(message, location1) { + return new SyntaxError(message, [], "", location1); + } + function peg$buildStructuredError(expected1, found, location1) { + return new SyntaxError(SyntaxError.buildMessage(expected1, found), expected1, found, location1); + } + function peg$decode(s) { + return s.split("").map(function (ch) { return ch.charCodeAt(0) - 32; }); + } + function peg$parseRule(index) { + var bc = peg$bytecode[index]; + var ip = 0; + var ips = []; + var end = bc.length; + var ends = []; + var stack = []; + var params; + while (true) { + while (ip < end) { + switch (bc[ip]) { + case 0: + stack.push(peg$consts[bc[ip + 1]]); + ip += 2; + break; + case 1: + stack.push(undefined); + ip++; + break; + case 2: + stack.push(null); + ip++; + break; + case 3: + stack.push(peg$FAILED); + ip++; + break; + case 4: + stack.push([]); + ip++; + break; + case 5: + stack.push(peg$currPos); + ip++; + break; + case 6: + stack.pop(); + ip++; + break; + case 7: + peg$currPos = stack.pop(); + ip++; + break; + case 8: + stack.length -= bc[ip + 1]; + ip += 2; + break; + case 9: + stack.splice(-2, 1); + ip++; + break; + case 10: + stack[stack.length - 2].push(stack.pop()); + ip++; + break; + case 11: + stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1])); + ip += 2; + break; + case 12: + stack.push(input.substring(stack.pop(), peg$currPos)); + ip++; + break; + case 13: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1]) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 14: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1] === peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 15: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1] !== peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 16: + if (stack[stack.length - 1] !== peg$FAILED) { + ends.push(end); + ips.push(ip); + end = ip + 2 + bc[ip + 1]; + ip += 2; + } + else { + ip += 2 + bc[ip + 1]; + } + break; + case 17: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (input.length > peg$currPos) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 18: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 19: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 20: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 21: + stack.push(input.substr(peg$currPos, bc[ip + 1])); + peg$currPos += bc[ip + 1]; + ip += 2; + break; + case 22: + stack.push(peg$consts[bc[ip + 1]]); + peg$currPos += peg$consts[bc[ip + 1]].length; + ip += 2; + break; + case 23: + stack.push(peg$FAILED); + if (peg$silentFails === 0) { + peg$fail(peg$consts[bc[ip + 1]]); + } + ip += 2; + break; + case 24: + peg$savedPos = stack[stack.length - 1 - bc[ip + 1]]; + ip += 2; + break; + case 25: + peg$savedPos = peg$currPos; + ip++; + break; + case 26: + params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]) + .map(function (p) { return stack[stack.length - 1 - p]; }); + stack.splice(stack.length - bc[ip + 2], bc[ip + 2], peg$consts[bc[ip + 1]].apply(null, params)); + ip += 4 + bc[ip + 3]; + break; + case 27: + stack.push(peg$parseRule(bc[ip + 1])); + ip += 2; + break; + case 28: + peg$silentFails++; + ip++; + break; + case 29: + peg$silentFails--; + ip++; + break; + default: + throw new Error("Invalid opcode: " + bc[ip] + "."); + } + } + if (ends.length > 0) { + end = ends.pop(); + ip = ips.pop(); + } + else { + break; + } + } + return stack[0]; + } + options.data = {}; // Object to which header attributes will be assigned during parsing + function list(head, tail) { + return [head].concat(tail); + } + peg$result = peg$parseRule(peg$startRuleIndex); + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } + else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + throw peg$buildStructuredError(peg$maxFailExpected, peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)); + } +} +exports.parse = peg$parse; + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var parameters_1 = __webpack_require__(14); +/** + * Name Address SIP header. + * @public + */ +var NameAddrHeader = /** @class */ (function (_super) { + tslib_1.__extends(NameAddrHeader, _super); + /** + * Constructor + * @param uri + * @param displayName + * @param parameters + */ + function NameAddrHeader(uri, displayName, parameters) { + var _this = _super.call(this, parameters) || this; + _this.uri = uri; + _this._displayName = displayName; + return _this; + } + Object.defineProperty(NameAddrHeader.prototype, "friendlyName", { + get: function () { + return this.displayName || this.uri.aor; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(NameAddrHeader.prototype, "displayName", { + get: function () { return this._displayName; }, + set: function (value) { + this._displayName = value; + }, + enumerable: true, + configurable: true + }); + NameAddrHeader.prototype.clone = function () { + return new NameAddrHeader(this.uri.clone(), this._displayName, JSON.parse(JSON.stringify(this.parameters))); + }; + NameAddrHeader.prototype.toString = function () { + var body = (this.displayName || this.displayName === "0") ? '"' + this.displayName + '" ' : ""; + body += "<" + this.uri.toString() + ">"; + for (var parameter in this.parameters) { + if (this.parameters.hasOwnProperty(parameter)) { + body += ";" + parameter; + if (this.parameters[parameter] !== null) { + body += "=" + this.parameters[parameter]; + } + } + } + return body; + }; + return NameAddrHeader; +}(parameters_1.Parameters)); +exports.NameAddrHeader = NameAddrHeader; + + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * @internal + */ +var Parameters = /** @class */ (function () { + function Parameters(parameters) { + this.parameters = {}; + for (var param in parameters) { + if (parameters.hasOwnProperty(param)) { + this.setParam(param, parameters[param]); + } + } + } + Parameters.prototype.setParam = function (key, value) { + if (key) { + this.parameters[key.toLowerCase()] = (typeof value === "undefined" || value === null) ? null : value.toString(); + } + }; + Parameters.prototype.getParam = function (key) { + if (key) { + return this.parameters[key.toLowerCase()]; + } + }; + Parameters.prototype.hasParam = function (key) { + if (key) { + return !!this.parameters.hasOwnProperty(key.toLowerCase()); + } + return false; + }; + Parameters.prototype.deleteParam = function (parameter) { + parameter = parameter.toLowerCase(); + if (this.parameters.hasOwnProperty(parameter)) { + var value = this.parameters[parameter]; + delete this.parameters[parameter]; + return value; + } + }; + Parameters.prototype.clearParams = function () { + this.parameters = {}; + }; + return Parameters; +}()); +exports.Parameters = Parameters; + + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var parameters_1 = __webpack_require__(14); +/** + * URI. + * @public + */ +var URI = /** @class */ (function (_super) { + tslib_1.__extends(URI, _super); + /** + * Constructor + * @param scheme + * @param user + * @param host + * @param port + * @param parameters + * @param headers + */ + function URI(scheme, user, host, port, parameters, headers) { + var _this = _super.call(this, parameters) || this; + _this.headers = {}; + // Checks + if (!host) { + throw new TypeError('missing or invalid "host" parameter'); + } + // Initialize parameters + scheme = scheme || "sip"; + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + _this.setHeader(header, headers[header]); + } + } + // Raw URI + _this.raw = { + scheme: scheme, + user: user, + host: host, + port: port + }; + // Normalized URI + _this.normal = { + scheme: scheme.toLowerCase(), + user: user, + host: host.toLowerCase(), + port: port + }; + return _this; + } + Object.defineProperty(URI.prototype, "scheme", { + get: function () { return this.normal.scheme; }, + set: function (value) { + this.raw.scheme = value; + this.normal.scheme = value.toLowerCase(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "user", { + get: function () { return this.normal.user; }, + set: function (value) { + this.normal.user = this.raw.user = value; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "host", { + get: function () { return this.normal.host; }, + set: function (value) { + this.raw.host = value; + this.normal.host = value.toLowerCase(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "aor", { + get: function () { return this.normal.user + "@" + this.normal.host; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "port", { + get: function () { return this.normal.port; }, + set: function (value) { + this.normal.port = this.raw.port = value === 0 ? value : value; + }, + enumerable: true, + configurable: true + }); + URI.prototype.setHeader = function (name, value) { + this.headers[this.headerize(name)] = (value instanceof Array) ? value : [value]; + }; + URI.prototype.getHeader = function (name) { + if (name) { + return this.headers[this.headerize(name)]; + } + }; + URI.prototype.hasHeader = function (name) { + return !!name && !!this.headers.hasOwnProperty(this.headerize(name)); + }; + URI.prototype.deleteHeader = function (header) { + header = this.headerize(header); + if (this.headers.hasOwnProperty(header)) { + var value = this.headers[header]; + delete this.headers[header]; + return value; + } + }; + URI.prototype.clearHeaders = function () { + this.headers = {}; + }; + URI.prototype.clone = function () { + return new URI(this._raw.scheme, this._raw.user || "", this._raw.host, this._raw.port, JSON.parse(JSON.stringify(this.parameters)), JSON.parse(JSON.stringify(this.headers))); + }; + URI.prototype.toRaw = function () { + return this._toString(this._raw); + }; + URI.prototype.toString = function () { + return this._toString(this._normal); + }; + Object.defineProperty(URI.prototype, "_normal", { + get: function () { return this.normal; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "_raw", { + get: function () { return this.raw; }, + enumerable: true, + configurable: true + }); + URI.prototype._toString = function (uri) { + var uriString = uri.scheme + ":"; + // add slashes if it's not a sip(s) URI + if (!uri.scheme.toLowerCase().match("^sips?$")) { + uriString += "//"; + } + if (uri.user) { + uriString += this.escapeUser(uri.user) + "@"; + } + uriString += uri.host; + if (uri.port || uri.port === 0) { + uriString += ":" + uri.port; + } + for (var parameter in this.parameters) { + if (this.parameters.hasOwnProperty(parameter)) { + uriString += ";" + parameter; + if (this.parameters[parameter] !== null) { + uriString += "=" + this.parameters[parameter]; + } + } + } + var headers = []; + for (var header in this.headers) { + if (this.headers.hasOwnProperty(header)) { + for (var idx in this.headers[header]) { + if (this.headers[header].hasOwnProperty(idx)) { + headers.push(header + "=" + this.headers[header][idx]); + } + } + } + } + if (headers.length > 0) { + uriString += "?" + headers.join("&"); + } + return uriString; + }; + // The following two functions were copied from Utils to break a circular dependency + /* + * Hex-escape a SIP URI user. + * @private + * @param {String} user + */ + URI.prototype.escapeUser = function (user) { + // Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F). + return encodeURIComponent(decodeURIComponent(user)) + .replace(/%3A/ig, ":") + .replace(/%2B/ig, "+") + .replace(/%3F/ig, "?") + .replace(/%2F/ig, "/"); + }; + URI.prototype.headerize = function (str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; + }; + return URI; +}(parameters_1.Parameters)); +exports.URI = URI; + + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * @param size - + * @param base - + * @internal + */ +function createRandomToken(size, base) { + if (base === void 0) { base = 32; } + var token = ""; + for (var i = 0; i < size; i++) { + var r = Math.floor(Math.random() * base); + token += r.toString(base); + } + return token; +} +exports.createRandomToken = createRandomToken; +/** + * @internal + */ +function getReasonPhrase(code) { + return REASON_PHRASE[code] || ""; +} +exports.getReasonPhrase = getReasonPhrase; +/** + * @internal + */ +function newTag() { + return createRandomToken(10); +} +exports.newTag = newTag; +/** + * @param str - + * @internal + */ +function headerize(str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; +} +exports.headerize = headerize; +/** + * @param str - + * @internal + */ +function str_utf8_length(str) { + return encodeURIComponent(str).replace(/%[A-F\d]{2}/g, "U").length; +} +exports.str_utf8_length = str_utf8_length; +/** + * SIP Response Reasons + * DOC: http://www.iana.org/assignments/sip-parameters + * @internal + */ +var REASON_PHRASE = { + 100: "Trying", + 180: "Ringing", + 181: "Call Is Being Forwarded", + 182: "Queued", + 183: "Session Progress", + 199: "Early Dialog Terminated", + 200: "OK", + 202: "Accepted", + 204: "No Notification", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Moved Temporarily", + 305: "Use Proxy", + 380: "Alternative Service", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 410: "Gone", + 412: "Conditional Request Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Unsupported URI Scheme", + 417: "Unknown Resource-Priority", + 420: "Bad Extension", + 421: "Extension Required", + 422: "Session Interval Too Small", + 423: "Interval Too Brief", + 428: "Use Identity Header", + 429: "Provide Referrer Identity", + 430: "Flow Failed", + 433: "Anonymity Disallowed", + 436: "Bad Identity-Info", + 437: "Unsupported Certificate", + 438: "Invalid Identity Header", + 439: "First Hop Lacks Outbound Support", + 440: "Max-Breadth Exceeded", + 469: "Bad Info Package", + 470: "Consent Needed", + 478: "Unresolvable Destination", + 480: "Temporarily Unavailable", + 481: "Call/Transaction Does Not Exist", + 482: "Loop Detected", + 483: "Too Many Hops", + 484: "Address Incomplete", + 485: "Ambiguous", + 486: "Busy Here", + 487: "Request Terminated", + 488: "Not Acceptable Here", + 489: "Bad Event", + 491: "Request Pending", + 493: "Undecipherable", + 494: "Security Agreement Required", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Server Time-out", + 505: "Version Not Supported", + 513: "Message Too Large", + 580: "Precondition Failure", + 600: "Busy Everywhere", + 603: "Decline", + 604: "Does Not Exist Anywhere", + 606: "Not Acceptable" +}; + + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var incoming_message_1 = __webpack_require__(10); +/** + * Incoming SIP response message. + */ +var IncomingResponseMessage = /** @class */ (function (_super) { + tslib_1.__extends(IncomingResponseMessage, _super); + function IncomingResponseMessage() { + var _this = _super.call(this) || this; + _this.headers = {}; + return _this; + } + return IncomingResponseMessage; +}(incoming_message_1.IncomingMessage)); +exports.IncomingResponseMessage = IncomingResponseMessage; + + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var name_addr_header_1 = __webpack_require__(13); +var utils_1 = __webpack_require__(16); +/** + * Outgoing SIP request message. + * @public + */ +var OutgoingRequestMessage = /** @class */ (function () { + function OutgoingRequestMessage(method, ruri, fromURI, toURI, options, extraHeaders, body) { + this.headers = {}; + this.extraHeaders = []; + this.options = OutgoingRequestMessage.getDefaultOptions(); + // Options - merge a deep copy + if (options) { + this.options = tslib_1.__assign({}, this.options, options); + if (this.options.optionTags && this.options.optionTags.length) { + this.options.optionTags = this.options.optionTags.slice(); + } + if (this.options.routeSet && this.options.routeSet.length) { + this.options.routeSet = this.options.routeSet.slice(); + } + } + // Extra headers - deep copy + if (extraHeaders && extraHeaders.length) { + this.extraHeaders = extraHeaders.slice(); + } + // Body - deep copy + if (body) { + // TODO: internal representation should be Body + // this.body = { ...body }; + this.body = { + body: body.content, + contentType: body.contentType + }; + } + // Method + this.method = method; + // RURI + this.ruri = ruri.clone(); + // From + this.fromURI = fromURI.clone(); + this.fromTag = this.options.fromTag ? this.options.fromTag : utils_1.newTag(); + this.from = OutgoingRequestMessage.makeNameAddrHeader(this.fromURI, this.options.fromDisplayName, this.fromTag); + // To + this.toURI = toURI.clone(); + this.toTag = this.options.toTag; + this.to = OutgoingRequestMessage.makeNameAddrHeader(this.toURI, this.options.toDisplayName, this.toTag); + // Call-ID + this.callId = this.options.callId ? this.options.callId : this.options.callIdPrefix + utils_1.createRandomToken(15); + // CSeq + this.cseq = this.options.cseq; + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + this.setHeader("route", this.options.routeSet); + this.setHeader("via", ""); + this.setHeader("to", this.to.toString()); + this.setHeader("from", this.from.toString()); + this.setHeader("cseq", this.cseq + " " + this.method); + this.setHeader("call-id", this.callId); + this.setHeader("max-forwards", "70"); + } + /** Get a copy of the default options. */ + OutgoingRequestMessage.getDefaultOptions = function () { + return { + callId: "", + callIdPrefix: "", + cseq: 1, + toDisplayName: "", + toTag: "", + fromDisplayName: "", + fromTag: "", + forceRport: false, + hackViaTcp: false, + optionTags: ["outbound"], + routeSet: [], + userAgentString: "sip.js", + viaHost: "" + }; + }; + OutgoingRequestMessage.makeNameAddrHeader = function (uri, displayName, tag) { + var parameters = {}; + if (tag) { + parameters.tag = tag; + } + return new name_addr_header_1.NameAddrHeader(uri, displayName, parameters); + }; + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + OutgoingRequestMessage.prototype.getHeader = function (name) { + var header = this.headers[utils_1.headerize(name)]; + if (header) { + if (header[0]) { + return header[0]; + } + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _i = 0, _a = this.extraHeaders; _i < _a.length; _i++) { + var exHeader = _a[_i]; + if (regexp.test(exHeader)) { + return exHeader.substring(exHeader.indexOf(":") + 1).trim(); + } + } + } + return; + }; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array with all the headers of the specified name. + */ + OutgoingRequestMessage.prototype.getHeaders = function (name) { + var result = []; + var headerArray = this.headers[utils_1.headerize(name)]; + if (headerArray) { + for (var _i = 0, headerArray_1 = headerArray; _i < headerArray_1.length; _i++) { + var headerPart = headerArray_1[_i]; + result.push(headerPart); + } + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _a = 0, _b = this.extraHeaders; _a < _b.length; _a++) { + var exHeader = _b[_a]; + if (regexp.test(exHeader)) { + result.push(exHeader.substring(exHeader.indexOf(":") + 1).trim()); + } + } + } + return result; + }; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + OutgoingRequestMessage.prototype.hasHeader = function (name) { + if (this.headers[utils_1.headerize(name)]) { + return true; + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _i = 0, _a = this.extraHeaders; _i < _a.length; _i++) { + var extraHeader = _a[_i]; + if (regexp.test(extraHeader)) { + return true; + } + } + } + return false; + }; + /** + * Replace the the given header by the given value. + * @param name - header name + * @param value - header value + */ + OutgoingRequestMessage.prototype.setHeader = function (name, value) { + this.headers[utils_1.headerize(name)] = (value instanceof Array) ? value : [value]; + }; + /** + * The Via header field indicates the transport used for the transaction + * and identifies the location where the response is to be sent. A Via + * header field value is added only after the transport that will be + * used to reach the next hop has been selected (which may involve the + * usage of the procedures in [4]). + * + * When the UAC creates a request, it MUST insert a Via into that + * request. The protocol name and protocol version in the header field + * MUST be SIP and 2.0, respectively. The Via header field value MUST + * contain a branch parameter. This parameter is used to identify the + * transaction created by that request. This parameter is used by both + * the client and the server. + * https://tools.ietf.org/html/rfc3261#section-8.1.1.7 + * @param branchParameter - The branch parameter. + * @param scheme - The scheme. + */ + OutgoingRequestMessage.prototype.setViaHeader = function (branch, scheme) { + if (scheme === void 0) { scheme = "WSS"; } + // FIXME: Hack + if (this.options.hackViaTcp) { + scheme = "TCP"; + } + var via = "SIP/2.0/" + scheme; + via += " " + this.options.viaHost + ";branch=" + branch; + if (this.options.forceRport) { + via += ";rport"; + } + this.setHeader("via", via); + this.branch = branch; + }; + OutgoingRequestMessage.prototype.toString = function () { + var msg = ""; + msg += this.method + " " + this.ruri.toRaw() + " SIP/2.0\r\n"; + for (var header in this.headers) { + if (this.headers[header]) { + for (var _i = 0, _a = this.headers[header]; _i < _a.length; _i++) { + var headerPart = _a[_i]; + msg += header + ": " + headerPart + "\r\n"; + } + } + } + for (var _b = 0, _c = this.extraHeaders; _b < _c.length; _b++) { + var header = _c[_b]; + msg += header.trim() + "\r\n"; + } + msg += "Supported: " + this.options.optionTags.join(", ") + "\r\n"; + msg += "User-Agent: " + this.options.userAgentString + "\r\n"; + if (this.body) { + if (typeof this.body === "string") { + msg += "Content-Length: " + utils_1.str_utf8_length(this.body) + "\r\n\r\n"; + msg += this.body; + } + else { + if (this.body.body && this.body.contentType) { + msg += "Content-Type: " + this.body.contentType + "\r\n"; + msg += "Content-Length: " + utils_1.str_utf8_length(this.body.body) + "\r\n\r\n"; + msg += this.body.body; + } + else { + msg += "Content-Length: " + 0 + "\r\n\r\n"; + } + } + } + else { + msg += "Content-Length: " + 0 + "\r\n\r\n"; + } + return msg; + }; + return OutgoingRequestMessage; +}()); +exports.OutgoingRequestMessage = OutgoingRequestMessage; + + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var md5_1 = tslib_1.__importDefault(__webpack_require__(20)); +var utils_1 = __webpack_require__(16); +/** + * Digest Authentication. + * @internal + */ +var DigestAuthentication = /** @class */ (function () { + /** + * Constructor. + * @param loggerFactory - LoggerFactory. + * @param username - Username. + * @param password - Password. + */ + function DigestAuthentication(loggerFactory, username, password) { + this.logger = loggerFactory.getLogger("sipjs.digestauthentication"); + this.username = username; + this.password = password; + this.nc = 0; + this.ncHex = "00000000"; + } + /** + * Performs Digest authentication given a SIP request and the challenge + * received in a response to that request. + * @param request - + * @param challenge - + * @returns true if credentials were successfully generated, false otherwise. + */ + DigestAuthentication.prototype.authenticate = function (request, challenge, body) { + // Inspect and validate the challenge. + this.algorithm = challenge.algorithm; + this.realm = challenge.realm; + this.nonce = challenge.nonce; + this.opaque = challenge.opaque; + this.stale = challenge.stale; + if (this.algorithm) { + if (this.algorithm !== "MD5") { + this.logger.warn("challenge with Digest algorithm different than 'MD5', authentication aborted"); + return false; + } + } + else { + this.algorithm = "MD5"; + } + if (!this.realm) { + this.logger.warn("challenge without Digest realm, authentication aborted"); + return false; + } + if (!this.nonce) { + this.logger.warn("challenge without Digest nonce, authentication aborted"); + return false; + } + // 'qop' can contain a list of values (Array). Let's choose just one. + if (challenge.qop) { + if (challenge.qop.indexOf("auth") > -1) { + this.qop = "auth"; + } + else if (challenge.qop.indexOf("auth-int") > -1) { + this.qop = "auth-int"; + } + else { + // Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here. + this.logger.warn("challenge without Digest qop different than 'auth' or 'auth-int', authentication aborted"); + return false; + } + } + else { + this.qop = undefined; + } + // Fill other attributes. + this.method = request.method; + this.uri = request.ruri; + this.cnonce = utils_1.createRandomToken(12); + this.nc += 1; + this.updateNcHex(); + // nc-value = 8LHEX. Max value = 'FFFFFFFF'. + if (this.nc === 4294967296) { + this.nc = 1; + this.ncHex = "00000001"; + } + // Calculate the Digest "response" value. + this.calculateResponse(body); + return true; + }; + /** + * Return the Proxy-Authorization or WWW-Authorization header value. + */ + DigestAuthentication.prototype.toString = function () { + var authParams = []; + if (!this.response) { + throw new Error("response field does not exist, cannot generate Authorization header"); + } + authParams.push("algorithm=" + this.algorithm); + authParams.push('username="' + this.username + '"'); + authParams.push('realm="' + this.realm + '"'); + authParams.push('nonce="' + this.nonce + '"'); + authParams.push('uri="' + this.uri + '"'); + authParams.push('response="' + this.response + '"'); + if (this.opaque) { + authParams.push('opaque="' + this.opaque + '"'); + } + if (this.qop) { + authParams.push("qop=" + this.qop); + authParams.push('cnonce="' + this.cnonce + '"'); + authParams.push("nc=" + this.ncHex); + } + return "Digest " + authParams.join(", "); + }; + /** + * Generate the 'nc' value as required by Digest in this.ncHex by reading this.nc. + */ + DigestAuthentication.prototype.updateNcHex = function () { + var hex = Number(this.nc).toString(16); + this.ncHex = "00000000".substr(0, 8 - hex.length) + hex; + }; + /** + * Generate Digest 'response' value. + */ + DigestAuthentication.prototype.calculateResponse = function (body) { + var ha2; + // HA1 = MD5(A1) = MD5(username:realm:password) + var ha1 = md5_1.default(this.username + ":" + this.realm + ":" + this.password); + if (this.qop === "auth") { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = md5_1.default(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth:" + ha2); + } + else if (this.qop === "auth-int") { + // HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody)) + ha2 = md5_1.default(this.method + ":" + this.uri + ":" + md5_1.default(body ? body : "")); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth-int:" + ha2); + } + else if (this.qop === undefined) { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = md5_1.default(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + ha2); + } + }; + return DigestAuthentication; +}()); +exports.DigestAuthentication = DigestAuthentication; + + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +;(function (root, factory) { + if (true) { + // CommonJS + module.exports = exports = factory(__webpack_require__(21)); + } + else {} +}(this, function (CryptoJS) { + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + return CryptoJS.MD5; + +})); + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +;(function (root, factory) { + if (true) { + // CommonJS + module.exports = exports = factory(); + } + else {} +}(this, function () { + + /** + * CryptoJS core components. + */ + var CryptoJS = CryptoJS || (function (Math, undefined) { + /* + * Local polyfil of Object.create + */ + var create = Object.create || (function () { + function F() {}; + + return function (obj) { + var subtype; + + F.prototype = obj; + + subtype = new F(); + + F.prototype = null; + + return subtype; + }; + }()) + + /** + * CryptoJS namespace. + */ + var C = {}; + + /** + * Library namespace. + */ + var C_lib = C.lib = {}; + + /** + * Base object for prototypal inheritance. + */ + var Base = C_lib.Base = (function () { + + + return { + /** + * Creates a new object that inherits from this object. + * + * @param {Object} overrides Properties to copy into the new object. + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * field: 'value', + * + * method: function () { + * } + * }); + */ + extend: function (overrides) { + // Spawn + var subtype = create(this); + + // Augment + if (overrides) { + subtype.mixIn(overrides); + } + + // Create default initializer + if (!subtype.hasOwnProperty('init') || this.init === subtype.init) { + subtype.init = function () { + subtype.$super.init.apply(this, arguments); + }; + } + + // Initializer's prototype is the subtype object + subtype.init.prototype = subtype; + + // Reference supertype + subtype.$super = this; + + return subtype; + }, + + /** + * Extends this object and runs the init method. + * Arguments to create() will be passed to init(). + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var instance = MyType.create(); + */ + create: function () { + var instance = this.extend(); + instance.init.apply(instance, arguments); + + return instance; + }, + + /** + * Initializes a newly created object. + * Override this method to add some logic when your objects are created. + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * init: function () { + * // ... + * } + * }); + */ + init: function () { + }, + + /** + * Copies properties into this object. + * + * @param {Object} properties The properties to mix in. + * + * @example + * + * MyType.mixIn({ + * field: 'value' + * }); + */ + mixIn: function (properties) { + for (var propertyName in properties) { + if (properties.hasOwnProperty(propertyName)) { + this[propertyName] = properties[propertyName]; + } + } + + // IE won't copy toString using the loop above + if (properties.hasOwnProperty('toString')) { + this.toString = properties.toString; + } + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = instance.clone(); + */ + clone: function () { + return this.init.prototype.extend(this); + } + }; + }()); + + /** + * An array of 32-bit words. + * + * @property {Array} words The array of 32-bit words. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var WordArray = C_lib.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of 32-bit words. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.create(); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 4; + } + }, + + /** + * Converts this word array to a string. + * + * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex + * + * @return {string} The stringified word array. + * + * @example + * + * var string = wordArray + ''; + * var string = wordArray.toString(); + * var string = wordArray.toString(CryptoJS.enc.Utf8); + */ + toString: function (encoder) { + return (encoder || Hex).stringify(this); + }, + + /** + * Concatenates a word array to this word array. + * + * @param {WordArray} wordArray The word array to append. + * + * @return {WordArray} This word array. + * + * @example + * + * wordArray1.concat(wordArray2); + */ + concat: function (wordArray) { + // Shortcuts + var thisWords = this.words; + var thatWords = wordArray.words; + var thisSigBytes = this.sigBytes; + var thatSigBytes = wordArray.sigBytes; + + // Clamp excess bits + this.clamp(); + + // Concat + if (thisSigBytes % 4) { + // Copy one byte at a time + for (var i = 0; i < thatSigBytes; i++) { + var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (var i = 0; i < thatSigBytes; i += 4) { + thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + }, + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp: function () { + // Shortcuts + var words = this.words; + var sigBytes = this.sigBytes; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + }, + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + }, + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random: function (nBytes) { + var words = []; + + var r = (function (m_w) { + var m_w = m_w; + var m_z = 0x3ade68b1; + var mask = 0xffffffff; + + return function () { + m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask; + m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask; + var result = ((m_z << 0x10) + m_w) & mask; + result /= 0x100000000; + result += 0.5; + return result * (Math.random() > .5 ? 1 : -1); + } + }); + + for (var i = 0, rcache; i < nBytes; i += 4) { + var _r = r((rcache || Math.random()) * 0x100000000); + + rcache = _r() * 0x3ade67b7; + words.push((_r() * 0x100000000) | 0); + } + + return new WordArray.init(words, nBytes); + } + }); + + /** + * Encoder namespace. + */ + var C_enc = C.enc = {}; + + /** + * Hex encoding strategy. + */ + var Hex = C_enc.Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var hexChars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse: function (hexStr) { + // Shortcut + var hexStrLength = hexStr.length; + + // Convert + var words = []; + for (var i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray.init(words, hexStrLength / 2); + } + }; + + /** + * Latin1 encoding strategy. + */ + var Latin1 = C_enc.Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var latin1Chars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse: function (latin1Str) { + // Shortcut + var latin1StrLength = latin1Str.length; + + // Convert + var words = []; + for (var i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray.init(words, latin1StrLength); + } + }; + + /** + * UTF-8 encoding strategy. + */ + var Utf8 = C_enc.Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify: function (wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse: function (utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } + }; + + /** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset: function () { + // Initial values + this._data = new WordArray.init(); + this._nDataBytes = 0; + }, + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append: function (data) { + // Convert string to WordArray, else assume WordArray already + if (typeof data == 'string') { + data = Utf8.parse(data); + } + + // Append + this._data.concat(data); + this._nDataBytes += data.sigBytes; + }, + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process: function (doFlush) { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var dataSigBytes = data.sigBytes; + var blockSize = this.blockSize; + var blockSizeBytes = blockSize * 4; + + // Count blocks ready + var nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + var nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (var offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + var processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray.init(processedWords, nBytesReady); + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone._data = this._data.clone(); + + return clone; + }, + + _minBufferSize: 0 + }); + + /** + * Abstract hasher template. + * + * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + */ + cfg: Base.extend(), + + /** + * Initializes a newly created hasher. + * + * @param {Object} cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + init: function (cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Set initial values + this.reset(); + }, + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + }, + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update: function (messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + }, + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + var hash = this._doFinalize(); + + return hash; + }, + + blockSize: 512/32, + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} hasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper: function (hasher) { + return function (message, cfg) { + return new hasher.init(cfg).finalize(message); + }; + }, + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} hasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper: function (hasher) { + return function (message, key) { + return new C_algo.HMAC.init(hasher, key).finalize(message); + }; + } + }); + + /** + * Algorithm namespace. + */ + var C_algo = C.algo = {}; + + return C; + }(Math)); + + + return CryptoJS; + +})); + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var utils_1 = __webpack_require__(16); +/** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + */ +function constructOutgoingResponse(message, options) { + var CRLF = "\r\n"; + if (options.statusCode < 100 || options.statusCode > 699) { + throw new TypeError("Invalid statusCode: " + options.statusCode); + } + var reasonPhrase = options.reasonPhrase ? options.reasonPhrase : utils_1.getReasonPhrase(options.statusCode); + // SIP responses are distinguished from requests by having a Status-Line + // as their start-line. A Status-Line consists of the protocol version + // followed by a numeric Status-Code and its associated textual phrase, + // with each element separated by a single SP character. + // https://tools.ietf.org/html/rfc3261#section-7.2 + var response = "SIP/2.0 " + options.statusCode + " " + reasonPhrase + CRLF; + // One largely non-method-specific guideline for the generation of + // responses is that UASs SHOULD NOT issue a provisional response for a + // non-INVITE request. Rather, UASs SHOULD generate a final response to + // a non-INVITE request as soon as possible. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.1 + if (options.statusCode >= 100 && options.statusCode < 200) { + // TODO + } + // When a 100 (Trying) response is generated, any Timestamp header field + // present in the request MUST be copied into this 100 (Trying) + // response. If there is a delay in generating the response, the UAS + // SHOULD add a delay value into the Timestamp value in the response. + // This value MUST contain the difference between the time of sending of + // the response and receipt of the request, measured in seconds. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.1 + if (options.statusCode === 100) { + // TODO + } + // The From field of the response MUST equal the From header field of + // the request. The Call-ID header field of the response MUST equal the + // Call-ID header field of the request. The CSeq header field of the + // response MUST equal the CSeq field of the request. The Via header + // field values in the response MUST equal the Via header field values + // in the request and MUST maintain the same ordering. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.2 + var fromHeader = "From: " + message.getHeader("From") + CRLF; + var callIdHeader = "Call-ID: " + message.callId + CRLF; + var cSeqHeader = "CSeq: " + message.cseq + " " + message.method + CRLF; + var viaHeaders = message.getHeaders("via").reduce(function (previous, current) { + return previous + "Via: " + current + CRLF; + }, ""); + // If a request contained a To tag in the request, the To header field + // in the response MUST equal that of the request. However, if the To + // header field in the request did not contain a tag, the URI in the To + // header field in the response MUST equal the URI in the To header + // field; additionally, the UAS MUST add a tag to the To header field in + // the response (with the exception of the 100 (Trying) response, in + // which a tag MAY be present). This serves to identify the UAS that is + // responding, possibly resulting in a component of a dialog ID. The + // same tag MUST be used for all responses to that request, both final + // and provisional (again excepting the 100 (Trying)). + // https://tools.ietf.org/html/rfc3261#section-8.2.6.2 + var toHeader = "To: " + message.getHeader("to"); + if (options.statusCode > 100 && !message.parseHeader("to").hasParam("tag")) { + var toTag = options.toTag; + if (!toTag) { + // Stateless UAS Behavior... + // o To header tags MUST be generated for responses in a stateless + // manner - in a manner that will generate the same tag for the + // same request consistently. For information on tag construction + // see Section 19.3. + // https://tools.ietf.org/html/rfc3261#section-8.2.7 + toTag = utils_1.newTag(); // FIXME: newTag() currently generates random tags + } + toHeader += ";tag=" + toTag; + } + toHeader += CRLF; + // FIXME: TODO: needs review... moved to InviteUserAgentServer (as it is specific to that) + // let recordRouteHeaders = ""; + // if (request.method === C.INVITE && statusCode > 100 && statusCode <= 200) { + // recordRouteHeaders = request.getHeaders("record-route").reduce((previous, current) => { + // return previous + "Record-Route: " + current + CRLF; + // }, ""); + // } + // FIXME: TODO: needs review... + var supportedHeader = ""; + if (options.supported) { + supportedHeader = "Supported: " + options.supported.join(", ") + CRLF; + } + // FIXME: TODO: needs review... + var userAgentHeader = ""; + if (options.userAgent) { + userAgentHeader = "User-Agent: " + options.userAgent + CRLF; + } + var extensionHeaders = ""; + if (options.extraHeaders) { + extensionHeaders = options.extraHeaders.reduce(function (previous, current) { + return previous + current.trim() + CRLF; + }, ""); + } + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + // response += recordRouteHeaders; + response += viaHeaders; + response += fromHeader; + response += toHeader; + response += cSeqHeader; + response += callIdHeader; + response += supportedHeader; + response += userAgentHeader; + response += extensionHeaders; + if (options.body) { + response += "Content-Type: " + options.body.contentType + CRLF; + response += "Content-Length: " + utils_1.str_utf8_length(options.body.content) + CRLF + CRLF; + response += options.body.content; + } + else { + response += "Content-Length: " + 0 + CRLF + CRLF; + } + return { message: response }; +} +exports.constructOutgoingResponse = constructOutgoingResponse; + + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var session_1 = __webpack_require__(24); +var timers_1 = __webpack_require__(26); +var transactions_1 = __webpack_require__(27); +var bye_user_agent_client_1 = __webpack_require__(41); +var bye_user_agent_server_1 = __webpack_require__(43); +var info_user_agent_client_1 = __webpack_require__(45); +var info_user_agent_server_1 = __webpack_require__(46); +var notify_user_agent_client_1 = __webpack_require__(47); +var notify_user_agent_server_1 = __webpack_require__(48); +var prack_user_agent_client_1 = __webpack_require__(49); +var prack_user_agent_server_1 = __webpack_require__(50); +var re_invite_user_agent_client_1 = __webpack_require__(51); +var re_invite_user_agent_server_1 = __webpack_require__(52); +var refer_user_agent_client_1 = __webpack_require__(53); +var refer_user_agent_server_1 = __webpack_require__(54); +var dialog_1 = __webpack_require__(4); +var SessionDialog = /** @class */ (function (_super) { + tslib_1.__extends(SessionDialog, _super); + function SessionDialog(initialTransaction, core, state, delegate) { + var _this = _super.call(this, core, state) || this; + _this.initialTransaction = initialTransaction; + /** The state of the offer/answer exchange. */ + _this._signalingState = session_1.SignalingState.Initial; + /** True if waiting for an ACK to the initial transaction 2xx (UAS only). */ + _this.ackWait = false; + _this.delegate = delegate; + if (initialTransaction instanceof transactions_1.InviteServerTransaction) { + // If we're created by an invite server transaction, we're + // going to be waiting for an ACK if are to be confirmed. + _this.ackWait = true; + } + // If we're confirmed upon creation start the retransmitting whatever + // the 2xx final response was that confirmed us into existence. + if (!_this.early) { + _this.start2xxRetransmissionTimer(); + } + _this.signalingStateTransition(initialTransaction.request); + _this.logger = core.loggerFactory.getLogger("sip.invite-dialog"); + _this.logger.log("INVITE dialog " + _this.id + " constructed"); + return _this; + } + SessionDialog.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._signalingState = session_1.SignalingState.Closed; + this._offer = undefined; + this._answer = undefined; + if (this.invite2xxTimer) { + clearTimeout(this.invite2xxTimer); + this.invite2xxTimer = undefined; + } + // The UAS MUST still respond to any pending requests received for that + // dialog. It is RECOMMENDED that a 487 (Request Terminated) response + // be generated to those pending requests. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + // TODO: + // this.userAgentServers.forEach((uas) => uas.reply(487)); + this.logger.log("INVITE dialog " + this.id + " destroyed"); + }; + Object.defineProperty(SessionDialog.prototype, "sessionState", { + // FIXME: Need real state machine + get: function () { + if (this.early) { + return session_1.SessionState.Early; + } + else if (this.ackWait) { + return session_1.SessionState.AckWait; + } + else if (this._signalingState === session_1.SignalingState.Closed) { + return session_1.SessionState.Terminated; + } + else { + return session_1.SessionState.Confirmed; + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "signalingState", { + /** The state of the offer/answer exchange. */ + get: function () { + return this._signalingState; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "offer", { + /** The current offer. Undefined unless signaling state HaveLocalOffer, HaveRemoteOffer, of Stable. */ + get: function () { + return this._offer; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "answer", { + /** The current answer. Undefined unless signaling state Stable. */ + get: function () { + return this._answer; + }, + enumerable: true, + configurable: true + }); + /** Confirm the dialog. Only matters if dialog is currently early. */ + SessionDialog.prototype.confirm = function () { + // When we're confirmed start the retransmitting whatever + // the 2xx final response that may have confirmed us. + if (this.early) { + this.start2xxRetransmissionTimer(); + } + _super.prototype.confirm.call(this); + }; + /** Re-confirm the dialog. Only matters if handling re-INVITE request. */ + SessionDialog.prototype.reConfirm = function () { + // When we're confirmed start the retransmitting whatever + // the 2xx final response that may have confirmed us. + if (this.reinviteUserAgentServer) { + this.startReInvite2xxRetransmissionTimer(); + } + }; + /** + * The UAC core MUST generate an ACK request for each 2xx received from + * the transaction layer. The header fields of the ACK are constructed + * in the same way as for any request sent within a dialog (see Section + * 12) with the exception of the CSeq and the header fields related to + * authentication. The sequence number of the CSeq header field MUST be + * the same as the INVITE being acknowledged, but the CSeq method MUST + * be ACK. The ACK MUST contain the same credentials as the INVITE. If + * the 2xx contains an offer (based on the rules above), the ACK MUST + * carry an answer in its body. If the offer in the 2xx response is not + * acceptable, the UAC core MUST generate a valid answer in the ACK and + * then send a BYE immediately. + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + * @param options ACK options bucket. + */ + SessionDialog.prototype.ack = function (options) { + if (options === void 0) { options = {}; } + this.logger.log("INVITE dialog " + this.id + " sending ACK request"); + var transaction; + if (this.reinviteUserAgentClient) { + // We're sending ACK for a re-INVITE + if (!(this.reinviteUserAgentClient.transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + transaction = this.reinviteUserAgentClient.transaction; + this.reinviteUserAgentClient = undefined; + } + else { + // We're sending ACK for the initial INVITE + if (!(this.initialTransaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Initial transaction not instance of InviteClientTransaction."); + } + transaction = this.initialTransaction; + } + options.cseq = transaction.request.cseq; // ACK cseq is INVITE cseq + var message = this.createOutgoingRequestMessage(messages_1.C.ACK, options); + transaction.ackResponse(message); // See InviteClientTransaction for details. + this.signalingStateTransition(message); + return { message: message }; + }; + /** + * Terminating a Session + * + * This section describes the procedures for terminating a session + * established by SIP. The state of the session and the state of the + * dialog are very closely related. When a session is initiated with an + * INVITE, each 1xx or 2xx response from a distinct UAS creates a + * dialog, and if that response completes the offer/answer exchange, it + * also creates a session. As a result, each session is "associated" + * with a single dialog - the one which resulted in its creation. If an + * initial INVITE generates a non-2xx final response, that terminates + * all sessions (if any) and all dialogs (if any) that were created + * through responses to the request. By virtue of completing the + * transaction, a non-2xx final response also prevents further sessions + * from being created as a result of the INVITE. The BYE request is + * used to terminate a specific session or attempted session. In this + * case, the specific session is the one with the peer UA on the other + * side of the dialog. When a BYE is received on a dialog, any session + * associated with that dialog SHOULD terminate. A UA MUST NOT send a + * BYE outside of a dialog. The caller's UA MAY send a BYE for either + * confirmed or early dialogs, and the callee's UA MAY send a BYE on + * confirmed dialogs, but MUST NOT send a BYE on early dialogs. + * + * However, the callee's UA MUST NOT send a BYE on a confirmed dialog + * until it has received an ACK for its 2xx response or until the server + * transaction times out. If no SIP extensions have defined other + * application layer states associated with the dialog, the BYE also + * terminates the dialog. + * + * https://tools.ietf.org/html/rfc3261#section-15 + * FIXME: Make these proper Exceptions... + * @param options BYE options bucket. + * @throws {Error} If callee's UA attempts a BYE on an early dialog. + * @throws {Error} If callee's UA attempts a BYE on a confirmed dialog + * while it's waiting on the ACK for its 2xx response. + */ + SessionDialog.prototype.bye = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending BYE request"); + // The caller's UA MAY send a BYE for either + // confirmed or early dialogs, and the callee's UA MAY send a BYE on + // confirmed dialogs, but MUST NOT send a BYE on early dialogs. + // + // However, the callee's UA MUST NOT send a BYE on a confirmed dialog + // until it has received an ACK for its 2xx response or until the server + // transaction times out. + // https://tools.ietf.org/html/rfc3261#section-15 + if (this.initialTransaction instanceof transactions_1.InviteServerTransaction) { + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("UAS MUST NOT send a BYE on early dialogs."); + } + if (this.ackWait && this.initialTransaction.state !== transactions_1.TransactionState.Terminated) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("UAS MUST NOT send a BYE on a confirmed dialog " + + "until it has received an ACK for its 2xx response " + + "or until the server transaction times out."); + } + } + // A BYE request is constructed as would any other request within a + // dialog, as described in Section 12. + // + // Once the BYE is constructed, the UAC core creates a new non-INVITE + // client transaction, and passes it the BYE request. The UAC MUST + // consider the session terminated (and therefore stop sending or + // listening for media) as soon as the BYE request is passed to the + // client transaction. If the response for the BYE is a 481 + // (Call/Transaction Does Not Exist) or a 408 (Request Timeout) or no + // response at all is received for the BYE (that is, a timeout is + // returned by the client transaction), the UAC MUST consider the + // session and the dialog terminated. + // https://tools.ietf.org/html/rfc3261#section-15.1.1 + return new bye_user_agent_client_1.ByeUserAgentClient(this, delegate, options); + }; + /** + * An INFO request can be associated with an Info Package (see + * Section 5), or associated with a legacy INFO usage (see Section 2). + * + * The construction of the INFO request is the same as any other + * non-target refresh request within an existing invite dialog usage as + * described in Section 12.2 of RFC 3261. + * https://tools.ietf.org/html/rfc6086#section-4.2.1 + * @param options Options bucket. + */ + SessionDialog.prototype.info = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending INFO request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + return new info_user_agent_client_1.InfoUserAgentClient(this, delegate, options); + }; + /** + * Modifying an Existing Session + * + * A successful INVITE request (see Section 13) establishes both a + * dialog between two user agents and a session using the offer-answer + * model. Section 12 explains how to modify an existing dialog using a + * target refresh request (for example, changing the remote target URI + * of the dialog). This section describes how to modify the actual + * session. This modification can involve changing addresses or ports, + * adding a media stream, deleting a media stream, and so on. This is + * accomplished by sending a new INVITE request within the same dialog + * that established the session. An INVITE request sent within an + * existing dialog is known as a re-INVITE. + * + * Note that a single re-INVITE can modify the dialog and the + * parameters of the session at the same time. + * + * Either the caller or callee can modify an existing session. + * https://tools.ietf.org/html/rfc3261#section-14 + * @param options Options bucket + */ + SessionDialog.prototype.invite = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending INVITE request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + // Note that a UAC MUST NOT initiate a new INVITE transaction within a + // dialog while another INVITE transaction is in progress in either + // direction. + // + // 1. If there is an ongoing INVITE client transaction, the TU MUST + // wait until the transaction reaches the completed or terminated + // state before initiating the new INVITE. + // + // 2. If there is an ongoing INVITE server transaction, the TU MUST + // wait until the transaction reaches the confirmed or terminated + // state before initiating the new INVITE. + // + // However, a UA MAY initiate a regular transaction while an INVITE + // transaction is in progress. A UA MAY also initiate an INVITE + // transaction while a regular transaction is in progress. + // https://tools.ietf.org/html/rfc3261#section-14.1 + if (this.reinviteUserAgentClient) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("There is an ongoing re-INVITE client transaction."); + } + if (this.reinviteUserAgentServer) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("There is an ongoing re-INVITE server transaction."); + } + return new re_invite_user_agent_client_1.ReInviteUserAgentClient(this, delegate, options); + }; + /** + * The NOTIFY mechanism defined in [2] MUST be used to inform the agent + * sending the REFER of the status of the reference. + * https://tools.ietf.org/html/rfc3515#section-2.4.4 + * @param options Options bucket. + */ + SessionDialog.prototype.notify = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending NOTIFY request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + return new notify_user_agent_client_1.NotifyUserAgentClient(this, delegate, options); + }; + /** + * Assuming the response is to be transmitted reliably, the UAC MUST + * create a new request with method PRACK. This request is sent within + * the dialog associated with the provisional response (indeed, the + * provisional response may have created the dialog). PRACK requests + * MAY contain bodies, which are interpreted according to their type and + * disposition. + * https://tools.ietf.org/html/rfc3262#section-4 + * @param options Options bucket. + */ + SessionDialog.prototype.prack = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending PRACK request"); + return new prack_user_agent_client_1.PrackUserAgentClient(this, delegate, options); + }; + /** + * REFER is a SIP request and is constructed as defined in [1]. A REFER + * request MUST contain exactly one Refer-To header field value. + * https://tools.ietf.org/html/rfc3515#section-2.4.1 + * @param options Options bucket. + */ + SessionDialog.prototype.refer = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending REFER request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + // FIXME: TODO: Validate Refer-To header field value. + return new refer_user_agent_client_1.ReferUserAgentClient(this, delegate, options); + }; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + SessionDialog.prototype.receiveRequest = function (message) { + this.logger.log("INVITE dialog " + this.id + " received " + message.method + " request"); + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + if (message.method === messages_1.C.ACK) { + // If ackWait is true, then this is the ACK to the initial INVITE, + // otherwise this is an ACK to an in dialog INVITE. In either case, + // guard to make sure the sequence number of the ACK matches the INVITE. + if (this.ackWait) { + if (this.initialTransaction instanceof transactions_1.InviteClientTransaction) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + if (this.initialTransaction.request.cseq !== message.cseq) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + this.ackWait = false; + } + else { + if (!this.reinviteUserAgentServer) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + if (this.reinviteUserAgentServer.transaction.request.cseq !== message.cseq) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + this.reinviteUserAgentServer = undefined; + } + this.signalingStateTransition(message); + if (this.delegate && this.delegate.onAck) { + this.delegate.onAck({ message: message }); + } + return; + } + // Request within a dialog out of sequence guard. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.sequenceGuard(message)) { + this.logger.log("INVITE dialog " + this.id + " rejected out of order " + message.method + " request."); + return; + } + if (message.method === messages_1.C.INVITE) { + // A UAS that receives a second INVITE before it sends the final + // response to a first INVITE with a lower CSeq sequence number on the + // same dialog MUST return a 500 (Server Internal Error) response to the + // second INVITE and MUST include a Retry-After header field with a + // randomly chosen value of between 0 and 10 seconds. + // https://tools.ietf.org/html/rfc3261#section-14.2 + if (this.reinviteUserAgentServer) { + // https://tools.ietf.org/html/rfc3261#section-20.33 + var retryAfter = Math.floor((Math.random() * 10)) + 1; + var extraHeaders = ["Retry-After: " + retryAfter]; + this.core.replyStateless(message, { statusCode: 500, extraHeaders: extraHeaders }); + return; + } + // A UAS that receives an INVITE on a dialog while an INVITE it had sent + // on that dialog is in progress MUST return a 491 (Request Pending) + // response to the received INVITE. + // https://tools.ietf.org/html/rfc3261#section-14.2 + if (this.reinviteUserAgentClient) { + this.core.replyStateless(message, { statusCode: 491 }); + return; + } + } + // Request within a dialog common processing. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + _super.prototype.receiveRequest.call(this, message); + // Requests within a dialog MAY contain Record-Route and Contact header + // fields. However, these requests do not cause the dialog's route set + // to be modified, although they may modify the remote target URI. + // Specifically, requests that are not target refresh requests do not + // modify the dialog's remote target URI, and requests that are target + // refresh requests do. For dialogs that have been established with an + // INVITE, the only target refresh request defined is re-INVITE (see + // Section 14). Other extensions may define different target refresh + // requests for dialogs established in other ways. + // + // Note that an ACK is NOT a target refresh request. + // + // Target refresh requests only update the dialog's remote target URI, + // and not the route set formed from the Record-Route. Updating the + // latter would introduce severe backwards compatibility problems with + // RFC 2543-compliant systems. + // https://tools.ietf.org/html/rfc3261#section-15 + if (message.method === messages_1.C.INVITE) { + // FIXME: parser needs to be typed... + var contact = message.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + this.dialogState.remoteTarget = contact.uri; + } + // Switch on method and then delegate. + switch (message.method) { + case messages_1.C.BYE: + // A UAS core receiving a BYE request for an existing dialog MUST follow + // the procedures of Section 12.2.2 to process the request. Once done, + // the UAS SHOULD terminate the session (and therefore stop sending and + // listening for media). The only case where it can elect not to are + // multicast sessions, where participation is possible even if the other + // participant in the dialog has terminated its involvement in the + // session. Whether or not it ends its participation on the session, + // the UAS core MUST generate a 2xx response to the BYE, and MUST pass + // that to the server transaction for transmission. + // + // The UAS MUST still respond to any pending requests received for that + // dialog. It is RECOMMENDED that a 487 (Request Terminated) response + // be generated to those pending requests. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + { + var uas = new bye_user_agent_server_1.ByeUserAgentServer(this, message); + this.delegate && this.delegate.onBye ? + this.delegate.onBye(uas) : + uas.accept(); + this.dispose(); + } + break; + case messages_1.C.INFO: + // If a UA receives an INFO request associated with an Info Package that + // the UA has not indicated willingness to receive, the UA MUST send a + // 469 (Bad Info Package) response (see Section 11.6), which contains a + // Recv-Info header field with Info Packages for which the UA is willing + // to receive INFO requests. + { + var uas = new info_user_agent_server_1.InfoUserAgentServer(this, message); + this.delegate && this.delegate.onInfo ? + this.delegate.onInfo(uas) : + uas.reject({ + statusCode: 469, + extraHeaders: ["Recv-Info :"] + }); + } + break; + case messages_1.C.INVITE: + // If the new session description is not acceptable, the UAS can reject + // it by returning a 488 (Not Acceptable Here) response for the re- + // INVITE. This response SHOULD include a Warning header field. + // https://tools.ietf.org/html/rfc3261#section-14.2 + { + var uas = new re_invite_user_agent_server_1.ReInviteUserAgentServer(this, message); + this.delegate && this.delegate.onInvite ? + this.delegate.onInvite(uas) : + uas.reject({ statusCode: 488 }); // TODO: Warning header field. + } + break; + case messages_1.C.NOTIFY: + // https://tools.ietf.org/html/rfc3515#section-2.4.4 + { + var uas = new notify_user_agent_server_1.NotifyUserAgentServer(this, message); + this.delegate && this.delegate.onNotify ? + this.delegate.onNotify(uas) : + uas.accept(); + } + break; + case messages_1.C.PRACK: + // https://tools.ietf.org/html/rfc3262#section-4 + { + var uas = new prack_user_agent_server_1.PrackUserAgentServer(this, message); + this.delegate && this.delegate.onPrack ? + this.delegate.onPrack(uas) : + uas.accept(); + } + break; + case messages_1.C.REFER: + // https://tools.ietf.org/html/rfc3515#section-2.4.2 + { + var uas = new refer_user_agent_server_1.ReferUserAgentServer(this, message); + this.delegate && this.delegate.onRefer ? + this.delegate.onRefer(uas) : + uas.reject(); + } + break; + default: + { + this.logger.log("INVITE dialog " + this.id + " received unimplemented " + message.method + " request"); + this.core.replyStateless(message, { statusCode: 501 }); + } + break; + } + }; + SessionDialog.prototype.reliableSequenceGuard = function (message) { + var statusCode = message.statusCode; + if (!statusCode) { + throw new Error("Status code undefined"); + } + if (statusCode > 100 && statusCode < 200) { + // If a provisional response is received for an initial request, and + // that response contains a Require header field containing the option + // tag 100rel, the response is to be sent reliably. If the response is + // a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be + // ignored, and the procedures below MUST NOT be used. + // https://tools.ietf.org/html/rfc3262#section-4 + var requireHeader = message.getHeader("require"); + var rseqHeader = message.getHeader("rseq"); + var rseq = requireHeader && requireHeader.includes("100rel") && rseqHeader ? Number(rseqHeader) : undefined; + if (rseq) { + // Handling of subsequent reliable provisional responses for the same + // initial request follows the same rules as above, with the following + // difference: reliable provisional responses are guaranteed to be in + // order. As a result, if the UAC receives another reliable provisional + // response to the same request, and its RSeq value is not one higher + // than the value of the sequence number, that response MUST NOT be + // acknowledged with a PRACK, and MUST NOT be processed further by the + // UAC. An implementation MAY discard the response, or MAY cache the + // response in the hopes of receiving the missing responses. + // https://tools.ietf.org/html/rfc3262#section-4 + if (this.rseq && this.rseq + 1 !== rseq) { + return false; + } + // Once a reliable provisional response is received, retransmissions of + // that response MUST be discarded. A response is a retransmission when + // its dialog ID, CSeq, and RSeq match the original response. The UAC + // MUST maintain a sequence number that indicates the most recently + // received in-order reliable provisional response for the initial + // request. This sequence number MUST be maintained until a final + // response is received for the initial request. Its value MUST be + // initialized to the RSeq header field in the first reliable + // provisional response received for the initial request. + // https://tools.ietf.org/html/rfc3262#section-4 + if (!this.rseq) { + this.rseq = rseq; + } + } + } + return true; + }; + /** + * Update the signaling state of the dialog. + * @param message The message to base the update off of. + */ + SessionDialog.prototype.signalingStateTransition = function (message) { + var body = messages_1.getBody(message); + // No body, no session. No, woman, no cry. + if (!body || body.contentDisposition !== "session") { + return; + } + // We're in UAS role, receiving incoming request with session description + if (message instanceof messages_1.IncomingRequestMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveRemoteOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.HaveRemoteOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAC role, receiving incoming response with session description + if (message instanceof messages_1.IncomingResponseMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveRemoteOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.HaveRemoteOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAC role, sending outgoing request with session description + if (message instanceof messages_1.OutgoingRequestMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveLocalOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.HaveRemoteOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAS role, sending outgoing response with session description + if (messages_1.isBody(message)) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveLocalOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.HaveRemoteOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + }; + SessionDialog.prototype.start2xxRetransmissionTimer = function () { + var _this = this; + if (this.initialTransaction instanceof transactions_1.InviteServerTransaction) { + var transaction_1 = this.initialTransaction; + // Once the response has been constructed, it is passed to the INVITE + // server transaction. In order to ensure reliable end-to-end + // transport of the response, it is necessary to periodically pass + // the response directly to the transport until the ACK arrives. The + // 2xx response is passed to the transport with an interval that + // starts at T1 seconds and doubles for each retransmission until it + // reaches T2 seconds (T1 and T2 are defined in Section 17). + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + var timeout_1 = timers_1.Timers.T1; + var retransmission_1 = function () { + if (!_this.ackWait) { + _this.invite2xxTimer = undefined; + return; + } + _this.logger.log("No ACK for 2xx response received, attempting retransmission"); + transaction_1.retransmitAcceptedResponse(); + timeout_1 = Math.min(timeout_1 * 2, timers_1.Timers.T2); + _this.invite2xxTimer = setTimeout(retransmission_1, timeout_1); + }; + this.invite2xxTimer = setTimeout(retransmission_1, timeout_1); + // If the server retransmits the 2xx response for 64*T1 seconds without + // receiving an ACK, the dialog is confirmed, but the session SHOULD be + // terminated. This is accomplished with a BYE, as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + var stateChanged_1 = function () { + if (transaction_1.state === transactions_1.TransactionState.Terminated) { + transaction_1.removeListener("stateChanged", stateChanged_1); + if (_this.invite2xxTimer) { + clearTimeout(_this.invite2xxTimer); + _this.invite2xxTimer = undefined; + } + if (_this.ackWait) { + if (_this.delegate && _this.delegate.onAckTimeout) { + _this.delegate.onAckTimeout(); + } + else { + _this.bye(); + } + } + } + }; + transaction_1.addListener("stateChanged", stateChanged_1); + } + }; + // FIXME: Refactor + SessionDialog.prototype.startReInvite2xxRetransmissionTimer = function () { + var _this = this; + if (this.reinviteUserAgentServer && this.reinviteUserAgentServer.transaction instanceof transactions_1.InviteServerTransaction) { + var transaction_2 = this.reinviteUserAgentServer.transaction; + // Once the response has been constructed, it is passed to the INVITE + // server transaction. In order to ensure reliable end-to-end + // transport of the response, it is necessary to periodically pass + // the response directly to the transport until the ACK arrives. The + // 2xx response is passed to the transport with an interval that + // starts at T1 seconds and doubles for each retransmission until it + // reaches T2 seconds (T1 and T2 are defined in Section 17). + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + var timeout_2 = timers_1.Timers.T1; + var retransmission_2 = function () { + if (!_this.reinviteUserAgentServer) { + _this.invite2xxTimer = undefined; + return; + } + _this.logger.log("No ACK for 2xx response received, attempting retransmission"); + transaction_2.retransmitAcceptedResponse(); + timeout_2 = Math.min(timeout_2 * 2, timers_1.Timers.T2); + _this.invite2xxTimer = setTimeout(retransmission_2, timeout_2); + }; + this.invite2xxTimer = setTimeout(retransmission_2, timeout_2); + // If the server retransmits the 2xx response for 64*T1 seconds without + // receiving an ACK, the dialog is confirmed, but the session SHOULD be + // terminated. This is accomplished with a BYE, as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + var stateChanged_2 = function () { + if (transaction_2.state === transactions_1.TransactionState.Terminated) { + transaction_2.removeListener("stateChanged", stateChanged_2); + if (_this.invite2xxTimer) { + clearTimeout(_this.invite2xxTimer); + _this.invite2xxTimer = undefined; + } + if (_this.reinviteUserAgentServer) { + // FIXME: TODO: What to do here + } + } + }; + transaction_2.addListener("stateChanged", stateChanged_2); + } + }; + return SessionDialog; +}(dialog_1.Dialog)); +exports.SessionDialog = SessionDialog; + + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(25), exports); + + +/***/ }), +/* 25 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Session state. + * https://tools.ietf.org/html/rfc3261#section-13 + */ +var SessionState; +(function (SessionState) { + SessionState["Initial"] = "Initial"; + SessionState["Early"] = "Early"; + SessionState["AckWait"] = "AckWait"; + SessionState["Confirmed"] = "Confirmed"; + SessionState["Terminated"] = "Terminated"; +})(SessionState = exports.SessionState || (exports.SessionState = {})); +/** + * Offer/Answer State + * + * Offer Answer RFC Ini Est Early + * ------------------------------------------------------------------- + * 1. INVITE Req. 2xx INVITE Resp. RFC 3261 Y Y N + * 2. 2xx INVITE Resp. ACK Req. RFC 3261 Y Y N + * 3. INVITE Req. 1xx-rel INVITE Resp. RFC 3262 Y Y N + * 4. 1xx-rel INVITE Resp. PRACK Req. RFC 3262 Y Y N + * 5. PRACK Req. 200 PRACK Resp. RFC 3262 N Y Y + * 6. UPDATE Req. 2xx UPDATE Resp. RFC 3311 N Y Y + * + * Table 1: Summary of SIP Usage of the Offer/Answer Model + * https://tools.ietf.org/html/rfc6337#section-2.2 + */ +var SignalingState; +(function (SignalingState) { + SignalingState["Initial"] = "Initial"; + SignalingState["HaveLocalOffer"] = "HaveLocalOffer"; + SignalingState["HaveRemoteOffer"] = "HaveRemoteOffer"; + SignalingState["Stable"] = "Stable"; + SignalingState["Closed"] = "Closed"; +})(SignalingState = exports.SignalingState || (exports.SignalingState = {})); + + +/***/ }), +/* 26 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var T1 = 500; +var T2 = 4000; +var T4 = 5000; +exports.Timers = { + T1: T1, + T2: T2, + T4: T4, + TIMER_B: 64 * T1, + TIMER_D: 0 * T1, + TIMER_F: 64 * T1, + TIMER_H: 64 * T1, + TIMER_I: 0 * T4, + TIMER_J: 0 * T1, + TIMER_K: 0 * T4, + TIMER_L: 64 * T1, + TIMER_M: 64 * T1, + TIMER_N: 64 * T1, + PROVISIONAL_RESPONSE_INTERVAL: 60000 // See RFC 3261 Section 13.3.1.1 +}; + + +/***/ }), +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(28), exports); +tslib_1.__exportStar(__webpack_require__(35), exports); +tslib_1.__exportStar(__webpack_require__(37), exports); +tslib_1.__exportStar(__webpack_require__(39), exports); +tslib_1.__exportStar(__webpack_require__(40), exports); +tslib_1.__exportStar(__webpack_require__(35), exports); +tslib_1.__exportStar(__webpack_require__(38), exports); +tslib_1.__exportStar(__webpack_require__(36), exports); +tslib_1.__exportStar(__webpack_require__(29), exports); + + +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transaction_1 = __webpack_require__(29); +/** + * Client Transaction + * + * The client transaction provides its functionality through the + * maintenance of a state machine. + * + * The TU communicates with the client transaction through a simple + * interface. When the TU wishes to initiate a new transaction, it + * creates a client transaction and passes it the SIP request to send + * and an IP address, port, and transport to which to send it. The + * client transaction begins execution of its state machine. Valid + * responses are passed up to the TU from the client transaction. + * https://tools.ietf.org/html/rfc3261#section-17.1 + */ +var ClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(ClientTransaction, _super); + function ClientTransaction(_request, transport, user, state, loggerCategory) { + var _this = _super.call(this, transport, user, ClientTransaction.makeId(_request), state, loggerCategory) || this; + _this._request = _request; + _this.user = user; + // The Via header field indicates the transport used for the transaction + // and identifies the location where the response is to be sent. A Via + // header field value is added only after the transport that will be + // used to reach the next hop has been selected (which may involve the + // usage of the procedures in [4]). + // https://tools.ietf.org/html/rfc3261#section-8.1.1.7 + // FIXME: Transport's server property is not typed (as of writing this). + var scheme = transport.server && transport.server.scheme ? transport.server.scheme : undefined; + _request.setViaHeader(_this.id, scheme); + return _this; + } + ClientTransaction.makeId = function (request) { + if (request.method === "CANCEL") { + if (!request.branch) { + throw new Error("Outgoing CANCEL request without a branch."); + } + return request.branch; + } + else { + return "z9hG4bK" + Math.floor(Math.random() * 10000000); + } + }; + Object.defineProperty(ClientTransaction.prototype, "request", { + /** The outgoing request the transaction handling. */ + get: function () { + return this._request; + }, + enumerable: true, + configurable: true + }); + /** + * A 408 to non-INVITE will always arrive too late to be useful ([3]), + * The client already has full knowledge of the timeout. The only + * information this message would convey is whether or not the server + * believed the transaction timed out. However, with the current design + * of the NIT, a client cannot do anything with this knowledge. Thus, + * the 408 is simply wasting network resources and contributes to the + * response bombardment illustrated in [3]. + * https://tools.ietf.org/html/rfc4320#section-4.1 + */ + ClientTransaction.prototype.onRequestTimeout = function () { + if (this.user.onRequestTimeout) { + this.user.onRequestTimeout(); + } + }; + return ClientTransaction; +}(transaction_1.Transaction)); +exports.ClientTransaction = ClientTransaction; + + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var exceptions_1 = __webpack_require__(31); +/** + * Transaction + * + * SIP is a transactional protocol: interactions between components take + * place in a series of independent message exchanges. Specifically, a + * SIP transaction consists of a single request and any responses to + * that request, which include zero or more provisional responses and + * one or more final responses. In the case of a transaction where the + * request was an INVITE (known as an INVITE transaction), the + * transaction also includes the ACK only if the final response was not + * a 2xx response. If the response was a 2xx, the ACK is not considered + * part of the transaction. + * https://tools.ietf.org/html/rfc3261#section-17 + */ +var Transaction = /** @class */ (function (_super) { + tslib_1.__extends(Transaction, _super); + function Transaction(_transport, _user, _id, _state, loggerCategory) { + var _this = _super.call(this) || this; + _this._transport = _transport; + _this._user = _user; + _this._id = _id; + _this._state = _state; + _this.logger = _user.loggerFactory.getLogger(loggerCategory, _id); + _this.logger.debug("Constructing " + _this.typeToString() + " with id " + _this.id + "."); + return _this; + } + /** + * Destructor. + * Once the transaction is in the "terminated" state, it is destroyed + * immediately and there is no need to call `dispose`. However, if a + * transaction needs to be ended prematurely, the transaction user may + * do so by calling this method (for example, perhaps the UA is shutting down). + * No state transition will occur upon calling this method, all outstanding + * transmission timers will be cancelled, and use of the transaction after + * calling `dispose` is undefined. + */ + Transaction.prototype.dispose = function () { + this.logger.debug("Destroyed " + this.typeToString() + " with id " + this.id + "."); + }; + Object.defineProperty(Transaction.prototype, "id", { + /** Transaction id. */ + get: function () { + return this._id; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + throw new Error("Invalid kind."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "state", { + /** Transaction state. */ + get: function () { + return this._state; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "transport", { + /** Transaction transport. */ + get: function () { + return this._transport; + }, + enumerable: true, + configurable: true + }); + Transaction.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + Transaction.prototype.logTransportError = function (error, message) { + this.logger.error(error.message); + this.logger.error("Transport error occurred in " + this.typeToString() + " with id " + this.id + "."); + this.logger.error(message); + }; + /** + * Pass message to transport for transmission. If transport fails, + * the transaction user is notified by callback to onTransportError(). + * @throws {TransportError} If transport fails. + */ + Transaction.prototype.send = function (message) { + var _this = this; + return this.transport.send(message).catch(function (error) { + // FIXME: Transport is not, yet, typed and it is not clear + // yet what send() may or may not send our way. So for now, + // make sure we convert it to a TransportError if need be. + if (error instanceof exceptions_1.TransportError) { + _this.onTransportError(error); + return; + } + var transportError; + if (error && typeof error.message === "string") { + transportError = new exceptions_1.TransportError(error.message); + } + else { + transportError = new exceptions_1.TransportError(); + } + _this.onTransportError(transportError); + throw transportError; + }); + }; + Transaction.prototype.setState = function (state) { + this.logger.debug("State change to \"" + state + "\" on " + this.typeToString() + " with id " + this.id + "."); + this._state = state; + if (this._user.onStateChange) { + this._user.onStateChange(state); + } + this.emit("stateChanged"); + }; + Transaction.prototype.typeToString = function () { + return "UnknownType"; + }; + return Transaction; +}(events_1.EventEmitter)); +exports.Transaction = Transaction; + + +/***/ }), +/* 30 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +var R = typeof Reflect === 'object' ? Reflect : null +var ReflectApply = R && typeof R.apply === 'function' + ? R.apply + : function ReflectApply(target, receiver, args) { + return Function.prototype.apply.call(target, receiver, args); + } + +var ReflectOwnKeys +if (R && typeof R.ownKeys === 'function') { + ReflectOwnKeys = R.ownKeys +} else if (Object.getOwnPropertySymbols) { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); + }; +} else { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); + }; +} + +function ProcessEmitWarning(warning) { + if (console && console.warn) console.warn(warning); +} + +var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { + return value !== value; +} + +function EventEmitter() { + EventEmitter.init.call(this); +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); + } + defaultMaxListeners = arg; + } +}); + +EventEmitter.init = function() { + + if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +}; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); + } + this._maxListeners = n; + return this; +}; + +function $getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return $getMaxListeners(this); +}; + +EventEmitter.prototype.emit = function emit(type) { + var args = []; + for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); + var doError = (type === 'error'); + + var events = this._events; + if (events !== undefined) + doError = (doError && events.error === undefined); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + var er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + // At least give some kind of context to the user + var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); + err.context = er; + throw err; // Unhandled 'error' event + } + + var handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + ReflectApply(handler, this, args); + } else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + ReflectApply(listeners[i], this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = $getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' ' + String(type) + ' listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + ProcessEmitWarning(w); + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + var args = []; + for (var i = 0; i < arguments.length; i++) args.push(arguments[i]); + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + ReflectApply(this.listener, this.target, args); + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } + + events = this._events; + if (events === undefined) + return this; + + list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (events === undefined) + return this; + + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = Object.keys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (events === undefined) + return []; + + var evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events !== undefined) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + + +/***/ }), +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(32), exports); +tslib_1.__exportStar(__webpack_require__(33), exports); +tslib_1.__exportStar(__webpack_require__(34), exports); + + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +/** + * An Exception is considered a condition that a reasonable application may wish to catch. + * An Error indicates serious problems that a reasonable application should not try to catch. + * @public + */ +var Exception = /** @class */ (function (_super) { + tslib_1.__extends(Exception, _super); + function Exception(message) { + var _newTarget = this.constructor; + var _this = _super.call(this, message) || this; + Object.setPrototypeOf(_this, _newTarget.prototype); // restore prototype chain + return _this; + } + return Exception; +}(Error)); +exports.Exception = Exception; + + +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var exception_1 = __webpack_require__(32); +/** + * Indicates that the operation could not be completed given the current transaction state. + * @public + */ +var TransactionStateError = /** @class */ (function (_super) { + tslib_1.__extends(TransactionStateError, _super); + function TransactionStateError(message) { + return _super.call(this, message ? message : "Transaction state error.") || this; + } + return TransactionStateError; +}(exception_1.Exception)); +exports.TransactionStateError = TransactionStateError; + + +/***/ }), +/* 34 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var exception_1 = __webpack_require__(32); +/** + * Transport error. + * @public + */ +var TransportError = /** @class */ (function (_super) { + tslib_1.__extends(TransportError, _super); + function TransportError(message) { + return _super.call(this, message ? message : "Unspecified transport error.") || this; + } + return TransportError; +}(exception_1.Exception)); +exports.TransportError = TransportError; + + +/***/ }), +/* 35 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var timers_1 = __webpack_require__(26); +var client_transaction_1 = __webpack_require__(28); +var transaction_state_1 = __webpack_require__(36); +/** + * INVITE Client Transaction + * + * The INVITE transaction consists of a three-way handshake. The client + * transaction sends an INVITE, the server transaction sends responses, + * and the client transaction sends an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + */ +var InviteClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(InviteClientTransaction, _super); + /** + * Constructor. + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + * @param request The outgoing INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + function InviteClientTransaction(request, transport, user) { + var _this = _super.call(this, request, transport, user, transaction_state_1.TransactionState.Calling, "sip.transaction.ict") || this; + /** + * Map of 2xx to-tag => ACK. + * If value is not undefined, value is the ACK which was sent. + * If key exists but value is undefined, a 2xx was received but the ACK not yet sent. + * Otherwise, a 2xx was not (yet) received for this transaction. + */ + _this.ackRetransmissionCache = new Map(); + // FIXME: Timer A for unreliable transport not implemented + // + // If an unreliable transport is being used, the client transaction + // MUST start timer A with a value of T1. If a reliable transport is being used, + // the client transaction SHOULD NOT start timer A (Timer A controls request retransmissions). + // For any transport, the client transaction MUST start timer B with a value + // of 64*T1 seconds (Timer B controls transaction timeouts). + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + // + // While not spelled out in the RFC, Timer B is the maximum amount of time that a sender + // will wait for an INVITE message to be acknowledged (a SIP response message is received). + // So Timer B should be cleared when the transaction state proceeds from "Calling". + _this.B = setTimeout(function () { return _this.timer_B(); }, timers_1.Timers.TIMER_B); + _this.send(request.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send initial outgoing request."); + }); + return _this; + } + /** + * Destructor. + */ + InviteClientTransaction.prototype.dispose = function () { + if (this.B) { + clearTimeout(this.B); + this.B = undefined; + } + if (this.D) { + clearTimeout(this.D); + this.D = undefined; + } + if (this.M) { + clearTimeout(this.M); + this.M = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(InviteClientTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "ict"; + }, + enumerable: true, + configurable: true + }); + /** + * ACK a 2xx final response. + * + * The transaction includes the ACK only if the final response was not a 2xx response (the + * transaction will generate and send the ACK to the transport automagically). If the + * final response was a 2xx, the ACK is not considered part of the transaction (the + * transaction user needs to generate and send the ACK). + * + * This library is not strictly RFC compliant with regard to ACK handling for 2xx final + * responses. Specifically, retransmissions of ACKs to a 2xx final responses is handled + * by the transaction layer (instead of the UAC core). The "standard" approach is for + * the UAC core to receive all 2xx responses and manage sending ACK retransmissions to + * the transport directly. Herein the transaction layer manages sending ACKs to 2xx responses + * and any retransmissions of those ACKs as needed. + * + * @param ack The outgoing ACK request. + */ + InviteClientTransaction.prototype.ackResponse = function (ack) { + var _this = this; + var toTag = ack.toTag; + if (!toTag) { + throw new Error("To tag undefined."); + } + var id = "z9hG4bK" + Math.floor(Math.random() * 10000000); + // FIXME: Transport's server property is not typed (as of writing this). + var scheme = this.transport.server && this.transport.server.scheme ? this.transport.server.scheme : undefined; + ack.setViaHeader(id, scheme); + this.ackRetransmissionCache.set(toTag, ack); // Add to ACK retransmission cache + this.send(ack.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send ACK to 2xx response."); + }); + }; + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + InviteClientTransaction.prototype.receiveResponse = function (response) { + var _this = this; + var statusCode = response.statusCode; + if (!statusCode || statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Calling: + // If the client transaction receives a provisional response while in + // the "Calling" state, it transitions to the "Proceeding" state. In the + // "Proceeding" state, the client transaction SHOULD NOT retransmit the + // request any longer. Furthermore, the provisional response MUST be + // passed to the TU. Any further provisional responses MUST be passed + // up to the TU while in the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + if (statusCode >= 100 && statusCode <= 199) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When a 2xx response is received while in either the "Calling" or + // "Proceeding" states, the client transaction MUST transition to + // the "Accepted" state... The 2xx response MUST be passed up to the TU. + // The client transaction MUST NOT generate an ACK to the 2xx response -- its + // handling is delegated to the TU. A UAC core will send an ACK to + // the 2xx response using a new transaction. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + this.stateTransition(transaction_state_1.TransactionState.Accepted); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When in either the "Calling" or "Proceeding" states, reception of + // a response with status code from 300-699 MUST cause the client + // transaction to transition to "Completed". The client transaction + // MUST pass the received response up to the TU, and the client + // transaction MUST generate an ACK request, even if the transport is + // reliable (guidelines for constructing the ACK from the response + // are given in Section 17.1.1.3), and then pass the ACK to the + // transport layer for transmission. The ACK MUST be sent to the + // same address, port, and transport to which the original request was sent. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.ack(response); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // In the "Proceeding" state, the client transaction SHOULD NOT retransmit the + // request any longer. Furthermore, the provisional response MUST be + // passed to the TU. Any further provisional responses MUST be passed + // up to the TU while in the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + if (statusCode >= 100 && statusCode <= 199) { + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When a 2xx response is received while in either the "Calling" or "Proceeding" states, + // the client transaction MUST transition to the "Accepted" state... + // The 2xx response MUST be passed up to the TU. The client + // transaction MUST NOT generate an ACK to the 2xx response -- its + // handling is delegated to the TU. A UAC core will send an ACK to + // the 2xx response using a new transaction. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + this.stateTransition(transaction_state_1.TransactionState.Accepted); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When in either the "Calling" or "Proceeding" states, reception of + // a response with status code from 300-699 MUST cause the client + // transaction to transition to "Completed". The client transaction + // MUST pass the received response up to the TU, and the client + // transaction MUST generate an ACK request, even if the transport is + // reliable (guidelines for constructing the ACK from the response + // are given in Section 17.1.1.3), and then pass the ACK to the + // transport layer for transmission. The ACK MUST be sent to the + // same address, port, and transport to which the original request was sent. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.ack(response); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // The purpose of the "Accepted" state is to allow the client + // transaction to continue to exist to receive, and pass to the TU, + // any retransmissions of the 2xx response and any additional 2xx + // responses from other branches of the INVITE if it forked + // downstream. Timer M reflects the amount of time that the + // transaction user will wait for such messages. + // + // Any 2xx responses that match this client transaction and that are + // received while in the "Accepted" state MUST be passed up to the + // TU. The client transaction MUST NOT generate an ACK to the 2xx + // response. The client transaction takes no further action. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + // NOTE: This implementation herein is intentionally not RFC compliant. + // While the first 2xx response for a given branch is passed up to the TU, + // retransmissions of 2xx responses are absorbed and the ACK associated + // with the original response is resent. This approach is taken because + // our current transaction users are not currently in a good position to + // deal with 2xx retransmission. This SHOULD NOT cause any compliance issues - ;) + // + // If we don't have a cache hit, pass the response to the TU. + if (!this.ackRetransmissionCache.has(response.toTag)) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // If we have a cache hit, try pulling the ACK from cache and retransmitting it. + var ack = this.ackRetransmissionCache.get(response.toTag); + if (ack) { + this.send(ack.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of ACK to 2xx response."); + }); + return; + } + // If an ACK was not found in cache then we have received a retransmitted 2xx + // response before the TU responded to the original response (we don't have an ACK yet). + // So discard this response under the assumption that the TU will eventually + // get us a ACK for the original response. + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Any retransmissions of a response with status code 300-699 that + // are received while in the "Completed" state MUST cause the ACK to + // be re-passed to the transport layer for retransmission, but the + // newly received response MUST NOT be passed up to the TU. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.ack(response); + return; + } + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + // Any response received that does not match an existing client + // transaction state machine is simply dropped. (Implementations are, + // of course, free to log or do other implementation-specific things + // with such responses, but the implementer should be sure to consider + // the impact of large numbers of malicious stray responses.) + // https://tools.ietf.org/html/rfc6026#section-7.2 + var message = "Received unexpected " + statusCode + " response while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * The client transaction SHOULD inform the TU that a transport failure + * has occurred, and the client transaction SHOULD transition directly + * to the "Terminated" state. The TU will handle the failover + * mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error The error. + */ + InviteClientTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + InviteClientTransaction.prototype.typeToString = function () { + return "INVITE client transaction"; + }; + InviteClientTransaction.prototype.ack = function (response) { + var _this = this; + // The ACK request constructed by the client transaction MUST contain + // values for the Call-ID, From, and Request-URI that are equal to the + // values of those header fields in the request passed to the transport + // by the client transaction (call this the "original request"). The To + // header field in the ACK MUST equal the To header field in the + // response being acknowledged, and therefore will usually differ from + // the To header field in the original request by the addition of the + // tag parameter. The ACK MUST contain a single Via header field, and + // this MUST be equal to the top Via header field of the original + // request. The CSeq header field in the ACK MUST contain the same + // value for the sequence number as was present in the original request, + // but the method parameter MUST be equal to "ACK". + // + // If the INVITE request whose response is being acknowledged had Route + // header fields, those header fields MUST appear in the ACK. This is + // to ensure that the ACK can be routed properly through any downstream + // stateless proxies. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.3 + var ruri = this.request.ruri; + var callId = this.request.callId; + var cseq = this.request.cseq; + var from = this.request.getHeader("from"); + var to = response.getHeader("to"); + var via = this.request.getHeader("via"); + var route = this.request.getHeader("route"); + if (!from) { + throw new Error("From undefined."); + } + if (!to) { + throw new Error("To undefined."); + } + if (!via) { + throw new Error("Via undefined."); + } + var ack = "ACK " + ruri + " SIP/2.0\r\n"; + if (route) { + ack += "Route: " + route + "\r\n"; + } + ack += "Via: " + via + "\r\n"; + ack += "To: " + to + "\r\n"; + ack += "From: " + from + "\r\n"; + ack += "Call-ID: " + callId + "\r\n"; + ack += "CSeq: " + cseq + " ACK\r\n"; + ack += "Max-Forwards: 70\r\n"; + ack += "Content-Length: 0\r\n\r\n"; + // TOOO: "User-Agent" header + this.send(ack).catch(function (error) { + _this.logTransportError(error, "Failed to send ACK to non-2xx response."); + }); + return; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + InviteClientTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Calling: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Calling) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Accepted: + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Calling && + this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Calling && + this.state !== transaction_state_1.TransactionState.Accepted && + this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // While not spelled out in the RFC, Timer B is the maximum amount of time that a sender + // will wait for an INVITE message to be acknowledged (a SIP response message is received). + // So Timer B should be cleared when the transaction state proceeds from "Calling". + if (this.B) { + clearTimeout(this.B); + this.B = undefined; + } + if (newState === transaction_state_1.TransactionState.Proceeding) { + // Timers have no effect on "Proceeding" state. + // In the "Proceeding" state, the client transaction + // SHOULD NOT retransmit the request any longer. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + } + // The client transaction MUST start Timer D when it enters the "Completed" state + // for any reason, with a value of at least 32 seconds for unreliable transports, + // and a value of zero seconds for reliable transports. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (newState === transaction_state_1.TransactionState.Completed) { + this.D = setTimeout(function () { return _this.timer_D(); }, timers_1.Timers.TIMER_D); + } + // The client transaction MUST transition to the "Accepted" state, + // and Timer M MUST be started with a value of 64*T1. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (newState === transaction_state_1.TransactionState.Accepted) { + this.M = setTimeout(function () { return _this.timer_M(); }, timers_1.Timers.TIMER_M); + } + // Once the transaction is in the "Terminated" state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * When timer A fires, the client transaction MUST retransmit the + * request by passing it to the transport layer, and MUST reset the + * timer with a value of 2*T1. + * When timer A fires 2*T1 seconds later, the request MUST be + * retransmitted again (assuming the client transaction is still in this + * state). This process MUST continue so that the request is + * retransmitted with intervals that double after each transmission. + * These retransmissions SHOULD only be done while the client + * transaction is in the "Calling" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + InviteClientTransaction.prototype.timer_A = function () { + // TODO + }; + /** + * If the client transaction is still in the "Calling" state when timer + * B fires, the client transaction SHOULD inform the TU that a timeout + * has occurred. The client transaction MUST NOT generate an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + InviteClientTransaction.prototype.timer_B = function () { + this.logger.debug("Timer B expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Calling) { + this.onRequestTimeout(); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer D fires while the client transaction is in the "Completed" state, + * the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + InviteClientTransaction.prototype.timer_D = function () { + this.logger.debug("Timer D expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer M fires while the client transaction is in the "Accepted" + * state, the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + InviteClientTransaction.prototype.timer_M = function () { + this.logger.debug("Timer M expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Accepted) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return InviteClientTransaction; +}(client_transaction_1.ClientTransaction)); +exports.InviteClientTransaction = InviteClientTransaction; + + +/***/ }), +/* 36 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** Transaction state. */ +var TransactionState; +(function (TransactionState) { + TransactionState["Accepted"] = "Accepted"; + TransactionState["Calling"] = "Calling"; + TransactionState["Completed"] = "Completed"; + TransactionState["Confirmed"] = "Confirmed"; + TransactionState["Proceeding"] = "Proceeding"; + TransactionState["Terminated"] = "Terminated"; + TransactionState["Trying"] = "Trying"; +})(TransactionState = exports.TransactionState || (exports.TransactionState = {})); + + +/***/ }), +/* 37 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var timers_1 = __webpack_require__(26); +var server_transaction_1 = __webpack_require__(38); +var transaction_state_1 = __webpack_require__(36); +/** + * INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ +var InviteServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(InviteServerTransaction, _super); + /** + * Constructor. + * Upon construction, a "100 Trying" reply will be immediately sent. + * After construction the transaction will be in the "proceeding" state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + * @param request Incoming INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + function InviteServerTransaction(request, transport, user) { + return _super.call(this, request, transport, user, transaction_state_1.TransactionState.Proceeding, "sip.transaction.ist") || this; + } + /** + * Destructor. + */ + InviteServerTransaction.prototype.dispose = function () { + this.stopProgressExtensionTimer(); + if (this.H) { + clearTimeout(this.H); + this.H = undefined; + } + if (this.I) { + clearTimeout(this.I); + this.I = undefined; + } + if (this.L) { + clearTimeout(this.L); + this.L = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(InviteServerTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "ist"; + }, + enumerable: true, + configurable: true + }); + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + InviteServerTransaction.prototype.receiveRequest = function (request) { + var _this = this; + switch (this.state) { + case transaction_state_1.TransactionState.Proceeding: + // If a request retransmission is received while in the "Proceeding" state, the most + // recent provisional response that was received from the TU MUST be passed to the + // transport layer for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE) { + if (this.lastProvisionalResponse) { + this.send(this.lastProvisionalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + } + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // While in the "Accepted" state, any retransmissions of the INVITE + // received will match this transaction state machine and will be + // absorbed by the machine without changing its state. These + // retransmissions are not passed onto the TU. + // https://tools.ietf.org/html/rfc6026#section-7.1 + if (request.method === messages_1.C.INVITE) { + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Furthermore, while in the "Completed" state, if a request retransmission is + // received, the server SHOULD pass the response to the transport for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE) { + if (!this.lastFinalResponse) { + throw new Error("Last final response undefined."); + } + this.send(this.lastFinalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of final response."); + }); + return; + } + // If an ACK is received while the server transaction is in the "Completed" state, + // the server transaction MUST transition to the "Confirmed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.ACK) { + this.stateTransition(transaction_state_1.TransactionState.Confirmed); + return; + } + break; + case transaction_state_1.TransactionState.Confirmed: + // The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, + // triggered from retransmissions of the final response. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE || request.method === messages_1.C.ACK) { + return; + } + break; + case transaction_state_1.TransactionState.Terminated: + // For good measure absorb any additional messages that arrive (should not happen). + if (request.method === messages_1.C.INVITE || request.method === messages_1.C.ACK) { + return; + } + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "INVITE server transaction received unexpected " + request.method + " request while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of response. + * @param response Response. + */ + InviteServerTransaction.prototype.receiveResponse = function (statusCode, response) { + var _this = this; + if (statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Proceeding: + // The TU passes any number of provisional responses to the server + // transaction. So long as the server transaction is in the + // "Proceeding" state, each of these MUST be passed to the transport + // layer for transmission. They are not sent reliably by the + // transaction layer (they are not retransmitted by it) and do not cause + // a change in the state of the server transaction. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (statusCode >= 100 && statusCode <= 199) { + this.lastProvisionalResponse = response; + // Start the progress extension timer only for a non-100 provisional response. + if (statusCode > 100) { + this.startProgressExtensionTimer(); // FIXME: remove + } + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 1xx response."); + }); + return; + } + // If, while in the "Proceeding" state, the TU passes a 2xx response + // to the server transaction, the server transaction MUST pass this + // response to the transport layer for transmission. It is not + // retransmitted by the server transaction; retransmissions of 2xx + // responses are handled by the TU. The server transaction MUST then + // transition to the "Accepted" state. + // https://tools.ietf.org/html/rfc6026#section-8.5 + if (statusCode >= 200 && statusCode <= 299) { + this.lastFinalResponse = response; + this.stateTransition(transaction_state_1.TransactionState.Accepted); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + return; + } + // While in the "Proceeding" state, if the TU passes a response with + // status code from 300 to 699 to the server transaction, the response + // MUST be passed to the transport layer for transmission, and the state + // machine MUST enter the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (statusCode >= 300 && statusCode <= 699) { + this.lastFinalResponse = response; + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send non-2xx final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // While in the "Accepted" state, if the TU passes a 2xx response, + // the server transaction MUST pass the response to the transport layer for transmission. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (statusCode >= 200 && statusCode <= 299) { + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Completed: + break; + case transaction_state_1.TransactionState.Confirmed: + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "INVITE server transaction received unexpected " + statusCode + " response from TU while in state " + this.state + "."; + this.logger.error(message); + throw new Error(message); + }; + /** + * Retransmit the last 2xx response. This is a noop if not in the "accepted" state. + */ + InviteServerTransaction.prototype.retransmitAcceptedResponse = function () { + var _this = this; + if (this.state === transaction_state_1.TransactionState.Accepted && this.lastFinalResponse) { + this.send(this.lastFinalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + } + }; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and MUST remain in the current state. + * https://tools.ietf.org/html/rfc6026#section-8.8 + */ + InviteServerTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + }; + /** For logging. */ + InviteServerTransaction.prototype.typeToString = function () { + return "INVITE server transaction"; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + InviteServerTransaction.prototype.stateTransition = function (newState) { + var _this = this; + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Proceeding: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Accepted: + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Confirmed: + if (this.state !== transaction_state_1.TransactionState.Completed) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Accepted && + this.state !== transaction_state_1.TransactionState.Completed && + this.state !== transaction_state_1.TransactionState.Confirmed) { + invalidStateTransition(); + } + break; + default: + invalidStateTransition(); + } + // On any state transition, stop resending provisonal responses + this.stopProgressExtensionTimer(); + // The purpose of the "Accepted" state is to absorb retransmissions of an accepted INVITE request. + // Any such retransmissions are absorbed entirely within the server transaction. + // They are not passed up to the TU since any downstream UAS cores that accepted the request have + // taken responsibility for reliability and will already retransmit their 2xx responses if necessary. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Accepted) { + this.L = setTimeout(function () { return _this.timer_L(); }, timers_1.Timers.TIMER_L); + } + // When the "Completed" state is entered, timer H MUST be set to fire in 64*T1 seconds for all transports. + // Timer H determines when the server transaction abandons retransmitting the response. + // If an ACK is received while the server transaction is in the "Completed" state, + // the server transaction MUST transition to the "Confirmed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (newState === transaction_state_1.TransactionState.Completed) { + // FIXME: Missing timer G for unreliable transports. + this.H = setTimeout(function () { return _this.timer_H(); }, timers_1.Timers.TIMER_H); + } + // The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, + // triggered from retransmissions of the final response. When this state is entered, timer I + // is set to fire in T4 seconds for unreliable transports, and zero seconds for reliable + // transports. Once timer I fires, the server MUST transition to the "Terminated" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (newState === transaction_state_1.TransactionState.Confirmed) { + // FIXME: This timer is not getting set correctly for unreliable transports. + this.I = setTimeout(function () { return _this.timer_I(); }, timers_1.Timers.TIMER_I); + } + // Once the transaction is in the "Terminated" state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * FIXME: UAS Provisional Retransmission Timer. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + InviteServerTransaction.prototype.startProgressExtensionTimer = function () { + var _this = this; + // Start the progress extension timer only for the first non-100 provisional response. + if (this.progressExtensionTimer === undefined) { + this.progressExtensionTimer = setInterval(function () { + _this.logger.debug("Progress extension timer expired for INVITE server transaction " + _this.id + "."); + if (!_this.lastProvisionalResponse) { + throw new Error("Last provisional response undefined."); + } + _this.send(_this.lastProvisionalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + }, timers_1.Timers.PROVISIONAL_RESPONSE_INTERVAL); + } + }; + /** + * FIXME: UAS Provisional Retransmission Timer id. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + InviteServerTransaction.prototype.stopProgressExtensionTimer = function () { + if (this.progressExtensionTimer !== undefined) { + clearInterval(this.progressExtensionTimer); + this.progressExtensionTimer = undefined; + } + }; + /** + * While in the "Proceeding" state, if the TU passes a response with status code + * from 300 to 699 to the server transaction, the response MUST be passed to the + * transport layer for transmission, and the state machine MUST enter the "Completed" state. + * For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for + * reliable transports. If timer G fires, the response is passed to the transport layer once + * more for retransmission, and timer G is set to fire in MIN(2*T1, T2) seconds. From then on, + * when timer G fires, the response is passed to the transport again for transmission, and + * timer G is reset with a value that doubles, unless that value exceeds T2, in which case + * it is reset with the value of T2. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_G = function () { + // TODO + }; + /** + * If timer H fires while in the "Completed" state, it implies that the ACK was never received. + * In this case, the server transaction MUST transition to the "Terminated" state, and MUST + * indicate to the TU that a transaction failure has occurred. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_H = function () { + this.logger.debug("Timer H expired for INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.logger.warn("ACK to negative final response was never received, terminating transaction."); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * Once timer I fires, the server MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_I = function () { + this.logger.debug("Timer I expired for INVITE server transaction " + this.id + "."); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + }; + /** + * When Timer L fires and the state machine is in the "Accepted" state, the machine MUST + * transition to the "Terminated" state. Once the transaction is in the "Terminated" state, + * it MUST be destroyed immediately. Timer L reflects the amount of time the server + * transaction could receive 2xx responses for retransmission from the + * TU while it is waiting to receive an ACK. + * https://tools.ietf.org/html/rfc6026#section-7.1 + * https://tools.ietf.org/html/rfc6026#section-8.7 + */ + InviteServerTransaction.prototype.timer_L = function () { + this.logger.debug("Timer L expired for INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Accepted) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return InviteServerTransaction; +}(server_transaction_1.ServerTransaction)); +exports.InviteServerTransaction = InviteServerTransaction; + + +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transaction_1 = __webpack_require__(29); +/** + * Server Transaction + * The server transaction is responsible for the delivery of requests to + * the TU and the reliable transmission of responses. It accomplishes + * this through a state machine. Server transactions are created by the + * core when a request is received, and transaction handling is desired + * for that request (this is not always the case). + * https://tools.ietf.org/html/rfc3261#section-17.2 + */ +var ServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(ServerTransaction, _super); + function ServerTransaction(_request, transport, user, state, loggerCategory) { + var _this = _super.call(this, transport, user, _request.viaBranch, state, loggerCategory) || this; + _this._request = _request; + _this.user = user; + return _this; + } + Object.defineProperty(ServerTransaction.prototype, "request", { + /** The incoming request the transaction handling. */ + get: function () { + return this._request; + }, + enumerable: true, + configurable: true + }); + return ServerTransaction; +}(transaction_1.Transaction)); +exports.ServerTransaction = ServerTransaction; + + +/***/ }), +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var timers_1 = __webpack_require__(26); +var client_transaction_1 = __webpack_require__(28); +var transaction_state_1 = __webpack_require__(36); +/** + * Non-INVITE Client Transaction + * + * Non-INVITE transactions do not make use of ACK. + * They are simple request-response interactions. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + */ +var NonInviteClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(NonInviteClientTransaction, _super); + /** + * Constructor + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + * @param request The outgoing Non-INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + function NonInviteClientTransaction(request, transport, user) { + var _this = _super.call(this, request, transport, user, transaction_state_1.TransactionState.Trying, "sip.transaction.nict") || this; + // FIXME: Timer E for unreliable transports not implemented. + // + // The "Trying" state is entered when the TU initiates a new client + // transaction with a request. When entering this state, the client + // transaction SHOULD set timer F to fire in 64*T1 seconds. The request + // MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + _this.F = setTimeout(function () { return _this.timer_F(); }, timers_1.Timers.TIMER_F); + _this.send(request.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send initial outgoing request."); + }); + return _this; + } + /** + * Destructor. + */ + NonInviteClientTransaction.prototype.dispose = function () { + if (this.F) { + clearTimeout(this.F); + this.F = undefined; + } + if (this.K) { + clearTimeout(this.K); + this.K = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(NonInviteClientTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "nict"; + }, + enumerable: true, + configurable: true + }); + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + NonInviteClientTransaction.prototype.receiveResponse = function (response) { + var statusCode = response.statusCode; + if (!statusCode || statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // If a provisional response is received while in the "Trying" state, the + // response MUST be passed to the TU, and then the client transaction + // SHOULD move to the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 100 && statusCode <= 199) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // If a final response (status codes 200-699) is received while in the + // "Trying" state, the response MUST be passed to the TU, and the + // client transaction MUST transition to the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + if (statusCode === 408) { + this.onRequestTimeout(); + return; + } + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // If a provisional response is received while in the "Proceeding" state, + // the response MUST be passed to the TU. (From Figure 6) + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 100 && statusCode <= 199) { + if (this.user.receiveResponse) { + return this.user.receiveResponse(response); + } + } + // If a final response (status codes 200-699) is received while in the + // "Proceeding" state, the response MUST be passed to the TU, and the + // client transaction MUST transition to the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + if (statusCode === 408) { + this.onRequestTimeout(); + return; + } + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + case transaction_state_1.TransactionState.Completed: + // The "Completed" state exists to buffer any additional response + // retransmissions that may be received (which is why the client + // transaction remains there only for unreliable transports). + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + return; + case transaction_state_1.TransactionState.Terminated: + // For good measure just absorb additional response retransmissions. + return; + default: + throw new Error("Invalid state " + this.state); + } + var message = "Non-INVITE client transaction received unexpected " + statusCode + " response while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * The client transaction SHOULD inform the TU that a transport failure has occurred, + * and the client transaction SHOULD transition directly to the "Terminated" state. + * The TU will handle the failover mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error Trasnsport error + */ + NonInviteClientTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + NonInviteClientTransaction.prototype.typeToString = function () { + return "non-INVITE client transaction"; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + NonInviteClientTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Trying: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Trying) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Trying && + this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Trying && + this.state !== transaction_state_1.TransactionState.Proceeding && + this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // Once the client transaction enters the "Completed" state, it MUST set + // Timer K to fire in T4 seconds for unreliable transports, and zero + // seconds for reliable transports The "Completed" state exists to + // buffer any additional response retransmissions that may be received + // (which is why the client transaction remains there only for unreliable transports). + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (newState === transaction_state_1.TransactionState.Completed) { + if (this.F) { + clearTimeout(this.F); + this.F = undefined; + } + this.K = setTimeout(function () { return _this.timer_K(); }, timers_1.Timers.TIMER_K); + } + // Once the transaction is in the terminated state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * If Timer F fires while the client transaction is still in the + * "Trying" state, the client transaction SHOULD inform the TU about the + * timeout, and then it SHOULD enter the "Terminated" state. + * If timer F fires while in the "Proceeding" state, the TU MUST be informed of + * a timeout, and the client transaction MUST transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + NonInviteClientTransaction.prototype.timer_F = function () { + this.logger.debug("Timer F expired for non-INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Trying || this.state === transaction_state_1.TransactionState.Proceeding) { + this.onRequestTimeout(); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer K fires while in this (COMPLETED) state, the client transaction + * MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + NonInviteClientTransaction.prototype.timer_K = function () { + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return NonInviteClientTransaction; +}(client_transaction_1.ClientTransaction)); +exports.NonInviteClientTransaction = NonInviteClientTransaction; + + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var timers_1 = __webpack_require__(26); +var server_transaction_1 = __webpack_require__(38); +var transaction_state_1 = __webpack_require__(36); +/** + * Non-INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ +var NonInviteServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(NonInviteServerTransaction, _super); + /** + * Constructor. + * After construction the transaction will be in the "trying": state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + * @param request Incoming Non-INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + function NonInviteServerTransaction(request, transport, user) { + return _super.call(this, request, transport, user, transaction_state_1.TransactionState.Trying, "sip.transaction.nist") || this; + } + /** + * Destructor. + */ + NonInviteServerTransaction.prototype.dispose = function () { + if (this.J) { + clearTimeout(this.J); + this.J = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(NonInviteServerTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "nist"; + }, + enumerable: true, + configurable: true + }); + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + NonInviteServerTransaction.prototype.receiveRequest = function (request) { + var _this = this; + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // Once in the "Trying" state, any further request retransmissions are discarded. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + break; + case transaction_state_1.TransactionState.Proceeding: + // If a retransmission of the request is received while in the "Proceeding" state, + // the most recently sent provisional response MUST be passed to the transport layer for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (!this.lastResponse) { + throw new Error("Last response undefined."); + } + this.send(this.lastResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + break; + case transaction_state_1.TransactionState.Completed: + // While in the "Completed" state, the server transaction MUST pass the final response to the transport + // layer for retransmission whenever a retransmission of the request is received. Any other final responses + // passed by the TU to the server transaction MUST be discarded while in the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (!this.lastResponse) { + throw new Error("Last response undefined."); + } + this.send(this.lastResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of final response."); + }); + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + }; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of repsonse. 101-199 not allowed per RFC 4320. + * @param response Response to send. + */ + NonInviteServerTransaction.prototype.receiveResponse = function (statusCode, response) { + var _this = this; + if (statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + // An SIP element MUST NOT send any provisional response with a + // Status-Code other than 100 to a non-INVITE request. + // An SIP element MUST NOT respond to a non-INVITE request with a + // Status-Code of 100 over any unreliable transport, such as UDP, + // before the amount of time it takes a client transaction's Timer E to be reset to T2. + // An SIP element MAY respond to a non-INVITE request with a + // Status-Code of 100 over a reliable transport at any time. + // https://tools.ietf.org/html/rfc4320#section-4.1 + if (statusCode > 100 && statusCode <= 199) { + throw new Error("Provisional response other than 100 not allowed."); + } + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // While in the "Trying" state, if the TU passes a provisional response + // to the server transaction, the server transaction MUST enter the "Proceeding" state. + // The response MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + this.lastResponse = response; + if (statusCode >= 100 && statusCode < 200) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send provisional response."); + }); + return; + } + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // Any further provisional responses that are received from the TU while + // in the "Proceeding" state MUST be passed to the transport layer for transmission. + // If the TU passes a final response (status codes 200-699) to the server while in + // the "Proceeding" state, the transaction MUST enter the "Completed" state, and + // the response MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + this.lastResponse = response; + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Any other final responses passed by the TU to the server + // transaction MUST be discarded while in the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + return; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "Non-INVITE server transaction received unexpected " + statusCode + " response from TU while in state " + this.state + "."; + this.logger.error(message); + throw new Error(message); + }; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and SHOULD transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.2.4 + */ + NonInviteServerTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + NonInviteServerTransaction.prototype.typeToString = function () { + return "non-INVITE server transaction"; + }; + NonInviteServerTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Trying: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Trying) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Trying && this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Proceeding && this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // When the server transaction enters the "Completed" state, it MUST set Timer J to fire + // in 64*T1 seconds for unreliable transports, and zero seconds for reliable transports. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (newState === transaction_state_1.TransactionState.Completed) { + this.J = setTimeout(function () { return _this.timer_J(); }, timers_1.Timers.TIMER_J); + } + // The server transaction MUST be destroyed the instant it enters the "Terminated" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + this.setState(newState); + }; + /** + * The server transaction remains in this state until Timer J fires, + * at which point it MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ + NonInviteServerTransaction.prototype.timer_J = function () { + this.logger.debug("Timer J expired for NON-INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return NonInviteServerTransaction; +}(server_transaction_1.ServerTransaction)); +exports.NonInviteServerTransaction = NonInviteServerTransaction; + + +/***/ }), +/* 41 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var ByeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ByeUserAgentClient, _super); + function ByeUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.BYE, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.dispose(); + return _this; + } + return ByeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ByeUserAgentClient = ByeUserAgentClient; + + +/***/ }), +/* 42 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +/* + * User Agent Client (UAC): A user agent client is a logical entity + * that creates a new request, and then uses the client + * transaction state machinery to send it. The role of UAC lasts + * only for the duration of that transaction. In other words, if + * a piece of software initiates a request, it acts as a UAC for + * the duration of that transaction. If it receives a request + * later, it assumes the role of a user agent server for the + * processing of that transaction. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentClient = /** @class */ (function () { + function UserAgentClient(transactionConstructor, core, message, delegate) { + this.transactionConstructor = transactionConstructor; + this.core = core; + this.message = message; + this.delegate = delegate; + this.challenged = false; + this.stale = false; + this.logger = this.loggerFactory.getLogger("sip.user-agent-client"); + this.init(); + } + UserAgentClient.prototype.dispose = function () { + this.transaction.dispose(); + }; + Object.defineProperty(UserAgentClient.prototype, "loggerFactory", { + get: function () { + return this.core.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentClient.prototype, "transaction", { + /** The transaction associated with this request. */ + get: function () { + if (!this._transaction) { + throw new Error("Transaction undefined."); + } + return this._transaction; + }, + enumerable: true, + configurable: true + }); + /** + * Since requests other than INVITE are responded to immediately, sending a + * CANCEL for a non-INVITE request would always create a race condition. + * A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + * https://tools.ietf.org/html/rfc3261#section-9.1 + * @param options Cancel options bucket. + */ + UserAgentClient.prototype.cancel = function (reason, options) { + var _this = this; + if (options === void 0) { options = {}; } + if (!this.transaction) { + throw new Error("Transaction undefined."); + } + if (!this.message.to) { + throw new Error("To undefined."); + } + if (!this.message.from) { + throw new Error("From undefined."); + } + // The following procedures are used to construct a CANCEL request. The + // Request-URI, Call-ID, To, the numeric part of CSeq, and From header + // fields in the CANCEL request MUST be identical to those in the + // request being cancelled, including tags. A CANCEL constructed by a + // client MUST have only a single Via header field value matching the + // top Via value in the request being cancelled. Using the same values + // for these header fields allows the CANCEL to be matched with the + // request it cancels (Section 9.2 indicates how such matching occurs). + // However, the method part of the CSeq header field MUST have a value + // of CANCEL. This allows it to be identified and processed as a + // transaction in its own right (See Section 17). + // https://tools.ietf.org/html/rfc3261#section-9.1 + var message = this.core.makeOutgoingRequestMessage(messages_1.C.CANCEL, this.message.ruri, this.message.from.uri, this.message.to.uri, { + toTag: this.message.toTag, + fromTag: this.message.fromTag, + callId: this.message.callId, + cseq: this.message.cseq + }, options.extraHeaders); + // TODO: Revisit this. + // The CANCEL needs to use the same branch parameter so that + // it matches the INVITE transaction, but this is a hacky way to do this. + // Or at the very least not well documented. If the the branch parameter + // is set on the outgoing request, the transaction will use it. + // Otherwise the transaction will make a new one. + message.branch = this.message.branch; + if (this.message.headers.Route) { + message.headers.Route = this.message.headers.Route; + } + if (reason) { + message.setHeader("Reason", reason); + } + // If no provisional response has been received, the CANCEL request MUST + // NOT be sent; rather, the client MUST wait for the arrival of a + // provisional response before sending the request. If the original + // request has generated a final response, the CANCEL SHOULD NOT be + // sent, as it is an effective no-op, since CANCEL has no effect on + // requests that have already generated a final response. + // https://tools.ietf.org/html/rfc3261#section-9.1 + if (this.transaction.state === transactions_1.TransactionState.Proceeding) { + var uac = new UserAgentClient(transactions_1.NonInviteClientTransaction, this.core, message); + } + else { + this.transaction.once("stateChanged", function () { + if (_this.transaction && _this.transaction.state === transactions_1.TransactionState.Proceeding) { + var uac = new UserAgentClient(transactions_1.NonInviteClientTransaction, _this.core, message); + } + }); + } + return message; + }; + /** + * If a 401 (Unauthorized) or 407 (Proxy Authentication Required) + * response is received, the UAC SHOULD follow the authorization + * procedures of Section 22.2 and Section 22.3 to retry the request with + * credentials. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + * 22 Usage of HTTP Authentication + * https://tools.ietf.org/html/rfc3261#section-22 + * 22.1 Framework + * https://tools.ietf.org/html/rfc3261#section-22.1 + * 22.2 User-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.2 + * 22.3 Proxy-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.3 + * + * FIXME: This "guard for and retry the request with credentials" + * implementation is not complete and at best minimally passable. + * @param response The incoming response to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise the request is retried with credentials and current request processing must stop. + */ + UserAgentClient.prototype.authenticationGuard = function (message) { + var statusCode = message.statusCode; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + // If a 401 (Unauthorized) or 407 (Proxy Authentication Required) + // response is received, the UAC SHOULD follow the authorization + // procedures of Section 22.2 and Section 22.3 to retry the request with + // credentials. + // https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + if (statusCode !== 401 && statusCode !== 407) { + return true; + } + // Get and parse the appropriate WWW-Authenticate or Proxy-Authenticate header. + var challenge; + var authorizationHeaderName; + if (statusCode === 401) { + challenge = message.parseHeader("www-authenticate"); + authorizationHeaderName = "authorization"; + } + else { + challenge = message.parseHeader("proxy-authenticate"); + authorizationHeaderName = "proxy-authorization"; + } + // Verify it seems a valid challenge. + if (!challenge) { + this.logger.warn(statusCode + " with wrong or missing challenge, cannot authenticate"); + return true; + } + // Avoid infinite authentications. + if (this.challenged && (this.stale || challenge.stale !== true)) { + this.logger.warn(statusCode + " apparently in authentication loop, cannot authenticate"); + return true; + } + // Get credentials. + if (!this.credentials) { + this.credentials = this.core.configuration.authenticationFactory(); + if (!this.credentials) { + this.logger.warn("Unable to obtain credentials, cannot authenticate"); + return true; + } + } + // Verify that the challenge is really valid. + if (!this.credentials.authenticate(this.message, challenge)) { + return true; + } + this.challenged = true; + if (challenge.stale) { + this.stale = true; + } + var cseq = this.message.cseq += 1; + this.message.setHeader("cseq", cseq + " " + this.message.method); + this.message.setHeader(authorizationHeaderName, this.credentials.toString()); + // Calling init (again) will swap out our existing client transaction with a new one. + // FIXME: HACK: An assumption is being made here that there is nothing that needs to + // be cleaned up beyond the client transaction which is being replaced. For example, + // it is assumed that no early dialogs have been created. + this.init(); + return false; + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + UserAgentClient.prototype.receiveResponse = function (message) { + if (!this.authenticationGuard(message)) { + return; + } + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + break; + case /^1[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ message: message }); + } + break; + case /^2[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ message: message }); + } + break; + case /^3[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + break; + case /^[4-6][0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + break; + default: + throw new Error("Invalid status code " + statusCode); + } + }; + UserAgentClient.prototype.init = function () { + var _this = this; + // We are the transaction user. + var user = { + loggerFactory: this.loggerFactory, + onRequestTimeout: function () { return _this.onRequestTimeout(); }, + onStateChange: function (newState) { + if (newState === transactions_1.TransactionState.Terminated) { + // Remove the terminated transaction from the core. + _this.core.userAgentClients.delete(userAgentClientId); + // FIXME: HACK: Our transaction may have been swapped out with a new one + // post authentication (see above), so make sure to only to dispose of + // ourselves if this terminating transaction is our current transaction. + if (transaction === _this._transaction) { + _this.dispose(); + } + } + }, + onTransportError: function (error) { return _this.onTransportError(error); }, + receiveResponse: function (message) { return _this.receiveResponse(message); } + }; + // Create a new transaction with us as the user. + var transaction = new this.transactionConstructor(this.message, this.core.transport, user); + this._transaction = transaction; + // Add the new transaction to the core. + var userAgentClientId = transaction.id + transaction.request.method; + this.core.userAgentClients.set(userAgentClientId, this); + }; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + UserAgentClient.prototype.onRequestTimeout = function () { + this.logger.warn("User agent client request timed out. Generating internal 408 Request Timeout."); + var message = new messages_1.IncomingResponseMessage(); + message.statusCode = 408; + message.reasonPhrase = "Request Timeout"; + this.receiveResponse(message); + return; + }; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + UserAgentClient.prototype.onTransportError = function (error) { + this.logger.error(error.message); + this.logger.error("User agent client request transport error. Generating internal 503 Service Unavailable."); + var message = new messages_1.IncomingResponseMessage(); + message.statusCode = 503; + message.reasonPhrase = "Service Unavailable"; + this.receiveResponse(message); + }; + return UserAgentClient; +}()); +exports.UserAgentClient = UserAgentClient; + + +/***/ }), +/* 43 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var ByeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ByeUserAgentServer, _super); + function ByeUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return ByeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ByeUserAgentServer = ByeUserAgentServer; + + +/***/ }), +/* 44 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var exceptions_1 = __webpack_require__(31); +var messages_1 = __webpack_require__(5); +var utils_1 = __webpack_require__(16); +var transactions_1 = __webpack_require__(27); +/** + * User Agent Server (UAS): A user agent server is a logical entity + * that generates a response to a SIP request. The response + * accepts, rejects, or redirects the request. This role lasts + * only for the duration of that transaction. In other words, if + * a piece of software responds to a request, it acts as a UAS for + * the duration of that transaction. If it generates a request + * later, it assumes the role of a user agent client for the + * processing of that transaction. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentServer = /** @class */ (function () { + function UserAgentServer(transactionConstructor, core, message, delegate) { + this.transactionConstructor = transactionConstructor; + this.core = core; + this.message = message; + this.delegate = delegate; + this.logger = this.loggerFactory.getLogger("sip.user-agent-server"); + this.toTag = message.toTag ? message.toTag : utils_1.newTag(); + this.init(); + } + UserAgentServer.prototype.dispose = function () { + this.transaction.dispose(); + }; + Object.defineProperty(UserAgentServer.prototype, "loggerFactory", { + get: function () { + return this.core.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "transaction", { + /** The transaction associated with this request. */ + get: function () { + if (!this._transaction) { + throw new Error("Transaction undefined."); + } + return this._transaction; + }, + enumerable: true, + configurable: true + }); + UserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (!this.acceptable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not acceptable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 200 || statusCode > 299) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + if (!this.progressable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not progressable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 101 || statusCode > 199) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.redirect = function (contacts, options) { + if (options === void 0) { options = { statusCode: 302 }; } + if (!this.redirectable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not redirectable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 300 || statusCode > 399) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var contactHeaders = new Array(); + contacts.forEach(function (contact) { return contactHeaders.push("Contact: " + contact.toString()); }); + options.extraHeaders = (options.extraHeaders || []).concat(contactHeaders); + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.reject = function (options) { + if (options === void 0) { options = { statusCode: 480 }; } + if (!this.rejectable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not rejectable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 400 || statusCode > 699) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.trying = function (options) { + if (!this.tryingable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not tryingable in state " + this.transaction.state + "."); + } + var response = this.reply({ statusCode: 100 }); + return response; + }; + /** + * If the UAS did not find a matching transaction for the CANCEL + * according to the procedure above, it SHOULD respond to the CANCEL + * with a 481 (Call Leg/Transaction Does Not Exist). If the transaction + * for the original request still exists, the behavior of the UAS on + * receiving a CANCEL request depends on whether it has already sent a + * final response for the original request. If it has, the CANCEL + * request has no effect on the processing of the original request, no + * effect on any session state, and no effect on the responses generated + * for the original request. If the UAS has not issued a final response + * for the original request, its behavior depends on the method of the + * original request. If the original request was an INVITE, the UAS + * SHOULD immediately respond to the INVITE with a 487 (Request + * Terminated). A CANCEL request has no impact on the processing of + * transactions with any other method defined in this specification. + * https://tools.ietf.org/html/rfc3261#section-9.2 + * @param request Incoming CANCEL request. + */ + UserAgentServer.prototype.receiveCancel = function (message) { + // Note: Currently CANCEL is being handled as a special case. + // No UAS is created to handle the CANCEL and the response to + // it CANCEL is being handled statelessly by the user agent core. + // As such, there is currently no way to externally impact the + // response to the a CANCEL request. + if (this.delegate && this.delegate.onCancel) { + this.delegate.onCancel(message); + } + }; + Object.defineProperty(UserAgentServer.prototype, "acceptable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Proceeding || + this.transaction.state === transactions_1.TransactionState.Accepted); + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "progressable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return false; // https://tools.ietf.org/html/rfc4320#section-4.1 + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "redirectable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "rejectable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "tryingable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Trying; + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + /** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * + * Once all procedures associated with the creation of a response have + * been completed, the UAS hands the response back to the server + * transaction from which it received the request. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + * @param statusCode Status code to reply with. + * @param options Reply options bucket. + */ + UserAgentServer.prototype.reply = function (options) { + if (!options.toTag && options.statusCode !== 100) { + options.toTag = this.toTag; + } + options.userAgent = options.userAgent || this.core.configuration.userAgentHeaderFieldValue; + options.supported = options.supported || this.core.configuration.supportedOptionTagsResponse; + var response = messages_1.constructOutgoingResponse(this.message, options); + this.transaction.receiveResponse(options.statusCode, response.message); + return response; + }; + UserAgentServer.prototype.init = function () { + var _this = this; + // We are the transaction user. + var user = { + loggerFactory: this.loggerFactory, + onStateChange: function (newState) { + if (newState === transactions_1.TransactionState.Terminated) { + // Remove the terminated transaction from the core. + _this.core.userAgentServers.delete(userAgentServerId); + _this.dispose(); + } + }, + onTransportError: function (error) { + _this.logger.error(error.message); + if (_this.delegate && _this.delegate.onTransportError) { + _this.delegate.onTransportError(error); + } + else { + _this.logger.error("User agent server response transport error."); + } + } + }; + // Create a new transaction with us as the user. + var transaction = new this.transactionConstructor(this.message, this.core.transport, user); + this._transaction = transaction; + // Add the new transaction to the core. + var userAgentServerId = transaction.id; + this.core.userAgentServers.set(transaction.id, this); + }; + return UserAgentServer; +}()); +exports.UserAgentServer = UserAgentServer; + + +/***/ }), +/* 45 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var InfoUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(InfoUserAgentClient, _super); + function InfoUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.INFO, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return InfoUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.InfoUserAgentClient = InfoUserAgentClient; + + +/***/ }), +/* 46 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var InfoUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(InfoUserAgentServer, _super); + function InfoUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return InfoUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.InfoUserAgentServer = InfoUserAgentServer; + + +/***/ }), +/* 47 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var NotifyUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(NotifyUserAgentClient, _super); + function NotifyUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.NOTIFY, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return NotifyUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.NotifyUserAgentClient = NotifyUserAgentClient; + + +/***/ }), +/* 48 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var NotifyUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(NotifyUserAgentServer, _super); + /** + * NOTIFY UAS constructor. + * @param dialogOrCore Dialog for in dialog NOTIFY, UserAgentCore for out of dialog NOTIFY (deprecated). + * @param message Incoming NOTIFY request message. + */ + function NotifyUserAgentServer(dialogOrCore, message, delegate) { + var _this = this; + var userAgentCore = instanceOfDialog(dialogOrCore) ? + dialogOrCore.userAgentCore : + dialogOrCore; + _this = _super.call(this, transactions_1.NonInviteServerTransaction, userAgentCore, message, delegate) || this; + return _this; + } + return NotifyUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.NotifyUserAgentServer = NotifyUserAgentServer; +function instanceOfDialog(object) { + return object.userAgentCore !== undefined; +} + + +/***/ }), +/* 49 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var PrackUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(PrackUserAgentClient, _super); + function PrackUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.PRACK, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.signalingStateTransition(message); + return _this; + } + return PrackUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.PrackUserAgentClient = PrackUserAgentClient; + + +/***/ }), +/* 50 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var PrackUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(PrackUserAgentServer, _super); + function PrackUserAgentServer(dialog, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + // Update dialog signaling state with offer/answer in body + dialog.signalingStateTransition(message); + _this.dialog = dialog; + return _this; + } + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + PrackUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (options.body) { + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(options.body); + } + return _super.prototype.accept.call(this, options); + }; + return PrackUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.PrackUserAgentServer = PrackUserAgentServer; + + +/***/ }), +/* 51 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.1 UAC Behavior + * https://tools.ietf.org/html/rfc3261#section-14.1 + */ +var ReInviteUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReInviteUserAgentClient, _super); + function ReInviteUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.INVITE, options); + _this = _super.call(this, transactions_1.InviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + _this.delegate = delegate; + dialog.signalingStateTransition(message); + // FIXME: TODO: next line obviously needs to be improved... + dialog.reinviteUserAgentClient = _this; // let the dialog know re-invite request sent + _this.dialog = dialog; + return _this; + } + ReInviteUserAgentClient.prototype.receiveResponse = function (message) { + var _this = this; + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + break; + case /^1[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ + message: message, + session: this.dialog, + prack: function (options) { + throw new Error("Unimplemented."); + } + }); + } + break; + case /^2[0-9]{2}$/.test(statusCode): + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(message); + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ + message: message, + session: this.dialog, + ack: function (options) { + var outgoingAckRequest = _this.dialog.ack(options); + return outgoingAckRequest; + } + }); + } + break; + case /^3[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + break; + case /^[4-6][0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + else { + // If a UA receives a non-2xx final response to a re-INVITE, the session + // parameters MUST remain unchanged, as if no re-INVITE had been issued. + // Note that, as stated in Section 12.2.1.2, if the non-2xx final + // response is a 481 (Call/Transaction Does Not Exist), or a 408 + // (Request Timeout), or no response at all is received for the re- + // INVITE (that is, a timeout is returned by the INVITE client + // transaction), the UAC will terminate the dialog. + // + // If a UAC receives a 491 response to a re-INVITE, it SHOULD start a + // timer with a value T chosen as follows: + // + // 1. If the UAC is the owner of the Call-ID of the dialog ID + // (meaning it generated the value), T has a randomly chosen value + // between 2.1 and 4 seconds in units of 10 ms. + // + // 2. If the UAC is not the owner of the Call-ID of the dialog ID, T + // has a randomly chosen value of between 0 and 2 seconds in units + // of 10 ms. + // + // When the timer fires, the UAC SHOULD attempt the re-INVITE once more, + // if it still desires for that session modification to take place. For + // example, if the call was already hung up with a BYE, the re-INVITE + // would not take place. + // https://tools.ietf.org/html/rfc3261#section-14.1 + // FIXME: TODO: The above. + } + break; + default: + throw new Error("Invalid status code " + statusCode); + } + }; + return ReInviteUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReInviteUserAgentClient = ReInviteUserAgentClient; + + +/***/ }), +/* 52 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.2 UAS Behavior + * https://tools.ietf.org/html/rfc3261#section-14.2 + */ +var ReInviteUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReInviteUserAgentServer, _super); + function ReInviteUserAgentServer(dialog, message, delegate) { + var _this = _super.call(this, transactions_1.InviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.reinviteUserAgentServer = _this; + _this.dialog = dialog; + return _this; + } + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + ReInviteUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + // FIXME: The next two lines SHOULD go away, but I suppose it's technically harmless... + // These are here because some versions of SIP.js prior to 0.13.8 set the route set + // of all in dialog ACKs based on the Record-Route headers in the associated 2xx + // response. While this worked for dialog forming 2xx responses, it was technically + // broken for re-INVITE ACKS as it only worked if the UAS populated the Record-Route + // headers in the re-INVITE 2xx response (which is not required and a waste of bandwidth + // as the should be ignored if present in re-INVITE ACKS) and the UAS populated + // the Record-Route headers with the correct values (would be weird not too, but...). + // Anyway, for now the technically useless Record-Route headers are being added + // to maintain "backwards compatibility" with the older broken versions of SIP.js. + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(this.dialog.routeSet.map(function (route) { return "Record-Route: " + route; })); + // Send and return the response + var response = _super.prototype.accept.call(this, options); + var session = this.dialog; + var result = tslib_1.__assign({}, response, { session: session }); + if (options.body) { + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(options.body); + } + // Update dialog + this.dialog.reConfirm(); + return result; + }; + /** + * Update the dialog signaling state on a 1xx response. + * @param options Progress options bucket. + */ + ReInviteUserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + // Send and return the response + var response = _super.prototype.progress.call(this, options); + var session = this.dialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.dialog.signalingStateTransition(options.body); + } + return result; + }; + return ReInviteUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReInviteUserAgentServer = ReInviteUserAgentServer; + + +/***/ }), +/* 53 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var ReferUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReferUserAgentClient, _super); + function ReferUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.REFER, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return ReferUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReferUserAgentClient = ReferUserAgentClient; + + +/***/ }), +/* 54 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var ReferUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReferUserAgentServer, _super); + /** + * REFER UAS constructor. + * @param dialogOrCore Dialog for in dialog REFER, UserAgentCore for out of dialog REFER. + * @param message Incoming REFER request message. + */ + function ReferUserAgentServer(dialogOrCore, message, delegate) { + var _this = this; + var userAgentCore = instanceOfSessionDialog(dialogOrCore) ? + dialogOrCore.userAgentCore : + dialogOrCore; + _this = _super.call(this, transactions_1.NonInviteServerTransaction, userAgentCore, message, delegate) || this; + return _this; + } + return ReferUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReferUserAgentServer = ReferUserAgentServer; +function instanceOfSessionDialog(object) { + return object.userAgentCore !== undefined; +} + + +/***/ }), +/* 55 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var subscription_1 = __webpack_require__(56); +var timers_1 = __webpack_require__(26); +var allowed_methods_1 = __webpack_require__(58); +var notify_user_agent_server_1 = __webpack_require__(48); +var re_subscribe_user_agent_client_1 = __webpack_require__(59); +var dialog_1 = __webpack_require__(4); +/** + * SIP-Specific Event Notification + * + * Abstract + * + * This document describes an extension to the Session Initiation + * Protocol (SIP) defined by RFC 3261. The purpose of this extension is + * to provide an extensible framework by which SIP nodes can request + * notification from remote nodes indicating that certain events have + * occurred. + * + * Note that the event notification mechanisms defined herein are NOT + * intended to be a general-purpose infrastructure for all classes of + * event subscription and notification. + * + * This document represents a backwards-compatible improvement on the + * original mechanism described by RFC 3265, taking into account several + * years of implementation experience. Accordingly, this document + * obsoletes RFC 3265. This document also updates RFC 4660 slightly to + * accommodate some small changes to the mechanism that were discussed + * in that document. + * + * https://tools.ietf.org/html/rfc6665 + */ +var SubscriptionDialog = /** @class */ (function (_super) { + tslib_1.__extends(SubscriptionDialog, _super); + function SubscriptionDialog(subscriptionEvent, subscriptionExpires, subscriptionState, core, state, delegate) { + var _this = _super.call(this, core, state) || this; + _this.delegate = delegate; + _this._autoRefresh = false; + _this._subscriptionEvent = subscriptionEvent; + _this._subscriptionExpires = subscriptionExpires; + _this._subscriptionExpiresInitial = subscriptionExpires; + _this._subscriptionExpiresLastSet = Math.floor(Date.now() / 1000); + _this._subscriptionRefresh = undefined; + _this._subscriptionRefreshLastSet = undefined; + _this._subscriptionState = subscriptionState; + _this.logger = core.loggerFactory.getLogger("sip.subscribe-dialog"); + _this.logger.log("SUBSCRIBE dialog " + _this.id + " constructed"); + return _this; + } + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + SubscriptionDialog.initialDialogStateForSubscription = function (outgoingSubscribeRequestMessage, incomingNotifyRequestMessage) { + // If the request was sent over TLS, and the Request-URI contained a + // SIPS URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the response, taken in reverse order and preserving + // all URI parameters. If no Record-Route header field is present in + // the response, the route set MUST be set to the empty set. This route + // set, even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the response. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var routeSet = incomingNotifyRequestMessage.getHeaders("record-route"); + var contact = incomingNotifyRequestMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The local sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The remote sequence + // number MUST be empty (it is established when the remote UA sends a + // request within the dialog). The call identifier component of the + // dialog ID MUST be set to the value of the Call-ID in the request. + // The local tag component of the dialog ID MUST be set to the tag in + // the From field in the request, and the remote tag component of the + // dialog ID MUST be set to the tag in the To field of the response. A + // UAC MUST be prepared to receive a response without a tag in the To + // field, in which case the tag is considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate To tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var localSequenceNumber = outgoingSubscribeRequestMessage.cseq; + var remoteSequenceNumber = undefined; + var callId = outgoingSubscribeRequestMessage.callId; + var localTag = outgoingSubscribeRequestMessage.fromTag; + var remoteTag = incomingNotifyRequestMessage.fromTag; + if (!callId) { // TODO: Review to make sure this will never happen + throw new Error("Call id undefined."); + } + if (!localTag) { // TODO: Review to make sure this will never happen + throw new Error("From tag undefined."); + } + if (!remoteTag) { // TODO: Review to make sure this will never happen + throw new Error("To tag undefined."); // FIXME: No backwards compatibility with RFC 2543 + } + // The remote URI MUST be set to the URI in the To field, and the local + // URI MUST be set to the URI in the From field. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + if (!outgoingSubscribeRequestMessage.from) { // TODO: Review to make sure this will never happen + throw new Error("From undefined."); + } + if (!outgoingSubscribeRequestMessage.to) { // TODO: Review to make sure this will never happen + throw new Error("To undefined."); + } + var localURI = outgoingSubscribeRequestMessage.from.uri; + var remoteURI = outgoingSubscribeRequestMessage.to.uri; + // A dialog can also be in the "early" state, which occurs when it is + // created with a provisional response, and then transition to the + // "confirmed" state when a 2xx final response arrives. + // https://tools.ietf.org/html/rfc3261#section-12 + var early = false; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + SubscriptionDialog.prototype.dispose = function () { + _super.prototype.dispose.call(this); + if (this.N) { + clearTimeout(this.N); + this.N = undefined; + } + this.refreshTimerClear(); + this.logger.log("SUBSCRIBE dialog " + this.id + " destroyed"); + }; + Object.defineProperty(SubscriptionDialog.prototype, "autoRefresh", { + get: function () { + return this._autoRefresh; + }, + set: function (autoRefresh) { + this._autoRefresh = true; + this.refreshTimerSet(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionEvent", { + get: function () { + return this._subscriptionEvent; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionExpires", { + /** Number of seconds until subscription expires. */ + get: function () { + var secondsSinceLastSet = Math.floor(Date.now() / 1000) - this._subscriptionExpiresLastSet; + var secondsUntilExpires = this._subscriptionExpires - secondsSinceLastSet; + return Math.max(secondsUntilExpires, 0); + }, + set: function (expires) { + if (expires < 0) { + throw new Error("Expires must be greater than or equal to zero."); + } + this._subscriptionExpires = expires; + this._subscriptionExpiresLastSet = Math.floor(Date.now() / 1000); + if (this.autoRefresh) { + var refresh = this.subscriptionRefresh; + if (refresh === undefined || refresh >= expires) { + this.refreshTimerSet(); + } + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionExpiresInitial", { + get: function () { + return this._subscriptionExpiresInitial; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionRefresh", { + /** Number of seconds until subscription auto refresh. */ + get: function () { + if (this._subscriptionRefresh === undefined || this._subscriptionRefreshLastSet === undefined) { + return undefined; + } + var secondsSinceLastSet = Math.floor(Date.now() / 1000) - this._subscriptionRefreshLastSet; + var secondsUntilExpires = this._subscriptionRefresh - secondsSinceLastSet; + return Math.max(secondsUntilExpires, 0); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionState", { + get: function () { + return this._subscriptionState; + }, + enumerable: true, + configurable: true + }); + /** + * Receive in dialog request message from transport. + * @param message The incoming request message. + */ + SubscriptionDialog.prototype.receiveRequest = function (message) { + this.logger.log("SUBSCRIBE dialog " + this.id + " received " + message.method + " request"); + // Request within a dialog out of sequence guard. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.sequenceGuard(message)) { + this.logger.log("SUBSCRIBE dialog " + this.id + " rejected out of order " + message.method + " request."); + return; + } + // Request within a dialog common processing. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + _super.prototype.receiveRequest.call(this, message); + // Switch on method and then delegate. + switch (message.method) { + case messages_1.C.NOTIFY: + this.onNotify(message); + break; + default: + this.logger.log("SUBSCRIBE dialog " + this.id + " received unimplemented " + message.method + " request"); + this.core.replyStateless(message, { statusCode: 501 }); + break; + } + }; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + SubscriptionDialog.prototype.refresh = function () { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var options = {}; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push("Event: " + this.subscriptionEvent); + options.extraHeaders.push("Expires: " + this.subscriptionExpiresInitial); + options.extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + return this.subscribe(undefined, options); + }; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + * @param delegate Delegate to handle responses. + * @param options Options bucket. + */ + SubscriptionDialog.prototype.subscribe = function (delegate, options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.subscriptionState !== subscription_1.SubscriptionState.Pending && this.subscriptionState !== subscription_1.SubscriptionState.Active) { + // FIXME: This needs to be a proper exception + throw new Error("Invalid state " + this.subscriptionState + ". May only re-subscribe while in state \"pending\" or \"active\"."); + } + this.logger.log("SUBSCRIBE dialog " + this.id + " sending SUBSCRIBE request"); + var uac = new re_subscribe_user_agent_client_1.ReSubscribeUserAgentClient(this, delegate, options); + // When refreshing a subscription, a subscriber starts Timer N, set to + // 64*T1, when it sends the SUBSCRIBE request. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + this.N = setTimeout(function () { return _this.timer_N(); }, timers_1.Timers.TIMER_N); + return uac; + }; + /** + * 4.4.1. Dialog Creation and Termination + * A subscription is destroyed after a notifier sends a NOTIFY request + * with a "Subscription-State" of "terminated", or in certain error + * situations described elsewhere in this document. + * https://tools.ietf.org/html/rfc6665#section-4.4.1 + */ + SubscriptionDialog.prototype.terminate = function () { + this.stateTransition(subscription_1.SubscriptionState.Terminated); + this.onTerminated(); + }; + /** + * 4.1.2.3. Unsubscribing + * https://tools.ietf.org/html/rfc6665#section-4.1.2.3 + */ + SubscriptionDialog.prototype.unsubscribe = function () { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var options = {}; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push("Event: " + this.subscriptionEvent); + options.extraHeaders.push("Expires: 0"); + options.extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + return this.subscribe(undefined, options); + }; + /** + * Handle in dialog NOTIFY requests. + * This does not include the first NOTIFY which created the dialog. + * @param message The incoming NOTIFY request message. + */ + SubscriptionDialog.prototype.onNotify = function (message) { + // If, for some reason, the event package designated in the "Event" + // header field of the NOTIFY request is not supported, the subscriber + // will respond with a 489 (Bad Event) response. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var event = message.parseHeader("Event").event; + if (!event || event !== this.subscriptionEvent) { + this.core.replyStateless(message, { statusCode: 489 }); + return; + } + // In the state diagram, "Re-subscription times out" means that an + // attempt to refresh or update the subscription using a new SUBSCRIBE + // request does not result in a NOTIFY request before the corresponding + // Timer N expires. + // https://tools.ietf.org/html/rfc6665#section-4.1.2 + if (this.N) { + clearTimeout(this.N); + this.N = undefined; + } + // NOTIFY requests MUST contain "Subscription-State" header fields that + // indicate the status of the subscription. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = message.parseHeader("Subscription-State"); + if (!subscriptionState || !subscriptionState.state) { + this.core.replyStateless(message, { statusCode: 489 }); + return; + } + var state = subscriptionState.state; + var expires = subscriptionState.expires ? Math.max(subscriptionState.expires, 0) : undefined; + // Update our state and expiration. + switch (state) { + case "pending": + this.stateTransition(subscription_1.SubscriptionState.Pending, expires); + break; + case "active": + this.stateTransition(subscription_1.SubscriptionState.Active, expires); + break; + case "terminated": + this.stateTransition(subscription_1.SubscriptionState.Terminated, expires); + break; + default: + this.logger.warn("Unrecognized subscription state."); + break; + } + // Delegate remainder of NOTIFY handling. + var uas = new notify_user_agent_server_1.NotifyUserAgentServer(this, message); + if (this.delegate && this.delegate.onNotify) { + this.delegate.onNotify(uas); + } + else { + uas.accept(); + } + }; + SubscriptionDialog.prototype.onRefresh = function (request) { + if (this.delegate && this.delegate.onRefresh) { + this.delegate.onRefresh(request); + } + }; + SubscriptionDialog.prototype.onTerminated = function () { + if (this.delegate && this.delegate.onTerminated) { + this.delegate.onTerminated(); + } + }; + SubscriptionDialog.prototype.refreshTimerClear = function () { + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); + this.refreshTimer = undefined; + } + }; + SubscriptionDialog.prototype.refreshTimerSet = function () { + var _this = this; + this.refreshTimerClear(); + if (this.autoRefresh && this.subscriptionExpires > 0) { + var refresh = this.subscriptionExpires * 900; + this._subscriptionRefresh = Math.floor(refresh / 1000); + this._subscriptionRefreshLastSet = Math.floor(Date.now() / 1000); + this.refreshTimer = setTimeout(function () { + _this.refreshTimer = undefined; + _this._subscriptionRefresh = undefined; + _this._subscriptionRefreshLastSet = undefined; + _this.onRefresh(_this.refresh()); + }, refresh); + } + }; + SubscriptionDialog.prototype.stateTransition = function (newState, newExpires) { + var _this = this; + // Assert valid state transitions. + var invalidStateTransition = function () { + _this.logger.warn("Invalid subscription state transition from " + _this.subscriptionState + " to " + newState); + }; + switch (newState) { + case subscription_1.SubscriptionState.Initial: + invalidStateTransition(); + return; + case subscription_1.SubscriptionState.NotifyWait: + invalidStateTransition(); + return; + case subscription_1.SubscriptionState.Pending: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending) { + invalidStateTransition(); + return; + } + break; + case subscription_1.SubscriptionState.Active: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending && + this.subscriptionState !== subscription_1.SubscriptionState.Active) { + invalidStateTransition(); + return; + } + break; + case subscription_1.SubscriptionState.Terminated: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending && + this.subscriptionState !== subscription_1.SubscriptionState.Active) { + invalidStateTransition(); + return; + } + break; + default: + invalidStateTransition(); + return; + } + // If the "Subscription-State" value is "pending", the subscription has + // been received by the notifier, but there is insufficient policy + // information to grant or deny the subscription yet. If the header + // field also contains an "expires" parameter, the subscriber SHOULD + // take it as the authoritative subscription duration and adjust + // accordingly. No further action is necessary on the part of the + // subscriber. The "retry-after" and "reason" parameters have no + // semantics for "pending". + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + if (newState === subscription_1.SubscriptionState.Pending) { + if (newExpires) { + this.subscriptionExpires = newExpires; + } + } + // If the "Subscription-State" header field value is "active", it means + // that the subscription has been accepted and (in general) has been + // authorized. If the header field also contains an "expires" + // parameter, the subscriber SHOULD take it as the authoritative + // subscription duration and adjust accordingly. The "retry-after" and + // "reason" parameters have no semantics for "active". + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + if (newState === subscription_1.SubscriptionState.Active) { + if (newExpires) { + this.subscriptionExpires = newExpires; + } + } + // If the "Subscription-State" value is "terminated", the subscriber + // MUST consider the subscription terminated. The "expires" parameter + // has no semantics for "terminated" -- notifiers SHOULD NOT include an + // "expires" parameter on a "Subscription-State" header field with a + // value of "terminated", and subscribers MUST ignore any such + // parameter, if present. + if (newState === subscription_1.SubscriptionState.Terminated) { + this.dispose(); + } + this._subscriptionState = newState; + }; + /** + * When refreshing a subscription, a subscriber starts Timer N, set to + * 64*T1, when it sends the SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription terminated. If the subscriber receives a success + * response to the SUBSCRIBE request that indicates that no NOTIFY + * request will be generated -- such as the 204 response defined for use + * with the optional extension described in [RFC5839] -- then it MUST + * cancel Timer N. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + SubscriptionDialog.prototype.timer_N = function () { + if (this.subscriptionState !== subscription_1.SubscriptionState.Terminated) { + this.stateTransition(subscription_1.SubscriptionState.Terminated); + this.onTerminated(); + } + }; + return SubscriptionDialog; +}(dialog_1.Dialog)); +exports.SubscriptionDialog = SubscriptionDialog; + + +/***/ }), +/* 56 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(57), exports); + + +/***/ }), +/* 57 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Subscription state. + * https://tools.ietf.org/html/rfc6665#section-4.1.2 + */ +var SubscriptionState; +(function (SubscriptionState) { + SubscriptionState["Initial"] = "Initial"; + SubscriptionState["NotifyWait"] = "NotifyWait"; + SubscriptionState["Pending"] = "Pending"; + SubscriptionState["Active"] = "Active"; + SubscriptionState["Terminated"] = "Terminated"; +})(SubscriptionState = exports.SubscriptionState || (exports.SubscriptionState = {})); + + +/***/ }), +/* 58 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = __webpack_require__(5); +/** + * FIXME: TODO: Should be configurable/variable. + */ +exports.AllowedMethods = [ + messages_1.C.ACK, + messages_1.C.BYE, + messages_1.C.CANCEL, + messages_1.C.INFO, + messages_1.C.INVITE, + messages_1.C.MESSAGE, + messages_1.C.NOTIFY, + messages_1.C.OPTIONS, + messages_1.C.PRACK, + messages_1.C.REFER, + messages_1.C.SUBSCRIBE +]; + + +/***/ }), +/* 59 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var ReSubscribeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReSubscribeUserAgentClient, _super); + function ReSubscribeUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.SUBSCRIBE, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + _this.dialog = dialog; + return _this; + } + ReSubscribeUserAgentClient.prototype.waitNotifyStop = function () { + // TODO: Placeholder. Not utilized currently. + return; + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + ReSubscribeUserAgentClient.prototype.receiveResponse = function (message) { + if (message.statusCode && message.statusCode >= 200 && message.statusCode < 300) { + // The "Expires" header field in a 200-class response to SUBSCRIBE + // request indicates the actual duration for which the subscription will + // remain active (unless refreshed). The received value might be + // smaller than the value indicated in the SUBSCRIBE request but cannot + // be larger; see Section 4.2.1 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + var expires = message.getHeader("Expires"); + if (!expires) { + this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE"); + } + else { + var subscriptionExpiresReceived = Number(expires); + if (this.dialog.subscriptionExpires > subscriptionExpiresReceived) { + this.dialog.subscriptionExpires = subscriptionExpiresReceived; + } + } + } + if (message.statusCode && message.statusCode >= 400 && message.statusCode < 700) { + // If a SUBSCRIBE request to refresh a subscription receives a 404, 405, + // 410, 416, 480-485, 489, 501, or 604 response, the subscriber MUST + // consider the subscription terminated. (See [RFC5057] for further + // details and notes about the effect of error codes on dialogs and + // usages within dialog, such as subscriptions). If the subscriber + // wishes to re-subscribe to the state, he does so by composing an + // unrelated initial SUBSCRIBE request with a freshly generated Call-ID + // and a new, unique "From" tag (see Section 4.1.2.1). + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + var errorCodes = [404, 405, 410, 416, 480, 481, 482, 483, 484, 485, 489, 501, 604]; + if (errorCodes.includes(message.statusCode)) { + this.dialog.terminate(); + } + // If a SUBSCRIBE request to refresh a subscription fails with any error + // code other than those listed above, the original subscription is + // still considered valid for the duration of the most recently known + // "Expires" value as negotiated by the most recent successful SUBSCRIBE + // transaction, or as communicated by a NOTIFY request in its + // "Subscription-State" header field "expires" parameter. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + } + _super.prototype.receiveResponse.call(this, message); + }; + return ReSubscribeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReSubscribeUserAgentClient = ReSubscribeUserAgentClient; + + +/***/ }), +/* 60 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(61), exports); +tslib_1.__exportStar(__webpack_require__(62), exports); +tslib_1.__exportStar(__webpack_require__(63), exports); + + +/***/ }), +/* 61 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Log levels. + * @public + */ +var Levels; +(function (Levels) { + Levels[Levels["error"] = 0] = "error"; + Levels[Levels["warn"] = 1] = "warn"; + Levels[Levels["log"] = 2] = "log"; + Levels[Levels["debug"] = 3] = "debug"; +})(Levels = exports.Levels || (exports.Levels = {})); + + +/***/ }), +/* 62 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var levels_1 = __webpack_require__(61); +var logger_1 = __webpack_require__(63); +/** + * Logger. + * @public + */ +var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + this.builtinEnabled = true; + this._level = levels_1.Levels.log; + this.loggers = {}; + this.logger = this.getLogger("sip:loggerfactory"); + } + Object.defineProperty(LoggerFactory.prototype, "level", { + get: function () { return this._level; }, + set: function (newLevel) { + if (newLevel >= 0 && newLevel <= 3) { + this._level = newLevel; + } + else if (newLevel > 3) { + this._level = 3; + } + else if (levels_1.Levels.hasOwnProperty(newLevel)) { + this._level = newLevel; + } + else { + this.logger.error("invalid 'level' parameter value: " + JSON.stringify(newLevel)); + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(LoggerFactory.prototype, "connector", { + get: function () { + return this._connector; + }, + set: function (value) { + if (!value) { + this._connector = undefined; + } + else if (typeof value === "function") { + this._connector = value; + } + else { + this.logger.error("invalid 'connector' parameter value: " + JSON.stringify(value)); + } + }, + enumerable: true, + configurable: true + }); + LoggerFactory.prototype.getLogger = function (category, label) { + if (label && this.level === 3) { + return new logger_1.Logger(this, category, label); + } + else if (this.loggers[category]) { + return this.loggers[category]; + } + else { + var logger = new logger_1.Logger(this, category); + this.loggers[category] = logger; + return logger; + } + }; + LoggerFactory.prototype.genericLog = function (levelToLog, category, label, content) { + if (this.level >= levelToLog) { + if (this.builtinEnabled) { + this.print(levelToLog, category, label, content); + } + } + if (this.connector) { + this.connector(levels_1.Levels[levelToLog], category, label, content); + } + }; + LoggerFactory.prototype.print = function (levelToLog, category, label, content) { + if (typeof content === "string") { + var prefix = [new Date(), category]; + if (label) { + prefix.push(label); + } + content = prefix.concat(content).join(" | "); + } + switch (levelToLog) { + case levels_1.Levels.error: + // tslint:disable-next-line:no-console + console.error(content); + break; + case levels_1.Levels.warn: + // tslint:disable-next-line:no-console + console.warn(content); + break; + case levels_1.Levels.log: + // tslint:disable-next-line:no-console + console.log(content); + break; + case levels_1.Levels.debug: + // tslint:disable-next-line:no-console + console.debug(content); + break; + default: + break; + } + }; + return LoggerFactory; +}()); +exports.LoggerFactory = LoggerFactory; + + +/***/ }), +/* 63 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var levels_1 = __webpack_require__(61); +/** + * Logger. + * @public + */ +var Logger = /** @class */ (function () { + function Logger(logger, category, label) { + this.logger = logger; + this.category = category; + this.label = label; + } + Logger.prototype.error = function (content) { this.genericLog(levels_1.Levels.error, content); }; + Logger.prototype.warn = function (content) { this.genericLog(levels_1.Levels.warn, content); }; + Logger.prototype.log = function (content) { this.genericLog(levels_1.Levels.log, content); }; + Logger.prototype.debug = function (content) { this.genericLog(levels_1.Levels.debug, content); }; + Logger.prototype.genericLog = function (level, content) { + this.logger.genericLog(level, this.category, this.label, content); + }; + return Logger; +}()); +exports.Logger = Logger; + + +/***/ }), +/* 64 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(65), exports); + + +/***/ }), +/* 65 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var messages_1 = __webpack_require__(5); +var transactions_1 = __webpack_require__(27); +var user_agents_1 = __webpack_require__(66); +var allowed_methods_1 = __webpack_require__(58); +/** + * This is ported from UA.C.ACCEPTED_BODY_TYPES. + * FIXME: TODO: Should be configurable/variable. + */ +var acceptedBodyTypes = [ + "application/sdp", + "application/dtmf-relay" +]; +/** + * Core: Core designates the functions specific to a particular type + * of SIP entity, i.e., specific to either a stateful or stateless + * proxy, a user agent or registrar. All cores, except those for + * the stateless proxy, are transaction users. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAC Core: The set of processing functions required of a UAC that + * reside above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAS Core: The set of processing functions required at a UAS that + * resides above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentCore = /** @class */ (function () { + /** + * Constructor. + * @param configuration Configuration. + * @param delegate Delegate. + */ + function UserAgentCore(configuration, delegate) { + if (delegate === void 0) { delegate = {}; } + /** UACs. */ + this.userAgentClients = new Map(); + /** UASs. */ + this.userAgentServers = new Map(); + this.configuration = configuration; + this.delegate = delegate; + this.dialogs = new Map(); + this.subscribers = new Map(); + this.logger = configuration.loggerFactory.getLogger("sip.user-agent-core"); + } + /** Destructor. */ + UserAgentCore.prototype.dispose = function () { + this.reset(); + }; + /** Reset. */ + UserAgentCore.prototype.reset = function () { + this.dialogs.forEach(function (dialog) { return dialog.dispose(); }); + this.dialogs.clear(); + this.subscribers.forEach(function (subscriber) { return subscriber.dispose(); }); + this.subscribers.clear(); + this.userAgentClients.forEach(function (uac) { return uac.dispose(); }); + this.userAgentClients.clear(); + this.userAgentServers.forEach(function (uac) { return uac.dispose(); }); + this.userAgentServers.clear(); + }; + Object.defineProperty(UserAgentCore.prototype, "loggerFactory", { + /** Logger factory. */ + get: function () { + return this.configuration.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentCore.prototype, "transport", { + /** Transport. */ + get: function () { + var transport = this.configuration.transportAccessor(); + if (!transport) { + throw new Error("Transport undefined."); + } + return transport; + }, + enumerable: true, + configurable: true + }); + /** + * Send INVITE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.invite = function (request, delegate) { + return new user_agents_1.InviteUserAgentClient(this, request, delegate); + }; + /** + * Send MESSAGE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.message = function (request, delegate) { + return new user_agents_1.MessageUserAgentClient(this, request, delegate); + }; + /** + * Send PUBLISH. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.publish = function (request, delegate) { + return new user_agents_1.PublishUserAgentClient(this, request, delegate); + }; + /** + * Send REGISTER. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.register = function (request, delegate) { + return new user_agents_1.RegisterUserAgentClient(this, request, delegate); + }; + /** + * Send SUBSCRIBE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.subscribe = function (request, delegate) { + return new user_agents_1.SubscribeUserAgentClient(this, request, delegate); + }; + /** + * Send a request. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.request = function (request, delegate) { + return new user_agents_1.UserAgentClient(transactions_1.NonInviteClientTransaction, this, request, delegate); + }; + /** + * Outgoing request message factory function. + * @param method Method. + * @param requestURI Request-URI. + * @param fromURI From URI. + * @param toURI To URI. + * @param options Request options. + * @param extraHeaders Extra headers to add. + * @param body Message body. + */ + UserAgentCore.prototype.makeOutgoingRequestMessage = function (method, requestURI, fromURI, toURI, options, extraHeaders, body) { + // default values from user agent configuration + var callIdPrefix = this.configuration.sipjsId; + var fromDisplayName = this.configuration.displayName; + var forceRport = this.configuration.viaForceRport; + var hackViaTcp = this.configuration.hackViaTcp; + var optionTags = this.configuration.supportedOptionTags.slice(); + if (method === messages_1.C.REGISTER) { + optionTags.push("path", "gruu"); + } + if (method === messages_1.C.INVITE && (this.configuration.contact.pubGruu || this.configuration.contact.tempGruu)) { + optionTags.push("gruu"); + } + var routeSet = this.configuration.routeSet; + var userAgentString = this.configuration.userAgentHeaderFieldValue; + var viaHost = this.configuration.viaHost; + var defaultOptions = { + callIdPrefix: callIdPrefix, + forceRport: forceRport, + fromDisplayName: fromDisplayName, + hackViaTcp: hackViaTcp, + optionTags: optionTags, + routeSet: routeSet, + userAgentString: userAgentString, + viaHost: viaHost, + }; + // merge provided options with default options + var requestOptions = tslib_1.__assign({}, defaultOptions, options); + return new messages_1.OutgoingRequestMessage(method, requestURI, fromURI, toURI, requestOptions, extraHeaders, body); + }; + /** + * Handle an incoming request message from the transport. + * @param message Incoming request message from transport layer. + */ + UserAgentCore.prototype.receiveIncomingRequestFromTransport = function (message) { + this.receiveRequestFromTransport(message); + }; + /** + * Handle an incoming response message from the transport. + * @param message Incoming response message from transport layer. + */ + UserAgentCore.prototype.receiveIncomingResponseFromTransport = function (message) { + this.receiveResponseFromTransport(message); + }; + /** + * A stateless UAS is a UAS that does not maintain transaction state. + * It replies to requests normally, but discards any state that would + * ordinarily be retained by a UAS after a response has been sent. If a + * stateless UAS receives a retransmission of a request, it regenerates + * the response and re-sends it, just as if it were replying to the first + * instance of the request. A UAS cannot be stateless unless the request + * processing for that method would always result in the same response + * if the requests are identical. This rules out stateless registrars, + * for example. Stateless UASs do not use a transaction layer; they + * receive requests directly from the transport layer and send responses + * directly to the transport layer. + * https://tools.ietf.org/html/rfc3261#section-8.2.7 + * @param message Incoming request message to reply to. + * @param statusCode Status code to reply with. + */ + UserAgentCore.prototype.replyStateless = function (message, options) { + var userAgent = this.configuration.userAgentHeaderFieldValue; + var supported = this.configuration.supportedOptionTagsResponse; + options = tslib_1.__assign({}, options, { userAgent: userAgent, supported: supported }); + var response = messages_1.constructOutgoingResponse(message, options); + this.transport.send(response.message); + return response; + }; + /** + * In Section 18.2.1, replace the last paragraph with: + * + * Next, the server transport attempts to match the request to a + * server transaction. It does so using the matching rules described + * in Section 17.2.3. If a matching server transaction is found, the + * request is passed to that transaction for processing. If no match + * is found, the request is passed to the core, which may decide to + * construct a new server transaction for that request. + * https://tools.ietf.org/html/rfc6026#section-8.10 + * @param message Incoming request message from transport layer. + */ + UserAgentCore.prototype.receiveRequestFromTransport = function (message) { + // When a request is received from the network by the server, it has to + // be matched to an existing transaction. This is accomplished in the + // following manner. + // + // The branch parameter in the topmost Via header field of the request + // is examined. If it is present and begins with the magic cookie + // "z9hG4bK", the request was generated by a client transaction + // compliant to this specification. Therefore, the branch parameter + // will be unique across all transactions sent by that client. The + // request matches a transaction if: + // + // 1. the branch parameter in the request is equal to the one in the + // top Via header field of the request that created the + // transaction, and + // + // 2. the sent-by value in the top Via of the request is equal to the + // one in the request that created the transaction, and + // + // 3. the method of the request matches the one that created the + // transaction, except for ACK, where the method of the request + // that created the transaction is INVITE. + // + // This matching rule applies to both INVITE and non-INVITE transactions + // alike. + // + // The sent-by value is used as part of the matching process because + // there could be accidental or malicious duplication of branch + // parameters from different clients. + // https://tools.ietf.org/html/rfc3261#section-17.2.3 + var transactionId = message.viaBranch; // FIXME: Currently only using rule 1... + var uas = this.userAgentServers.get(transactionId); + // When receiving an ACK that matches an existing INVITE server + // transaction and that does not contain a branch parameter containing + // the magic cookie defined in RFC 3261, the matching transaction MUST + // be checked to see if it is in the "Accepted" state. If it is, then + // the ACK must be passed directly to the transaction user instead of + // being absorbed by the transaction state machine. This is necessary + // as requests from RFC 2543 clients will not include a unique branch + // parameter, and the mechanisms for calculating the transaction ID from + // such a request will be the same for both INVITE and ACKs. + // https://tools.ietf.org/html/rfc6026#section-6 + // Any ACKs received from the network while in the "Accepted" state MUST be + // passed directly to the TU and not absorbed. + // https://tools.ietf.org/html/rfc6026#section-7.1 + if (message.method === messages_1.C.ACK) { + if (uas && uas.transaction.state === transactions_1.TransactionState.Accepted) { + if (uas instanceof user_agents_1.InviteUserAgentServer) { + // These are ACKs matching an INVITE server transaction. + // These should never happen with RFC 3261 compliant user agents + // (would be a broken ACK to negative final response or something) + // but is apparently how RFC 2543 user agents do things. + // We are not currently supporting this case. + // NOTE: Not backwards compatible with RFC 2543 (no support for strict-routing). + this.logger.warn("Discarding out of dialog ACK after 2xx response sent on transaction " + transactionId + "."); + return; + } + } + } + // The CANCEL method requests that the TU at the server side cancel a + // pending transaction. The TU determines the transaction to be + // cancelled by taking the CANCEL request, and then assuming that the + // request method is anything but CANCEL or ACK and applying the + // transaction matching procedures of Section 17.2.3. The matching + // transaction is the one to be cancelled. + // https://tools.ietf.org/html/rfc3261#section-9.2 + if (message.method === messages_1.C.CANCEL) { + if (uas) { + // Regardless of the method of the original request, as long as the + // CANCEL matched an existing transaction, the UAS answers the CANCEL + // request itself with a 200 (OK) response. + // https://tools.ietf.org/html/rfc3261#section-9.2 + this.replyStateless(message, { statusCode: 200 }); + // If the transaction for the original request still exists, the behavior + // of the UAS on receiving a CANCEL request depends on whether it has already + // sent a final response for the original request. If it has, the CANCEL + // request has no effect on the processing of the original request, no + // effect on any session state, and no effect on the responses generated + // for the original request. If the UAS has not issued a final response + // for the original request, its behavior depends on the method of the + // original request. If the original request was an INVITE, the UAS + // SHOULD immediately respond to the INVITE with a 487 (Request + // Terminated). + // https://tools.ietf.org/html/rfc3261#section-9.2 + if (uas.transaction instanceof transactions_1.InviteServerTransaction && + uas.transaction.state === transactions_1.TransactionState.Proceeding) { + if (uas instanceof user_agents_1.InviteUserAgentServer) { + uas.receiveCancel(message); + } + // A CANCEL request has no impact on the processing of + // transactions with any other method defined in this specification. + // https://tools.ietf.org/html/rfc3261#section-9.2 + } + } + else { + // If the UAS did not find a matching transaction for the CANCEL + // according to the procedure above, it SHOULD respond to the CANCEL + // with a 481 (Call Leg/Transaction Does Not Exist). + // https://tools.ietf.org/html/rfc3261#section-9.2 + this.replyStateless(message, { statusCode: 481 }); + } + return; + } + // If a matching server transaction is found, the request is passed to that + // transaction for processing. + // https://tools.ietf.org/html/rfc6026#section-8.10 + if (uas) { + uas.transaction.receiveRequest(message); + return; + } + // If no match is found, the request is passed to the core, which may decide to + // construct a new server transaction for that request. + // https://tools.ietf.org/html/rfc6026#section-8.10 + this.receiveRequest(message); + return; + }; + /** + * UAC and UAS procedures depend strongly on two factors. First, based + * on whether the request or response is inside or outside of a dialog, + * and second, based on the method of a request. Dialogs are discussed + * thoroughly in Section 12; they represent a peer-to-peer relationship + * between user agents and are established by specific SIP methods, such + * as INVITE. + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveRequest = function (message) { + // 8.2 UAS Behavior + // UASs SHOULD process the requests in the order of the steps that + // follow in this section (that is, starting with authentication, then + // inspecting the method, the header fields, and so on throughout the + // remainder of this section). + // https://tools.ietf.org/html/rfc3261#section-8.2 + // 8.2.1 Method Inspection + // Once a request is authenticated (or authentication is skipped), the + // UAS MUST inspect the method of the request. If the UAS recognizes + // but does not support the method of a request, it MUST generate a 405 + // (Method Not Allowed) response. Procedures for generating responses + // are described in Section 8.2.6. The UAS MUST also add an Allow + // header field to the 405 (Method Not Allowed) response. The Allow + // header field MUST list the set of methods supported by the UAS + // generating the message. + // https://tools.ietf.org/html/rfc3261#section-8.2.1 + if (!allowed_methods_1.AllowedMethods.includes(message.method)) { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + this.replyStateless(message, { + statusCode: 405, + extraHeaders: [allowHeader] + }); + return; + } + // 8.2.2 Header Inspection + // https://tools.ietf.org/html/rfc3261#section-8.2.2 + if (!message.ruri) { // FIXME: A request message should always have an ruri + throw new Error("Request-URI undefined."); + } + // 8.2.2.1 To and Request-URI + // If the Request-URI uses a scheme not supported by the UAS, it SHOULD + // reject the request with a 416 (Unsupported URI Scheme) response. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + if (message.ruri.scheme !== "sip") { + this.replyStateless(message, { statusCode: 416 }); + return; + } + // 8.2.2.1 To and Request-URI + // If the Request-URI does not identify an address that the + // UAS is willing to accept requests for, it SHOULD reject + // the request with a 404 (Not Found) response. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + var ruri = message.ruri; + var ruriMatches = function (uri) { + return !!uri && uri.user === ruri.user; + }; + if (!ruriMatches(this.configuration.aor) && + !(ruriMatches(this.configuration.contact.uri) || + ruriMatches(this.configuration.contact.pubGruu) || + ruriMatches(this.configuration.contact.tempGruu))) { + this.logger.warn("Request-URI does not point to us."); + if (message.method !== messages_1.C.ACK) { + this.replyStateless(message, { statusCode: 404 }); + } + return; + } + // 8.2.2.1 To and Request-URI + // Other potential sources of received Request-URIs include + // the Contact header fields of requests and responses sent by the UA + // that establish or refresh dialogs. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + if (message.method === messages_1.C.INVITE) { + if (!message.hasHeader("Contact")) { + this.replyStateless(message, { + statusCode: 400, + reasonPhrase: "Missing Contact Header" + }); + return; + } + } + // 8.2.2.2 Merged Requests + // If the request has no tag in the To header field, the UAS core MUST + // check the request against ongoing transactions. If the From tag, + // Call-ID, and CSeq exactly match those associated with an ongoing + // transaction, but the request does not match that transaction (based + // on the matching rules in Section 17.2.3), the UAS core SHOULD + // generate a 482 (Loop Detected) response and pass it to the server + // transaction. + // + // The same request has arrived at the UAS more than once, following + // different paths, most likely due to forking. The UAS processes + // the first such request received and responds with a 482 (Loop + // Detected) to the rest of them. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.2 + if (!message.toTag) { + var transactionId = message.viaBranch; + if (!this.userAgentServers.has(transactionId)) { + var mergedRequest = Array.from(this.userAgentServers.values()) + .some(function (uas) { + return uas.transaction.request.fromTag === message.fromTag && + uas.transaction.request.callId === message.callId && + uas.transaction.request.cseq === message.cseq; + }); + if (mergedRequest) { + this.replyStateless(message, { statusCode: 482 }); + return; + } + } + } + // 8.2.2.3 Require + // https://tools.ietf.org/html/rfc3261#section-8.2.2.3 + // TODO + // 8.2.3 Content Processing + // https://tools.ietf.org/html/rfc3261#section-8.2.3 + // TODO + // 8.2.4 Applying Extensions + // https://tools.ietf.org/html/rfc3261#section-8.2.4 + // TODO + // 8.2.5 Processing the Request + // Assuming all of the checks in the previous subsections are passed, + // the UAS processing becomes method-specific. + // https://tools.ietf.org/html/rfc3261#section-8.2.5 + // The UAS will receive the request from the transaction layer. If the + // request has a tag in the To header field, the UAS core computes the + // dialog identifier corresponding to the request and compares it with + // existing dialogs. If there is a match, this is a mid-dialog request. + // In that case, the UAS first applies the same processing rules for + // requests outside of a dialog, discussed in Section 8.2. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (message.toTag) { + this.receiveInsideDialogRequest(message); + } + else { + this.receiveOutsideDialogRequest(message); + } + return; + }; + /** + * Once a dialog has been established between two UAs, either of them + * MAY initiate new transactions as needed within the dialog. The UA + * sending the request will take the UAC role for the transaction. The + * UA receiving the request will take the UAS role. Note that these may + * be different roles than the UAs held during the transaction that + * established the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2 + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveInsideDialogRequest = function (message) { + // NOTIFY requests are matched to such SUBSCRIBE requests if they + // contain the same "Call-ID", a "To" header field "tag" parameter that + // matches the "From" header field "tag" parameter of the SUBSCRIBE + // request, and the same "Event" header field. Rules for comparisons of + // the "Event" header fields are described in Section 8.2.1. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (message.method === messages_1.C.NOTIFY) { + var event_1 = message.parseHeader("Event"); + if (!event_1 || !event_1.event) { + this.replyStateless(message, { statusCode: 489 }); + return; + } + // FIXME: Subscriber id should also matching on event id. + var subscriberId = message.callId + message.toTag + event_1.event; + var subscriber = this.subscribers.get(subscriberId); + if (subscriber) { + var uas = new user_agents_1.NotifyUserAgentServer(this, message); + subscriber.onNotify(uas); + return; + } + } + // Requests sent within a dialog, as any other requests, are atomic. If + // a particular request is accepted by the UAS, all the state changes + // associated with it are performed. If the request is rejected, none + // of the state changes are performed. + // + // Note that some requests, such as INVITEs, affect several pieces of + // state. + // + // The UAS will receive the request from the transaction layer. If the + // request has a tag in the To header field, the UAS core computes the + // dialog identifier corresponding to the request and compares it with + // existing dialogs. If there is a match, this is a mid-dialog request. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + var dialogId = message.callId + message.toTag + message.fromTag; + var dialog = this.dialogs.get(dialogId); + if (dialog) { + // [Sip-implementors] Reg. SIP reinvite, UPDATE and OPTIONS + // You got the question right. + // + // And you got the right answer too. :-) + // + // Thanks, + // Paul + // + // Robert Sparks wrote: + // > So I've lost track of the question during the musing. + // > + // > I _think_ the fundamental question being asked is this: + // > + // > Is an endpoint required to reject (with a 481) an OPTIONS request that + // > arrives with at to-tag but does not match any existing dialog state. + // > (Assuming some earlier requirement hasn't forced another error code). Or + // > is it OK if it just sends + // > a 200 OK anyhow. + // > + // > My take on the collection of specs is that its _not_ ok for it to send + // > the 200 OK anyhow and that it is required to send + // > the 481. I base this primarily on these sentences from 11.2 in 3261: + // > + // > The response to an OPTIONS is constructed using the standard rules + // > for a SIP response as discussed in Section 8.2.6. The response code + // > chosen MUST be the same that would have been chosen had the request + // > been an INVITE. + // > + // > Did I miss the point of the question? + // > + // > On May 15, 2008, at 12:48 PM, Paul Kyzivat wrote: + // > + // >> [Including Robert in hopes of getting his insight on this.] + // https://lists.cs.columbia.edu/pipermail/sip-implementors/2008-May/019178.html + // + // Requests that do not change in any way the state of a dialog may be + // received within a dialog (for example, an OPTIONS request). They are + // processed as if they had been received outside the dialog. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (message.method === messages_1.C.OPTIONS) { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var acceptHeader = "Accept: " + acceptedBodyTypes.toString(); + this.replyStateless(message, { + statusCode: 200, + extraHeaders: [allowHeader, acceptHeader] + }); + return; + } + // Pass the incoming request to the dialog for further handling. + dialog.receiveRequest(message); + return; + } + // The most important behaviors of a stateless UAS are the following: + // ... + // o A stateless UAS MUST ignore ACK requests. + // ... + // https://tools.ietf.org/html/rfc3261#section-8.2.7 + if (message.method === messages_1.C.ACK) { + // If a final response to an INVITE was sent statelessly, + // the corresponding ACK: + // - will not match an existing transaction + // - may have tag in the To header field + // - not not match any existing dialogs + // Absorb unmatched ACKs. + return; + } + // If the request has a tag in the To header field, but the dialog + // identifier does not match any existing dialogs, the UAS may have + // crashed and restarted, or it may have received a request for a + // different (possibly failed) UAS (the UASs can construct the To tags + // so that a UAS can identify that the tag was for a UAS for which it is + // providing recovery). Another possibility is that the incoming + // request has been simply mis-routed. Based on the To tag, the UAS MAY + // either accept or reject the request. Accepting the request for + // acceptable To tags provides robustness, so that dialogs can persist + // even through crashes. UAs wishing to support this capability must + // take into consideration some issues such as choosing monotonically + // increasing CSeq sequence numbers even across reboots, reconstructing + // the route set, and accepting out-of-range RTP timestamps and sequence + // numbers. + // + // If the UAS wishes to reject the request because it does not wish to + // recreate the dialog, it MUST respond to the request with a 481 + // (Call/Transaction Does Not Exist) status code and pass that to the + // server transaction. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + this.replyStateless(message, { statusCode: 481 }); + return; + }; + /** + * Assuming all of the checks in the previous subsections are passed, + * the UAS processing becomes method-specific. + * https://tools.ietf.org/html/rfc3261#section-8.2.5 + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveOutsideDialogRequest = function (message) { + switch (message.method) { + case messages_1.C.ACK: + // Absorb stray out of dialog ACKs + break; + case messages_1.C.BYE: + // If the BYE does not match an existing dialog, the UAS core SHOULD + // generate a 481 (Call/Transaction Does Not Exist) response and pass + // that to the server transaction. This rule means that a BYE sent + // without tags by a UAC will be rejected. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + this.replyStateless(message, { statusCode: 481 }); + break; + case messages_1.C.CANCEL: + throw new Error("Unexpected out of dialog request method " + message.method + "."); + break; + case messages_1.C.INFO: + // Use of the INFO method does not constitute a separate dialog usage. + // INFO messages are always part of, and share the fate of, an invite + // dialog usage [RFC5057]. INFO messages cannot be sent as part of + // other dialog usages, or outside an existing dialog. + // https://tools.ietf.org/html/rfc6086#section-1 + this.replyStateless(message, { statusCode: 405 }); // Should never happen + break; + case messages_1.C.INVITE: + // https://tools.ietf.org/html/rfc3261#section-13.3.1 + { + var uas = new user_agents_1.InviteUserAgentServer(this, message); + this.delegate.onInvite ? + this.delegate.onInvite(uas) : + uas.reject(); + } + break; + case messages_1.C.MESSAGE: + // MESSAGE requests are discouraged inside a dialog. Implementations + // are restricted from creating a usage for the purpose of carrying a + // sequence of MESSAGE requests (though some implementations use it that + // way, against the standard recommendation). + // https://tools.ietf.org/html/rfc5057#section-5.3 + { + var uas = new user_agents_1.MessageUserAgentServer(this, message); + this.delegate.onMessage ? + this.delegate.onMessage(uas) : + uas.accept(); + } + break; + case messages_1.C.NOTIFY: + // Obsoleted by: RFC 6665 + // If any non-SUBSCRIBE mechanisms are defined to create subscriptions, + // it is the responsibility of the parties defining those mechanisms to + // ensure that correlation of a NOTIFY message to the corresponding + // subscription is possible. Designers of such mechanisms are also + // warned to make a distinction between sending a NOTIFY message to a + // subscriber who is aware of the subscription, and sending a NOTIFY + // message to an unsuspecting node. The latter behavior is invalid, and + // MUST receive a "481 Subscription does not exist" response (unless + // some other 400- or 500-class error code is more applicable), as + // described in section 3.2.4. In other words, knowledge of a + // subscription must exist in both the subscriber and the notifier to be + // valid, even if installed via a non-SUBSCRIBE mechanism. + // https://tools.ietf.org/html/rfc3265#section-3.2 + // + // NOTIFY requests are sent to inform subscribers of changes in state to + // which the subscriber has a subscription. Subscriptions are created + // using the SUBSCRIBE method. In legacy implementations, it is + // possible that other means of subscription creation have been used. + // However, this specification does not allow the creation of + // subscriptions except through SUBSCRIBE requests and (for backwards- + // compatibility) REFER requests [RFC3515]. + // https://tools.ietf.org/html/rfc6665#section-3.2 + { + var uas = new user_agents_1.NotifyUserAgentServer(this, message); + this.delegate.onNotify ? + this.delegate.onNotify(uas) : + this.replyStateless(message, { statusCode: 405 }); + } + break; + case messages_1.C.OPTIONS: + // https://tools.ietf.org/html/rfc3261#section-11.2 + { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var acceptHeader = "Accept: " + acceptedBodyTypes.toString(); + this.replyStateless(message, { + statusCode: 200, + extraHeaders: [allowHeader, acceptHeader] + }); + } + break; + case messages_1.C.REFER: + // https://tools.ietf.org/html/rfc3515#section-2.4.2 + { + var uas = new user_agents_1.ReferUserAgentServer(this, message); + this.delegate.onRefer ? + this.delegate.onRefer(uas) : + this.replyStateless(message, { statusCode: 405 }); + } + break; + case messages_1.C.SUBSCRIBE: + // https://tools.ietf.org/html/rfc6665#section-4.2 + { + var uas = new user_agents_1.SubscribeUserAgentServer(this, message); + this.delegate.onSubscribe ? + this.delegate.onSubscribe(uas) : + uas.reject(); + } + break; + default: + throw new Error("Unexpected out of dialog request method " + message.method + "."); + } + return; + }; + /** + * Responses are first processed by the transport layer and then passed + * up to the transaction layer. The transaction layer performs its + * processing and then passes the response up to the TU. The majority + * of response processing in the TU is method specific. However, there + * are some general behaviors independent of the method. + * https://tools.ietf.org/html/rfc3261#section-8.1.3 + * @param message Incoming response message from transport layer. + */ + UserAgentCore.prototype.receiveResponseFromTransport = function (message) { + // 8.1.3.1 Transaction Layer Errors + // https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + // Handled by transaction layer callbacks. + // 8.1.3.2 Unrecognized Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + // TODO + // 8.1.3.3 Vias + // https://tools.ietf.org/html/rfc3261#section-8.1.3.3 + if (message.getHeaders("via").length > 1) { + this.logger.warn("More than one Via header field present in the response, dropping"); + return; + } + // 8.1.3.4 Processing 3xx Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.4 + // TODO + // 8.1.3.5 Processing 4xx Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + // TODO + // When the transport layer in the client receives a response, it has to + // determine which client transaction will handle the response, so that + // the processing of Sections 17.1.1 and 17.1.2 can take place. The + // branch parameter in the top Via header field is used for this + // purpose. A response matches a client transaction under two + // conditions: + // + // 1. If the response has the same value of the branch parameter in + // the top Via header field as the branch parameter in the top + // Via header field of the request that created the transaction. + // + // 2. If the method parameter in the CSeq header field matches the + // method of the request that created the transaction. The + // method is needed since a CANCEL request constitutes a + // different transaction, but shares the same value of the branch + // parameter. + // https://tools.ietf.org/html/rfc3261#section-17.1.3 + var userAgentClientId = message.viaBranch + message.method; + var userAgentClient = this.userAgentClients.get(userAgentClientId); + // The client transport uses the matching procedures of Section + // 17.1.3 to attempt to match the response to an existing + // transaction. If there is a match, the response MUST be passed to + // that transaction. Otherwise, any element other than a stateless + // proxy MUST silently discard the response. + // https://tools.ietf.org/html/rfc6026#section-8.9 + if (userAgentClient) { + userAgentClient.transaction.receiveResponse(message); + } + else { + this.logger.warn("Discarding unmatched " + message.statusCode + " response to " + message.method + " " + userAgentClientId + "."); + } + }; + return UserAgentCore; +}()); +exports.UserAgentCore = UserAgentCore; + + +/***/ }), +/* 66 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +tslib_1.__exportStar(__webpack_require__(41), exports); +tslib_1.__exportStar(__webpack_require__(43), exports); +tslib_1.__exportStar(__webpack_require__(67), exports); +tslib_1.__exportStar(__webpack_require__(46), exports); +tslib_1.__exportStar(__webpack_require__(68), exports); +tslib_1.__exportStar(__webpack_require__(69), exports); +tslib_1.__exportStar(__webpack_require__(70), exports); +tslib_1.__exportStar(__webpack_require__(71), exports); +tslib_1.__exportStar(__webpack_require__(47), exports); +tslib_1.__exportStar(__webpack_require__(48), exports); +tslib_1.__exportStar(__webpack_require__(72), exports); +tslib_1.__exportStar(__webpack_require__(49), exports); +tslib_1.__exportStar(__webpack_require__(50), exports); +tslib_1.__exportStar(__webpack_require__(51), exports); +tslib_1.__exportStar(__webpack_require__(52), exports); +tslib_1.__exportStar(__webpack_require__(59), exports); +tslib_1.__exportStar(__webpack_require__(73), exports); +tslib_1.__exportStar(__webpack_require__(53), exports); +tslib_1.__exportStar(__webpack_require__(54), exports); +tslib_1.__exportStar(__webpack_require__(74), exports); +tslib_1.__exportStar(__webpack_require__(75), exports); +tslib_1.__exportStar(__webpack_require__(76), exports); +tslib_1.__exportStar(__webpack_require__(42), exports); +tslib_1.__exportStar(__webpack_require__(44), exports); + + +/***/ }), +/* 67 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var CancelUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(CancelUserAgentClient, _super); + function CancelUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return CancelUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.CancelUserAgentClient = CancelUserAgentClient; + + +/***/ }), +/* 68 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var dialogs_1 = __webpack_require__(3); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.2 UAC Processing + * https://tools.ietf.org/html/rfc3261#section-13.2 + */ +var InviteUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(InviteUserAgentClient, _super); + function InviteUserAgentClient(core, message, delegate) { + var _this = _super.call(this, transactions_1.InviteClientTransaction, core, message, delegate) || this; + _this.confirmedDialogAcks = new Map(); + _this.confirmedDialogs = new Map(); + _this.earlyDialogs = new Map(); + _this.delegate = delegate; + return _this; + } + InviteUserAgentClient.prototype.dispose = function () { + // The UAC core considers the INVITE transaction completed 64*T1 seconds + // after the reception of the first 2xx response. At this point all the + // early dialogs that have not transitioned to established dialogs are + // terminated. Once the INVITE transaction is considered completed by + // the UAC core, no more new 2xx responses are expected to arrive. + // + // If, after acknowledging any 2xx response to an INVITE, the UAC does + // not want to continue with that dialog, then the UAC MUST terminate + // the dialog by sending a BYE request as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + _super.prototype.dispose.call(this); + }; + /** + * Once the INVITE has been passed to the INVITE client transaction, the + * UAC waits for responses for the INVITE. + * https://tools.ietf.org/html/rfc3261#section-13.2.2 + * @param incomingResponse Incoming response to INVITE request. + */ + InviteUserAgentClient.prototype.receiveResponse = function (message) { + var _this = this; + if (!this.authenticationGuard(message)) { + return; + } + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + return; + case /^1[0-9]{2}$/.test(statusCode): + // Zero, one or multiple provisional responses may arrive before one or + // more final responses are received. Provisional responses for an + // INVITE request can create "early dialogs". If a provisional response + // has a tag in the To field, and if the dialog ID of the response does + // not match an existing dialog, one is constructed using the procedures + // defined in Section 12.1.2. + // + // The early dialog will only be needed if the UAC needs to send a + // request to its peer within the dialog before the initial INVITE + // transaction completes. Header fields present in a provisional + // response are applicable as long as the dialog is in the early state + // (for example, an Allow header field in a provisional response + // contains the methods that can be used in the dialog while this is in + // the early state). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.1 + { + // Provisional without to tag, no dialog to create. + if (!message.toTag) { + this.logger.warn("Non-100 1xx INVITE response received without a to tag, dropping."); + return; + } + // Compute dialog state. + var dialogState = dialogs_1.Dialog.initialDialogStateForUserAgentClient(this.message, message); + // Have existing early dialog or create a new one. + var earlyDialog = this.earlyDialogs.get(dialogState.id); + if (!earlyDialog) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + earlyDialog = new dialogs_1.SessionDialog(transaction, this.core, dialogState); + this.earlyDialogs.set(earlyDialog.id, earlyDialog); + } + // Guard against out of order reliable provisional responses. + // Note that this is where the rseq tracking is done. + if (!earlyDialog.reliableSequenceGuard(message)) { + this.logger.warn("1xx INVITE reliable response received out of order, dropping."); + return; + } + // Update dialog signaling state if need be. + earlyDialog.signalingStateTransition(message); + // Pass response to delegate. + var session_1 = earlyDialog; + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ + message: message, + session: session_1, + prack: function (options) { + var outgoingPrackRequest = session_1.prack(undefined, options); + return outgoingPrackRequest; + } + }); + } + } + return; + case /^2[0-9]{2}$/.test(statusCode): + // Multiple 2xx responses may arrive at the UAC for a single INVITE + // request due to a forking proxy. Each response is distinguished by + // the tag parameter in the To header field, and each represents a + // distinct dialog, with a distinct dialog identifier. + // + // If the dialog identifier in the 2xx response matches the dialog + // identifier of an existing dialog, the dialog MUST be transitioned to + // the "confirmed" state, and the route set for the dialog MUST be + // recomputed based on the 2xx response using the procedures of Section + // 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + // constructed using the procedures of Section 12.1.2. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + { + // Compute dialog state. + var dialogState = dialogs_1.Dialog.initialDialogStateForUserAgentClient(this.message, message); + // NOTE: Currently our transaction layer is caching the 2xx ACKs and + // handling retransmissions of the ACK which is an approach which is + // not to spec. In any event, this block is intended to provide a to + // spec implementation of ACK retransmissions, but it should not be + // hit currently. + var dialog = this.confirmedDialogs.get(dialogState.id); + if (dialog) { + // Once the ACK has been constructed, the procedures of [4] are used to + // determine the destination address, port and transport. However, the + // request is passed to the transport layer directly for transmission, + // rather than a client transaction. This is because the UAC core + // handles retransmissions of the ACK, not the transaction layer. The + // ACK MUST be passed to the client transport every time a + // retransmission of the 2xx final response that triggered the ACK + // arrives. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + var outgoingAckRequest = this.confirmedDialogAcks.get(dialogState.id); + if (outgoingAckRequest) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Client transaction not instance of InviteClientTransaction."); + } + transaction.ackResponse(outgoingAckRequest.message); + } + else { + // If still waiting for an ACK, drop the retransmission of the 2xx final response. + } + return; + } + // If the dialog identifier in the 2xx response matches the dialog + // identifier of an existing dialog, the dialog MUST be transitioned to + // the "confirmed" state, and the route set for the dialog MUST be + // recomputed based on the 2xx response using the procedures of Section + // 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + // constructed using the procedures of Section 12.1.2. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + dialog = this.earlyDialogs.get(dialogState.id); + if (dialog) { + dialog.confirm(); + dialog.recomputeRouteSet(message); + this.earlyDialogs.delete(dialog.id); + this.confirmedDialogs.set(dialog.id, dialog); + } + else { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + dialog = new dialogs_1.SessionDialog(transaction, this.core, dialogState); + this.confirmedDialogs.set(dialog.id, dialog); + } + // Update dialog signaling state if need be. + dialog.signalingStateTransition(message); + // Session Initiated! :) + var session_2 = dialog; + // The UAC core MUST generate an ACK request for each 2xx received from + // the transaction layer. The header fields of the ACK are constructed + // in the same way as for any request sent within a dialog (see Section + // 12) with the exception of the CSeq and the header fields related to + // authentication. The sequence number of the CSeq header field MUST be + // the same as the INVITE being acknowledged, but the CSeq method MUST + // be ACK. The ACK MUST contain the same credentials as the INVITE. If + // the 2xx contains an offer (based on the rules above), the ACK MUST + // carry an answer in its body. If the offer in the 2xx response is not + // acceptable, the UAC core MUST generate a valid answer in the ACK and + // then send a BYE immediately. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ + message: message, + session: session_2, + ack: function (options) { + var outgoingAckRequest = session_2.ack(options); + _this.confirmedDialogAcks.set(session_2.id, outgoingAckRequest); + return outgoingAckRequest; + } + }); + } + else { + var outgoingAckRequest = session_2.ack(); + this.confirmedDialogAcks.set(session_2.id, outgoingAckRequest); + } + } + return; + case /^3[0-9]{2}$/.test(statusCode): + // 12.3 Termination of a Dialog + // + // Independent of the method, if a request outside of a dialog generates + // a non-2xx final response, any early dialogs created through + // provisional responses to that request are terminated. The mechanism + // for terminating confirmed dialogs is method specific. In this + // specification, the BYE method terminates a session and the dialog + // associated with it. See Section 15 for details. + // https://tools.ietf.org/html/rfc3261#section-12.3 + // All early dialogs are considered terminated upon reception of the + // non-2xx final response. + // + // After having received the non-2xx final response the UAC core + // considers the INVITE transaction completed. The INVITE client + // transaction handles the generation of ACKs for the response (see + // Section 17). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + // A 3xx response may contain one or more Contact header field values + // providing new addresses where the callee might be reachable. + // Depending on the status code of the 3xx response (see Section 21.3), + // the UAC MAY choose to try those new addresses. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.2 + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + return; + case /^[4-6][0-9]{2}$/.test(statusCode): + // 12.3 Termination of a Dialog + // + // Independent of the method, if a request outside of a dialog generates + // a non-2xx final response, any early dialogs created through + // provisional responses to that request are terminated. The mechanism + // for terminating confirmed dialogs is method specific. In this + // specification, the BYE method terminates a session and the dialog + // associated with it. See Section 15 for details. + // https://tools.ietf.org/html/rfc3261#section-12.3 + // All early dialogs are considered terminated upon reception of the + // non-2xx final response. + // + // After having received the non-2xx final response the UAC core + // considers the INVITE transaction completed. The INVITE client + // transaction handles the generation of ACKs for the response (see + // Section 17). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + // A single non-2xx final response may be received for the INVITE. 4xx, + // 5xx and 6xx responses may contain a Contact header field value + // indicating the location where additional information about the error + // can be found. Subsequent final responses (which would only arrive + // under error conditions) MUST be ignored. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + return; + default: + throw new Error("Invalid status code " + statusCode); + } + throw new Error("Executing what should be an unreachable code path receiving " + statusCode + " response."); + }; + return InviteUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.InviteUserAgentClient = InviteUserAgentClient; + + +/***/ }), +/* 69 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var dialogs_1 = __webpack_require__(3); +var exceptions_1 = __webpack_require__(31); +var session_1 = __webpack_require__(24); +var transactions_1 = __webpack_require__(27); +var allowed_methods_1 = __webpack_require__(58); +var user_agent_server_1 = __webpack_require__(44); +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.3 UAS Processing + * https://tools.ietf.org/html/rfc3261#section-13.3 + */ +var InviteUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(InviteUserAgentServer, _super); + function InviteUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.InviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + InviteUserAgentServer.prototype.dispose = function () { + if (this.earlyDialog) { + this.earlyDialog.dispose(); + } + _super.prototype.dispose.call(this); + }; + /** + * 13.3.1.4 The INVITE is Accepted + * The UAS core generates a 2xx response. This response establishes a + * dialog, and therefore follows the procedures of Section 12.1.1 in + * addition to those of Section 8.2.6. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + * @param options Accept options bucket. + */ + InviteUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (!this.acceptable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not acceptable in state " + this.transaction.state + "."); + } + // This response establishes a dialog... + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!this.confirmedDialog) { + if (this.earlyDialog) { + this.earlyDialog.confirm(); + this.confirmedDialog = this.earlyDialog; + this.earlyDialog = undefined; + } + else { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteServerTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + var state = dialogs_1.Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag); + this.confirmedDialog = new dialogs_1.SessionDialog(transaction, this.core, state); + } + } + // When a UAS responds to a request with a response that establishes a + // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route + // header field values from the request into the response (including the + // URIs, URI parameters, and any Record-Route header field parameters, + // whether they are known or unknown to the UAS) and MUST maintain the + // order of those values. The UAS MUST add a Contact header field to + // the response. The Contact header field contains an address where the + // UAS would like to be contacted for subsequent requests in the dialog + // (which includes the ACK for a 2xx response in the case of an INVITE). + // Generally, the host portion of this URI is the IP address or FQDN of + // the host. The URI provided in the Contact header field MUST be a SIP + // or SIPS URI. If the request that initiated the dialog contained a + // SIPS URI in the Request-URI or in the top Record-Route header field + // value, if there was any, or the Contact header field if there was no + // Record-Route header field, the Contact header field in the response + // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the + // same URI can be used in messages outside this dialog). The same way, + // the scope of the URI in the Contact header field of the INVITE is not + // limited to this dialog either. It can therefore be used in messages + // to the UAC even outside this dialog. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var recordRouteHeader = this.message + .getHeaders("record-route") + .map(function (header) { return "Record-Route: " + header; }); + var contactHeader = "Contact: " + this.core.configuration.contact.toString(); + // A 2xx response to an INVITE SHOULD contain the Allow header field and + // the Supported header field, and MAY contain the Accept header field. + // Including these header fields allows the UAC to determine the + // features and extensions supported by the UAS for the duration of the + // call, without probing. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + // FIXME: TODO: This should not be hard coded. + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + // FIXME: TODO: Supported header (see reply()) + // FIXME: TODO: Accept header + // If the INVITE request contained an offer, and the UAS had not yet + // sent an answer, the 2xx MUST contain an answer. If the INVITE did + // not contain an offer, the 2xx MUST contain an offer if the UAS had + // not yet sent an offer. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!options.body) { + if (this.confirmedDialog.signalingState === session_1.SignalingState.Initial || + this.confirmedDialog.signalingState === session_1.SignalingState.HaveRemoteOffer) { + throw new Error("Response must have a body."); + } + } + // FIXME: TODO: Guard offer/answer + options.statusCode = options.statusCode || 200; + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push(contactHeader); + var response = _super.prototype.accept.call(this, options); + var session = this.confirmedDialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.confirmedDialog.signalingStateTransition(options.body); + } + return result; + }; + /** + * 13.3.1.1 Progress + * If the UAS is not able to answer the invitation immediately, it can + * choose to indicate some kind of progress to the UAC (for example, an + * indication that a phone is ringing). This is accomplished with a + * provisional response between 101 and 199. These provisional + * responses establish early dialogs and therefore follow the procedures + * of Section 12.1.1 in addition to those of Section 8.2.6. A UAS MAY + * send as many provisional responses as it likes. Each of these MUST + * indicate the same dialog ID. However, these will not be delivered + * reliably. + * + * If the UAS desires an extended period of time to answer the INVITE, + * it will need to ask for an "extension" in order to prevent proxies + * from canceling the transaction. A proxy has the option of canceling + * a transaction when there is a gap of 3 minutes between responses in a + * transaction. To prevent cancellation, the UAS MUST send a non-100 + * provisional response at every minute, to handle the possibility of + * lost provisional responses. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.1 + * @param options Progress options bucket. + */ + InviteUserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + if (!this.progressable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not progressable in state " + this.transaction.state + "."); + } + // This response establishes a dialog... + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!this.earlyDialog) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteServerTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + var state = dialogs_1.Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag, true); + this.earlyDialog = new dialogs_1.SessionDialog(transaction, this.core, state); + } + // When a UAS responds to a request with a response that establishes a + // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route + // header field values from the request into the response (including the + // URIs, URI parameters, and any Record-Route header field parameters, + // whether they are known or unknown to the UAS) and MUST maintain the + // order of those values. The UAS MUST add a Contact header field to + // the response. The Contact header field contains an address where the + // UAS would like to be contacted for subsequent requests in the dialog + // (which includes the ACK for a 2xx response in the case of an INVITE). + // Generally, the host portion of this URI is the IP address or FQDN of + // the host. The URI provided in the Contact header field MUST be a SIP + // or SIPS URI. If the request that initiated the dialog contained a + // SIPS URI in the Request-URI or in the top Record-Route header field + // value, if there was any, or the Contact header field if there was no + // Record-Route header field, the Contact header field in the response + // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the + // same URI can be used in messages outside this dialog). The same way, + // the scope of the URI in the Contact header field of the INVITE is not + // limited to this dialog either. It can therefore be used in messages + // to the UAC even outside this dialog. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var recordRouteHeader = this.message + .getHeaders("record-route") + .map(function (header) { return "Record-Route: " + header; }); + var contactHeader = "Contact: " + this.core.configuration.contact; + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); + options.extraHeaders.push(contactHeader); + var response = _super.prototype.progress.call(this, options); + var session = this.earlyDialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.earlyDialog.signalingStateTransition(options.body); + } + return result; + }; + /** + * 13.3.1.2 The INVITE is Redirected + * If the UAS decides to redirect the call, a 3xx response is sent. A + * 300 (Multiple Choices), 301 (Moved Permanently) or 302 (Moved + * Temporarily) response SHOULD contain a Contact header field + * containing one or more URIs of new addresses to be tried. The + * response is passed to the INVITE server transaction, which will deal + * with its retransmissions. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.2 + * @param options Reject options bucket. + */ + InviteUserAgentServer.prototype.redirect = function (contacts, options) { + if (options === void 0) { options = { statusCode: 302 }; } + return _super.prototype.redirect.call(this, contacts, options); + }; + /** + * 13.3.1.3 The INVITE is Rejected + * A common scenario occurs when the callee is currently not willing or + * able to take additional calls at this end system. A 486 (Busy Here) + * SHOULD be returned in such a scenario. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.3 + * @param options Reject options bucket. + */ + InviteUserAgentServer.prototype.reject = function (options) { + if (options === void 0) { options = { statusCode: 486 }; } + return _super.prototype.reject.call(this, options); + }; + return InviteUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.InviteUserAgentServer = InviteUserAgentServer; + + +/***/ }), +/* 70 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var MessageUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(MessageUserAgentClient, _super); + function MessageUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return MessageUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.MessageUserAgentClient = MessageUserAgentClient; + + +/***/ }), +/* 71 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var MessageUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(MessageUserAgentServer, _super); + function MessageUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + return MessageUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.MessageUserAgentServer = MessageUserAgentServer; + + +/***/ }), +/* 72 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var PublishUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(PublishUserAgentClient, _super); + function PublishUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return PublishUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.PublishUserAgentClient = PublishUserAgentClient; + + +/***/ }), +/* 73 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var ReSubscribeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReSubscribeUserAgentServer, _super); + function ReSubscribeUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return ReSubscribeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReSubscribeUserAgentServer = ReSubscribeUserAgentServer; + + +/***/ }), +/* 74 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +var RegisterUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(RegisterUserAgentClient, _super); + function RegisterUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return RegisterUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.RegisterUserAgentClient = RegisterUserAgentClient; + + +/***/ }), +/* 75 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var subscription_dialog_1 = __webpack_require__(55); +var subscription_1 = __webpack_require__(56); +var timers_1 = __webpack_require__(26); +var transactions_1 = __webpack_require__(27); +var user_agent_client_1 = __webpack_require__(42); +/** + * 4.1. Subscriber Behavior + * https://tools.ietf.org/html/rfc6665#section-4.1 + * + * User agent client for installation of a single subscription per SUBSCRIBE request. + * TODO: Support for installation of multiple subscriptions on forked SUBSCRIBE reqeuests. + */ +var SubscribeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(SubscribeUserAgentClient, _super); + function SubscribeUserAgentClient(core, message, delegate) { + var _this = this; + // Get event from request message. + var event = message.getHeader("Event"); + if (!event) { + throw new Error("Event undefined"); + } + // Get expires from reqeust message. + var expires = message.getHeader("Expires"); + if (!expires) { + throw new Error("Expires undefined"); + } + _this = _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + _this.delegate = delegate; + // FIXME: Subscriber id should also be matching on event id. + _this.subscriberId = message.callId + message.fromTag + event; + _this.subscriptionExpiresRequested = _this.subscriptionExpires = Number(expires); + _this.subscriptionEvent = event; + _this.subscriptionState = subscription_1.SubscriptionState.NotifyWait; + // Start waiting for a NOTIFY we can use to create a subscription. + _this.waitNotifyStart(); + return _this; + } + /** + * Destructor. + * Note that Timer N may live on waiting for an initial NOTIFY and + * the delegate may still receive that NOTIFY. If you don't want + * that behavior then either clear the delegate so the delegate + * doesn't get called (a 200 will be sent in response to the NOTIFY) + * or call `waitNotifyStop` which will clear Timer N and remove this + * UAC from the core (a 481 will be sent in response to the NOTIFY). + */ + SubscribeUserAgentClient.prototype.dispose = function () { + _super.prototype.dispose.call(this); + }; + /** + * Handle out of dialog NOTIFY assoicated with SUBSCRIBE request. + * This is the first NOTIFY received after the SUBSCRIBE request. + * @param uas User agent server handling the subscription creating NOTIFY. + */ + SubscribeUserAgentClient.prototype.onNotify = function (uas) { + // NOTIFY requests are matched to such SUBSCRIBE requests if they + // contain the same "Call-ID", a "To" header field "tag" parameter that + // matches the "From" header field "tag" parameter of the SUBSCRIBE + // request, and the same "Event" header field. Rules for comparisons of + // the "Event" header fields are described in Section 8.2.1. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + var event = uas.message.parseHeader("Event").event; + if (!event || event !== this.subscriptionEvent) { + this.logger.warn("Failed to parse event."); + uas.reject({ statusCode: 489 }); + return; + } + // NOTIFY requests MUST contain "Subscription-State" header fields that + // indicate the status of the subscription. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = uas.message.parseHeader("Subscription-State"); + if (!subscriptionState || !subscriptionState.state) { + this.logger.warn("Failed to parse subscription state."); + uas.reject({ statusCode: 489 }); + return; + } + // Validate subscription state. + var state = subscriptionState.state; + switch (state) { + case "pending": + break; + case "active": + break; + case "terminated": + break; + default: + this.logger.warn("Invalid subscription state " + state); + uas.reject({ statusCode: 489 }); + return; + } + // Dialogs usages are created upon completion of a NOTIFY transaction + // for a new subscription, unless the NOTIFY request contains a + // "Subscription-State" of "terminated." + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (state !== "terminated") { + // The Contact header field MUST be present and contain exactly one SIP + // or SIPS URI in any request that can result in the establishment of a + // dialog. + // https://tools.ietf.org/html/rfc3261#section-8.1.1.8 + var contact = uas.message.parseHeader("contact"); + if (!contact) { + this.logger.warn("Failed to parse contact."); + uas.reject({ statusCode: 489 }); + return; + } + } + // In accordance with the rules for proxying non-INVITE requests as + // defined in [RFC3261], successful SUBSCRIBE requests will receive only + // one 200-class response; however, due to forking, the subscription may + // have been accepted by multiple nodes. The subscriber MUST therefore + // be prepared to receive NOTIFY requests with "From:" tags that differ + // from the "To:" tag received in the SUBSCRIBE 200-class response. + // + // If multiple NOTIFY requests are received in different dialogs in + // response to a single SUBSCRIBE request, each dialog represents a + // different destination to which the SUBSCRIBE request was forked. + // Subscriber handling in such situations varies by event package; see + // Section 5.4.9 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.4 + // Each event package MUST specify whether forked SUBSCRIBE requests are + // allowed to install multiple subscriptions. + // + // If such behavior is not allowed, the first potential dialog- + // establishing message will create a dialog. All subsequent NOTIFY + // requests that correspond to the SUBSCRIBE request (i.e., have + // matching "To", "From", "Call-ID", and "Event" header fields, as well + // as "From" header field "tag" parameter and "Event" header field "id" + // parameter) but that do not match the dialog would be rejected with a + // 481 response. Note that the 200-class response to the SUBSCRIBE + // request can arrive after a matching NOTIFY request has been received; + // such responses might not correlate to the same dialog established by + // the NOTIFY request. Except as required to complete the SUBSCRIBE + // transaction, such non-matching 200-class responses are ignored. + // + // If installing of multiple subscriptions by way of a single forked + // SUBSCRIBE request is allowed, the subscriber establishes a new dialog + // towards each notifier by returning a 200-class response to each + // NOTIFY request. Each dialog is then handled as its own entity and is + // refreshed independently of the other dialogs. + // + // In the case that multiple subscriptions are allowed, the event + // package MUST specify whether merging of the notifications to form a + // single state is required, and how such merging is to be performed. + // Note that it is possible that some event packages may be defined in + // such a way that each dialog is tied to a mutually exclusive state + // that is unaffected by the other dialogs; this MUST be clearly stated + // if it is the case. + // https://tools.ietf.org/html/rfc6665#section-5.4.9 + // *** NOTE: This implementation is only for event packages which + // do not allow forked requests to install muliple subscriptions. + // As such and in accordance with the specificaiton, we stop waiting + // and any future NOTIFY requests will be rejected with a 481. + if (this.dialog) { + throw new Error("Dialog already created. This implementation only supports install of single subscriptions."); + } + this.waitNotifyStop(); + // Update expires. + this.subscriptionExpires = + subscriptionState.expires ? + Math.min(this.subscriptionExpires, Math.max(subscriptionState.expires, 0)) : + this.subscriptionExpires; + // Update subscriptoin state. + switch (state) { + case "pending": + this.subscriptionState = subscription_1.SubscriptionState.Pending; + break; + case "active": + this.subscriptionState = subscription_1.SubscriptionState.Active; + break; + case "terminated": + this.subscriptionState = subscription_1.SubscriptionState.Terminated; + break; + default: + throw new Error("Unrecognized state " + state + "."); + } + // Dialogs usages are created upon completion of a NOTIFY transaction + // for a new subscription, unless the NOTIFY request contains a + // "Subscription-State" of "terminated." + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (this.subscriptionState !== subscription_1.SubscriptionState.Terminated) { + // Because the dialog usage is established by the NOTIFY request, the + // route set at the subscriber is taken from the NOTIFY request itself, + // as opposed to the route set present in the 200-class response to the + // SUBSCRIBE request. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + var dialogState = subscription_dialog_1.SubscriptionDialog.initialDialogStateForSubscription(this.message, uas.message); + // Subscription Initiated! :) + this.dialog = new subscription_dialog_1.SubscriptionDialog(this.subscriptionEvent, this.subscriptionExpires, this.subscriptionState, this.core, dialogState); + } + // Delegate. + if (this.delegate && this.delegate.onNotify) { + var request = uas; + var subscription = this.dialog; + this.delegate.onNotify({ request: request, subscription: subscription }); + } + else { + uas.accept(); + } + }; + SubscribeUserAgentClient.prototype.waitNotifyStart = function () { + var _this = this; + if (!this.N) { + // Add ourselves to the core's subscriber map. + // This allows the core to route out of dialog NOTIFY messages to us. + this.core.subscribers.set(this.subscriberId, this); + this.N = setTimeout(function () { return _this.timer_N(); }, timers_1.Timers.TIMER_N); + } + }; + SubscribeUserAgentClient.prototype.waitNotifyStop = function () { + if (this.N) { + // Remove ourselves to the core's subscriber map. + // Any future out of dialog NOTIFY messages will be rejected with a 481. + this.core.subscribers.delete(this.subscriberId); + clearTimeout(this.N); + this.N = undefined; + } + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + SubscribeUserAgentClient.prototype.receiveResponse = function (message) { + if (!this.authenticationGuard(message)) { + return; + } + if (message.statusCode && message.statusCode >= 200 && message.statusCode < 300) { + // The "Expires" header field in a 200-class response to SUBSCRIBE + // request indicates the actual duration for which the subscription will + // remain active (unless refreshed). The received value might be + // smaller than the value indicated in the SUBSCRIBE request but cannot + // be larger; see Section 4.2.1 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + // The "Expires" values present in SUBSCRIBE 200-class responses behave + // in the same way as they do in REGISTER responses: the server MAY + // shorten the interval but MUST NOT lengthen it. + // + // If the duration specified in a SUBSCRIBE request is unacceptably + // short, the notifier may be able to send a 423 response, as + // described earlier in this section. + // + // 200-class responses to SUBSCRIBE requests will not generally contain + // any useful information beyond subscription duration; their primary + // purpose is to serve as a reliability mechanism. State information + // will be communicated via a subsequent NOTIFY request from the + // notifier. + // https://tools.ietf.org/html/rfc6665#section-4.2.1.1 + var expires = message.getHeader("Expires"); + if (!expires) { + this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE"); + } + else { + var subscriptionExpiresReceived = Number(expires); + if (subscriptionExpiresReceived > this.subscriptionExpiresRequested) { + this.logger.warn("Expires header in a 200-class response to SUBSCRIBE with a higher value than the one in the request"); + } + if (subscriptionExpiresReceived < this.subscriptionExpires) { + this.subscriptionExpires = subscriptionExpiresReceived; + } + } + // If a NOTIFY arrived before 200-class response a dialog may have been created. + // Updated the dialogs expiration only if this indicates earlier expiration. + if (this.dialog) { + if (this.dialog.subscriptionExpires > this.subscriptionExpires) { + this.dialog.subscriptionExpires = this.subscriptionExpires; + } + } + } + if (message.statusCode && message.statusCode >= 300 && message.statusCode < 700) { + this.waitNotifyStop(); // No NOTIFY will be sent after a negative final response. + } + _super.prototype.receiveResponse.call(this, message); + }; + /** + * To ensure that subscribers do not wait indefinitely for a + * subscription to be established, a subscriber starts a Timer N, set to + * 64*T1, when it sends a SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription failed, and cleans up any state associated with the + * subscription attempt. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + */ + SubscribeUserAgentClient.prototype.timer_N = function () { + this.logger.warn("Timer N expired for SUBSCRIBE user agent client. Timed out waiting for NOTIFY."); + this.waitNotifyStop(); + if (this.delegate && this.delegate.onNotifyTimeout) { + this.delegate.onNotifyTimeout(); + } + }; + return SubscribeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.SubscribeUserAgentClient = SubscribeUserAgentClient; + + +/***/ }), +/* 76 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var transactions_1 = __webpack_require__(27); +var user_agent_server_1 = __webpack_require__(44); +var SubscribeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(SubscribeUserAgentServer, _super); + function SubscribeUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + return SubscribeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.SubscribeUserAgentServer = SubscribeUserAgentServer; + + +/***/ }), +/* 77 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +/** + * Transport + * @remarks + * Abstract transport layer base class. + * @param logger - Logger. + * @param options - Options bucket. + * @public + */ +var Transport = /** @class */ (function (_super) { + tslib_1.__extends(Transport, _super); + function Transport(logger, options) { + var _this = _super.call(this) || this; + _this.logger = logger; + return _this; + } + /** + * Returns the promise designated by the child layer then emits a connected event. + * Automatically emits an event upon resolution, unless overrideEvent is set. If you + * override the event in this fashion, you should emit it in your implementation of connectPromise + * @param options - Options bucket. + */ + Transport.prototype.connect = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.connectPromise(options).then(function (data) { + if (!data.overrideEvent) { + _this.emit("connected"); + } + }); + }; + /** + * Sends a message then emits a 'messageSent' event. Automatically emits an + * event upon resolution, unless data.overrideEvent is set. If you override + * the event in this fashion, you should emit it in your implementation of sendPromise + * @param msg - Message. + * @param options - Options bucket. + */ + Transport.prototype.send = function (msg, options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.sendPromise(msg).then(function (data) { + if (!data.overrideEvent) { + _this.emit("messageSent", data.msg); + } + }); + }; + /** + * Returns the promise designated by the child layer then emits a + * disconnected event. Automatically emits an event upon resolution, + * unless overrideEvent is set. If you override the event in this fashion, + * you should emit it in your implementation of disconnectPromise + * @param options - Options bucket + */ + Transport.prototype.disconnect = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.disconnectPromise(options).then(function (data) { + if (!data.overrideEvent) { + _this.emit("disconnected"); + } + }); + }; + Transport.prototype.afterConnected = function (callback) { + if (this.isConnected()) { + callback(); + } + else { + this.once("connected", callback); + } + }; + /** + * Returns a promise which resolves once the UA is connected. DEPRECATION WARNING: just use afterConnected() + */ + Transport.prototype.waitForConnected = function () { + var _this = this; + // tslint:disable-next-line:no-console + console.warn("DEPRECATION WARNING Transport.waitForConnected(): use afterConnected() instead"); + return new Promise(function (resolve) { + _this.afterConnected(resolve); + }); + }; + return Transport; +}(events_1.EventEmitter)); +exports.Transport = Transport; + + +/***/ }), +/* 78 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var Enums_1 = __webpack_require__(81); +var Utils_1 = __webpack_require__(82); +var ClientContext = /** @class */ (function (_super) { + tslib_1.__extends(ClientContext, _super); + function ClientContext(ua, method, target, options) { + var _this = _super.call(this) || this; + _this.data = {}; + ClientContext.initializer(_this, ua, method, target, options); + return _this; + } + ClientContext.initializer = function (objToConstruct, ua, method, originalTarget, options) { + objToConstruct.type = Enums_1.TypeStrings.ClientContext; + // Validate arguments + if (originalTarget === undefined) { + throw new TypeError("Not enough arguments"); + } + objToConstruct.ua = ua; + objToConstruct.logger = ua.getLogger("sip.clientcontext"); + objToConstruct.method = method; + var target = ua.normalizeTarget(originalTarget); + if (!target) { + throw new TypeError("Invalid target: " + originalTarget); + } + /* Options + * - extraHeaders + * - params + * - contentType + * - body + */ + options = Object.create(options || Object.prototype); + var extraHeaders = (options.extraHeaders || []).slice(); + var params = options.params || {}; + var bodyObj; + if (options.body) { + bodyObj = { + body: options.body, + contentType: options.contentType ? options.contentType : "application/sdp" + }; + objToConstruct.body = bodyObj; + } + var body; + if (bodyObj) { + body = Utils_1.Utils.fromBodyObj(bodyObj); + } + // Build the request + objToConstruct.request = ua.userAgentCore.makeOutgoingRequestMessage(method, target, params.fromUri ? params.fromUri : ua.userAgentCore.configuration.aor, params.toUri ? params.toUri : target, params, extraHeaders, body); + /* Set other properties from the request */ + if (objToConstruct.request.from) { + objToConstruct.localIdentity = objToConstruct.request.from; + } + if (objToConstruct.request.to) { + objToConstruct.remoteIdentity = objToConstruct.request.to; + } + }; + ClientContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.request(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + ClientContext.prototype.receiveResponse = function (response) { + var statusCode = response.statusCode || 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + this.emit("progress", response, cause); + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + if (this.ua.applicants[this.toString()]) { + delete this.ua.applicants[this.toString()]; + } + this.emit("accepted", response, cause); + break; + default: + if (this.ua.applicants[this.toString()]) { + delete this.ua.applicants[this.toString()]; + } + this.emit("rejected", response, cause); + this.emit("failed", response, cause); + break; + } + }; + ClientContext.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + ClientContext.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + return ClientContext; +}(events_1.EventEmitter)); +exports.ClientContext = ClientContext; + + +/***/ }), +/* 79 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +// tslint:disable-next-line:no-var-requires +var pkg = __webpack_require__(80); +var C; +(function (C) { + C.USER_AGENT = pkg.title + "/" + pkg.version; + // SIP scheme + C.SIP = "sip"; + C.SIPS = "sips"; + // End and Failure causes + var causes; + (function (causes) { + // Generic error causes + causes["CONNECTION_ERROR"] = "Connection Error"; + causes["INTERNAL_ERROR"] = "Internal Error"; + causes["REQUEST_TIMEOUT"] = "Request Timeout"; + causes["SIP_FAILURE_CODE"] = "SIP Failure Code"; + // SIP error causes + causes["ADDRESS_INCOMPLETE"] = "Address Incomplete"; + causes["AUTHENTICATION_ERROR"] = "Authentication Error"; + causes["BUSY"] = "Busy"; + causes["DIALOG_ERROR"] = "Dialog Error"; + causes["INCOMPATIBLE_SDP"] = "Incompatible SDP"; + causes["NOT_FOUND"] = "Not Found"; + causes["REDIRECTED"] = "Redirected"; + causes["REJECTED"] = "Rejected"; + causes["UNAVAILABLE"] = "Unavailable"; + // Session error causes + causes["BAD_MEDIA_DESCRIPTION"] = "Bad Media Description"; + causes["CANCELED"] = "Canceled"; + causes["EXPIRES"] = "Expires"; + causes["NO_ACK"] = "No ACK"; + causes["NO_ANSWER"] = "No Answer"; + causes["NO_PRACK"] = "No PRACK"; + causes["RTP_TIMEOUT"] = "RTP Timeout"; + causes["USER_DENIED_MEDIA_ACCESS"] = "User Denied Media Access"; + causes["WEBRTC_ERROR"] = "WebRTC Error"; + causes["WEBRTC_NOT_SUPPORTED"] = "WebRTC Not Supported"; + })(causes = C.causes || (C.causes = {})); + var supported; + (function (supported) { + supported["REQUIRED"] = "required"; + supported["SUPPORTED"] = "supported"; + supported["UNSUPPORTED"] = "none"; + })(supported = C.supported || (C.supported = {})); + C.SIP_ERROR_CAUSES = { + ADDRESS_INCOMPLETE: [484], + AUTHENTICATION_ERROR: [401, 407], + BUSY: [486, 600], + INCOMPATIBLE_SDP: [488, 606], + NOT_FOUND: [404, 604], + REDIRECTED: [300, 301, 302, 305, 380], + REJECTED: [403, 603], + UNAVAILABLE: [480, 410, 408, 430] + }; + // SIP Methods + C.ACK = "ACK"; + C.BYE = "BYE"; + C.CANCEL = "CANCEL"; + C.INFO = "INFO"; + C.INVITE = "INVITE"; + C.MESSAGE = "MESSAGE"; + C.NOTIFY = "NOTIFY"; + C.OPTIONS = "OPTIONS"; + C.REGISTER = "REGISTER"; + C.UPDATE = "UPDATE"; + C.SUBSCRIBE = "SUBSCRIBE"; + C.PUBLISH = "PUBLISH"; + C.REFER = "REFER"; + C.PRACK = "PRACK"; + /* SIP Response Reasons + * DOC: http://www.iana.org/assignments/sip-parameters + * Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7 + */ + C.REASON_PHRASE = { + 100: "Trying", + 180: "Ringing", + 181: "Call Is Being Forwarded", + 182: "Queued", + 183: "Session Progress", + 199: "Early Dialog Terminated", + 200: "OK", + 202: "Accepted", + 204: "No Notification", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Moved Temporarily", + 305: "Use Proxy", + 380: "Alternative Service", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 410: "Gone", + 412: "Conditional Request Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Unsupported URI Scheme", + 417: "Unknown Resource-Priority", + 420: "Bad Extension", + 421: "Extension Required", + 422: "Session Interval Too Small", + 423: "Interval Too Brief", + 428: "Use Identity Header", + 429: "Provide Referrer Identity", + 430: "Flow Failed", + 433: "Anonymity Disallowed", + 436: "Bad Identity-Info", + 437: "Unsupported Certificate", + 438: "Invalid Identity Header", + 439: "First Hop Lacks Outbound Support", + 440: "Max-Breadth Exceeded", + 469: "Bad Info Package", + 470: "Consent Needed", + 478: "Unresolvable Destination", + 480: "Temporarily Unavailable", + 481: "Call/Transaction Does Not Exist", + 482: "Loop Detected", + 483: "Too Many Hops", + 484: "Address Incomplete", + 485: "Ambiguous", + 486: "Busy Here", + 487: "Request Terminated", + 488: "Not Acceptable Here", + 489: "Bad Event", + 491: "Request Pending", + 493: "Undecipherable", + 494: "Security Agreement Required", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Server Time-out", + 505: "Version Not Supported", + 513: "Message Too Large", + 580: "Precondition Failure", + 600: "Busy Everywhere", + 603: "Decline", + 604: "Does Not Exist Anywhere", + 606: "Not Acceptable" + }; + /* SIP Option Tags + * DOC: http://www.iana.org/assignments/sip-parameters/sip-parameters.xhtml#sip-parameters-4 + */ + C.OPTION_TAGS = { + "100rel": true, + "199": true, + "answermode": true, + "early-session": true, + "eventlist": true, + "explicitsub": true, + "from-change": true, + "geolocation-http": true, + "geolocation-sip": true, + "gin": true, + "gruu": true, + "histinfo": true, + "ice": true, + "join": true, + "multiple-refer": true, + "norefersub": true, + "nosub": true, + "outbound": true, + "path": true, + "policy": true, + "precondition": true, + "pref": true, + "privacy": true, + "recipient-list-invite": true, + "recipient-list-message": true, + "recipient-list-subscribe": true, + "replaces": true, + "resource-priority": true, + "sdp-anat": true, + "sec-agree": true, + "tdialog": true, + "timer": true, + "uui": true // RFC 7433 + }; + var dtmfType; + (function (dtmfType) { + dtmfType["INFO"] = "info"; + dtmfType["RTP"] = "rtp"; + })(dtmfType = C.dtmfType || (C.dtmfType = {})); +})(C = exports.C || (exports.C = {})); + + +/***/ }), +/* 80 */ +/***/ (function(module) { + +module.exports = {"name":"sip.js","title":"SIP.js","description":"A simple, intuitive, and powerful JavaScript signaling library","version":"0.14.2","license":"MIT","main":"./lib/index.js","types":"./lib/index.d.ts","homepage":"https://sipjs.com","author":"OnSIP (https://sipjs.com/aboutus/)","contributors":[{"url":"https://github.com/onsip/SIP.js/blob/master/THANKS.md"}],"repository":{"type":"git","url":"https://github.com/onsip/SIP.js.git"},"keywords":["sip","webrtc","library","websocket","javascript","typescript"],"dependencies":{"crypto-js":"^3.1.9-1"},"devDependencies":{"@types/crypto-js":"^3.1.43","@types/jasmine":"^3.3.13","@types/node":"^12.0.4","circular-dependency-plugin":"^5.0.2","jasmine-core":"^3.4.0","karma":"^4.1.0","karma-chrome-launcher":"^2.2.0","karma-cli":"^2.0.0","karma-jasmine":"^2.0.1","karma-jasmine-html-reporter":"^1.4.2","karma-mocha-reporter":"^2.2.5","karma-sourcemap-loader":"^0.3.7","karma-webpack":"^3.0.5","pegjs":"^0.10.0","ts-loader":"^6.0.2","ts-pegjs":"0.2.5","tslint":"^5.17.0","typescript":"^3.5.1","webpack":"^4.32.2","webpack-cli":"^3.3.2"},"engines":{"node":">=8.0"},"scripts":{"prebuild":"tslint -p tsconfig-base.json -c tslint.json","generate-grammar":"node build/grammarGenerator.js","build-reg-bundle":"webpack --progress --config build/webpack.config.js --env.buildType reg","build-min-bundle":"webpack --progress --config build/webpack.config.js --env.buildType min","build-bundles":"npm run build-reg-bundle && npm run build-min-bundle","build-lib":"tsc -p src","build-test":"tsc -p test","copy-dist-files":"cp dist/sip.js dist/sip-$npm_package_version.js && cp dist/sip.min.js dist/sip-$npm_package_version.min.js","build":"npm run generate-grammar && npm run build-lib && npm run build-reg-bundle && npm run build-min-bundle && npm run copy-dist-files","browserTest":"npm run build-test && sleep 2 && open http://0.0.0.0:9876/debug.html & karma start --reporters kjhtml --no-single-run","commandLineTest":"npm run build-test && karma start --reporters mocha --browsers ChromeHeadless --single-run","buildAndTest":"npm run build && npm run commandLineTest","buildAndBrowserTest":"npm run build && npm run browserTest"}}; + +/***/ }), +/* 81 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +// enums can't really be declared, so they are set here. +// pulled out of individual files to avoid circular dependencies +Object.defineProperty(exports, "__esModule", { value: true }); +var DialogStatus; +(function (DialogStatus) { + DialogStatus[DialogStatus["STATUS_EARLY"] = 1] = "STATUS_EARLY"; + DialogStatus[DialogStatus["STATUS_CONFIRMED"] = 2] = "STATUS_CONFIRMED"; +})(DialogStatus = exports.DialogStatus || (exports.DialogStatus = {})); +var SessionStatus; +(function (SessionStatus) { + // Session states + SessionStatus[SessionStatus["STATUS_NULL"] = 0] = "STATUS_NULL"; + SessionStatus[SessionStatus["STATUS_INVITE_SENT"] = 1] = "STATUS_INVITE_SENT"; + SessionStatus[SessionStatus["STATUS_1XX_RECEIVED"] = 2] = "STATUS_1XX_RECEIVED"; + SessionStatus[SessionStatus["STATUS_INVITE_RECEIVED"] = 3] = "STATUS_INVITE_RECEIVED"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_ANSWER"] = 4] = "STATUS_WAITING_FOR_ANSWER"; + SessionStatus[SessionStatus["STATUS_ANSWERED"] = 5] = "STATUS_ANSWERED"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_PRACK"] = 6] = "STATUS_WAITING_FOR_PRACK"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_ACK"] = 7] = "STATUS_WAITING_FOR_ACK"; + SessionStatus[SessionStatus["STATUS_CANCELED"] = 8] = "STATUS_CANCELED"; + SessionStatus[SessionStatus["STATUS_TERMINATED"] = 9] = "STATUS_TERMINATED"; + SessionStatus[SessionStatus["STATUS_ANSWERED_WAITING_FOR_PRACK"] = 10] = "STATUS_ANSWERED_WAITING_FOR_PRACK"; + SessionStatus[SessionStatus["STATUS_EARLY_MEDIA"] = 11] = "STATUS_EARLY_MEDIA"; + SessionStatus[SessionStatus["STATUS_CONFIRMED"] = 12] = "STATUS_CONFIRMED"; +})(SessionStatus = exports.SessionStatus || (exports.SessionStatus = {})); +var TypeStrings; +(function (TypeStrings) { + TypeStrings[TypeStrings["ClientContext"] = 0] = "ClientContext"; + TypeStrings[TypeStrings["ConfigurationError"] = 1] = "ConfigurationError"; + TypeStrings[TypeStrings["Dialog"] = 2] = "Dialog"; + TypeStrings[TypeStrings["DigestAuthentication"] = 3] = "DigestAuthentication"; + TypeStrings[TypeStrings["DTMF"] = 4] = "DTMF"; + TypeStrings[TypeStrings["IncomingMessage"] = 5] = "IncomingMessage"; + TypeStrings[TypeStrings["IncomingRequest"] = 6] = "IncomingRequest"; + TypeStrings[TypeStrings["IncomingResponse"] = 7] = "IncomingResponse"; + TypeStrings[TypeStrings["InvalidStateError"] = 8] = "InvalidStateError"; + TypeStrings[TypeStrings["InviteClientContext"] = 9] = "InviteClientContext"; + TypeStrings[TypeStrings["InviteServerContext"] = 10] = "InviteServerContext"; + TypeStrings[TypeStrings["Logger"] = 11] = "Logger"; + TypeStrings[TypeStrings["LoggerFactory"] = 12] = "LoggerFactory"; + TypeStrings[TypeStrings["MethodParameterError"] = 13] = "MethodParameterError"; + TypeStrings[TypeStrings["NameAddrHeader"] = 14] = "NameAddrHeader"; + TypeStrings[TypeStrings["NotSupportedError"] = 15] = "NotSupportedError"; + TypeStrings[TypeStrings["OutgoingRequest"] = 16] = "OutgoingRequest"; + TypeStrings[TypeStrings["Parameters"] = 17] = "Parameters"; + TypeStrings[TypeStrings["PublishContext"] = 18] = "PublishContext"; + TypeStrings[TypeStrings["ReferClientContext"] = 19] = "ReferClientContext"; + TypeStrings[TypeStrings["ReferServerContext"] = 20] = "ReferServerContext"; + TypeStrings[TypeStrings["RegisterContext"] = 21] = "RegisterContext"; + TypeStrings[TypeStrings["RenegotiationError"] = 22] = "RenegotiationError"; + TypeStrings[TypeStrings["RequestSender"] = 23] = "RequestSender"; + TypeStrings[TypeStrings["ServerContext"] = 24] = "ServerContext"; + TypeStrings[TypeStrings["Session"] = 25] = "Session"; + TypeStrings[TypeStrings["SessionDescriptionHandler"] = 26] = "SessionDescriptionHandler"; + TypeStrings[TypeStrings["SessionDescriptionHandlerError"] = 27] = "SessionDescriptionHandlerError"; + TypeStrings[TypeStrings["SessionDescriptionHandlerObserver"] = 28] = "SessionDescriptionHandlerObserver"; + TypeStrings[TypeStrings["Subscription"] = 29] = "Subscription"; + TypeStrings[TypeStrings["Transport"] = 30] = "Transport"; + TypeStrings[TypeStrings["UA"] = 31] = "UA"; + TypeStrings[TypeStrings["URI"] = 32] = "URI"; +})(TypeStrings = exports.TypeStrings || (exports.TypeStrings = {})); +// UA status codes +var UAStatus; +(function (UAStatus) { + UAStatus[UAStatus["STATUS_INIT"] = 0] = "STATUS_INIT"; + UAStatus[UAStatus["STATUS_STARTING"] = 1] = "STATUS_STARTING"; + UAStatus[UAStatus["STATUS_READY"] = 2] = "STATUS_READY"; + UAStatus[UAStatus["STATUS_USER_CLOSED"] = 3] = "STATUS_USER_CLOSED"; + UAStatus[UAStatus["STATUS_NOT_READY"] = 4] = "STATUS_NOT_READY"; +})(UAStatus = exports.UAStatus || (exports.UAStatus = {})); + + +/***/ }), +/* 82 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var Constants_1 = __webpack_require__(79); +var grammar_1 = __webpack_require__(11); +var uri_1 = __webpack_require__(15); +var Utils; +(function (Utils) { + function defer() { + var deferred = {}; + deferred.promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; + } + Utils.defer = defer; + function reducePromises(arr, val) { + return arr.reduce(function (acc, fn) { + acc = acc.then(fn); + return acc; + }, Promise.resolve(val)); + } + Utils.reducePromises = reducePromises; + function str_utf8_length(str) { + return encodeURIComponent(str).replace(/%[A-F\d]{2}/g, "U").length; + } + Utils.str_utf8_length = str_utf8_length; + function generateFakeSDP(body) { + if (!body) { + return; + } + var start = body.indexOf("o="); + var end = body.indexOf("\r\n", start); + return "v=0\r\n" + body.slice(start, end) + "\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0"; + } + Utils.generateFakeSDP = generateFakeSDP; + function isDecimal(num) { + var numAsNum = parseInt(num, 10); + return !isNaN(numAsNum) && (parseFloat(num) === numAsNum); + } + Utils.isDecimal = isDecimal; + function createRandomToken(size, base) { + if (base === void 0) { base = 32; } + var token = ""; + for (var i = 0; i < size; i++) { + var r = Math.floor(Math.random() * base); + token += r.toString(base); + } + return token; + } + Utils.createRandomToken = createRandomToken; + function newTag() { + // used to use the constant in UA + return Utils.createRandomToken(10); + } + Utils.newTag = newTag; + // http://stackoverflow.com/users/109538/broofa + function newUUID() { + var UUID = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + var r = Math.floor(Math.random() * 16); + var v = c === "x" ? r : (r % 4 + 8); + return v.toString(16); + }); + return UUID; + } + Utils.newUUID = newUUID; + /* + * Normalize SIP URI. + * NOTE: It does not allow a SIP URI without username. + * Accepts 'sip', 'sips' and 'tel' URIs and convert them into 'sip'. + * Detects the domain part (if given) and properly hex-escapes the user portion. + * If the user portion has only 'tel' number symbols the user portion is clean of 'tel' visual separators. + * @private + * @param {String} target + * @param {String} [domain] + */ + function normalizeTarget(target, domain) { + // If no target is given then raise an error. + if (!target) { + return; + // If a SIP.URI instance is given then return it. + } + else if (target instanceof uri_1.URI) { + return target; + // If a string is given split it by '@': + // - Last fragment is the desired domain. + // - Otherwise append the given domain argument. + } + else if (typeof target === "string") { + var targetArray = target.split("@"); + var targetUser = void 0; + var targetDomain = void 0; + switch (targetArray.length) { + case 1: + if (!domain) { + return; + } + targetUser = target; + targetDomain = domain; + break; + case 2: + targetUser = targetArray[0]; + targetDomain = targetArray[1]; + break; + default: + targetUser = targetArray.slice(0, targetArray.length - 1).join("@"); + targetDomain = targetArray[targetArray.length - 1]; + } + // Remove the URI scheme (if present). + targetUser = targetUser.replace(/^(sips?|tel):/i, ""); + // Remove 'tel' visual separators if the user portion just contains 'tel' number symbols. + if (/^[\-\.\(\)]*\+?[0-9\-\.\(\)]+$/.test(targetUser)) { + targetUser = targetUser.replace(/[\-\.\(\)]/g, ""); + } + // Build the complete SIP URI. + target = Constants_1.C.SIP + ":" + Utils.escapeUser(targetUser) + "@" + targetDomain; + // Finally parse the resulting URI. + return grammar_1.Grammar.URIParse(target); + } + else { + return; + } + } + Utils.normalizeTarget = normalizeTarget; + /* + * Hex-escape a SIP URI user. + * @private + * @param {String} user + */ + function escapeUser(user) { + // Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F). + return encodeURIComponent(decodeURIComponent(user)) + .replace(/%3A/ig, ":") + .replace(/%2B/ig, "+") + .replace(/%3F/ig, "?") + .replace(/%2F/ig, "/"); + } + Utils.escapeUser = escapeUser; + function headerize(str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; + } + Utils.headerize = headerize; + function sipErrorCause(statusCode) { + for (var cause in Constants_1.C.SIP_ERROR_CAUSES) { + if (Constants_1.C.SIP_ERROR_CAUSES[cause].indexOf(statusCode) !== -1) { + return Constants_1.C.causes[cause]; + } + } + return Constants_1.C.causes.SIP_FAILURE_CODE; + } + Utils.sipErrorCause = sipErrorCause; + function getReasonPhrase(code, specific) { + return specific || Constants_1.C.REASON_PHRASE[code] || ""; + } + Utils.getReasonPhrase = getReasonPhrase; + function getReasonHeaderValue(code, reason) { + reason = Utils.getReasonPhrase(code, reason); + return "SIP;cause=" + code + ';text="' + reason + '"'; + } + Utils.getReasonHeaderValue = getReasonHeaderValue; + function getCancelReason(code, reason) { + if (code && code < 200 || code > 699) { + throw new TypeError("Invalid statusCode: " + code); + } + else if (code) { + return Utils.getReasonHeaderValue(code, reason); + } + } + Utils.getCancelReason = getCancelReason; + function buildStatusLine(code, reason) { + // Validate code and reason values + if (!code || (code < 100 || code > 699)) { + throw new TypeError("Invalid statusCode: " + code); + } + else if (reason && typeof reason !== "string" && !(reason instanceof String)) { + throw new TypeError("Invalid reason: " + reason); + } + reason = Utils.getReasonPhrase(code, reason); + return "SIP/2.0 " + code + " " + reason + "\r\n"; + } + Utils.buildStatusLine = buildStatusLine; + /** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ + function fromBodyObj(bodyObj) { + var content = bodyObj.body; + var contentType = bodyObj.contentType; + var contentDisposition = contentTypeToContentDisposition(contentType); + var body = { contentDisposition: contentDisposition, contentType: contentType, content: content }; + return body; + } + Utils.fromBodyObj = fromBodyObj; + /** + * Create a BodyObj given a Body. + * @param bodyObj Body Object + */ + function toBodyObj(body) { + var bodyObj = { + body: body.content, + contentType: body.contentType + }; + return bodyObj; + } + Utils.toBodyObj = toBodyObj; + // If the Content-Disposition header field is missing, bodies of + // Content-Type application/sdp imply the disposition "session", while + // other content types imply "render". + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + function contentTypeToContentDisposition(contentType) { + if (contentType === "application/sdp") { + return "session"; + } + else { + return "render"; + } + } +})(Utils = exports.Utils || (exports.Utils = {})); + + +/***/ }), +/* 83 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +// tslint:disable:max-classes-per-file +var Exceptions; +(function (Exceptions) { + /** + * Indicates the session description handler has closed. + * Occurs when getDescription() or setDescription() are called after close() has been called. + * Occurs when close() is called while getDescription() or setDescription() are in progress. + */ + var ClosedSessionDescriptionHandlerError = /** @class */ (function (_super) { + tslib_1.__extends(ClosedSessionDescriptionHandlerError, _super); + function ClosedSessionDescriptionHandlerError() { + return _super.call(this, "The session description handler has closed.") || this; + } + return ClosedSessionDescriptionHandlerError; + }(core_1.Exception)); + Exceptions.ClosedSessionDescriptionHandlerError = ClosedSessionDescriptionHandlerError; + /** + * Indicates the session terminated before the action completed. + */ + var TerminatedSessionError = /** @class */ (function (_super) { + tslib_1.__extends(TerminatedSessionError, _super); + function TerminatedSessionError() { + return _super.call(this, "The session has terminated.") || this; + } + return TerminatedSessionError; + }(core_1.Exception)); + Exceptions.TerminatedSessionError = TerminatedSessionError; + /** + * Unsupported session description content type. + */ + var UnsupportedSessionDescriptionContentTypeError = /** @class */ (function (_super) { + tslib_1.__extends(UnsupportedSessionDescriptionContentTypeError, _super); + function UnsupportedSessionDescriptionContentTypeError(message) { + return _super.call(this, message ? message : "Unsupported session description content type.") || this; + } + return UnsupportedSessionDescriptionContentTypeError; + }(core_1.Exception)); + Exceptions.UnsupportedSessionDescriptionContentTypeError = UnsupportedSessionDescriptionContentTypeError; +})(Exceptions = exports.Exceptions || (exports.Exceptions = {})); +/** + * DEPRECATED: The original implementation of exceptions in this library attempted to + * deal with the lack of type checking in JavaScript by adding a "type" attribute + * to objects and using that to discriminate. On top of that it layered allcoated + * "code" numbers and constant "name" strings. All of that is unnecessary when using + * TypeScript, inheriting from Error and properly setting up the prototype chain... + */ +var LegacyException = /** @class */ (function (_super) { + tslib_1.__extends(LegacyException, _super); + function LegacyException(code, name, message) { + var _this = _super.call(this, message) || this; + _this.code = code; + _this.name = name; + _this.message = message; + return _this; + } + return LegacyException; +}(core_1.Exception)); +(function (Exceptions) { + var ConfigurationError = /** @class */ (function (_super) { + tslib_1.__extends(ConfigurationError, _super); + function ConfigurationError(parameter, value) { + var _this = _super.call(this, 1, "CONFIGURATION_ERROR", (!value) ? "Missing parameter: " + parameter : + "Invalid value " + JSON.stringify(value) + " for parameter '" + parameter + "'") || this; + _this.type = Enums_1.TypeStrings.ConfigurationError; + _this.parameter = parameter; + _this.value = value; + return _this; + } + return ConfigurationError; + }(LegacyException)); + Exceptions.ConfigurationError = ConfigurationError; + var InvalidStateError = /** @class */ (function (_super) { + tslib_1.__extends(InvalidStateError, _super); + function InvalidStateError(status) { + var _this = _super.call(this, 2, "INVALID_STATE_ERROR", "Invalid status: " + status) || this; + _this.type = Enums_1.TypeStrings.InvalidStateError; + _this.status = status; + return _this; + } + return InvalidStateError; + }(LegacyException)); + Exceptions.InvalidStateError = InvalidStateError; + var NotSupportedError = /** @class */ (function (_super) { + tslib_1.__extends(NotSupportedError, _super); + function NotSupportedError(message) { + var _this = _super.call(this, 3, "NOT_SUPPORTED_ERROR", message) || this; + _this.type = Enums_1.TypeStrings.NotSupportedError; + return _this; + } + return NotSupportedError; + }(LegacyException)); + Exceptions.NotSupportedError = NotSupportedError; + // 4 was GetDescriptionError, which was deprecated and now removed + var RenegotiationError = /** @class */ (function (_super) { + tslib_1.__extends(RenegotiationError, _super); + function RenegotiationError(message) { + var _this = _super.call(this, 5, "RENEGOTIATION_ERROR", message) || this; + _this.type = Enums_1.TypeStrings.RenegotiationError; + return _this; + } + return RenegotiationError; + }(LegacyException)); + Exceptions.RenegotiationError = RenegotiationError; + var MethodParameterError = /** @class */ (function (_super) { + tslib_1.__extends(MethodParameterError, _super); + function MethodParameterError(method, parameter, value) { + var _this = _super.call(this, 6, "METHOD_PARAMETER_ERROR", (!value) ? + "Missing parameter: " + parameter : + "Invalid value " + JSON.stringify(value) + " for parameter '" + parameter + "'") || this; + _this.type = Enums_1.TypeStrings.MethodParameterError; + _this.method = method; + _this.parameter = parameter; + _this.value = value; + return _this; + } + return MethodParameterError; + }(LegacyException)); + Exceptions.MethodParameterError = MethodParameterError; + // 7 was TransportError, which was replaced + var SessionDescriptionHandlerError = /** @class */ (function (_super) { + tslib_1.__extends(SessionDescriptionHandlerError, _super); + function SessionDescriptionHandlerError(method, error, message) { + var _this = _super.call(this, 8, "SESSION_DESCRIPTION_HANDLER_ERROR", message || "Error with Session Description Handler") || this; + _this.type = Enums_1.TypeStrings.SessionDescriptionHandlerError; + _this.method = method; + _this.error = error; + return _this; + } + return SessionDescriptionHandlerError; + }(LegacyException)); + Exceptions.SessionDescriptionHandlerError = SessionDescriptionHandlerError; +})(Exceptions = exports.Exceptions || (exports.Exceptions = {})); + + +/***/ }), +/* 84 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var core_1 = __webpack_require__(2); +/** + * Extract and parse every header of a SIP message. + * @namespace + */ +var Parser; +(function (Parser) { + function getHeader(data, headerStart) { + // 'start' position of the header. + var start = headerStart; + // 'end' position of the header. + var end = 0; + // 'partial end' position of the header. + var partialEnd = 0; + // End of message. + if (data.substring(start, start + 2).match(/(^\r\n)/)) { + return -2; + } + while (end === 0) { + // Partial End of Header. + partialEnd = data.indexOf("\r\n", start); + // 'indexOf' returns -1 if the value to be found never occurs. + if (partialEnd === -1) { + return partialEnd; + } + if (!data.substring(partialEnd + 2, partialEnd + 4).match(/(^\r\n)/) && + data.charAt(partialEnd + 2).match(/(^\s+)/)) { + // Not the end of the message. Continue from the next position. + start = partialEnd + 2; + } + else { + end = partialEnd; + } + } + return end; + } + Parser.getHeader = getHeader; + function parseHeader(message, data, headerStart, headerEnd) { + var hcolonIndex = data.indexOf(":", headerStart); + var headerName = data.substring(headerStart, hcolonIndex).trim(); + var headerValue = data.substring(hcolonIndex + 1, headerEnd).trim(); + var parsed; + // If header-field is well-known, parse it. + switch (headerName.toLowerCase()) { + case "via": + case "v": + message.addHeader("via", headerValue); + if (message.getHeaders("via").length === 1) { + parsed = message.parseHeader("Via"); + if (parsed) { + message.via = parsed; + message.viaBranch = parsed.branch; + } + } + else { + parsed = 0; + } + break; + case "from": + case "f": + message.setHeader("from", headerValue); + parsed = message.parseHeader("from"); + if (parsed) { + message.from = parsed; + message.fromTag = parsed.getParam("tag"); + } + break; + case "to": + case "t": + message.setHeader("to", headerValue); + parsed = message.parseHeader("to"); + if (parsed) { + message.to = parsed; + message.toTag = parsed.getParam("tag"); + } + break; + case "record-route": + parsed = core_1.Grammar.parse(headerValue, "Record_Route"); + if (parsed === -1) { + parsed = undefined; + break; + } + for (var header in parsed) { + if (parsed[header]) { + message.addHeader("record-route", headerValue.substring(parsed[header].position, parsed[header].offset)); + message.headers["Record-Route"][message.getHeaders("record-route").length - 1].parsed = + parsed[header].parsed; + } + } + break; + case "call-id": + case "i": + message.setHeader("call-id", headerValue); + parsed = message.parseHeader("call-id"); + if (parsed) { + message.callId = headerValue; + } + break; + case "contact": + case "m": + parsed = core_1.Grammar.parse(headerValue, "Contact"); + if (parsed === -1) { + parsed = undefined; + break; + } + if (!(parsed instanceof Array)) { + parsed = undefined; + break; + } + parsed.forEach(function (header) { + message.addHeader("contact", headerValue.substring(header.position, header.offset)); + message.headers.Contact[message.getHeaders("contact").length - 1].parsed = header.parsed; + }); + break; + case "content-length": + case "l": + message.setHeader("content-length", headerValue); + parsed = message.parseHeader("content-length"); + break; + case "content-type": + case "c": + message.setHeader("content-type", headerValue); + parsed = message.parseHeader("content-type"); + break; + case "cseq": + message.setHeader("cseq", headerValue); + parsed = message.parseHeader("cseq"); + if (parsed) { + message.cseq = parsed.value; + } + if (message instanceof core_1.IncomingResponseMessage) { + message.method = parsed.method; + } + break; + case "max-forwards": + message.setHeader("max-forwards", headerValue); + parsed = message.parseHeader("max-forwards"); + break; + case "www-authenticate": + message.setHeader("www-authenticate", headerValue); + parsed = message.parseHeader("www-authenticate"); + break; + case "proxy-authenticate": + message.setHeader("proxy-authenticate", headerValue); + parsed = message.parseHeader("proxy-authenticate"); + break; + case "refer-to": + case "r": + message.setHeader("refer-to", headerValue); + parsed = message.parseHeader("refer-to"); + if (parsed) { + message.referTo = parsed; + } + break; + default: + // Do not parse this header. + message.setHeader(headerName, headerValue); + parsed = 0; + } + if (parsed === undefined) { + return { + error: "error parsing header '" + headerName + "'" + }; + } + else { + return true; + } + } + Parser.parseHeader = parseHeader; + /** Parse SIP Message + * @function + * @param {String} message SIP message. + * @param {Object} logger object. + * @returns {SIP.IncomingRequest|SIP.IncomingResponse|undefined} + */ + function parseMessage(data, logger) { + var headerStart = 0; + var headerEnd = data.indexOf("\r\n"); + if (headerEnd === -1) { + logger.warn("no CRLF found, not a SIP message, discarded"); + return; + } + // Parse first line. Check if it is a Request or a Reply. + var firstLine = data.substring(0, headerEnd); + var parsed = core_1.Grammar.parse(firstLine, "Request_Response"); + var message; + if (parsed === -1) { + logger.warn('error parsing first line of SIP message: "' + firstLine + '"'); + return; + } + else if (!parsed.status_code) { + message = new core_1.IncomingRequestMessage(); + message.method = parsed.method; + message.ruri = parsed.uri; + } + else { + message = new core_1.IncomingResponseMessage(); + message.statusCode = parsed.status_code; + message.reasonPhrase = parsed.reason_phrase; + } + message.data = data; + headerStart = headerEnd + 2; + /* Loop over every line in data. Detect the end of each header and parse + * it or simply add to the headers collection. + */ + var bodyStart; + while (true) { + headerEnd = getHeader(data, headerStart); + // The SIP message has normally finished. + if (headerEnd === -2) { + bodyStart = headerStart + 2; + break; + } + else if (headerEnd === -1) { + // data.indexOf returned -1 due to a malformed message. + logger.error("malformed message"); + return; + } + var parsedHeader = parseHeader(message, data, headerStart, headerEnd); + if (parsedHeader !== true) { + logger.error(parsed.error); + return; + } + headerStart = headerEnd + 2; + } + /* RFC3261 18.3. + * If there are additional bytes in the transport packet + * beyond the end of the body, they MUST be discarded. + */ + if (message.hasHeader("content-length")) { + message.body = data.substr(bodyStart, Number(message.getHeader("content-length"))); + } + else { + message.body = data.substring(bodyStart); + } + return message; + } + Parser.parseMessage = parseMessage; +})(Parser = exports.Parser || (exports.Parser = {})); + + +/***/ }), +/* 85 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +/** + * SIP Publish (SIP Extension for Event State Publication RFC3903) + * @class Class creating a SIP PublishContext. + */ +var PublishContext = /** @class */ (function (_super) { + tslib_1.__extends(PublishContext, _super); + function PublishContext(ua, target, event, options) { + if (options === void 0) { options = {}; } + var _this = this; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.contentType = (options.contentType || "text/plain"); + if (typeof options.expires !== "number" || (options.expires % 1) !== 0) { + options.expires = 3600; + } + else { + options.expires = Number(options.expires); + } + if (typeof (options.unpublishOnClose) !== "boolean") { + options.unpublishOnClose = true; + } + if (target === undefined || target === null || target === "") { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Target", target); + } + else { + target = ua.normalizeTarget(target); + if (target === undefined) { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Target", target); + } + } + _this = _super.call(this, ua, Constants_1.C.PUBLISH, target, options) || this; + _this.type = Enums_1.TypeStrings.PublishContext; + _this.options = options; + _this.target = target; + if (event === undefined || event === null || event === "") { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Event", event); + } + else { + _this.event = event; + } + _this.logger = ua.getLogger("sip.publish"); + _this.pubRequestExpires = _this.options.expires; + ua.on("transportCreated", function (transport) { + transport.on("transportError", function () { return _this.onTransportError(); }); + }); + return _this; + } + /** + * Publish + * @param {string} Event body to publish, optional + */ + PublishContext.prototype.publish = function (body) { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + // is Inital or Modify request + this.options.body = body; + this.pubRequestBody = this.options.body; + if (this.pubRequestExpires === 0) { + // This is Initial request after unpublish + this.pubRequestExpires = this.options.expires; + this.pubRequestEtag = undefined; + } + if (!(this.ua.publishers[this.target.toString() + ":" + this.event])) { + this.ua.publishers[this.target.toString() + ":" + this.event] = this; + } + this.sendPublishRequest(); + }; + /** + * Unpublish + */ + PublishContext.prototype.unpublish = function () { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestExpires = 0; + if (this.pubRequestEtag !== undefined) { + this.sendPublishRequest(); + } + }; + /** + * Close + */ + PublishContext.prototype.close = function () { + // Send unpublish, if requested + if (this.options.unpublishOnClose) { + this.unpublish(); + } + else { + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestExpires = 0; + this.pubRequestEtag = undefined; + } + if (this.ua.publishers[this.target.toString() + ":" + this.event]) { + delete this.ua.publishers[this.target.toString() + ":" + this.event]; + } + }; + PublishContext.prototype.onRequestTimeout = function () { + _super.prototype.onRequestTimeout.call(this); + this.emit("unpublished", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + PublishContext.prototype.onTransportError = function () { + _super.prototype.onTransportError.call(this); + this.emit("unpublished", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + PublishContext.prototype.receiveResponse = function (response) { + var _this = this; + var statusCode = response.statusCode || 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + this.emit("progress", response, cause); + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + // Set SIP-Etag + if (response.hasHeader("SIP-ETag")) { + this.pubRequestEtag = response.getHeader("SIP-ETag"); + } + else { + this.logger.warn("SIP-ETag header missing in a 200-class response to PUBLISH"); + } + // Update Expire + if (response.hasHeader("Expires")) { + var expires = Number(response.getHeader("Expires")); + if (typeof expires === "number" && expires >= 0 && expires <= this.pubRequestExpires) { + this.pubRequestExpires = expires; + } + else { + this.logger.warn("Bad Expires header in a 200-class response to PUBLISH"); + } + } + else { + this.logger.warn("Expires header missing in a 200-class response to PUBLISH"); + } + if (this.pubRequestExpires !== 0) { + // Schedule refresh + this.publishRefreshTimer = setTimeout(function () { return _this.refreshRequest(); }, this.pubRequestExpires * 900); + this.emit("published", response, cause); + } + else { + this.emit("unpublished", response, cause); + } + break; + case /^412$/.test(statusCode.toString()): + // 412 code means no matching ETag - possibly the PUBLISH expired + // Resubmit as new request, if the current request is not a "remove" + if (this.pubRequestEtag !== undefined && this.pubRequestExpires !== 0) { + this.logger.warn("412 response to PUBLISH, recovering"); + this.pubRequestEtag = undefined; + this.emit("progress", response, cause); + this.publish(this.options.body); + } + else { + this.logger.warn("412 response to PUBLISH, recovery failed"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + break; + case /^423$/.test(statusCode.toString()): + // 423 code means we need to adjust the Expires interval up + if (this.pubRequestExpires !== 0 && response.hasHeader("Min-Expires")) { + var minExpires = Number(response.getHeader("Min-Expires")); + if (typeof minExpires === "number" || minExpires > this.pubRequestExpires) { + this.logger.warn("423 code in response to PUBLISH, adjusting the Expires value and trying to recover"); + this.pubRequestExpires = minExpires; + this.emit("progress", response, cause); + this.publish(this.options.body); + } + else { + this.logger.warn("Bad 423 response Min-Expires header received for PUBLISH"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + } + else { + this.logger.warn("423 response to PUBLISH, recovery failed"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + break; + default: + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + break; + } + // Do the cleanup + if (this.pubRequestExpires === 0) { + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestEtag = undefined; + } + }; + PublishContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.publish(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + PublishContext.prototype.refreshRequest = function () { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + // This is Refresh request + this.pubRequestBody = undefined; + if (this.pubRequestEtag === undefined) { + // Request not valid + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Body", undefined); + } + if (this.pubRequestExpires === 0) { + // Request not valid + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Expire", this.pubRequestExpires); + } + this.sendPublishRequest(); + }; + PublishContext.prototype.sendPublishRequest = function () { + var reqOptions = Object.create(this.options || Object.prototype); + reqOptions.extraHeaders = (this.options.extraHeaders || []).slice(); + reqOptions.extraHeaders.push("Event: " + this.event); + reqOptions.extraHeaders.push("Expires: " + this.pubRequestExpires); + if (this.pubRequestEtag !== undefined) { + reqOptions.extraHeaders.push("SIP-If-Match: " + this.pubRequestEtag); + } + var ruri = this.target instanceof core_1.URI ? this.target : this.ua.normalizeTarget(this.target); + if (!ruri) { + throw new Error("ruri undefined."); + } + var params = this.options.params || {}; + var bodyObj; + if (this.pubRequestBody !== undefined) { + bodyObj = { + body: this.pubRequestBody, + contentType: this.options.contentType + }; + } + var body; + if (bodyObj) { + body = Utils_1.Utils.fromBodyObj(bodyObj); + } + this.request = this.ua.userAgentCore.makeOutgoingRequestMessage(Constants_1.C.PUBLISH, ruri, params.fromUri ? params.fromUri : this.ua.userAgentCore.configuration.aor, params.toUri ? params.toUri : this.target, params, reqOptions.extraHeaders, body); + this.send(); + }; + return PublishContext; +}(ClientContext_1.ClientContext)); +exports.PublishContext = PublishContext; + + +/***/ }), +/* 86 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var ServerContext_1 = __webpack_require__(87); +// tslint:disable-next-line:max-classes-per-file +var ReferClientContext = /** @class */ (function (_super) { + tslib_1.__extends(ReferClientContext, _super); + function ReferClientContext(ua, applicant, target, options) { + if (options === void 0) { options = {}; } + var _this = this; + if (ua === undefined || applicant === undefined || target === undefined) { + throw new TypeError("Not enough arguments"); + } + _this = _super.call(this, ua, Constants_1.C.REFER, applicant.remoteIdentity.uri.toString(), options) || this; + _this.type = Enums_1.TypeStrings.ReferClientContext; + _this.options = options; + _this.extraHeaders = (_this.options.extraHeaders || []).slice(); + _this.applicant = applicant; + _this.target = _this.initReferTo(target); + if (_this.ua) { + _this.extraHeaders.push("Referred-By: <" + _this.ua.configuration.uri + ">"); + } + // TODO: Check that this is correct isc/icc + _this.extraHeaders.push("Contact: " + applicant.contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + _this.extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + _this.extraHeaders.push("Refer-To: " + _this.target); + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + ReferClientContext.prototype.refer = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var extraHeaders = (this.extraHeaders || []).slice(); + if (options.extraHeaders) { + extraHeaders.concat(options.extraHeaders); + } + this.applicant.sendRequest(Constants_1.C.REFER, { + extraHeaders: this.extraHeaders, + receiveResponse: function (response) { + var statusCode = response && response.statusCode ? response.statusCode.toString() : ""; + if (/^1[0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestProgress", _this); + } + else if (/^2[0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestAccepted", _this); + } + else if (/^[4-6][0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestRejected", _this); + } + if (options.receiveResponse) { + options.receiveResponse(response); + } + } + }); + return this; + }; + ReferClientContext.prototype.receiveNotify = function (request) { + // If we can correctly handle this, then we need to send a 200 OK! + var contentType = request.message.hasHeader("Content-Type") ? + request.message.getHeader("Content-Type") : undefined; + if (contentType && contentType.search(/^message\/sipfrag/) !== -1) { + var messageBody = core_1.Grammar.parse(request.message.body, "sipfrag"); + if (messageBody === -1) { + request.reject({ + statusCode: 489, + reasonPhrase: "Bad Event" + }); + return; + } + switch (true) { + case (/^1[0-9]{2}$/.test(messageBody.status_code)): + this.emit("referProgress", this); + break; + case (/^2[0-9]{2}$/.test(messageBody.status_code)): + this.emit("referAccepted", this); + if (!this.options.activeAfterTransfer && this.applicant.terminate) { + this.applicant.terminate(); + } + break; + default: + this.emit("referRejected", this); + break; + } + request.accept(); + this.emit("notify", request.message); + return; + } + request.reject({ + statusCode: 489, + reasonPhrase: "Bad Event" + }); + }; + ReferClientContext.prototype.initReferTo = function (target) { + var stringOrURI; + if (typeof target === "string") { + // REFER without Replaces (Blind Transfer) + var targetString = core_1.Grammar.parse(target, "Refer_To"); + stringOrURI = targetString && targetString.uri ? targetString.uri : target; + // Check target validity + var targetUri = this.ua.normalizeTarget(target); + if (!targetUri) { + throw new TypeError("Invalid target: " + target); + } + stringOrURI = targetUri; + } + else { + // REFER with Replaces (Attended Transfer) + if (!target.session) { + throw new Error("Session undefined."); + } + var displayName = target.remoteIdentity.friendlyName; + var remoteTarget = target.session.remoteTarget.toString(); + var callId = target.session.callId; + var remoteTag = target.session.remoteTag; + var localTag = target.session.localTag; + var replaces = encodeURIComponent(callId + ";to-tag=" + remoteTag + ";from-tag=" + localTag); + stringOrURI = "\"" + displayName + "\" <" + remoteTarget + "?Replaces=" + replaces + ">"; + } + return stringOrURI; + }; + return ReferClientContext; +}(ClientContext_1.ClientContext)); +exports.ReferClientContext = ReferClientContext; +// tslint:disable-next-line:max-classes-per-file +var ReferServerContext = /** @class */ (function (_super) { + tslib_1.__extends(ReferServerContext, _super); + function ReferServerContext(ua, incomingRequest, session) { + var _this = _super.call(this, ua, incomingRequest) || this; + _this.session = session; + _this.type = Enums_1.TypeStrings.ReferServerContext; + _this.ua = ua; + _this.status = Enums_1.SessionStatus.STATUS_INVITE_RECEIVED; + _this.fromTag = _this.request.fromTag; + _this.id = _this.request.callId + _this.fromTag; + _this.contact = _this.ua.contact.toString(); + _this.logger = ua.getLogger("sip.referservercontext", _this.id); + // Needed to send the NOTIFY's + _this.cseq = Math.floor(Math.random() * 10000); + _this.callId = _this.request.callId; + _this.fromUri = _this.request.to.uri; + _this.fromTag = _this.request.to.parameters.tag; + _this.remoteTarget = _this.request.headers.Contact[0].parsed.uri; + _this.toUri = _this.request.from.uri; + _this.toTag = _this.request.fromTag; + _this.routeSet = _this.request.getHeaders("record-route"); + // RFC 3515 2.4.1 + if (!_this.request.hasHeader("refer-to")) { + _this.logger.warn("Invalid REFER packet. A refer-to header is required. Rejecting refer."); + _this.reject(); + return _this; + } + _this.referTo = _this.request.parseHeader("refer-to"); + // TODO: Must set expiration timer and send 202 if there is no response by then + _this.referredSession = _this.ua.findSession(_this.request); + if (_this.request.hasHeader("referred-by")) { + _this.referredBy = _this.request.getHeader("referred-by"); + } + if (_this.referTo.uri.hasHeader("replaces")) { + _this.replaces = _this.referTo.uri.getHeader("replaces"); + } + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + return _this; + } + ReferServerContext.prototype.progress = function () { + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.incomingRequest.trying(); + }; + ReferServerContext.prototype.reject = function (options) { + if (options === void 0) { options = {}; } + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.logger.log("Rejecting refer"); + this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + _super.prototype.reject.call(this, options); + this.emit("referRequestRejected", this); + }; + ReferServerContext.prototype.accept = function (options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + } + else { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.incomingRequest.accept({ + statusCode: 202, + reasonPhrase: "Accepted" + }); + this.emit("referRequestAccepted", this); + if (options.followRefer) { + this.logger.log("Accepted refer, attempting to automatically follow it"); + var target = this.referTo.uri; + if (!target.scheme || !target.scheme.match("^sips?$")) { + this.logger.error("SIP.js can only automatically follow SIP refer target"); + this.reject(); + return; + } + var inviteOptions = options.inviteOptions || {}; + var extraHeaders = (inviteOptions.extraHeaders || []).slice(); + if (this.replaces) { + // decodeURIComponent is a holdover from 2c086eb4. Not sure that it is actually necessary + extraHeaders.push("Replaces: " + decodeURIComponent(this.replaces)); + } + if (this.referredBy) { + extraHeaders.push("Referred-By: " + this.referredBy); + } + inviteOptions.extraHeaders = extraHeaders; + target.clearHeaders(); + this.targetSession = this.ua.invite(target.toString(), inviteOptions, modifiers); + this.emit("referInviteSent", this); + if (this.targetSession) { + this.targetSession.once("progress", function (response) { + var statusCode = response.statusCode || 100; + var reasonPhrase = response.reasonPhrase; + _this.sendNotify(("SIP/2.0 " + statusCode + " " + reasonPhrase).trim()); + _this.emit("referProgress", _this); + if (_this.referredSession) { + _this.referredSession.emit("referProgress", _this); + } + }); + this.targetSession.once("accepted", function () { + _this.logger.log("Successfully followed the refer"); + _this.sendNotify("SIP/2.0 200 OK"); + _this.emit("referAccepted", _this); + if (_this.referredSession) { + _this.referredSession.emit("referAccepted", _this); + } + }); + var referFailed = function (response) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; // No throw here because it is possible this gets called multiple times + } + _this.logger.log("Refer was not successful. Resuming session"); + if (response && response.statusCode === 429) { + _this.logger.log("Alerting referrer that identity is required."); + _this.sendNotify("SIP/2.0 429 Provide Referrer Identity"); + return; + } + _this.sendNotify("SIP/2.0 603 Declined"); + // Must change the status after sending the final Notify or it will not send due to check + _this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + _this.emit("referRejected", _this); + if (_this.referredSession) { + _this.referredSession.emit("referRejected"); + } + }; + this.targetSession.once("rejected", referFailed); + this.targetSession.once("failed", referFailed); + } + } + else { + this.logger.log("Accepted refer, but did not automatically follow it"); + this.sendNotify("SIP/2.0 200 OK"); + this.emit("referAccepted", this); + if (this.referredSession) { + this.referredSession.emit("referAccepted", this); + } + } + }; + ReferServerContext.prototype.sendNotify = function (bodyStr) { + // FIXME: Ported this. Clean it up. Session knows its state. + if (this.status !== Enums_1.SessionStatus.STATUS_ANSWERED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (core_1.Grammar.parse(bodyStr, "sipfrag") === -1) { + throw new Error("sipfrag body is required to send notify for refer"); + } + var body = { + contentDisposition: "render", + contentType: "message/sipfrag", + content: bodyStr + }; + // NOTIFY requests sent in same dialog as in dialog REFER. + if (this.session) { + this.session.notify(undefined, { + extraHeaders: [ + "Event: refer", + "Subscription-State: terminated", + ], + body: body + }); + return; + } + // The implicit subscription created by a REFER is the same as a + // subscription created with a SUBSCRIBE request. The agent issuing the + // REFER can terminate this subscription prematurely by unsubscribing + // using the mechanisms described in [2]. Terminating a subscription, + // either by explicitly unsubscribing or rejecting NOTIFY, is not an + // indication that the referenced request should be withdrawn or + // abandoned. + // https://tools.ietf.org/html/rfc3515#section-2.4.4 + // NOTIFY requests sent in new dialog for out of dialog REFER. + // FIXME: TODO: This should be done in a subscribe dialog to satisfy the above. + var request = this.ua.userAgentCore.makeOutgoingRequestMessage(Constants_1.C.NOTIFY, this.remoteTarget, this.fromUri, this.toUri, { + cseq: this.cseq += 1, + callId: this.callId, + fromTag: this.fromTag, + toTag: this.toTag, + routeSet: this.routeSet + }, [ + "Event: refer", + "Subscription-State: terminated", + "Content-Type: message/sipfrag" + ], body); + var transport = this.ua.transport; + if (!transport) { + throw new Error("Transport undefined."); + } + var user = { + loggerFactory: this.ua.getLoggerFactory() + }; + var nic = new core_1.NonInviteClientTransaction(request, transport, user); + }; + ReferServerContext.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + return ReferServerContext; +}(ServerContext_1.ServerContext)); +exports.ReferServerContext = ReferServerContext; + + +/***/ }), +/* 87 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Utils_1 = __webpack_require__(82); +var ServerContext = /** @class */ (function (_super) { + tslib_1.__extends(ServerContext, _super); + function ServerContext(ua, incomingRequest) { + var _this = _super.call(this) || this; + _this.incomingRequest = incomingRequest; + _this.data = {}; + ServerContext.initializer(_this, ua, incomingRequest); + return _this; + } + // hack to get around our multiple inheritance issues + ServerContext.initializer = function (objectToConstruct, ua, incomingRequest) { + var request = incomingRequest.message; + objectToConstruct.type = Enums_1.TypeStrings.ServerContext; + objectToConstruct.ua = ua; + objectToConstruct.logger = ua.getLogger("sip.servercontext"); + objectToConstruct.request = request; + if (request.body) { + objectToConstruct.body = request.body; + } + if (request.hasHeader("Content-Type")) { + objectToConstruct.contentType = request.getHeader("Content-Type"); + } + objectToConstruct.method = request.method; + objectToConstruct.localIdentity = request.to; + objectToConstruct.remoteIdentity = request.from; + var hasAssertedIdentity = request.hasHeader("P-Asserted-Identity"); + if (hasAssertedIdentity) { + var assertedIdentity = request.getHeader("P-Asserted-Identity"); + if (assertedIdentity) { + objectToConstruct.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(assertedIdentity); + } + } + }; + ServerContext.prototype.progress = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 180; + options.minCode = 100; + options.maxCode = 199; + options.events = ["progress"]; + return this.reply(options); + }; + ServerContext.prototype.accept = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 200; + options.minCode = 200; + options.maxCode = 299; + options.events = ["accepted"]; + return this.reply(options); + }; + ServerContext.prototype.reject = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 480; + options.minCode = 300; + options.maxCode = 699; + options.events = ["rejected", "failed"]; + return this.reply(options); + }; + ServerContext.prototype.reply = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 100; + var minCode = options.minCode || 100; + var maxCode = options.maxCode || 699; + var reasonPhrase = Utils_1.Utils.getReasonPhrase(statusCode, options.reasonPhrase); + var extraHeaders = options.extraHeaders || []; + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + var events = options.events || []; + if (statusCode < minCode || statusCode > maxCode) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var responseOptions = { + statusCode: statusCode, + reasonPhrase: reasonPhrase, + extraHeaders: extraHeaders, + body: body + }; + var response; + var statusCodeString = statusCode.toString(); + switch (true) { + case /^100$/.test(statusCodeString): + response = this.incomingRequest.trying(responseOptions).message; + break; + case /^1[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.progress(responseOptions).message; + break; + case /^2[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.accept(responseOptions).message; + break; + case /^3[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.redirect([], responseOptions).message; + break; + case /^[4-6][0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.reject(responseOptions).message; + break; + default: + throw new Error("Invalid status code " + statusCode); + } + events.forEach(function (event) { + _this.emit(event, response, reasonPhrase); + }); + return this; + }; + ServerContext.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + ServerContext.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + return ServerContext; +}(events_1.EventEmitter)); +exports.ServerContext = ServerContext; + + +/***/ }), +/* 88 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +/** + * Configuration load. + * @private + * returns {any} + */ +function loadConfig(configuration) { + var settings = { + expires: 600, + extraContactHeaderParams: [], + instanceId: undefined, + params: {}, + regId: undefined, + registrar: undefined, + }; + var configCheck = getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + if (value instanceof Array && value.length === 0) { + continue; + } + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if (value === null || value === "" || value === undefined || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + return settings; +} +function getConfigurationCheck() { + return { + mandatory: {}, + optional: { + expires: function (expires) { + if (Utils_1.Utils.isDecimal(expires)) { + var value = Number(expires); + if (value >= 0) { + return value; + } + } + }, + extraContactHeaderParams: function (extraContactHeaderParams) { + if (extraContactHeaderParams instanceof Array) { + return extraContactHeaderParams.filter(function (contactHeaderParam) { return (typeof contactHeaderParam === "string"); }); + } + }, + instanceId: function (instanceId) { + if (typeof instanceId !== "string") { + return; + } + if ((/^uuid:/i.test(instanceId))) { + instanceId = instanceId.substr(5); + } + if (core_1.Grammar.parse(instanceId, "uuid") === -1) { + return; + } + else { + return instanceId; + } + }, + params: function (params) { + if (typeof params === "object") { + return params; + } + }, + regId: function (regId) { + if (Utils_1.Utils.isDecimal(regId)) { + var value = Number(regId); + if (value >= 0) { + return value; + } + } + }, + registrar: function (registrar) { + if (typeof registrar !== "string") { + return; + } + if (!/^sip:/i.test(registrar)) { + registrar = Constants_1.C.SIP + ":" + registrar; + } + var parsed = core_1.Grammar.URIParse(registrar); + if (!parsed) { + return; + } + else if (parsed.user) { + return; + } + else { + return parsed; + } + } + } + }; +} +var RegisterContext = /** @class */ (function (_super) { + tslib_1.__extends(RegisterContext, _super); + function RegisterContext(ua, options) { + if (options === void 0) { options = {}; } + var _this = this; + var settings = loadConfig(options); + if (settings.regId && !settings.instanceId) { + settings.instanceId = Utils_1.Utils.newUUID(); + } + else if (!settings.regId && settings.instanceId) { + settings.regId = 1; + } + settings.params.toUri = settings.params.toUri || ua.configuration.uri; + settings.params.toDisplayName = settings.params.toDisplayName || ua.configuration.displayName; + settings.params.callId = settings.params.callId || Utils_1.Utils.createRandomToken(22); + settings.params.cseq = settings.params.cseq || Math.floor(Math.random() * 10000); + /* If no 'registrarServer' is set use the 'uri' value without user portion. */ + if (!settings.registrar) { + var registrarServer = {}; + if (typeof ua.configuration.uri === "object") { + registrarServer = ua.configuration.uri.clone(); + registrarServer.user = undefined; + } + else { + registrarServer = ua.configuration.uri; + } + settings.registrar = registrarServer; + } + _this = _super.call(this, ua, Constants_1.C.REGISTER, settings.registrar, settings) || this; + _this.type = Enums_1.TypeStrings.RegisterContext; + _this.options = settings; + _this.logger = ua.getLogger("sip.registercontext"); + _this.logger.log("configuration parameters for RegisterContext after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + _this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + // Registration expires + _this.expires = settings.expires; + // Contact header + _this.contact = ua.contact.toString(); + // Set status + _this.registered = false; + ua.on("transportCreated", function (transport) { + transport.on("disconnected", function () { return _this.onTransportDisconnected(); }); + }); + return _this; + } + RegisterContext.prototype.register = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Handle Options + this.options = tslib_1.__assign({}, this.options, options); + var extraHeaders = (this.options.extraHeaders || []).slice(); + extraHeaders.push("Contact: " + this.generateContactHeader(this.expires)); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + // Save original extraHeaders to be used in .close + this.closeHeaders = this.options.closeWithHeaders ? + (this.options.extraHeaders || []).slice() : []; + this.receiveResponse = function (response) { + // Discard responses to older REGISTER/un-REGISTER requests. + if (response.cseq !== _this.request.cseq) { + return; + } + // Clear registration timer + if (_this.registrationTimer !== undefined) { + clearTimeout(_this.registrationTimer); + _this.registrationTimer = undefined; + } + var statusCode = (response.statusCode || 0).toString(); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode): + _this.emit("progress", response); + break; + case /^2[0-9]{2}$/.test(statusCode): + _this.emit("accepted", response); + var expires = void 0; + if (response.hasHeader("expires")) { + expires = Number(response.getHeader("expires")); + } + if (_this.registrationExpiredTimer !== undefined) { + clearTimeout(_this.registrationExpiredTimer); + _this.registrationExpiredTimer = undefined; + } + // Search the Contact pointing to us and update the expires value accordingly. + var contacts = response.getHeaders("contact").length; + if (!contacts) { + _this.logger.warn("no Contact header in response to REGISTER, response ignored"); + break; + } + var contact = void 0; + while (contacts--) { + contact = response.parseHeader("contact", contacts); + if (contact.uri.user === _this.ua.contact.uri.user) { + expires = contact.getParam("expires"); + break; + } + else { + contact = undefined; + } + } + if (!contact) { + _this.logger.warn("no Contact header pointing to us, response ignored"); + break; + } + if (expires === undefined) { + expires = _this.expires; + } + // Re-Register before the expiration interval has elapsed. + // For that, decrease the expires value. ie: 3 seconds + _this.registrationTimer = setTimeout(function () { + _this.registrationTimer = undefined; + _this.register(_this.options); + }, (expires * 1000) - 3000); + _this.registrationExpiredTimer = setTimeout(function () { + _this.logger.warn("registration expired"); + if (_this.registered) { + _this.unregistered(undefined, Constants_1.C.causes.EXPIRES); + } + }, expires * 1000); + // Save gruu values + if (contact.hasParam("temp-gruu")) { + _this.ua.contact.tempGruu = core_1.Grammar.URIParse(contact.getParam("temp-gruu").replace(/"/g, "")); + } + if (contact.hasParam("pub-gruu")) { + _this.ua.contact.pubGruu = core_1.Grammar.URIParse(contact.getParam("pub-gruu").replace(/"/g, "")); + } + _this.registered = true; + _this.emit("registered", response || undefined); + break; + // Interval too brief RFC3261 10.2.8 + case /^423$/.test(statusCode): + if (response.hasHeader("min-expires")) { + // Increase our registration interval to the suggested minimum + _this.expires = Number(response.getHeader("min-expires")); + // Attempt the registration again immediately + _this.register(_this.options); + } + else { // This response MUST contain a Min-Expires header field + _this.logger.warn("423 response received for REGISTER without Min-Expires"); + _this.registrationFailure(response, Constants_1.C.causes.SIP_FAILURE_CODE); + } + break; + default: + _this.registrationFailure(response, Utils_1.Utils.sipErrorCause(response.statusCode || 0)); + } + }; + this.onRequestTimeout = function () { + _this.registrationFailure(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + this.onTransportError = function () { + _this.registrationFailure(undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + this.request.cseq++; + this.request.setHeader("cseq", this.request.cseq + " REGISTER"); + this.request.extraHeaders = extraHeaders; + this.send(); + }; + RegisterContext.prototype.close = function () { + var options = { + all: false, + extraHeaders: this.closeHeaders + }; + this.registeredBefore = this.registered; + if (this.registered) { + this.unregister(options); + } + }; + RegisterContext.prototype.unregister = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (!this.registered && !options.all) { + this.logger.warn("Already unregistered, but sending an unregister anyways."); + } + var extraHeaders = (options.extraHeaders || []).slice(); + this.registered = false; + // Clear the registration timer. + if (this.registrationTimer !== undefined) { + clearTimeout(this.registrationTimer); + this.registrationTimer = undefined; + } + if (options.all) { + extraHeaders.push("Contact: *"); + extraHeaders.push("Expires: 0"); + } + else { + extraHeaders.push("Contact: " + this.generateContactHeader(0)); + } + this.receiveResponse = function (response) { + var statusCode = (response && response.statusCode) ? response.statusCode.toString() : ""; + switch (true) { + case /^1[0-9]{2}$/.test(statusCode): + _this.emit("progress", response); + break; + case /^2[0-9]{2}$/.test(statusCode): + _this.emit("accepted", response); + if (_this.registrationExpiredTimer !== undefined) { + clearTimeout(_this.registrationExpiredTimer); + _this.registrationExpiredTimer = undefined; + } + _this.unregistered(response); + break; + default: + _this.unregistered(response, Utils_1.Utils.sipErrorCause(response.statusCode || 0)); + } + }; + this.onRequestTimeout = function () { + // Not actually unregistered... + // this.unregistered(undefined, SIP.C.causes.REQUEST_TIMEOUT); + }; + this.request.cseq++; + this.request.setHeader("cseq", this.request.cseq + " REGISTER"); + this.request.extraHeaders = extraHeaders; + this.send(); + }; + RegisterContext.prototype.unregistered = function (response, cause) { + this.registered = false; + this.emit("unregistered", response || undefined, cause || undefined); + }; + RegisterContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.register(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + RegisterContext.prototype.registrationFailure = function (response, cause) { + this.emit("failed", response || undefined, cause || undefined); + }; + RegisterContext.prototype.onTransportDisconnected = function () { + this.registeredBefore = this.registered; + if (this.registrationTimer !== undefined) { + clearTimeout(this.registrationTimer); + this.registrationTimer = undefined; + } + if (this.registrationExpiredTimer !== undefined) { + clearTimeout(this.registrationExpiredTimer); + this.registrationExpiredTimer = undefined; + } + if (this.registered) { + this.unregistered(undefined, Constants_1.C.causes.CONNECTION_ERROR); + } + }; + /** + * Helper Function to generate Contact Header + * @private + * returns {String} + */ + RegisterContext.prototype.generateContactHeader = function (expires) { + if (expires === void 0) { expires = 0; } + var contact = this.contact; + if (this.options.regId && this.options.instanceId) { + contact += ";reg-id=" + this.options.regId; + contact += ';+sip.instance=""'; + } + if (this.options.extraContactHeaderParams) { + this.options.extraContactHeaderParams.forEach(function (header) { + contact += ";" + header; + }); + } + contact += ";expires=" + expires; + return contact; + }; + return RegisterContext; +}(ClientContext_1.ClientContext)); +exports.RegisterContext = RegisterContext; + + +/***/ }), +/* 89 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var ReferContext_1 = __webpack_require__(86); +var ServerContext_1 = __webpack_require__(87); +var DTMF_1 = __webpack_require__(90); +var Utils_1 = __webpack_require__(82); +/* + * @param {function returning SIP.sessionDescriptionHandler} [sessionDescriptionHandlerFactory] + * (See the documentation for the sessionDescriptionHandlerFactory argument of the UA constructor.) + */ +var Session = /** @class */ (function (_super) { + tslib_1.__extends(Session, _super); + function Session(sessionDescriptionHandlerFactory) { + var _this = _super.call(this) || this; + _this.data = {}; + _this.type = Enums_1.TypeStrings.Session; + if (!sessionDescriptionHandlerFactory) { + throw new Exceptions_1.Exceptions.SessionDescriptionHandlerError("A session description handler is required for the session to function"); + } + _this.status = Session.C.STATUS_NULL; + _this.pendingReinvite = false; + _this.sessionDescriptionHandlerFactory = sessionDescriptionHandlerFactory; + _this.hasOffer = false; + _this.hasAnswer = false; + // Session Timers + _this.timers = { + ackTimer: undefined, + expiresTimer: undefined, + invite2xxTimer: undefined, + userNoAnswerTimer: undefined, + rel1xxTimer: undefined, + prackTimer: undefined + }; + // Session info + _this.startTime = undefined; + _this.endTime = undefined; + _this.tones = undefined; + // Hold state + _this.localHold = false; + _this.earlySdp = undefined; + _this.rel100 = Constants_1.C.supported.UNSUPPORTED; + return _this; + } + Session.prototype.dtmf = function (tones, options) { + var _this = this; + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + // Check tones + if (!tones || !tones.toString().match(/^[0-9A-D#*,]+$/i)) { + throw new TypeError("Invalid tones: " + tones); + } + var sendDTMF = function () { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED || !_this.tones || _this.tones.length === 0) { + // Stop sending DTMF + _this.tones = undefined; + return; + } + var dtmf = _this.tones.shift(); + var timeout; + if (dtmf.tone === ",") { + timeout = 2000; + } + else { + dtmf.on("failed", function () { _this.tones = undefined; }); + dtmf.send(options); + timeout = dtmf.duration + dtmf.interToneGap; + } + // Set timeout for the next tone + setTimeout(sendDTMF, timeout); + }; + tones = tones.toString(); + var dtmfType = this.ua.configuration.dtmfType; + if (this.sessionDescriptionHandler && dtmfType === Constants_1.C.dtmfType.RTP) { + var sent = this.sessionDescriptionHandler.sendDtmf(tones, options); + if (!sent) { + this.logger.warn("Attempt to use dtmfType 'RTP' has failed, falling back to INFO packet method"); + dtmfType = Constants_1.C.dtmfType.INFO; + } + } + if (dtmfType === Constants_1.C.dtmfType.INFO) { + var dtmfs = []; + var tonesArray = tones.split(""); + while (tonesArray.length > 0) { + dtmfs.push(new DTMF_1.DTMF(this, tonesArray.shift(), options)); + } + if (this.tones) { + // Tones are already queued, just add to the queue + this.tones = this.tones.concat(dtmfs); + return this; + } + this.tones = dtmfs; + sendDTMF(); + } + return this; + }; + Session.prototype.bye = function (options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + this.logger.error("Error: Attempted to send BYE in a terminated session."); + return this; + } + this.logger.log("terminating Session"); + var statusCode = options.statusCode; + if (statusCode && (statusCode < 200 || statusCode >= 700)) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + options.receiveResponse = function () { }; + return this.sendRequest(Constants_1.C.BYE, options).terminated(); + }; + Session.prototype.refer = function (target, options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.referContext = new ReferContext_1.ReferClientContext(this.ua, this, target, options); + this.emit("referRequested", this.referContext); + this.referContext.refer(options); + return this.referContext; + }; + /** + * Sends in dialog request. + * @param method Request method. + * @param options Options bucket. + */ + Session.prototype.sendRequest = function (method, options) { + if (options === void 0) { options = {}; } + if (!this.session) { + throw new Error("Session undefined."); + } + // Convert any "body" option to a Body. + if (options.body) { + options.body = Utils_1.Utils.fromBodyObj(options.body); + } + // Convert any "receiveResponse" callback option passed to an OutgoingRequestDelegate. + var delegate; + var callback = options.receiveResponse; + if (callback) { + delegate = { + onAccept: function (response) { return callback(response.message); }, + onProgress: function (response) { return callback(response.message); }, + onRedirect: function (response) { return callback(response.message); }, + onReject: function (response) { return callback(response.message); }, + onTrying: function (response) { return callback(response.message); } + }; + } + var request; + var requestOptions = options; + switch (method) { + case Constants_1.C.BYE: + request = this.session.bye(delegate, requestOptions); + break; + case Constants_1.C.INVITE: + request = this.session.invite(delegate, requestOptions); + break; + case Constants_1.C.REFER: + request = this.session.refer(delegate, requestOptions); + break; + default: + throw new Error("Unexpected " + method + ". Method not implemented by user agent core."); + } + // Ported - Emit the request event + this.emit(method.toLowerCase(), request.message); + return this; + }; + Session.prototype.close = function () { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.logger.log("closing INVITE session " + this.id); + // 1st Step. Terminate media. + if (this.sessionDescriptionHandler) { + this.sessionDescriptionHandler.close(); + } + // 2nd Step. Terminate signaling. + // Clear session timers + for (var timer in this.timers) { + if (this.timers[timer]) { + clearTimeout(this.timers[timer]); + } + } + this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + if (this.ua.transport) { + this.ua.transport.removeListener("transportError", this.errorListener); + } + delete this.ua.sessions[this.id]; + return this; + }; + Session.prototype.hold = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK && this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (this.localHold) { + this.logger.log("Session is already on hold, cannot put it on hold again"); + return; + } + options.modifiers = modifiers; + if (this.sessionDescriptionHandler) { + options.modifiers.push(this.sessionDescriptionHandler.holdModifier); + } + this.localHold = true; + this.sendReinvite(options); + }; + Session.prototype.unhold = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK && this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (!this.localHold) { + this.logger.log("Session is not on hold, cannot unhold it"); + return; + } + options.modifiers = modifiers; + this.localHold = false; + this.sendReinvite(options); + }; + Session.prototype.reinvite = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + options.modifiers = modifiers; + return this.sendReinvite(options); + }; + Session.prototype.terminate = function (options) { + // here for types and to be overridden + return this; + }; + Session.prototype.onTransportError = function () { + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(undefined, Constants_1.C.causes.CONNECTION_ERROR); + } + }; + Session.prototype.onRequestTimeout = function () { + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.terminated(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + } + else if (this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + this.terminated(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + } + }; + Session.prototype.onDialogError = function (response) { + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.terminated(response, Constants_1.C.causes.DIALOG_ERROR); + } + else if (this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(response, Constants_1.C.causes.DIALOG_ERROR); + this.terminated(response, Constants_1.C.causes.DIALOG_ERROR); + } + }; + Session.prototype.on = function (name, callback) { + return _super.prototype.on.call(this, name, callback); + }; + Session.prototype.onAck = function (incomingRequest) { + var _this = this; + var confirmSession = function () { + clearTimeout(_this.timers.ackTimer); + clearTimeout(_this.timers.invite2xxTimer); + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var contentDisp = incomingRequest.message.getHeader("Content-Disposition"); + if (contentDisp && contentDisp.type === "render") { + _this.renderbody = incomingRequest.message.body; + _this.rendertype = incomingRequest.message.getHeader("Content-Type"); + } + _this.emit("confirmed", incomingRequest.message); + }; + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + if (this.sessionDescriptionHandler && + this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + this.hasAnswer = true; + this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).catch(function (e) { + _this.logger.warn(e); + _this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + _this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + _this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + throw e; + }).then(function () { return confirmSession(); }); + } + else { + confirmSession(); + } + } + }; + Session.prototype.receiveRequest = function (incomingRequest) { + switch (incomingRequest.message.method) { // TODO: This needs a default case + case Constants_1.C.BYE: + incomingRequest.accept(); + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.emit("bye", incomingRequest.message); + this.terminated(incomingRequest.message, Constants_1.C.BYE); + } + break; + case Constants_1.C.INVITE: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.logger.log("re-INVITE received"); + this.receiveReinvite(incomingRequest); + } + break; + case Constants_1.C.INFO: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED || this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + if (this.onInfo) { + return this.onInfo(incomingRequest.message); + } + var contentType = incomingRequest.message.getHeader("content-type"); + if (contentType) { + if (contentType.match(/^application\/dtmf-relay/i)) { + if (incomingRequest.message.body) { + var body = incomingRequest.message.body.split("\r\n", 2); + if (body.length === 2) { + var tone = void 0; + var duration = void 0; + var regTone = /^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/; + if (regTone.test(body[0])) { + tone = body[0].replace(regTone, "$2"); + } + var regDuration = /^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/; + if (regDuration.test(body[1])) { + duration = parseInt(body[1].replace(regDuration, "$2"), 10); + } + if (tone && duration) { + new DTMF_1.DTMF(this, tone, { duration: duration }).init_incoming(incomingRequest); + } + } + } + } + else { + incomingRequest.reject({ + statusCode: 415, + extraHeaders: ["Accept: application/dtmf-relay"] + }); + } + } + } + break; + case Constants_1.C.REFER: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.logger.log("REFER received"); + this.referContext = new ReferContext_1.ReferServerContext(this.ua, incomingRequest, this.session); + if (this.listeners("referRequested").length) { + this.emit("referRequested", this.referContext); + } + else { + this.logger.log("No referRequested listeners, automatically accepting and following the refer"); + var options = { followRefer: true }; + if (this.passedOptions) { + options.inviteOptions = this.passedOptions; + } + this.referContext.accept(options, this.modifiers); + } + } + break; + case Constants_1.C.NOTIFY: + if (this.referContext && + this.referContext.type === Enums_1.TypeStrings.ReferClientContext && + incomingRequest.message.hasHeader("event") && + /^refer(;.*)?$/.test(incomingRequest.message.getHeader("event"))) { + this.referContext.receiveNotify(incomingRequest); + return; + } + incomingRequest.accept(); + this.emit("notify", incomingRequest.message); + break; + } + }; + // In dialog INVITE Reception + Session.prototype.receiveReinvite = function (incomingRequest) { + // TODO: Should probably check state of the session + var _this = this; + this.emit("reinvite", this, incomingRequest.message); + if (incomingRequest.message.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = + core_1.Grammar.nameAddrHeaderParse(incomingRequest.message.getHeader("P-Asserted-Identity")); + } + var promise; + if (!this.sessionDescriptionHandler) { + this.logger.warn("No SessionDescriptionHandler to reinvite"); + return; + } + if (incomingRequest.message.getHeader("Content-Length") === "0" && + !incomingRequest.message.getHeader("Content-Type")) { // Invite w/o SDP + promise = this.sessionDescriptionHandler.getDescription(this.sessionDescriptionHandlerOptions, this.modifiers); + } + else if (this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + // Invite w/ SDP + promise = this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).then(this.sessionDescriptionHandler.getDescription.bind(this.sessionDescriptionHandler, this.sessionDescriptionHandlerOptions, this.modifiers)); + } + else { // Bad Packet (should never get hit) + incomingRequest.reject({ statusCode: 415 }); + this.emit("reinviteFailed", this); + return; + } + promise.catch(function (e) { + var statusCode; + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + statusCode = 500; + } + else if (e.type === Enums_1.TypeStrings.RenegotiationError) { + _this.emit("renegotiationError", e); + _this.logger.warn(e.toString()); + statusCode = 488; + } + else { + _this.logger.error(e); + statusCode = 488; + } + incomingRequest.reject({ statusCode: statusCode }); + _this.emit("reinviteFailed", _this); + // TODO: This could be better + throw e; + }).then(function (description) { + var extraHeaders = ["Contact: " + _this.contact]; + incomingRequest.accept({ + statusCode: 200, + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(description) + }); + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK; + _this.emit("reinviteAccepted", _this); + }); + }; + Session.prototype.sendReinvite = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.pendingReinvite) { + this.logger.warn("Reinvite in progress. Please wait until complete, then try again."); + return; + } + if (!this.sessionDescriptionHandler) { + this.logger.warn("No SessionDescriptionHandler, can't reinvite.."); + return; + } + this.pendingReinvite = true; + options.modifiers = options.modifiers || []; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Contact: " + this.contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + this.sessionDescriptionHandler.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function (description) { + if (!_this.session) { + throw new Error("Session undefined."); + } + var delegate = { + onAccept: function (response) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + _this.logger.error("Received reinvite response, but in STATUS_TERMINATED"); + // TODO: Do we need to send a SIP response? + return; + } + if (!_this.pendingReinvite) { + _this.logger.error("Received reinvite response, but have no pending reinvite"); + // TODO: Do we need to send a SIP response? + return; + } + // FIXME: Why is this set here? + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + // 17.1.1.1 - For each final response that is received at the client transaction, + // the client transaction sends an ACK, + _this.emit("ack", response.ack()); + _this.pendingReinvite = false; + // TODO: All of these timers should move into the Transaction layer + clearTimeout(_this.timers.invite2xxTimer); + if (!_this.sessionDescriptionHandler || + !_this.sessionDescriptionHandler.hasDescription(response.message.getHeader("Content-Type") || "")) { + _this.logger.error("2XX response received to re-invite but did not have a description"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("2XX response received to re-invite but did not have a description")); + return; + } + _this.sessionDescriptionHandler + .setDescription(response.message.body, _this.sessionDescriptionHandlerOptions, _this.modifiers) + .catch(function (e) { + _this.logger.error("Could not set the description in 2XX response"); + _this.logger.error(e); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", e); + _this.sendRequest(Constants_1.C.BYE, { + extraHeaders: ["Reason: " + Utils_1.Utils.getReasonHeaderValue(488, "Not Acceptable Here")] + }); + _this.terminated(undefined, Constants_1.C.causes.INCOMPATIBLE_SDP); + throw e; + }) + .then(function () { + _this.emit("reinviteAccepted", _this); + }); + }, + onProgress: function (response) { + return; + }, + onRedirect: function (response) { + // FIXME: Does ACK need to be sent? + _this.pendingReinvite = false; + _this.logger.log("Received a non 1XX or 2XX response to a re-invite"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("Invalid response to a re-invite")); + }, + onReject: function (response) { + // FIXME: Does ACK need to be sent? + _this.pendingReinvite = false; + _this.logger.log("Received a non 1XX or 2XX response to a re-invite"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("Invalid response to a re-invite")); + }, + onTrying: function (response) { + return; + } + }; + var requestOptions = { + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(description) + }; + _this.session.invite(delegate, requestOptions); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.RenegotiationError) { + _this.pendingReinvite = false; + _this.emit("renegotiationError", e); + _this.logger.warn("Renegotiation Error"); + _this.logger.warn(e.toString()); + throw e; + } + _this.logger.error("sessionDescriptionHandler error"); + _this.logger.error(e); + throw e; + }); + }; + Session.prototype.failed = function (response, cause) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.emit("failed", response, cause); + return this; + }; + Session.prototype.rejected = function (response, cause) { + this.emit("rejected", response, cause); + return this; + }; + Session.prototype.canceled = function () { + if (this.sessionDescriptionHandler) { + this.sessionDescriptionHandler.close(); + } + this.emit("cancel"); + return this; + }; + Session.prototype.accepted = function (response, cause) { + if (!(response instanceof String)) { + cause = Utils_1.Utils.getReasonPhrase((response && response.statusCode) || 0, cause); + } + this.startTime = new Date(); + if (this.replacee) { + this.replacee.emit("replaced", this); + this.replacee.terminate(); + } + this.emit("accepted", response, cause); + return this; + }; + Session.prototype.terminated = function (message, cause) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.endTime = new Date(); + this.close(); + this.emit("terminated", message, cause); + return this; + }; + Session.prototype.connecting = function (request) { + this.emit("connecting", { request: request }); + return this; + }; + Session.C = Enums_1.SessionStatus; + return Session; +}(events_1.EventEmitter)); +exports.Session = Session; +// tslint:disable-next-line:max-classes-per-file +var InviteServerContext = /** @class */ (function (_super) { + tslib_1.__extends(InviteServerContext, _super); + function InviteServerContext(ua, incomingInviteRequest) { + var _this = this; + if (!ua.configuration.sessionDescriptionHandlerFactory) { + ua.logger.warn("Can't build ISC without SDH Factory"); + throw new Error("ISC Constructor Failed"); + } + _this = _super.call(this, ua.configuration.sessionDescriptionHandlerFactory) || this; + _this._canceled = false; + _this.rseq = Math.floor(Math.random() * 10000); + _this.incomingRequest = incomingInviteRequest; + var request = incomingInviteRequest.message; + ServerContext_1.ServerContext.initializer(_this, ua, incomingInviteRequest); + _this.type = Enums_1.TypeStrings.InviteServerContext; + var contentDisp = request.parseHeader("Content-Disposition"); + if (contentDisp && contentDisp.type === "render") { + _this.renderbody = request.body; + _this.rendertype = request.getHeader("Content-Type"); + } + _this.status = Enums_1.SessionStatus.STATUS_INVITE_RECEIVED; + _this.fromTag = request.fromTag; + _this.id = request.callId + _this.fromTag; + _this.request = request; + _this.contact = _this.ua.contact.toString(); + _this.logger = ua.getLogger("sip.inviteservercontext", _this.id); + // Save the session into the ua sessions collection. + _this.ua.sessions[_this.id] = _this; + // Set 100rel if necessary + var set100rel = function (header, relSetting) { + if (request.hasHeader(header) && request.getHeader(header).toLowerCase().indexOf("100rel") >= 0) { + _this.rel100 = relSetting; + } + }; + set100rel("require", Constants_1.C.supported.REQUIRED); + set100rel("supported", Constants_1.C.supported.SUPPORTED); + // Set the toTag on the incoming request to the toTag which + // will be used in the response to the incoming request!!! + // FIXME: HACK: This is a hack to port an existing behavior. + // The behavior being ported appears to be a hack itself, + // so this is a hack to port a hack. At least one test spec + // relies on it (which is yet another hack). + _this.request.toTag = incomingInviteRequest.toTag; + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + // Set userNoAnswerTimer + _this.timers.userNoAnswerTimer = setTimeout(function () { + incomingInviteRequest.reject({ statusCode: 408 }); + _this.failed(request, Constants_1.C.causes.NO_ANSWER); + _this.terminated(request, Constants_1.C.causes.NO_ANSWER); + }, _this.ua.configuration.noAnswerTimeout || 60); + /* Set expiresTimer + * RFC3261 13.3.1 + */ + // Get the Expires header value if exists + if (request.hasHeader("expires")) { + var expires = Number(request.getHeader("expires") || 0) * 1000; + _this.timers.expiresTimer = setTimeout(function () { + if (_this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + incomingInviteRequest.reject({ statusCode: 487 }); + _this.failed(request, Constants_1.C.causes.EXPIRES); + _this.terminated(request, Constants_1.C.causes.EXPIRES); + } + }, expires); + } + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + Object.defineProperty(InviteServerContext.prototype, "autoSendAnInitialProvisionalResponse", { + /** + * If true, a first provisional response after the 100 Trying + * will be sent automatically. This is false it the UAC required + * reliable provisional responses (100rel in Require header), + * otherwise it is true. The provisional is sent by calling + * `progress()` without any options. + * + * FIXME: TODO: It seems reasonable that the ISC user should + * be able to optionally disable this behavior. As the provisional + * is sent prior to the "invite" event being emitted, it's a known + * issue that the ISC user cannot register listeners or do any other + * setup prior to the call to `progress()`. As an example why this is + * an issue, setting `ua.configuration.rel100` to REQUIRED will result + * in an attempt by `progress()` to send a 183 with SDP produced by + * calling `getDescription()` on a session description handler, but + * the ISC user cannot perform any potentially required session description + * handler initialization (thus preventing the utilization of setting + * `ua.configuration.rel100` to REQUIRED). That begs the question of + * why this behavior is disabled when the UAC requires 100rel but not + * when the UAS requires 100rel? But ignoring that, it's just one example + * of a class of cases where the ISC user needs to do something prior + * to the first call to `progress()` and is unable to do so. + */ + get: function () { + return this.rel100 === Constants_1.C.supported.REQUIRED ? false : true; + }, + enumerable: true, + configurable: true + }); + // type hack for servercontext interface + InviteServerContext.prototype.reply = function (options) { + if (options === void 0) { options = {}; } + return this; + }; + // typing note: this was the only function using its super in ServerContext + // so the bottom half of this function is copied and paired down from that + InviteServerContext.prototype.reject = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.logger.log("rejecting RTCSession"); + var statusCode = options.statusCode || 480; + var reasonPhrase = Utils_1.Utils.getReasonPhrase(statusCode, options.reasonPhrase); + var extraHeaders = options.extraHeaders || []; + if (statusCode < 300 || statusCode > 699) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + // FIXME: Need to redirect to someplae + var response = statusCode < 400 ? + this.incomingRequest.redirect([], { statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }) : + this.incomingRequest.reject({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + (["rejected", "failed"]).forEach(function (event) { + _this.emit(event, response.message, reasonPhrase); + }); + return this.terminated(); + }; + /** + * Accept the incoming INVITE request to start a Session. + * Replies to the INVITE request with a 200 Ok response. + * @param options Options bucket. + */ + InviteServerContext.prototype.accept = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // FIXME: Need guard against calling more than once. + this._accept(options) + .then(function (_a) { + var message = _a.message, session = _a.session; + session.delegate = { + onAck: function (ackRequest) { return _this.onAck(ackRequest); }, + onAckTimeout: function () { return _this.onAckTimeout(); }, + onBye: function (byeRequest) { return _this.receiveRequest(byeRequest); }, + onInfo: function (infoRequest) { return _this.receiveRequest(infoRequest); }, + onInvite: function (inviteRequest) { return _this.receiveRequest(inviteRequest); }, + onNotify: function (notifyRequest) { return _this.receiveRequest(notifyRequest); }, + onPrack: function (prackRequest) { return _this.receiveRequest(prackRequest); }, + onRefer: function (referRequest) { return _this.receiveRequest(referRequest); } + }; + _this.session = session; + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK; + _this.accepted(message, Utils_1.Utils.getReasonPhrase(200)); + }) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + }; + /** + * Report progress to the the caller. + * Replies to the INVITE request with a 1xx provisional response. + * @param options Options bucket. + */ + InviteServerContext.prototype.progress = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Ported + var statusCode = options.statusCode || 180; + if (statusCode < 100 || statusCode > 199) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + // Ported + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + this.logger.warn("Unexpected call for progress while terminated, ignoring"); + return this; + } + // Added + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED) { + this.logger.warn("Unexpected call for progress while answered, ignoring"); + return this; + } + // Added + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + this.logger.warn("Unexpected call for progress while answered (waiting for prack), ignoring"); + return this; + } + // After the first reliable provisional response for a request has been + // acknowledged, the UAS MAY send additional reliable provisional + // responses. The UAS MUST NOT send a second reliable provisional + // response until the first is acknowledged. After the first, it is + // RECOMMENDED that the UAS not send an additional reliable provisional + // response until the previous is acknowledged. The first reliable + // provisional response receives special treatment because it conveys + // the initial sequence number. If additional reliable provisional + // responses were sent before the first was acknowledged, the UAS could + // not be certain these were received in order. + // https://tools.ietf.org/html/rfc3262#section-3 + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + this.logger.warn("Unexpected call for progress while waiting for prack, ignoring"); + return this; + } + // Ported + if (options.statusCode === 100) { + try { + this.incomingRequest.trying(); + } + catch (error) { + this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!this._canceled) { + throw error; + } + } + return this; + } + // Standard provisional response. + if (!(this.rel100 === Constants_1.C.supported.REQUIRED) && + !(this.rel100 === Constants_1.C.supported.SUPPORTED && options.rel100) && + !(this.rel100 === Constants_1.C.supported.SUPPORTED && this.ua.configuration.rel100 === Constants_1.C.supported.REQUIRED)) { + this._progress(options) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + } + // Reliable provisional response. + this._reliableProgressWaitForPrack(options) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + }; + /** + * Reject an unaccepted incoming INVITE request or send BYE if established session. + * @param options Options bucket. FIXME: This options bucket needs to be typed. + */ + InviteServerContext.prototype.terminate = function (options) { + // The caller's UA MAY send a BYE for either confirmed or early dialogs, + // and the callee's UA MAY send a BYE on confirmed dialogs, but MUST NOT + // send a BYE on early dialogs. However, the callee's UA MUST NOT send a + // BYE on a confirmed dialog until it has received an ACK for its 2xx + // response or until the server transaction times out. + // https://tools.ietf.org/html/rfc3261#section-15 + var _this = this; + if (options === void 0) { options = {}; } + // We don't yet have a dialog, so reject request. + if (!this.session) { + this.reject(options); + return this; + } + switch (this.session.sessionState) { + case core_1.SessionState.Initial: + this.reject(options); + return this; + case core_1.SessionState.Early: + this.reject(options); + return this; + case core_1.SessionState.AckWait: + this.session.delegate = { + // When ACK shows up, say BYE. + onAck: function () { + _this.sendRequest(Constants_1.C.BYE, options); + }, + // Or the server transaction times out before the ACK arrives. + onAckTimeout: function () { + _this.sendRequest(Constants_1.C.BYE, options); + } + }; + // Ported + this.emit("bye", this.request); + this.terminated(); + return this; + case core_1.SessionState.Confirmed: + this.bye(options); + return this; + case core_1.SessionState.Terminated: + return this; + default: + return this; + } + }; + InviteServerContext.prototype.onCancel = function (message) { + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER || + this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_EARLY_MEDIA || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED) { + this.status = Enums_1.SessionStatus.STATUS_CANCELED; + this.incomingRequest.reject({ statusCode: 487 }); + this.canceled(); + this.rejected(message, Constants_1.C.causes.CANCELED); + this.failed(message, Constants_1.C.causes.CANCELED); + this.terminated(message, Constants_1.C.causes.CANCELED); + } + }; + InviteServerContext.prototype.receiveRequest = function (incomingRequest) { + var _this = this; + switch (incomingRequest.message.method) { + case Constants_1.C.PRACK: + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + if (!this.hasAnswer) { + this.sessionDescriptionHandler = this.setupSessionDescriptionHandler(); + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + if (this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + this.hasAnswer = true; + this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).then(function () { + clearTimeout(_this.timers.rel1xxTimer); + clearTimeout(_this.timers.prackTimer); + incomingRequest.accept(); + if (_this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + _this.accept(); + } + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + }, function (e) { + _this.logger.warn(e); + _this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + _this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + _this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + }); + } + else { + this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + } + } + else { + clearTimeout(this.timers.rel1xxTimer); + clearTimeout(this.timers.prackTimer); + incomingRequest.accept(); + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + this.accept(); + } + this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + } + } + else if (this.status === Enums_1.SessionStatus.STATUS_EARLY_MEDIA) { + incomingRequest.accept(); + } + break; + default: + _super.prototype.receiveRequest.call(this, incomingRequest); + break; + } + }; + // Internal Function to setup the handler consistently + InviteServerContext.prototype.setupSessionDescriptionHandler = function () { + if (this.sessionDescriptionHandler) { + return this.sessionDescriptionHandler; + } + return this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions); + }; + InviteServerContext.prototype.generateResponseOfferAnswer = function (options) { + if (!this.session) { + var body = core_1.getBody(this.incomingRequest.message); + if (!body || body.contentDisposition !== "session") { + return this.getOffer(options); + } + else { + return this.setOfferAndGetAnswer(body, options); + } + } + else { + switch (this.session.signalingState) { + case core_1.SignalingState.Initial: + return this.getOffer(options); + case core_1.SignalingState.Stable: + return Promise.resolve(undefined); + case core_1.SignalingState.HaveLocalOffer: + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + return Promise.resolve(undefined); + case core_1.SignalingState.HaveRemoteOffer: + if (!this.session.offer) { + throw new Error("Session offer undefined"); + } + return this.setOfferAndGetAnswer(this.session.offer, options); + case core_1.SignalingState.Closed: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + default: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + } + } + }; + InviteServerContext.prototype.handlePrackOfferAnswer = function (request, options) { + if (!this.session) { + throw new Error("Session undefined."); + } + // If the PRACK doesn't have an offer/answer, nothing to be done. + var body = core_1.getBody(request.message); + if (!body || body.contentDisposition !== "session") { + return Promise.resolve(undefined); + } + // If the UAC receives a reliable provisional response with an offer + // (this would occur if the UAC sent an INVITE without an offer, in + // which case the first reliable provisional response will contain the + // offer), it MUST generate an answer in the PRACK. If the UAC receives + // a reliable provisional response with an answer, it MAY generate an + // additional offer in the PRACK. If the UAS receives a PRACK with an + // offer, it MUST place the answer in the 2xx to the PRACK. + // https://tools.ietf.org/html/rfc3262#section-5 + switch (this.session.signalingState) { + case core_1.SignalingState.Initial: + // State should never be reached as first reliable provisional response must have answer/offer. + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + case core_1.SignalingState.Stable: + // Receved answer. + return this.setAnswer(body, options).then(function () { return undefined; }); + case core_1.SignalingState.HaveLocalOffer: + // State should never be reached as local offer would be answered by this PRACK + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + case core_1.SignalingState.HaveRemoteOffer: + // Receved offer, generate answer. + return this.setOfferAndGetAnswer(body, options); + case core_1.SignalingState.Closed: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + default: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + } + }; + /** + * Called when session canceled. + */ + InviteServerContext.prototype.canceled = function () { + this._canceled = true; + return _super.prototype.canceled.call(this); + }; + /** + * Called when session terminated. + * Using it here just for the PRACK timeout. + */ + InviteServerContext.prototype.terminated = function (message, cause) { + this.prackNeverArrived(); + return _super.prototype.terminated.call(this, message, cause); + }; + /** + * A version of `accept` which resolves a session when the 200 Ok response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `accept()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._accept = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // FIXME: Ported - callback for in dialog INFO requests. + // Turns out accept() can be called more than once if we are waiting + // for a PRACK in which case "options" get completely tossed away. + // So this is broken in that case (and potentially other uses of options). + // Tempted to just try to fix it now, but leaving it broken for the moment. + this.onInfo = options.onInfo; + // The UAS MAY send a final response to the initial request before + // having received PRACKs for all unacknowledged reliable provisional + // responses, unless the final response is 2xx and any of the + // unacknowledged reliable provisional responses contained a session + // description. In that case, it MUST NOT send a final response until + // those provisional responses are acknowledged. If the UAS does send a + // final response when reliable responses are still unacknowledged, it + // SHOULD NOT continue to retransmit the unacknowledged reliable + // provisional responses, but it MUST be prepared to process PRACK + // requests for those outstanding responses. A UAS MUST NOT send new + // reliable provisional responses (as opposed to retransmissions of + // unacknowledged ones) after sending a final response to a request. + // https://tools.ietf.org/html/rfc3262#section-3 + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK; + return this.waitForArrivalOfPrack() + .then(function () { + _this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + clearTimeout(_this.timers.userNoAnswerTimer); // Ported + }) + .then(function () { return _this.generateResponseOfferAnswer(options); }) + .then(function (body) { return _this.incomingRequest.accept({ statusCode: 200, body: body }); }); + } + // Ported + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + } + else { + return Promise.reject(new Exceptions_1.Exceptions.InvalidStateError(this.status)); + } + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + clearTimeout(this.timers.userNoAnswerTimer); // Ported + return this.generateResponseOfferAnswer(options) + .then(function (body) { return _this.incomingRequest.accept({ statusCode: 200, body: body }); }); + }; + /** + * A version of `progress` which resolves when the provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._progress = function (options) { + if (options === void 0) { options = {}; } + // Ported + var statusCode = options.statusCode || 180; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + try { + var progressResponse = this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + this.emit("progress", progressResponse.message, reasonPhrase); // Ported + this.session = progressResponse.session; + return Promise.resolve(progressResponse); + } + catch (error) { + return Promise.reject(error); + } + }; + /** + * A version of `progress` which resolves when the reliable provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._reliableProgress = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 183; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Require: 100rel"); + extraHeaders.push("RSeq: " + Math.floor(Math.random() * 10000)); + // Get an offer/answer and send a reply. + return this.generateResponseOfferAnswer(options) + .then(function (body) { return _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); }) + .then(function (progressResponse) { + _this.emit("progress", progressResponse.message, reasonPhrase); // Ported + _this.session = progressResponse.session; + return progressResponse; + }); + }; + /** + * A version of `progress` which resolves when the reliable provisional response is acknowledged. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._reliableProgressWaitForPrack = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 183; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Require: 100rel"); + extraHeaders.push("RSeq: " + this.rseq++); + var body; + // Ported - set status. + this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK; + return new Promise(function (resolve, reject) { + var waitingForPrack = true; + return _this.generateResponseOfferAnswer(options) + .then(function (offerAnswer) { + body = offerAnswer; + return _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + }) + .then(function (progressResponse) { + _this.emit("progress", progressResponse.message, reasonPhrase); // Ported + _this.session = progressResponse.session; + var prackRequest; + var prackResponse; + progressResponse.session.delegate = { + onPrack: function (request) { + prackRequest = request; + clearTimeout(prackWaitTimeoutTimer); + clearTimeout(rel1xxRetransmissionTimer); + if (!waitingForPrack) { + return; + } + waitingForPrack = false; + _this.handlePrackOfferAnswer(prackRequest, options) + .then(function (prackResponseBody) { + try { + prackResponse = prackRequest.accept({ statusCode: 200, body: prackResponseBody }); + // Ported - set status. + if (_this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + } + _this.prackArrived(); + resolve({ prackRequest: prackRequest, prackResponse: prackResponse, progressResponse: progressResponse }); + } + catch (error) { + reject(error); + } + }); + } + }; + // https://tools.ietf.org/html/rfc3262#section-3 + var prackWaitTimeout = function () { + if (!waitingForPrack) { + return; + } + waitingForPrack = false; + _this.logger.warn("No PRACK received, rejecting INVITE."); + clearTimeout(rel1xxRetransmissionTimer); + try { + _this.incomingRequest.reject({ statusCode: 504 }); + _this.terminated(undefined, Constants_1.C.causes.NO_PRACK); + reject(new Exceptions_1.Exceptions.TerminatedSessionError()); + } + catch (error) { + reject(error); + } + }; + var prackWaitTimeoutTimer = setTimeout(prackWaitTimeout, core_1.Timers.T1 * 64); + // https://tools.ietf.org/html/rfc3262#section-3 + var rel1xxRetransmission = function () { + try { + _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + } + catch (error) { + waitingForPrack = false; + reject(error); + return; + } + rel1xxRetransmissionTimer = setTimeout(rel1xxRetransmission, timeout *= 2); + }; + var timeout = core_1.Timers.T1; + var rel1xxRetransmissionTimer = setTimeout(rel1xxRetransmission, timeout); + }); + }); + }; + /** + * Callback for when ACK for a 2xx response is never received. + * @param session Session the ACK never arrived for + */ + InviteServerContext.prototype.onAckTimeout = function () { + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + this.logger.log("no ACK received for an extended period of time, terminating the call"); + if (!this.session) { + throw new Error("Session undefined."); + } + this.session.bye(); + this.terminated(undefined, Constants_1.C.causes.NO_ACK); + } + }; + /** + * FIXME: TODO: The current library interface presents async methods without a + * proper async error handling mechanism. Arguably a promise based interface + * would be an improvement over the pattern of returning `this`. The approach has + * been generally along the lines of log a error and terminate. + */ + InviteServerContext.prototype.onContextError = function (error) { + var statusCode = 480; + if (error instanceof core_1.Exception) { // There might be interest in catching these Exceptions. + if (error instanceof Exceptions_1.Exceptions.SessionDescriptionHandlerError) { + this.logger.error(error.message); + if (error.error) { + this.logger.error(error.error); + } + } + else if (error instanceof Exceptions_1.Exceptions.TerminatedSessionError) { + // PRACK never arrived, so we timed out waiting for it. + this.logger.warn("Incoming session terminated while waiting for PRACK."); + } + else if (error instanceof Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError) { + statusCode = 415; + } + else if (error instanceof core_1.Exception) { + this.logger.error(error.message); + } + } + else if (error instanceof Error) { // Other Errors hould go uncaught. + this.logger.error(error.message); + } + else { + // We don't actually know what a session description handler implementation might throw + // our way, so as a last resort, just assume we are getting an "any" and log it. + this.logger.error("An error occurred in the session description handler."); + this.logger.error(error); + } + try { + this.incomingRequest.reject({ statusCode: statusCode }); // "Temporarily Unavailable" + this.failed(this.incomingRequest.message, error.message); + this.terminated(this.incomingRequest.message, error.message); + } + catch (error) { + return; + } + }; + InviteServerContext.prototype.prackArrived = function () { + if (this.waitingForPrackResolve) { + this.waitingForPrackResolve(); + } + this.waitingForPrackPromise = undefined; + this.waitingForPrackResolve = undefined; + this.waitingForPrackReject = undefined; + }; + InviteServerContext.prototype.prackNeverArrived = function () { + if (this.waitingForPrackReject) { + this.waitingForPrackReject(new Exceptions_1.Exceptions.TerminatedSessionError()); + } + this.waitingForPrackPromise = undefined; + this.waitingForPrackResolve = undefined; + this.waitingForPrackReject = undefined; + }; + /** + * @throws {Exceptions.TerminatedSessionError} The session terminated before being accepted (i.e. cancel arrived). + */ + InviteServerContext.prototype.waitForArrivalOfPrack = function () { + var _this = this; + if (this.waitingForPrackPromise) { + throw new Error("Already waiting for PRACK"); + } + this.waitingForPrackPromise = new Promise(function (resolve, reject) { + _this.waitingForPrackResolve = resolve; + _this.waitingForPrackReject = reject; + }); + return this.waitingForPrackPromise; + }; + InviteServerContext.prototype.getOffer = function (options) { + this.hasOffer = true; + var sdh = this.getSessionDescriptionHandler(); + return sdh + .getDescription(options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function (bodyObj) { return Utils_1.Utils.fromBodyObj(bodyObj); }); + }; + InviteServerContext.prototype.setAnswer = function (answer, options) { + this.hasAnswer = true; + var sdh = this.getSessionDescriptionHandler(); + if (!sdh.hasDescription(answer.contentType)) { + return Promise.reject(new Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError()); + } + return sdh + .setDescription(answer.content, options.sessionDescriptionHandlerOptions, options.modifiers); + }; + InviteServerContext.prototype.setOfferAndGetAnswer = function (offer, options) { + this.hasOffer = true; + this.hasAnswer = true; + var sdh = this.getSessionDescriptionHandler(); + if (!sdh.hasDescription(offer.contentType)) { + return Promise.reject(new Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError()); + } + return sdh + .setDescription(offer.content, options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function () { return sdh.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers); }) + .then(function (bodyObj) { return Utils_1.Utils.fromBodyObj(bodyObj); }); + }; + InviteServerContext.prototype.getSessionDescriptionHandler = function () { + // Create our session description handler if not already done so... + var sdh = this.sessionDescriptionHandler = this.setupSessionDescriptionHandler(); + // FIXME: Ported - this can get emitted multiple times even when only created once... don't we care? + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + // Return. + return sdh; + }; + return InviteServerContext; +}(Session)); +exports.InviteServerContext = InviteServerContext; +// tslint:disable-next-line:max-classes-per-file +var InviteClientContext = /** @class */ (function (_super) { + tslib_1.__extends(InviteClientContext, _super); + function InviteClientContext(ua, target, options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + var _this = this; + if (!ua.configuration.sessionDescriptionHandlerFactory) { + ua.logger.warn("Can't build ISC without SDH Factory"); + throw new Error("ICC Constructor Failed"); + } + options.params = options.params || {}; + var anonymous = options.anonymous || false; + var fromTag = Utils_1.Utils.newTag(); + options.params.fromTag = fromTag; + /* Do not add ;ob in initial forming dialog requests if the registration over + * the current connection got a GRUU URI. + */ + var contact = ua.contact.toString({ + anonymous: anonymous, + outbound: anonymous ? !ua.contact.tempGruu : !ua.contact.pubGruu + }); + var extraHeaders = (options.extraHeaders || []).slice(); + if (anonymous && ua.configuration.uri) { + options.params.from_displayName = "Anonymous"; + options.params.from_uri = "sip:anonymous@anonymous.invalid"; + extraHeaders.push("P-Preferred-Identity: " + ua.configuration.uri.toString()); + extraHeaders.push("Privacy: id"); + } + extraHeaders.push("Contact: " + contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + if (ua.configuration.rel100 === Constants_1.C.supported.REQUIRED) { + extraHeaders.push("Require: 100rel"); + } + if (ua.configuration.replaces === Constants_1.C.supported.REQUIRED) { + extraHeaders.push("Require: replaces"); + } + options.extraHeaders = extraHeaders; + _this = _super.call(this, ua.configuration.sessionDescriptionHandlerFactory) || this; + ClientContext_1.ClientContext.initializer(_this, ua, Constants_1.C.INVITE, target, options); + _this.earlyMediaSessionDescriptionHandlers = new Map(); + _this.type = Enums_1.TypeStrings.InviteClientContext; + _this.passedOptions = options; // Save for later to use with refer + _this.sessionDescriptionHandlerOptions = options.sessionDescriptionHandlerOptions || {}; + _this.modifiers = modifiers; + _this.inviteWithoutSdp = options.inviteWithoutSdp || false; + // Set anonymous property + _this.anonymous = options.anonymous || false; + // Custom data to be sent either in INVITE or in ACK + _this.renderbody = options.renderbody || undefined; + _this.rendertype = options.rendertype || "text/plain"; + // Session parameter initialization + _this.fromTag = fromTag; + _this.contact = contact; + // Check Session Status + if (_this.status !== Enums_1.SessionStatus.STATUS_NULL) { + throw new Exceptions_1.Exceptions.InvalidStateError(_this.status); + } + // OutgoingSession specific parameters + _this.isCanceled = false; + _this.received100 = false; + _this.method = Constants_1.C.INVITE; + _this.logger = ua.getLogger("sip.inviteclientcontext"); + ua.applicants[_this.toString()] = _this; + _this.id = _this.request.callId + _this.fromTag; + _this.onInfo = options.onInfo; + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + InviteClientContext.prototype.receiveResponse = function (response) { + throw new Error("Unimplemented."); + }; + // hack for getting around ClientContext interface + InviteClientContext.prototype.send = function () { + this.sendInvite(); + return this; + }; + InviteClientContext.prototype.invite = function () { + var _this = this; + // Save the session into the ua sessions collection. + // Note: placing in constructor breaks call to request.cancel on close... User does not need this anyway + this.ua.sessions[this.id] = this; + // This should allow the function to return so that listeners can be set up for these events + Promise.resolve().then(function () { + // FIXME: There is a race condition where cancel (or terminate) can be called synchronously after invite. + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + if (_this.inviteWithoutSdp) { + // just send an invite with no sdp... + if (_this.renderbody && _this.rendertype) { + _this.request.body = { + body: _this.renderbody, + contentType: _this.rendertype + }; + } + _this.status = Enums_1.SessionStatus.STATUS_INVITE_SENT; + _this.send(); + } + else { + // Initialize Media Session + _this.sessionDescriptionHandler = _this.sessionDescriptionHandlerFactory(_this, _this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + _this.emit("SessionDescriptionHandler-created", _this.sessionDescriptionHandler); + _this.sessionDescriptionHandler.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers) + .then(function (description) { + // FIXME: There is a race condition where cancel (or terminate) can be called (a)synchronously after invite. + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.hasOffer = true; + _this.request.body = description; + _this.status = Enums_1.SessionStatus.STATUS_INVITE_SENT; + _this.send(); + }, function (err) { + if (err.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + _this.logger.log(err.message); + if (err.error) { + _this.logger.log(err.error); + } + } + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.failed(undefined, Constants_1.C.causes.WEBRTC_ERROR); + _this.terminated(undefined, Constants_1.C.causes.WEBRTC_ERROR); + }); + } + }); + return this; + }; + InviteClientContext.prototype.cancel = function (options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED || this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (this.isCanceled) { + throw new Exceptions_1.Exceptions.InvalidStateError(Enums_1.SessionStatus.STATUS_CANCELED); + } + this.isCanceled = true; + this.logger.log("Canceling session"); + var cancelReason = Utils_1.Utils.getCancelReason(options.statusCode, options.reasonPhrase); + options.extraHeaders = (options.extraHeaders || []).slice(); + if (this.outgoingInviteRequest) { + this.logger.warn("Canceling session before it was created"); + this.outgoingInviteRequest.cancel(cancelReason, options); + } + return this.canceled(); + }; + InviteClientContext.prototype.terminate = function (options) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK || this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.bye(options); + } + else { + this.cancel(options); + } + return this; + }; + /** + * 13.2.1 Creating the Initial INVITE + * + * Since the initial INVITE represents a request outside of a dialog, + * its construction follows the procedures of Section 8.1.1. Additional + * processing is required for the specific case of INVITE. + * + * An Allow header field (Section 20.5) SHOULD be present in the INVITE. + * It indicates what methods can be invoked within a dialog, on the UA + * sending the INVITE, for the duration of the dialog. For example, a + * UA capable of receiving INFO requests within a dialog [34] SHOULD + * include an Allow header field listing the INFO method. + * + * A Supported header field (Section 20.37) SHOULD be present in the + * INVITE. It enumerates all the extensions understood by the UAC. + * + * An Accept (Section 20.1) header field MAY be present in the INVITE. + * It indicates which Content-Types are acceptable to the UA, in both + * the response received by it, and in any subsequent requests sent to + * it within dialogs established by the INVITE. The Accept header field + * is especially useful for indicating support of various session + * description formats. + * + * The UAC MAY add an Expires header field (Section 20.19) to limit the + * validity of the invitation. If the time indicated in the Expires + * header field is reached and no final answer for the INVITE has been + * received, the UAC core SHOULD generate a CANCEL request for the + * INVITE, as per Section 9. + * + * A UAC MAY also find it useful to add, among others, Subject (Section + * 20.36), Organization (Section 20.25) and User-Agent (Section 20.41) + * header fields. They all contain information related to the INVITE. + * + * The UAC MAY choose to add a message body to the INVITE. Section + * 8.1.1.10 deals with how to construct the header fields -- Content- + * Type among others -- needed to describe the message body. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.1 + */ + InviteClientContext.prototype.sendInvite = function () { + // There are special rules for message bodies that contain a session + // description - their corresponding Content-Disposition is "session". + // SIP uses an offer/answer model where one UA sends a session + // description, called the offer, which contains a proposed description + // of the session. The offer indicates the desired communications means + // (audio, video, games), parameters of those means (such as codec + // types) and addresses for receiving media from the answerer. The + // other UA responds with another session description, called the + // answer, which indicates which communications means are accepted, the + // parameters that apply to those means, and addresses for receiving + // media from the offerer. An offer/answer exchange is within the + // context of a dialog, so that if a SIP INVITE results in multiple + // dialogs, each is a separate offer/answer exchange. The offer/answer + // model defines restrictions on when offers and answers can be made + // (for example, you cannot make a new offer while one is in progress). + // This results in restrictions on where the offers and answers can + // appear in SIP messages. In this specification, offers and answers + // can only appear in INVITE requests and responses, and ACK. The usage + // of offers and answers is further restricted. For the initial INVITE + // transaction, the rules are: + // + // o The initial offer MUST be in either an INVITE or, if not there, + // in the first reliable non-failure message from the UAS back to + // the UAC. In this specification, that is the final 2xx + // response. + // + // o If the initial offer is in an INVITE, the answer MUST be in a + // reliable non-failure message from UAS back to UAC which is + // correlated to that INVITE. For this specification, that is + // only the final 2xx response to that INVITE. That same exact + // answer MAY also be placed in any provisional responses sent + // prior to the answer. The UAC MUST treat the first session + // description it receives as the answer, and MUST ignore any + // session descriptions in subsequent responses to the initial + // INVITE. + // + // o If the initial offer is in the first reliable non-failure + // message from the UAS back to UAC, the answer MUST be in the + // acknowledgement for that message (in this specification, ACK + // for a 2xx response). + // + // o After having sent or received an answer to the first offer, the + // UAC MAY generate subsequent offers in requests based on rules + // specified for that method, but only if it has received answers + // to any previous offers, and has not sent any offers to which it + // hasn't gotten an answer. + // + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + var _this = this; + // 5 The Offer/Answer Model and PRACK + // + // RFC 3261 describes guidelines for the sets of messages in which + // offers and answers [3] can appear. Based on those guidelines, this + // extension provides additional opportunities for offer/answer + // exchanges. + // If the INVITE contained an offer, the UAS MAY generate an answer in a + // reliable provisional response (assuming these are supported by the + // UAC). That results in the establishment of the session before + // completion of the call. Similarly, if a reliable provisional + // response is the first reliable message sent back to the UAC, and the + // INVITE did not contain an offer, one MUST appear in that reliable + // provisional response. + // If the UAC receives a reliable provisional response with an offer + // (this would occur if the UAC sent an INVITE without an offer, in + // which case the first reliable provisional response will contain the + // offer), it MUST generate an answer in the PRACK. If the UAC receives + // a reliable provisional response with an answer, it MAY generate an + // additional offer in the PRACK. If the UAS receives a PRACK with an + // offer, it MUST place the answer in the 2xx to the PRACK. + // Once an answer has been sent or received, the UA SHOULD establish the + // session based on the parameters of the offer and answer, even if the + // original INVITE itself has not been responded to. + // If the UAS had placed a session description in any reliable + // provisional response that is unacknowledged when the INVITE is + // accepted, the UAS MUST delay sending the 2xx until the provisional + // response is acknowledged. Otherwise, the reliability of the 1xx + // cannot be guaranteed, and reliability is needed for proper operation + // of the offer/answer exchange. + // All user agents that support this extension MUST support all + // offer/answer exchanges that are possible based on the rules in + // Section 13.2 of RFC 3261, based on the existence of INVITE and PRACK + // as requests, and 2xx and reliable 1xx as non-failure reliable + // responses. + // + // https://tools.ietf.org/html/rfc3262#section-5 + //// + // The Offer/Answer Model Implementation + // + // The offer/answer model is straight forward, but one MUST READ the specifications... + // + // 13.2.1 Creating the Initial INVITE (paragraph 8 in particular) + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // 5 The Offer/Answer Model and PRACK + // https://tools.ietf.org/html/rfc3262#section-5 + // + // Session Initiation Protocol (SIP) Usage of the Offer/Answer Model + // https://tools.ietf.org/html/rfc6337 + // + // *** IMPORTANT IMPLEMENTATION CHOICES *** + // + // TLDR... + // + // 1) Only one offer/answer exchange permitted during initial INVITE. + // 2) No "early media" if the initial offer is in an INVITE. + // + // + // 1) Initial Offer/Answer Restriction. + // + // Our implementation replaces the following bullet point... + // + // o After having sent or received an answer to the first offer, the + // UAC MAY generate subsequent offers in requests based on rules + // specified for that method, but only if it has received answers + // to any previous offers, and has not sent any offers to which it + // hasn't gotten an answer. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // ...with... + // + // o After having sent or received an answer to the first offer, the + // UAC MUST NOT generate subsequent offers in requests based on rules + // specified for that method. + // + // ...which in combination with this bullet point... + // + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // ...ensures that EXACTLY ONE offer/answer exchange will occur + // during an initial out of dialog INVITE request made by our UAC. + // + // + // 2) Early Media Restriction. + // + // While our implementation adheres to the following bullet point... + // + // o If the initial offer is in an INVITE, the answer MUST be in a + // reliable non-failure message from UAS back to UAC which is + // correlated to that INVITE. For this specification, that is + // only the final 2xx response to that INVITE. That same exact + // answer MAY also be placed in any provisional responses sent + // prior to the answer. The UAC MUST treat the first session + // description it receives as the answer, and MUST ignore any + // session descriptions in subsequent responses to the initial + // INVITE. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // We have made the following implementation decision with regard to early media... + // + // o If the initial offer is in the INVITE, the answer from the + // UAS back to the UAC will establish a media session only + // only after the final 2xx response to that INVITE is received. + // + // The reason for this decision is rooted in a restriction currently + // inherent in WebRTC. Specifically, while a SIP INVITE request with an + // initial offer may fork resulting in more than one provisional answer, + // there is currently no easy/good way to to "fork" an offer generated + // by a peer connection. In particular, a WebRTC offer currently may only + // be matched with one answer and we have no good way to know which + // "provisional answer" is going to be the "final answer". So we have + // decided to punt and not create any "early media" sessions in this case. + // + // The upshot is that if you want "early media", you must not put the + // initial offer in the INVITE. Instead, force the UAS to provide the + // initial offer by sending an INVITE without an offer. In the WebRTC + // case this allows us to create a unique peer connection with a unique + // answer for every provisional offer with "early media" on all of them. + //// + //// + // ROADMAP: The Offer/Answer Model Implementation + // + // The "no early media if offer in INVITE" implementation is not a + // welcome one. The masses want it. The want it and they want it + // to work for WebRTC (so they want to have their cake and eat too). + // + // So while we currently cannot make the offer in INVITE+forking+webrtc + // case work, we decided to do the following... + // + // 1) modify SDH Factory to provide an initial offer without giving us the SDH, and then... + // 2) stick that offer in the initial INVITE, and when 183 with initial answer is received... + // 3) ask SDH Factory if it supports "earlyRemoteAnswer" + // a) if true, ask SDH Factory to createSDH(localOffer).then((sdh) => sdh.setDescription(remoteAnswer) + // b) if false, defer getting a SDH until 2xx response is received + // + // Our supplied WebRTC SDH will default to behavior 3b which works in forking environment (without) + // early media if initial offer is in the INVITE). We will, however, provide an "inviteWillNotFork" + // option which if set to "true" will have our supplied WebRTC SDH behave in the 3a manner. + // That will result in + // - early media working with initial offer in the INVITE, and... + // - if the INVITE forks, the session terminating with an ERROR that reads like + // "You set 'inviteWillNotFork' to true but the INVITE forked. You can't eat your cake, and have it too." + // - furthermore, we accept that users will report that error to us as "bug" regardless + // + // So, SDH Factory is going to end up with a new interface along the lines of... + // + // interface SessionDescriptionHandlerFactory { + // makeLocalOffer(): Promise; + // makeSessionDescriptionHandler( + // initialOffer: ContentTypeAndBody, offerType: "local" | "remote" + // ): Promise; + // supportsEarlyRemoteAnswer: boolean; + // supportsContentType(contentType: string): boolean; + // getDescription(description: ContentTypeAndBody): Promise + // setDescription(description: ContentTypeAndBody): Promise + // } + // + // We should be able to get rid of all the hasOffer/hasAnswer tracking code and otherwise code + // it up to the same interaction with the SDH Factory and SDH regardless of signaling scenario. + //// + // Send the INVITE request. + this.outgoingInviteRequest = this.ua.userAgentCore.invite(this.request, { + onAccept: function (inviteResponse) { return _this.onAccept(inviteResponse); }, + onProgress: function (inviteResponse) { return _this.onProgress(inviteResponse); }, + onRedirect: function (inviteResponse) { return _this.onRedirect(inviteResponse); }, + onReject: function (inviteResponse) { return _this.onReject(inviteResponse); }, + onTrying: function (inviteResponse) { return _this.onTrying(inviteResponse); } + }); + }; + InviteClientContext.prototype.ackAndBye = function (inviteResponse, session, statusCode, reasonPhrase) { + if (!this.ua.userAgentCore) { + throw new Error("Method requires user agent core."); + } + var extraHeaders = []; + if (statusCode) { + extraHeaders.push("Reason: " + Utils_1.Utils.getReasonHeaderValue(statusCode, reasonPhrase)); + } + var outgoingAckRequest = inviteResponse.ack(); + this.emit("ack", outgoingAckRequest.message); + var outgoingByeRequest = session.bye(undefined, { extraHeaders: extraHeaders }); + this.emit("bye", outgoingByeRequest.message); + }; + InviteClientContext.prototype.disposeEarlyMedia = function () { + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + this.earlyMediaSessionDescriptionHandlers.forEach(function (sessionDescriptionHandler) { + sessionDescriptionHandler.close(); + }); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 2xx response. + */ + InviteClientContext.prototype.onAccept = function (inviteResponse) { + var _this = this; + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + var response = inviteResponse.message; + var session = inviteResponse.session; + // Our transaction layer is "non-standard" in that it will only + // pass us a 2xx response once per branch, so there is no need to + // worry about dealing with 2xx retransmissions. However, we can + // and do still get 2xx responses for multiple branches (when an + // INVITE is forked) which may create multiple confirmed dialogs. + // Herein we are acking and sending a bye to any confirmed dialogs + // which arrive beyond the first one. This is the desired behavior + // for most applications (but certainly not all). + // If we already received a confirmed dialog, ack & bye this session. + if (this.session) { + this.ackAndBye(inviteResponse, session); + return; + } + // If the user requested cancellation, ack & bye this session. + if (this.isCanceled) { + this.ackAndBye(inviteResponse, session); + this.emit("bye", this.request); // FIXME: Ported this odd second "bye" emit + return; + } + // Ported behavior. + if (response.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(response.getHeader("P-Asserted-Identity")); + } + // We have a confirmed dialog. + this.session = session; + this.session.delegate = { + onAck: function (ackRequest) { return _this.onAck(ackRequest); }, + onBye: function (byeRequest) { return _this.receiveRequest(byeRequest); }, + onInfo: function (infoRequest) { return _this.receiveRequest(infoRequest); }, + onInvite: function (inviteRequest) { return _this.receiveRequest(inviteRequest); }, + onNotify: function (notifyRequest) { return _this.receiveRequest(notifyRequest); }, + onPrack: function (prackRequest) { return _this.receiveRequest(prackRequest); }, + onRefer: function (referRequest) { return _this.receiveRequest(referRequest); } + }; + switch (session.signalingState) { + case core_1.SignalingState.Initial: + // INVITE without Offer, so MUST have Offer at this point, so invalid state. + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + case core_1.SignalingState.HaveLocalOffer: + // INVITE with Offer, so MUST have Answer at this point, so invalid state. + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + case core_1.SignalingState.HaveRemoteOffer: + // INVITE without Offer, received offer in 2xx, so MUST send Answer in ACK. + var sdh_1 = this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + this.sessionDescriptionHandler = sdh_1; + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + if (!sdh_1.hasDescription(response.getHeader("Content-Type") || "")) { + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + } + this.hasOffer = true; + sdh_1 + .setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { return sdh_1.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers); }) + .then(function (description) { + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + _this.hasAnswer = true; + var body = { + contentDisposition: "session", contentType: description.contentType, content: description.body + }; + var ackRequest = inviteResponse.ack({ body: body }); + _this.emit("ack", ackRequest.message); + _this.accepted(response); + }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + _this.logger.warn("invalid description"); + _this.logger.warn(e.toString()); + // TODO: This message is inconsistent + _this.ackAndBye(inviteResponse, session, 488, "Invalid session description"); + _this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + } + else { + throw e; + } + }); + break; + case core_1.SignalingState.Stable: + // This session has completed an initial offer/answer exchange... + var options_1; + if (this.renderbody && this.rendertype) { + options_1 = { body: { contentDisposition: "render", contentType: this.rendertype, content: this.renderbody } }; + } + // If INVITE with Offer and we have been waiting till now to apply the answer. + if (this.hasOffer && !this.hasAnswer) { + if (!this.sessionDescriptionHandler) { + throw new Error("Session description handler undefined."); + } + var answer = session.answer; + if (!answer) { + throw new Error("Answer is undefined."); + } + this.sessionDescriptionHandler + .setDescription(answer.content, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { + _this.hasAnswer = true; + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var ackRequest = inviteResponse.ack(options_1); + _this.emit("ack", ackRequest.message); + _this.accepted(response); + }) + .catch(function (error) { + _this.logger.error(error); + _this.ackAndBye(inviteResponse, session, 488, "Not Acceptable Here"); + _this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + // FIME: DON'T EAT UNHANDLED ERRORS! + }); + } + else { + // Otherwise INVITE with or without Offer and we have already completed the initial exchange. + this.sessionDescriptionHandler = this.earlyMediaSessionDescriptionHandlers.get(session.id); + if (!this.sessionDescriptionHandler) { + throw new Error("Session description handler undefined."); + } + this.earlyMediaSessionDescriptionHandlers.delete(session.id); + this.hasOffer = true; + this.hasAnswer = true; + this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var ackRequest = inviteResponse.ack(); + this.emit("ack", ackRequest.message); + this.accepted(response); + } + break; + case core_1.SignalingState.Closed: + // Dialog has terminated. + break; + default: + throw new Error("Unknown session signaling state."); + } + this.disposeEarlyMedia(); + }; + /** + * Handle provisional response to initial INVITE. + * @param inviteResponse 1xx response. + */ + InviteClientContext.prototype.onProgress = function (inviteResponse) { + var _this = this; + // Ported - User requested cancellation. + if (this.isCanceled) { + return; + } + if (!this.outgoingInviteRequest) { + throw new Error("Outgoing INVITE request undefined."); + } + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + var response = inviteResponse.message; + var session = inviteResponse.session; + // Ported - Set status. + this.status = Enums_1.SessionStatus.STATUS_1XX_RECEIVED; + // Ported - Set assertedIdentity. + if (response.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(response.getHeader("P-Asserted-Identity")); + } + // The provisional response MUST establish a dialog if one is not yet created. + // https://tools.ietf.org/html/rfc3262#section-4 + if (!session) { + // A response with a to tag MUST create a session (should never get here). + throw new Error("Session undefined."); + } + // If a provisional response is received for an initial request, and + // that response contains a Require header field containing the option + // tag 100rel, the response is to be sent reliably. If the response is + // a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be + // ignored, and the procedures below MUST NOT be used. + // https://tools.ietf.org/html/rfc3262#section-4 + var requireHeader = response.getHeader("require"); + var rseqHeader = response.getHeader("rseq"); + var rseq = requireHeader && requireHeader.includes("100rel") && rseqHeader ? Number(rseqHeader) : undefined; + var responseReliable = !!rseq; + var extraHeaders = []; + if (responseReliable) { + extraHeaders.push("RAck: " + response.getHeader("rseq") + " " + response.getHeader("cseq")); + } + // INVITE without Offer and session still has no offer (and no answer). + if (session.signalingState === core_1.SignalingState.Initial) { + // Similarly, if a reliable provisional + // response is the first reliable message sent back to the UAC, and the + // INVITE did not contain an offer, one MUST appear in that reliable + // provisional response. + // https://tools.ietf.org/html/rfc3262#section-5 + if (responseReliable) { + this.logger.warn("First reliable provisional response received MUST contain an offer when INVITE does not contain an offer."); + // FIXME: Known popular UA's currently end up here... + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + this.emit("progress", response); + return; + } + // INVITE with Offer and session only has that initial local offer. + if (session.signalingState === core_1.SignalingState.HaveLocalOffer) { + if (responseReliable) { + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + this.emit("progress", response); + return; + } + // INVITE without Offer and received initial offer in provisional response + if (session.signalingState === core_1.SignalingState.HaveRemoteOffer) { + // The initial offer MUST be in either an INVITE or, if not there, + // in the first reliable non-failure message from the UAS back to + // the UAC. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // According to Section 13.2.1 of [RFC3261], 'The first reliable + // non-failure message' must have an offer if there is no offer in the + // INVITE request. This means that the User Agent (UA) that receives + // the INVITE request without an offer must include an offer in the + // first reliable response with 100rel extension. If no reliable + // provisional response has been sent, the User Agent Server (UAS) must + // include an offer when sending 2xx response. + // https://tools.ietf.org/html/rfc6337#section-2.2 + if (!responseReliable) { + this.logger.warn("Non-reliable provisional response MUST NOT contain an initial offer, discarding response."); + return; + } + // If the initial offer is in the first reliable non-failure + // message from the UAS back to UAC, the answer MUST be in the + // acknowledgement for that message + var sdh_2 = this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + this.emit("SessionDescriptionHandler-created", sdh_2); + this.earlyMediaSessionDescriptionHandlers.set(session.id, sdh_2); + sdh_2 + .setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { return sdh_2.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers); }) + .then(function (description) { + var body = { + contentDisposition: "session", contentType: description.contentType, content: description.body + }; + inviteResponse.prack({ extraHeaders: extraHeaders, body: body }); + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + _this.emit("progress", response); + }) + .catch(function (error) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.failed(undefined, Constants_1.C.causes.WEBRTC_ERROR); + _this.terminated(undefined, Constants_1.C.causes.WEBRTC_ERROR); + }); + return; + } + // This session has completed an initial offer/answer exchange, so... + // - INVITE with SDP and this provisional response MAY be reliable + // - INVITE without SDP and this provisional response MAY be reliable + if (session.signalingState === core_1.SignalingState.Stable) { + if (responseReliable) { + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + // Note: As documented, no early media if offer was in INVITE, so nothing to be done. + // FIXME: TODO: Add a flag/hack to allow early media in this case. There are people + // in non-forking environments (think straight to FreeSWITCH) who want + // early media on a 183. Not sure how to actually make it work, basically + // something like... + if (false) {} + this.emit("progress", response); + return; + } + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 3xx response. + */ + InviteClientContext.prototype.onRedirect = function (inviteResponse) { + this.disposeEarlyMedia(); + var response = inviteResponse.message; + var statusCode = response.statusCode; + var cause = Utils_1.Utils.sipErrorCause(statusCode || 0); + this.rejected(response, cause); + this.failed(response, cause); + this.terminated(response, cause); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 4xx, 5xx, or 6xx response. + */ + InviteClientContext.prototype.onReject = function (inviteResponse) { + this.disposeEarlyMedia(); + var response = inviteResponse.message; + var statusCode = response.statusCode; + var cause = Utils_1.Utils.sipErrorCause(statusCode || 0); + this.rejected(response, cause); + this.failed(response, cause); + this.terminated(response, cause); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 100 response. + */ + InviteClientContext.prototype.onTrying = function (inviteResponse) { + this.received100 = true; + this.emit("progress", inviteResponse.message); + }; + return InviteClientContext; +}(Session)); +exports.InviteClientContext = InviteClientContext; + + +/***/ }), +/* 90 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +/** + * @class DTMF + * @param {SIP.Session} session + */ +var DTMF = /** @class */ (function (_super) { + tslib_1.__extends(DTMF, _super); + function DTMF(session, tone, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.C = { + MIN_DURATION: 70, + MAX_DURATION: 6000, + DEFAULT_DURATION: 100, + MIN_INTER_TONE_GAP: 50, + DEFAULT_INTER_TONE_GAP: 500 + }; + _this.type = Enums_1.TypeStrings.DTMF; + if (tone === undefined) { + throw new TypeError("Not enough arguments"); + } + _this.logger = session.ua.getLogger("sip.invitecontext.dtmf", session.id); + _this.owner = session; + // Check tone type + if (typeof tone === "string") { + tone = tone.toUpperCase(); + } + else if (typeof tone === "number") { + tone = tone.toString(); + } + else { + throw new TypeError("Invalid tone: " + tone); + } + // Check tone value + if (!tone.match(/^[0-9A-D#*]$/)) { + throw new TypeError("Invalid tone: " + tone); + } + else { + _this.tone = tone; + } + var duration = options.duration; + var interToneGap = options.interToneGap; + // Check duration + if (duration && !Utils_1.Utils.isDecimal(duration)) { + throw new TypeError("Invalid tone duration: " + duration); + } + else if (!duration) { + duration = _this.C.DEFAULT_DURATION; + } + else if (duration < _this.C.MIN_DURATION) { + _this.logger.warn("'duration' value is lower than the minimum allowed, setting it to " + + _this.C.MIN_DURATION + " milliseconds"); + duration = _this.C.MIN_DURATION; + } + else if (duration > _this.C.MAX_DURATION) { + _this.logger.warn("'duration' value is greater than the maximum allowed, setting it to " + + _this.C.MAX_DURATION + " milliseconds"); + duration = _this.C.MAX_DURATION; + } + else { + duration = Math.abs(duration); + } + _this.duration = duration; + // Check interToneGap + if (interToneGap && !Utils_1.Utils.isDecimal(interToneGap)) { + throw new TypeError("Invalid interToneGap: " + interToneGap); + } + else if (!interToneGap) { + interToneGap = _this.C.DEFAULT_INTER_TONE_GAP; + } + else if (interToneGap < _this.C.MIN_INTER_TONE_GAP) { + _this.logger.warn("'interToneGap' value is lower than the minimum allowed, setting it to " + + _this.C.MIN_INTER_TONE_GAP + " milliseconds"); + interToneGap = _this.C.MIN_INTER_TONE_GAP; + } + else { + interToneGap = Math.abs(interToneGap); + } + _this.interToneGap = interToneGap; + return _this; + } + DTMF.prototype.send = function (options) { + if (options === void 0) { options = {}; } + // Check RTCSession Status + if (this.owner.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && + this.owner.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.owner.status); + } + // Get DTMF options + var extraHeaders = options.extraHeaders ? options.extraHeaders.slice() : []; + var body = { + contentType: "application/dtmf-relay", + body: "Signal= " + this.tone + "\r\nDuration= " + this.duration + }; + if (this.owner.session) { + var request = this.owner.session.info(undefined, { + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(body) + }); + this.owner.emit("dtmf", request.message, this); + return; + } + }; + DTMF.prototype.init_incoming = function (request) { + request.accept(); + if (!this.tone || !this.duration) { + this.logger.warn("invalid INFO DTMF received, discarded"); + } + else { + this.owner.emit("dtmf", request.message, this); + } + }; + DTMF.prototype.receiveResponse = function (response) { + var statusCode = response && response.statusCode ? response.statusCode : 0; + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + // Ignore provisional responses. + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + this.emit("succeeded", { + originator: "remote", + response: response + }); + break; + default: + var cause = Utils_1.Utils.sipErrorCause(statusCode); + this.emit("failed", response, cause); + break; + } + }; + DTMF.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + this.owner.onRequestTimeout(); + }; + DTMF.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + this.owner.onTransportError(); + }; + DTMF.prototype.onDialogError = function (response) { + this.emit("failed", response, Constants_1.C.causes.DIALOG_ERROR); + this.owner.onDialogError(response); + }; + return DTMF; +}(events_1.EventEmitter)); +exports.DTMF = DTMF; + + +/***/ }), +/* 91 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var allowed_methods_1 = __webpack_require__(58); +var Enums_1 = __webpack_require__(81); +var Utils_1 = __webpack_require__(82); +/** + * While this class is named `Subscription`, it is closer to + * an implementation of a "subscriber" as defined in RFC 6665 + * "SIP-Specific Event Notifications". + * https://tools.ietf.org/html/rfc6665 + * @class Class creating a SIP Subscriber. + */ +var Subscription = /** @class */ (function (_super) { + tslib_1.__extends(Subscription, _super); + /** + * Constructor. + * @param ua User agent. + * @param target Subscription target. + * @param event Subscription event. + * @param options Options bucket. + */ + function Subscription(ua, target, event, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.data = {}; + _this.method = Constants_1.C.SUBSCRIBE; + _this.body = undefined; + // ClientContext interface + _this.type = Enums_1.TypeStrings.Subscription; + _this.ua = ua; + _this.logger = ua.getLogger("sip.subscription"); + if (options.body) { + _this.body = { + body: options.body, + contentType: options.contentType ? options.contentType : "application/sdp" + }; + } + // Target URI + var uri = ua.normalizeTarget(target); + if (!uri) { + throw new TypeError("Invalid target: " + target); + } + _this.uri = uri; + // Subscription event + _this.event = event; + // Subscription expires + if (options.expires === undefined) { + _this.expires = 3600; + } + else if (typeof options.expires !== "number") { // pre-typescript type guard + ua.logger.warn("Option \"expires\" must be a number. Using default of 3600."); + _this.expires = 3600; + } + else { + _this.expires = options.expires; + } + // Subscription extra headers + _this.extraHeaders = (options.extraHeaders || []).slice(); + // Subscription context. + _this.context = _this.initContext(); + _this.disposed = false; + // ClientContext interface + _this.request = _this.context.message; + if (!_this.request.from) { + throw new Error("From undefined."); + } + if (!_this.request.to) { + throw new Error("From undefined."); + } + _this.localIdentity = _this.request.from; + _this.remoteIdentity = _this.request.to; + // Add to UA's collection + _this.id = _this.request.callId + _this.request.from.parameters.tag + _this.event; + _this.ua.subscriptions[_this.id] = _this; + return _this; + } + /** + * Destructor. + */ + Subscription.prototype.dispose = function () { + if (this.disposed) { + return; + } + if (this.retryAfterTimer) { + clearTimeout(this.retryAfterTimer); + this.retryAfterTimer = undefined; + } + this.context.dispose(); + this.disposed = true; + // Remove from UA's collection + delete this.ua.subscriptions[this.id]; + }; + Subscription.prototype.on = function (name, callback) { + return _super.prototype.on.call(this, name, callback); + }; + Subscription.prototype.emit = function (event) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + return _super.prototype.emit.apply(this, [event].concat(args)); + }; + /** + * Gracefully terminate. + */ + Subscription.prototype.close = function () { + if (this.disposed) { + return; + } + this.dispose(); + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + this.onTerminated(); + break; + case core_1.SubscriptionState.NotifyWait: + this.onTerminated(); + break; + case core_1.SubscriptionState.Pending: + this.unsubscribe(); + break; + case core_1.SubscriptionState.Active: + this.unsubscribe(); + break; + case core_1.SubscriptionState.Terminated: + this.onTerminated(); + break; + default: + break; + } + }; + /** + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + Subscription.prototype.refresh = function () { + var _this = this; + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + break; + case core_1.SubscriptionState.Active: + if (this.subscription) { + var request = this.subscription.refresh(); + request.delegate = { + onAccept: (function (response) { return _this.onAccepted(response); }), + onRedirect: (function (response) { return _this.onFailed(response); }), + onReject: (function (response) { return _this.onFailed(response); }), + }; + } + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + }; + /** + * Send an initial SUBSCRIBE request if no subscription. + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + Subscription.prototype.subscribe = function () { + var _this = this; + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + this.context.subscribe().then(function (result) { + if (result.success) { + if (result.success.subscription) { + _this.subscription = result.success.subscription; + _this.subscription.delegate = { + onNotify: function (request) { return _this.onNotify(request); }, + onRefresh: function (request) { return _this.onRefresh(request); }, + onTerminated: function () { return _this.close(); } + }; + } + _this.onNotify(result.success.request); + } + else if (result.failure) { + _this.onFailed(result.failure.response); + } + }); + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + break; + case core_1.SubscriptionState.Active: + this.refresh(); + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + return this; + }; + /** + * Send a re-SUBSCRIBE request if there is a "pending" or "active" subscription. + */ + Subscription.prototype.unsubscribe = function () { + this.dispose(); + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + if (this.subscription) { + this.subscription.unsubscribe(); + // responses intentionally ignored + } + break; + case core_1.SubscriptionState.Active: + if (this.subscription) { + this.subscription.unsubscribe(); + // responses intentionally ignored + } + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + this.onTerminated(); + }; + Subscription.prototype.onAccepted = function (response) { + var statusCode = response.message.statusCode ? response.message.statusCode : 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + this.emit("accepted", response.message, cause); + }; + Subscription.prototype.onFailed = function (response) { + this.close(); + if (response) { + var statusCode = response.message.statusCode ? response.message.statusCode : 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + this.emit("failed", response.message, cause); + this.emit("rejected", response.message, cause); + } + }; + Subscription.prototype.onNotify = function (request) { + var _this = this; + request.accept(); // Send 200 response. + this.emit("notify", { request: request.message }); + // If we've set state to done, no further processing should take place + // and we are only interested in cleaning up after the appropriate NOTIFY. + if (this.disposed) { + return; + } + // If the "Subscription-State" value is "terminated", the subscriber + // MUST consider the subscription terminated. The "expires" parameter + // has no semantics for "terminated" -- notifiers SHOULD NOT include an + // "expires" parameter on a "Subscription-State" header field with a + // value of "terminated", and subscribers MUST ignore any such + // parameter, if present. If a reason code is present, the client + // should behave as described below. If no reason code or an unknown + // reason code is present, the client MAY attempt to re-subscribe at any + // time (unless a "retry-after" parameter is present, in which case the + // client SHOULD NOT attempt re-subscription until after the number of + // seconds specified by the "retry-after" parameter). The reason codes + // defined by this document are: + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = request.message.parseHeader("Subscription-State"); + if (subscriptionState && subscriptionState.state) { + switch (subscriptionState.state) { + case "terminated": + if (subscriptionState.reason) { + this.logger.log("Terminated subscription with reason " + subscriptionState.reason); + switch (subscriptionState.reason) { + case "deactivated": + case "timeout": + this.initContext(); + this.subscribe(); + return; + case "probation": + case "giveup": + this.initContext(); + if (subscriptionState.params && subscriptionState.params["retry-after"]) { + this.retryAfterTimer = setTimeout(function () { return _this.subscribe(); }, subscriptionState.params["retry-after"]); + } + else { + this.subscribe(); + } + return; + case "rejected": + case "noresource": + case "invariant": + break; + } + } + this.close(); + break; + default: + break; + } + } + }; + Subscription.prototype.onRefresh = function (request) { + var _this = this; + request.delegate = { + onAccept: function (response) { return _this.onAccepted(response); } + }; + }; + Subscription.prototype.onTerminated = function () { + this.emit("terminated"); + }; + Subscription.prototype.initContext = function () { + var _this = this; + var options = { + extraHeaders: this.extraHeaders, + body: this.body ? Utils_1.Utils.fromBodyObj(this.body) : undefined + }; + this.context = new SubscribeClientContext(this.ua.userAgentCore, this.uri, this.event, this.expires, options); + this.context.delegate = { + onAccept: (function (response) { return _this.onAccepted(response); }) + }; + return this.context; + }; + return Subscription; +}(events_1.EventEmitter)); +exports.Subscription = Subscription; +// tslint:disable-next-line:max-classes-per-file +var SubscribeClientContext = /** @class */ (function () { + function SubscribeClientContext(core, target, event, expires, options, delegate) { + this.core = core; + this.target = target; + this.event = event; + this.expires = expires; + this.subscribed = false; + this.logger = core.loggerFactory.getLogger("sip.subscription"); + this.delegate = delegate; + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var extraHeaders = (options && options.extraHeaders || []).slice(); + extraHeaders.push(allowHeader); + extraHeaders.push("Event: " + this.event); + extraHeaders.push("Expires: " + this.expires); + extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + var body = options && options.body; + this.message = core.makeOutgoingRequestMessage(Constants_1.C.SUBSCRIBE, this.target, this.core.configuration.aor, this.target, {}, extraHeaders, body); + } + /** Destructor. */ + SubscribeClientContext.prototype.dispose = function () { + if (this.subscription) { + this.subscription.dispose(); + } + if (this.request) { + this.request.waitNotifyStop(); + this.request.dispose(); + } + }; + Object.defineProperty(SubscribeClientContext.prototype, "state", { + /** Subscription state. */ + get: function () { + if (this.subscription) { + return this.subscription.subscriptionState; + } + else if (this.subscribed) { + return core_1.SubscriptionState.NotifyWait; + } + else { + return core_1.SubscriptionState.Initial; + } + }, + enumerable: true, + configurable: true + }); + /** + * Establish subscription. + * @param options Options bucket. + */ + SubscribeClientContext.prototype.subscribe = function () { + var _this = this; + if (this.subscribed) { + return Promise.reject(new Error("Not in initial state. Did you call subscribe more than once?")); + } + this.subscribed = true; + return new Promise(function (resolve, reject) { + if (!_this.message) { + throw new Error("Message undefined."); + } + _this.request = _this.core.subscribe(_this.message, { + // This SUBSCRIBE request will be confirmed with a final response. + // 200-class responses indicate that the subscription has been accepted + // and that a NOTIFY request will be sent immediately. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onAccept: function (response) { + if (_this.delegate && _this.delegate.onAccept) { + _this.delegate.onAccept(response); + } + }, + // Due to the potential for out-of-order messages, packet loss, and + // forking, the subscriber MUST be prepared to receive NOTIFY requests + // before the SUBSCRIBE transaction has completed. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + onNotify: function (requestWithSubscription) { + _this.subscription = requestWithSubscription.subscription; + if (_this.subscription) { + _this.subscription.autoRefresh = true; + } + resolve({ success: requestWithSubscription }); + }, + // If this Timer N expires prior to the receipt of a NOTIFY request, + // the subscriber considers the subscription failed, and cleans up + // any state associated with the subscription attempt. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + onNotifyTimeout: function () { + resolve({ failure: {} }); + }, + // This SUBSCRIBE request will be confirmed with a final response. + // Non-200-class final responses indicate that no subscription or new + // dialog usage has been created, and no subsequent NOTIFY request will + // be sent. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onRedirect: function (response) { + resolve({ failure: { response: response } }); + }, + // This SUBSCRIBE request will be confirmed with a final response. + // Non-200-class final responses indicate that no subscription or new + // dialog usage has been created, and no subsequent NOTIFY request will + // be sent. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onReject: function (response) { + resolve({ failure: { response: response } }); + } + }); + }); + }; + return SubscribeClientContext; +}()); + + +/***/ }), +/* 92 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var ClientContext_1 = __webpack_require__(78); +var Constants_1 = __webpack_require__(79); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Parser_1 = __webpack_require__(84); +var PublishContext_1 = __webpack_require__(85); +var ReferContext_1 = __webpack_require__(86); +var RegisterContext_1 = __webpack_require__(88); +var ServerContext_1 = __webpack_require__(87); +var Session_1 = __webpack_require__(89); +var Subscription_1 = __webpack_require__(91); +var Utils_1 = __webpack_require__(82); +var SessionDescriptionHandler_1 = __webpack_require__(94); +var Transport_1 = __webpack_require__(97); +var environment = global.window || global; +/** + * @class Class creating a SIP User Agent. + * @param {function returning SIP.sessionDescriptionHandler} [configuration.sessionDescriptionHandlerFactory] + * A function will be invoked by each of the UA's Sessions to build the sessionDescriptionHandler for that Session. + * If no (or a falsy) value is provided, each Session will use a default (WebRTC) sessionDescriptionHandler. + */ +var UA = /** @class */ (function (_super) { + tslib_1.__extends(UA, _super); + function UA(configuration) { + var _this = _super.call(this) || this; + _this.type = Enums_1.TypeStrings.UA; + _this.log = new core_1.LoggerFactory(); + _this.logger = _this.getLogger("sip.ua"); + _this.configuration = {}; + // User actions outside any session/dialog (MESSAGE) + _this.applicants = {}; + _this.data = {}; + _this.sessions = {}; + _this.subscriptions = {}; + _this.publishers = {}; + _this.status = Enums_1.UAStatus.STATUS_INIT; + /** + * Load configuration + * + * @throws {SIP.Exceptions.ConfigurationError} + * @throws {TypeError} + */ + if (configuration === undefined) { + configuration = {}; + } + else if (typeof configuration === "string" || configuration instanceof String) { + configuration = { + uri: configuration + }; + } + // Apply log configuration if present + if (configuration.log) { + if (configuration.log.hasOwnProperty("builtinEnabled")) { + _this.log.builtinEnabled = configuration.log.builtinEnabled; + } + if (configuration.log.hasOwnProperty("connector")) { + _this.log.connector = configuration.log.connector; + } + if (configuration.log.hasOwnProperty("level")) { + var level = configuration.log.level; + var normalized = void 0; + if (typeof level === "string") { + switch (level) { + case "error": + normalized = core_1.Levels.error; + break; + case "warn": + normalized = core_1.Levels.warn; + break; + case "log": + normalized = core_1.Levels.log; + break; + case "debug": + normalized = core_1.Levels.debug; + break; + default: + break; + } + } + else { + switch (level) { + case 0: + normalized = core_1.Levels.error; + break; + case 1: + normalized = core_1.Levels.warn; + break; + case 2: + normalized = core_1.Levels.log; + break; + case 3: + normalized = core_1.Levels.debug; + break; + default: + break; + } + } + // avoid setting level when invalid, use default level instead + if (normalized === undefined) { + _this.logger.error("Invalid \"level\" parameter value: " + JSON.stringify(level)); + } + else { + _this.log.level = normalized; + } + } + } + try { + _this.loadConfig(configuration); + } + catch (e) { + _this.status = Enums_1.UAStatus.STATUS_NOT_READY; + _this.error = UA.C.CONFIGURATION_ERROR; + throw e; + } + var userAgentCoreConfiguration = makeUserAgentCoreConfigurationFromUA(_this); + // The Replaces header contains information used to match an existing + // SIP dialog (call-id, to-tag, and from-tag). Upon receiving an INVITE + // with a Replaces header, the User Agent (UA) attempts to match this + // information with a confirmed or early dialog. + // https://tools.ietf.org/html/rfc3891#section-3 + var handleInviteWithReplacesHeader = function (context, request) { + if (_this.configuration.replaces !== Constants_1.C.supported.UNSUPPORTED) { + var replaces = request.parseHeader("replaces"); + if (replaces) { + var targetSession = _this.sessions[replaces.call_id + replaces.replaces_from_tag] || + _this.sessions[replaces.call_id + replaces.replaces_to_tag] || + undefined; + if (!targetSession) { + _this.userAgentCore.replyStateless(request, { statusCode: 481 }); + return; + } + if (targetSession.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + _this.userAgentCore.replyStateless(request, { statusCode: 603 }); + return; + } + var targetDialogId = replaces.call_id + replaces.replaces_to_tag + replaces.replaces_from_tag; + var targetDialog = _this.userAgentCore.dialogs.get(targetDialogId); + if (!targetDialog) { + _this.userAgentCore.replyStateless(request, { statusCode: 481 }); + return; + } + if (!targetDialog.early && replaces.early_only) { + _this.userAgentCore.replyStateless(request, { statusCode: 486 }); + return; + } + context.replacee = targetSession; + } + } + }; + var userAgentCoreDelegate = { + onInvite: function (incomingInviteRequest) { + // FIXME: Ported - 100 Trying send should be configurable. + // Only required if TU will not respond in 200ms. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + incomingInviteRequest.trying(); + incomingInviteRequest.delegate = { + onCancel: function (cancel) { + context.onCancel(cancel); + }, + onTransportError: function (error) { + context.onTransportError(); + } + }; + var context = new Session_1.InviteServerContext(_this, incomingInviteRequest); + // Ported - handling of out of dialog INVITE with Replaces. + handleInviteWithReplacesHeader(context, incomingInviteRequest.message); + // Ported - make the first call to progress automatically. + if (context.autoSendAnInitialProvisionalResponse) { + context.progress(); + } + _this.emit("invite", context); + }, + onMessage: function (incomingMessageRequest) { + // Ported - handling of out of dialog MESSAGE. + var serverContext = new ServerContext_1.ServerContext(_this, incomingMessageRequest); + serverContext.body = incomingMessageRequest.message.body; + serverContext.contentType = incomingMessageRequest.message.getHeader("Content-Type") || "text/plain"; + incomingMessageRequest.accept(); + _this.emit("message", serverContext); // TODO: Review. Why is a "ServerContext" emitted? What use it is? + }, + onNotify: function (incomingNotifyRequest) { + // DEPRECATED: Out of dialog NOTIFY is an obsolete usage. + // Ported - handling of out of dialog NOTIFY. + if (_this.configuration.allowLegacyNotifications && _this.listeners("notify").length > 0) { + incomingNotifyRequest.accept(); + _this.emit("notify", { request: incomingNotifyRequest.message }); + } + else { + incomingNotifyRequest.reject({ statusCode: 481 }); + } + }, + onRefer: function (incomingReferRequest) { + // Ported - handling of out of dialog REFER. + _this.logger.log("Received an out of dialog refer"); + if (!_this.configuration.allowOutOfDialogRefers) { + incomingReferRequest.reject({ statusCode: 405 }); + } + _this.logger.log("Allow out of dialog refers is enabled on the UA"); + var referContext = new ReferContext_1.ReferServerContext(_this, incomingReferRequest); + if (_this.listeners("outOfDialogReferRequested").length) { + _this.emit("outOfDialogReferRequested", referContext); + } + else { + _this.logger.log("No outOfDialogReferRequest listeners, automatically accepting and following the out of dialog refer"); + referContext.accept({ followRefer: true }); + } + }, + onSubscribe: function (incomingSubscribeRequest) { + _this.emit("subscribe", incomingSubscribeRequest); + }, + }; + _this.userAgentCore = new core_1.UserAgentCore(userAgentCoreConfiguration, userAgentCoreDelegate); + // Initialize registerContext + _this.registerContext = new RegisterContext_1.RegisterContext(_this, configuration.registerOptions); + _this.registerContext.on("failed", _this.emit.bind(_this, "registrationFailed")); + _this.registerContext.on("registered", _this.emit.bind(_this, "registered")); + _this.registerContext.on("unregistered", _this.emit.bind(_this, "unregistered")); + if (_this.configuration.autostart) { + _this.start(); + } + return _this; + } + // ================= + // High Level API + // ================= + UA.prototype.register = function (options) { + if (options === void 0) { options = {}; } + if (options.register) { + this.configuration.register = true; + } + this.registerContext.register(options); + return this; + }; + /** + * Unregister. + * + * @param {Boolean} [all] unregister all user bindings. + * + */ + UA.prototype.unregister = function (options) { + var _this = this; + this.configuration.register = false; + if (this.transport) { + this.transport.afterConnected(function () { + _this.registerContext.unregister(options); + }); + } + return this; + }; + UA.prototype.isRegistered = function () { + return this.registerContext.registered; + }; + /** + * Make an outgoing call. + * + * @param {String} target + * @param {Object} views + * @param {Object} [options.media] gets passed to SIP.sessionDescriptionHandler.getDescription as mediaHint + * + * @throws {TypeError} + * + */ + UA.prototype.invite = function (target, options, modifiers) { + var _this = this; + var context = new Session_1.InviteClientContext(this, target, options, modifiers); + // Delay sending actual invite until the next 'tick' if we are already + // connected, so that API consumers can register to events fired by the + // the session. + if (this.transport) { + this.transport.afterConnected(function () { + context.invite(); + _this.emit("inviteSent", context); + }); + } + return context; + }; + UA.prototype.subscribe = function (target, event, options) { + var sub = new Subscription_1.Subscription(this, target, event, options); + if (this.transport) { + this.transport.afterConnected(function () { return sub.subscribe(); }); + } + return sub; + }; + /** + * Send PUBLISH Event State Publication (RFC3903) + * + * @param {String} target + * @param {String} event + * @param {String} body + * @param {Object} [options] + * + * @throws {SIP.Exceptions.MethodParameterError} + */ + UA.prototype.publish = function (target, event, body, options) { + var pub = new PublishContext_1.PublishContext(this, target, event, options); + if (this.transport) { + this.transport.afterConnected(function () { + pub.publish(body); + }); + } + return pub; + }; + /** + * Send a message. + * + * @param {String} target + * @param {String} body + * @param {Object} [options] + * + * @throws {TypeError} + */ + UA.prototype.message = function (target, body, options) { + if (options === void 0) { options = {}; } + if (body === undefined) { + throw new TypeError("Not enough arguments"); + } + // There is no Message module, so it is okay that the UA handles defaults here. + options.contentType = options.contentType || "text/plain"; + options.body = body; + return this.request(Constants_1.C.MESSAGE, target, options); + }; + UA.prototype.request = function (method, target, options) { + var req = new ClientContext_1.ClientContext(this, method, target, options); + if (this.transport) { + this.transport.afterConnected(function () { return req.send(); }); + } + return req; + }; + /** + * Gracefully close. + */ + UA.prototype.stop = function () { + this.logger.log("user requested closure..."); + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + this.logger.warn("UA already closed"); + return this; + } + // Close registerContext + this.logger.log("closing registerContext"); + this.registerContext.close(); + // Run terminate on every Session + for (var session in this.sessions) { + if (this.sessions[session]) { + this.logger.log("closing session " + session); + this.sessions[session].terminate(); + } + } + // Run unsubscribe on every Subscription + for (var subscription in this.subscriptions) { + if (this.subscriptions[subscription]) { + this.logger.log("unsubscribe " + subscription); + this.subscriptions[subscription].unsubscribe(); + } + } + // Run close on every Publisher + for (var publisher in this.publishers) { + if (this.publishers[publisher]) { + this.logger.log("unpublish " + publisher); + this.publishers[publisher].close(); + } + } + // Run close on every applicant + for (var applicant in this.applicants) { + if (this.applicants[applicant]) { + this.applicants[applicant].close(); + } + } + this.status = Enums_1.UAStatus.STATUS_USER_CLOSED; + // Disconnect the transport and reset user agent core + if (this.transport) { + this.transport.disconnect(); + } + this.userAgentCore.reset(); + if (typeof environment.removeEventListener === "function") { + // Google Chrome Packaged Apps don't allow 'unload' listeners: + // unload is not available in packaged apps + if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) { + environment.removeEventListener("unload", this.environListener); + } + } + return this; + }; + /** + * Connect to the WS server if status = STATUS_INIT. + * Resume UA after being closed. + * + */ + UA.prototype.start = function () { + var _this = this; + this.logger.log("user requested startup..."); + if (this.status === Enums_1.UAStatus.STATUS_INIT) { + this.status = Enums_1.UAStatus.STATUS_STARTING; + if (!this.configuration.transportConstructor) { + throw new core_1.TransportError("Transport constructor not set"); + } + this.transport = new this.configuration.transportConstructor(this.getLogger("sip.transport"), this.configuration.transportOptions); + this.setTransportListeners(); + this.emit("transportCreated", this.transport); + this.transport.connect(); + } + else if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + this.logger.log("resuming"); + this.status = Enums_1.UAStatus.STATUS_READY; + if (this.transport) { + this.transport.connect(); + } + } + else if (this.status === Enums_1.UAStatus.STATUS_STARTING) { + this.logger.log("UA is in STARTING status, not opening new connection"); + } + else if (this.status === Enums_1.UAStatus.STATUS_READY) { + this.logger.log("UA is in READY status, not resuming"); + } + else { + this.logger.error("Connection is down. Auto-Recovery system is trying to connect"); + } + if (this.configuration.autostop && typeof environment.addEventListener === "function") { + // Google Chrome Packaged Apps don't allow 'unload' listeners: + // unload is not available in packaged apps + if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) { + this.environListener = this.stop; + environment.addEventListener("unload", function () { return _this.environListener(); }); + } + } + return this; + }; + /** + * Normalize a string into a valid SIP request URI + * + * @param {String} target + * + * @returns {SIP.URI|undefined} + */ + UA.prototype.normalizeTarget = function (target) { + return Utils_1.Utils.normalizeTarget(target, this.configuration.hostportParams); + }; + UA.prototype.getLogger = function (category, label) { + return this.log.getLogger(category, label); + }; + UA.prototype.getLoggerFactory = function () { + return this.log; + }; + UA.prototype.getSupportedResponseOptions = function () { + var optionTags = []; + if (this.contact.pubGruu || this.contact.tempGruu) { + optionTags.push("gruu"); + } + if (this.configuration.rel100 === Constants_1.C.supported.SUPPORTED) { + optionTags.push("100rel"); + } + if (this.configuration.replaces === Constants_1.C.supported.SUPPORTED) { + optionTags.push("replaces"); + } + optionTags.push("outbound"); + optionTags = optionTags.concat(this.configuration.extraSupported || []); + var allowUnregistered = this.configuration.hackAllowUnregisteredOptionTags || false; + var optionTagSet = {}; + optionTags = optionTags.filter(function (optionTag) { + var registered = Constants_1.C.OPTION_TAGS[optionTag]; + var unique = !optionTagSet[optionTag]; + optionTagSet[optionTag] = true; + return (registered || allowUnregistered) && unique; + }); + return optionTags; + }; + /** + * Get the session to which the request belongs to, if any. + * @param {SIP.IncomingRequest} request. + * @returns {SIP.OutgoingSession|SIP.IncomingSession|undefined} + */ + UA.prototype.findSession = function (request) { + return this.sessions[request.callId + request.fromTag] || + this.sessions[request.callId + request.toTag] || + undefined; + }; + UA.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + // ============================== + // Event Handlers + // ============================== + UA.prototype.onTransportError = function () { + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + return; + } + if (!this.error || this.error !== UA.C.NETWORK_ERROR) { + this.status = Enums_1.UAStatus.STATUS_NOT_READY; + this.error = UA.C.NETWORK_ERROR; + } + }; + /** + * Helper function. Sets transport listeners + */ + UA.prototype.setTransportListeners = function () { + var _this = this; + if (this.transport) { + this.transport.on("connected", function () { return _this.onTransportConnected(); }); + this.transport.on("message", function (message) { return _this.onTransportReceiveMsg(message); }); + this.transport.on("transportError", function () { return _this.onTransportError(); }); + } + }; + /** + * Transport connection event. + * @event + * @param {SIP.Transport} transport. + */ + UA.prototype.onTransportConnected = function () { + var _this = this; + if (this.configuration.register) { + // In an effor to maintain behavior from when we "initialized" an + // authentication factory, this is in a Promise.then + Promise.resolve().then(function () { return _this.registerContext.register(); }); + } + }; + /** + * Handle SIP message received from the transport. + * @param messageString The message. + */ + UA.prototype.onTransportReceiveMsg = function (messageString) { + var _this = this; + var message = Parser_1.Parser.parseMessage(messageString, this.getLogger("sip.parser")); + if (!message) { + this.logger.warn("UA failed to parse incoming SIP message - discarding."); + return; + } + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED && message instanceof core_1.IncomingRequestMessage) { + this.logger.warn("UA received message when status = USER_CLOSED - aborting"); + return; + } + // A valid SIP request formulated by a UAC MUST, at a minimum, contain + // the following header fields: To, From, CSeq, Call-ID, Max-Forwards, + // and Via; all of these header fields are mandatory in all SIP + // requests. + // https://tools.ietf.org/html/rfc3261#section-8.1.1 + var hasMinimumHeaders = function () { + var mandatoryHeaders = ["from", "to", "call_id", "cseq", "via"]; + for (var _i = 0, mandatoryHeaders_1 = mandatoryHeaders; _i < mandatoryHeaders_1.length; _i++) { + var header = mandatoryHeaders_1[_i]; + if (!message.hasHeader(header)) { + _this.logger.warn("Missing mandatory header field : " + header + "."); + return false; + } + } + return true; + }; + // Request Checks + if (message instanceof core_1.IncomingRequestMessage) { + // This is port of SanityCheck.minimumHeaders(). + if (!hasMinimumHeaders()) { + this.logger.warn("Request missing mandatory header field. Dropping."); + return; + } + // FIXME: This is non-standard and should be a configruable behavior (desirable regardless). + // Custom SIP.js check to reject request from ourself (this instance of SIP.js). + // This is port of SanityCheck.rfc3261_16_3_4(). + if (!message.toTag && message.callId.substr(0, 5) === this.configuration.sipjsId) { + this.userAgentCore.replyStateless(message, { statusCode: 482 }); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to reject requests if body length wrong. + // This is port of SanityCheck.rfc3261_18_3_request(). + var len = Utils_1.Utils.str_utf8_length(message.body); + var contentLength = message.getHeader("content-length"); + if (contentLength && len < Number(contentLength)) { + this.userAgentCore.replyStateless(message, { statusCode: 400 }); + return; + } + } + // Reponse Checks + if (message instanceof core_1.IncomingResponseMessage) { + // This is port of SanityCheck.minimumHeaders(). + if (!hasMinimumHeaders()) { + this.logger.warn("Response missing mandatory header field. Dropping."); + return; + } + // Custom SIP.js check to drop responses if multiple Via headers. + // This is port of SanityCheck.rfc3261_8_1_3_3(). + if (message.getHeaders("via").length > 1) { + this.logger.warn("More than one Via header field present in the response. Dropping."); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to drop responses if bad Via header. + // This is port of SanityCheck.rfc3261_18_1_2(). + if (message.via.host !== this.configuration.viaHost || message.via.port !== undefined) { + this.logger.warn("Via sent-by in the response does not match UA Via host value. Dropping."); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to reject requests if body length wrong. + // This is port of SanityCheck.rfc3261_18_3_response(). + var len = Utils_1.Utils.str_utf8_length(message.body); + var contentLength = message.getHeader("content-length"); + if (contentLength && len < Number(contentLength)) { + this.logger.warn("Message body length is lower than the value in Content-Length header field. Dropping."); + return; + } + } + // Handle Request + if (message instanceof core_1.IncomingRequestMessage) { + this.userAgentCore.receiveIncomingRequestFromTransport(message); + return; + } + // Handle Response + if (message instanceof core_1.IncomingResponseMessage) { + this.userAgentCore.receiveIncomingResponseFromTransport(message); + return; + } + throw new Error("Invalid message type."); + }; + // ================= + // Utils + // ================= + UA.prototype.checkAuthenticationFactory = function (authenticationFactory) { + if (!(authenticationFactory instanceof Function)) { + return; + } + if (!authenticationFactory.initialize) { + authenticationFactory.initialize = function () { + return Promise.resolve(); + }; + } + return authenticationFactory; + }; + /** + * Configuration load. + * returns {void} + */ + UA.prototype.loadConfig = function (configuration) { + var _this = this; + // Settings and default values + var settings = { + /* Host address + * Value to be set in Via sent_by and host part of Contact FQDN + */ + viaHost: Utils_1.Utils.createRandomToken(12) + ".invalid", + uri: new core_1.URI("sip", "anonymous." + Utils_1.Utils.createRandomToken(6), "anonymous.invalid", undefined, undefined), + // Custom Configuration Settings + custom: {}, + // Display name + displayName: "", + // Password + password: undefined, + register: true, + // Registration parameters + registerOptions: {}, + // Transport related parameters + transportConstructor: Transport_1.Transport, + transportOptions: {}, + // string to be inserted into User-Agent request header + userAgentString: Constants_1.C.USER_AGENT, + // Session parameters + noAnswerTimeout: 60, + // Hacks + hackViaTcp: false, + hackIpInContact: false, + hackWssInTransport: false, + hackAllowUnregisteredOptionTags: false, + // Session Description Handler Options + sessionDescriptionHandlerFactoryOptions: { + constraints: {}, + peerConnectionOptions: {} + }, + extraSupported: [], + contactName: Utils_1.Utils.createRandomToken(8), + contactTransport: "ws", + forceRport: false, + // autostarting + autostart: true, + autostop: true, + // Reliable Provisional Responses + rel100: Constants_1.C.supported.UNSUPPORTED, + // DTMF type: 'info' or 'rtp' (RFC 4733) + // RTP Payload Spec: https://tools.ietf.org/html/rfc4733 + // WebRTC Audio Spec: https://tools.ietf.org/html/rfc7874 + dtmfType: Constants_1.C.dtmfType.INFO, + // Replaces header (RFC 3891) + // http://tools.ietf.org/html/rfc3891 + replaces: Constants_1.C.supported.UNSUPPORTED, + sessionDescriptionHandlerFactory: SessionDescriptionHandler_1.SessionDescriptionHandler.defaultFactory, + authenticationFactory: this.checkAuthenticationFactory(function (ua) { + return new core_1.DigestAuthentication(ua.getLoggerFactory(), _this.configuration.authorizationUser, _this.configuration.password); + }), + allowLegacyNotifications: false, + allowOutOfDialogRefers: false, + experimentalFeatures: false + }; + var configCheck = this.getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if ((value instanceof Array && value.length === 0) || + (value === null || value === "" || value === undefined) || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Post Configuration Process + // Allow passing 0 number as displayName. + if (settings.displayName === 0) { + settings.displayName = "0"; + } + // sipjsId instance parameter. Static random tag of length 5 + settings.sipjsId = Utils_1.Utils.createRandomToken(5); + // String containing settings.uri without scheme and user. + var hostportParams = settings.uri.clone(); + hostportParams.user = undefined; + settings.hostportParams = hostportParams.toRaw().replace(/^sip:/i, ""); + /* Check whether authorizationUser is explicitly defined. + * Take 'settings.uri.user' value if not. + */ + if (!settings.authorizationUser) { + settings.authorizationUser = settings.uri.user; + } + // User noAnswerTimeout + settings.noAnswerTimeout = settings.noAnswerTimeout * 1000; + // Via Host + if (settings.hackIpInContact) { + if (typeof settings.hackIpInContact === "boolean") { + var from = 1; + var to = 254; + var octet = Math.floor(Math.random() * (to - from + 1) + from); + // random Test-Net IP (http://tools.ietf.org/html/rfc5735) + settings.viaHost = "192.0.2." + octet; + } + else if (typeof settings.hackIpInContact === "string") { + settings.viaHost = settings.hackIpInContact; + } + } + // Contact transport parameter + if (settings.hackWssInTransport) { + settings.contactTransport = "wss"; + } + this.contact = { + pubGruu: undefined, + tempGruu: undefined, + uri: new core_1.URI("sip", settings.contactName, settings.viaHost, undefined, { transport: settings.contactTransport }), + toString: function (options) { + if (options === void 0) { options = {}; } + var anonymous = options.anonymous || false; + var outbound = options.outbound || false; + var contact = "<"; + if (anonymous) { + contact += (_this.contact.tempGruu || + ("sip:anonymous@anonymous.invalid;transport=" + settings.contactTransport)).toString(); + } + else { + contact += (_this.contact.pubGruu || _this.contact.uri).toString(); + } + if (outbound) { + contact += ";ob"; + } + contact += ">"; + return contact; + } + }; + var skeleton = {}; + // Fill the value of the configuration_skeleton + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + skeleton[parameter] = settings[parameter]; + } + } + Object.assign(this.configuration, skeleton); + this.logger.log("configuration parameters after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + switch (parameter) { + case "uri": + case "sessionDescriptionHandlerFactory": + this.logger.log("· " + parameter + ": " + settings[parameter]); + break; + case "password": + this.logger.log("· " + parameter + ": " + "NOT SHOWN"); + break; + case "transportConstructor": + this.logger.log("· " + parameter + ": " + settings[parameter].name); + break; + default: + this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + } + return; + }; + /** + * Configuration checker. + * @return {Boolean} + */ + UA.prototype.getConfigurationCheck = function () { + return { + mandatory: {}, + optional: { + uri: function (uri) { + if (!(/^sip:/i).test(uri)) { + uri = Constants_1.C.SIP + ":" + uri; + } + var parsed = core_1.Grammar.URIParse(uri); + if (!parsed || !parsed.user) { + return; + } + else { + return parsed; + } + }, + transportConstructor: function (transportConstructor) { + if (transportConstructor instanceof Function) { + return transportConstructor; + } + }, + transportOptions: function (transportOptions) { + if (typeof transportOptions === "object") { + return transportOptions; + } + }, + authorizationUser: function (authorizationUser) { + if (core_1.Grammar.parse('"' + authorizationUser + '"', "quoted_string") === -1) { + return; + } + else { + return authorizationUser; + } + }, + displayName: function (displayName) { + if (core_1.Grammar.parse('"' + displayName + '"', "displayName") === -1) { + return; + } + else { + return displayName; + } + }, + dtmfType: function (dtmfType) { + switch (dtmfType) { + case Constants_1.C.dtmfType.RTP: + return Constants_1.C.dtmfType.RTP; + case Constants_1.C.dtmfType.INFO: + // Fall through + default: + return Constants_1.C.dtmfType.INFO; + } + }, + hackViaTcp: function (hackViaTcp) { + if (typeof hackViaTcp === "boolean") { + return hackViaTcp; + } + }, + hackIpInContact: function (hackIpInContact) { + if (typeof hackIpInContact === "boolean") { + return hackIpInContact; + } + else if (typeof hackIpInContact === "string" && core_1.Grammar.parse(hackIpInContact, "host") !== -1) { + return hackIpInContact; + } + }, + hackWssInTransport: function (hackWssInTransport) { + if (typeof hackWssInTransport === "boolean") { + return hackWssInTransport; + } + }, + hackAllowUnregisteredOptionTags: function (hackAllowUnregisteredOptionTags) { + if (typeof hackAllowUnregisteredOptionTags === "boolean") { + return hackAllowUnregisteredOptionTags; + } + }, + contactTransport: function (contactTransport) { + if (typeof contactTransport === "string") { + return contactTransport; + } + }, + extraSupported: function (optionTags) { + if (!(optionTags instanceof Array)) { + return; + } + for (var _i = 0, optionTags_1 = optionTags; _i < optionTags_1.length; _i++) { + var tag = optionTags_1[_i]; + if (typeof tag !== "string") { + return; + } + } + return optionTags; + }, + forceRport: function (forceRport) { + if (typeof forceRport === "boolean") { + return forceRport; + } + }, + noAnswerTimeout: function (noAnswerTimeout) { + if (Utils_1.Utils.isDecimal(noAnswerTimeout)) { + var value = Number(noAnswerTimeout); + if (value > 0) { + return value; + } + } + }, + password: function (password) { + return String(password); + }, + rel100: function (rel100) { + if (rel100 === Constants_1.C.supported.REQUIRED) { + return Constants_1.C.supported.REQUIRED; + } + else if (rel100 === Constants_1.C.supported.SUPPORTED) { + return Constants_1.C.supported.SUPPORTED; + } + else { + return Constants_1.C.supported.UNSUPPORTED; + } + }, + replaces: function (replaces) { + if (replaces === Constants_1.C.supported.REQUIRED) { + return Constants_1.C.supported.REQUIRED; + } + else if (replaces === Constants_1.C.supported.SUPPORTED) { + return Constants_1.C.supported.SUPPORTED; + } + else { + return Constants_1.C.supported.UNSUPPORTED; + } + }, + register: function (register) { + if (typeof register === "boolean") { + return register; + } + }, + registerOptions: function (registerOptions) { + if (typeof registerOptions === "object") { + return registerOptions; + } + }, + userAgentString: function (userAgentString) { + if (typeof userAgentString === "string") { + return userAgentString; + } + }, + autostart: function (autostart) { + if (typeof autostart === "boolean") { + return autostart; + } + }, + autostop: function (autostop) { + if (typeof autostop === "boolean") { + return autostop; + } + }, + sessionDescriptionHandlerFactory: function (sessionDescriptionHandlerFactory) { + if (sessionDescriptionHandlerFactory instanceof Function) { + return sessionDescriptionHandlerFactory; + } + }, + sessionDescriptionHandlerFactoryOptions: function (options) { + if (typeof options === "object") { + return options; + } + }, + authenticationFactory: this.checkAuthenticationFactory, + allowLegacyNotifications: function (allowLegacyNotifications) { + if (typeof allowLegacyNotifications === "boolean") { + return allowLegacyNotifications; + } + }, + custom: function (custom) { + if (typeof custom === "object") { + return custom; + } + }, + contactName: function (contactName) { + if (typeof contactName === "string") { + return contactName; + } + }, + experimentalFeatures: function (experimentalFeatures) { + if (typeof experimentalFeatures === "boolean") { + return experimentalFeatures; + } + }, + } + }; + }; + UA.C = { + // UA status codes + STATUS_INIT: 0, + STATUS_STARTING: 1, + STATUS_READY: 2, + STATUS_USER_CLOSED: 3, + STATUS_NOT_READY: 4, + // UA error codes + CONFIGURATION_ERROR: 1, + NETWORK_ERROR: 2, + ALLOWED_METHODS: [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ], + ACCEPTED_BODY_TYPES: [ + "application/sdp", + "application/dtmf-relay" + ], + MAX_FORWARDS: 70, + TAG_LENGTH: 10 + }; + return UA; +}(events_1.EventEmitter)); +exports.UA = UA; +(function (UA) { + var DtmfType; + (function (DtmfType) { + DtmfType["RTP"] = "rtp"; + DtmfType["INFO"] = "info"; + })(DtmfType = UA.DtmfType || (UA.DtmfType = {})); +})(UA = exports.UA || (exports.UA = {})); +exports.UA = UA; +/** + * Factory function to generate configuration give a UA. + * @param ua UA + */ +function makeUserAgentCoreConfigurationFromUA(ua) { + // FIXME: Configuration URI is a bad mix of types currently. It also needs to exist. + if (!(ua.configuration.uri instanceof core_1.URI)) { + throw new Error("Configuration URI not instance of URI."); + } + var aor = ua.configuration.uri; + var contact = ua.contact; + var displayName = ua.configuration.displayName ? ua.configuration.displayName : ""; + var hackViaTcp = ua.configuration.hackViaTcp ? true : false; + var routeSet = ua.configuration.usePreloadedRoute && ua.transport ? [ua.transport.server.sipUri] : []; + var sipjsId = ua.configuration.sipjsId || Utils_1.Utils.createRandomToken(5); + var supportedOptionTags = []; + supportedOptionTags.push("outbound"); // TODO: is this really supported? + if (ua.configuration.rel100 === Constants_1.C.supported.SUPPORTED) { + supportedOptionTags.push("100rel"); + } + if (ua.configuration.replaces === Constants_1.C.supported.SUPPORTED) { + supportedOptionTags.push("replaces"); + } + if (ua.configuration.extraSupported) { + supportedOptionTags.push.apply(supportedOptionTags, ua.configuration.extraSupported); + } + if (!ua.configuration.hackAllowUnregisteredOptionTags) { + supportedOptionTags = supportedOptionTags.filter(function (optionTag) { return Constants_1.C.OPTION_TAGS[optionTag]; }); + } + supportedOptionTags = Array.from(new Set(supportedOptionTags)); // array of unique values + var supportedOptionTagsResponse = ua.getSupportedResponseOptions(); + var userAgentHeaderFieldValue = ua.configuration.userAgentString || "sipjs"; + if (!(ua.configuration.viaHost)) { + throw new Error("Configuration via host undefined"); + } + var viaForceRport = ua.configuration.forceRport ? true : false; + var viaHost = ua.configuration.viaHost; + var configuration = { + aor: aor, + contact: contact, + displayName: displayName, + hackViaTcp: hackViaTcp, + loggerFactory: ua.getLoggerFactory(), + routeSet: routeSet, + sipjsId: sipjsId, + supportedOptionTags: supportedOptionTags, + supportedOptionTagsResponse: supportedOptionTagsResponse, + userAgentHeaderFieldValue: userAgentHeaderFieldValue, + viaForceRport: viaForceRport, + viaHost: viaHost, + authenticationFactory: function () { + if (ua.configuration.authenticationFactory) { + return ua.configuration.authenticationFactory(ua); + } + return undefined; + }, + transportAccessor: function () { return ua.transport; } + }; + return configuration; +} +exports.makeUserAgentCoreConfigurationFromUA = makeUserAgentCoreConfigurationFromUA; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }), +/* 93 */ +/***/ (function(module, exports) { + +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || new Function("return this")(); +} catch (e) { + // This works if the window reference is available + if (typeof window === "object") g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; + + +/***/ }), +/* 94 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +var Modifiers = tslib_1.__importStar(__webpack_require__(95)); +var SessionDescriptionHandlerObserver_1 = __webpack_require__(96); +/* SessionDescriptionHandler + * @class PeerConnection helper Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandler = /** @class */ (function (_super) { + tslib_1.__extends(SessionDescriptionHandler, _super); + function SessionDescriptionHandler(logger, observer, options) { + var _this = _super.call(this) || this; + _this.type = Enums_1.TypeStrings.SessionDescriptionHandler; + // TODO: Validate the options + _this.options = options || {}; + _this.logger = logger; + _this.observer = observer; + _this.dtmfSender = undefined; + _this.shouldAcquireMedia = true; + _this.CONTENT_TYPE = "application/sdp"; + _this.C = { + DIRECTION: { + NULL: null, + SENDRECV: "sendrecv", + SENDONLY: "sendonly", + RECVONLY: "recvonly", + INACTIVE: "inactive" + } + }; + _this.logger.log("SessionDescriptionHandlerOptions: " + JSON.stringify(_this.options)); + _this.direction = _this.C.DIRECTION.NULL; + _this.modifiers = _this.options.modifiers || []; + if (!Array.isArray(_this.modifiers)) { + _this.modifiers = [_this.modifiers]; + } + var environment = global.window || global; + _this.WebRTC = { + MediaStream: environment.MediaStream, + getUserMedia: environment.navigator.mediaDevices.getUserMedia.bind(environment.navigator.mediaDevices), + RTCPeerConnection: environment.RTCPeerConnection + }; + _this.iceGatheringTimeout = false; + _this.initPeerConnection(_this.options.peerConnectionOptions); + _this.constraints = _this.checkAndDefaultConstraints(_this.options.constraints); + return _this; + } + /** + * @param {SIP.Session} session + * @param {Object} [options] + */ + SessionDescriptionHandler.defaultFactory = function (session, options) { + var logger = session.ua.getLogger("sip.invitecontext.sessionDescriptionHandler", session.id); + var observer = new SessionDescriptionHandlerObserver_1.SessionDescriptionHandlerObserver(session, options); + return new SessionDescriptionHandler(logger, observer, options); + }; + // Functions the sesssion can use + /** + * Destructor + */ + SessionDescriptionHandler.prototype.close = function () { + this.logger.log("closing PeerConnection"); + // have to check signalingState since this.close() gets called multiple times + if (this.peerConnection && this.peerConnection.signalingState !== "closed") { + if (this.peerConnection.getSenders) { + this.peerConnection.getSenders().forEach(function (sender) { + if (sender.track) { + sender.track.stop(); + } + }); + } + else { + this.logger.warn("Using getLocalStreams which is deprecated"); + this.peerConnection.getLocalStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + if (this.peerConnection.getReceivers) { + this.peerConnection.getReceivers().forEach(function (receiver) { + if (receiver.track) { + receiver.track.stop(); + } + }); + } + else { + this.logger.warn("Using getRemoteStreams which is deprecated"); + this.peerConnection.getRemoteStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + }; + /** + * Gets the local description from the underlying media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves with the local description to be used for the session + */ + SessionDescriptionHandler.prototype.getDescription = function (options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + // Merge passed constraints with saved constraints and save + var newConstraints = Object.assign({}, this.constraints, options.constraints); + newConstraints = this.checkAndDefaultConstraints(newConstraints); + if (JSON.stringify(newConstraints) !== JSON.stringify(this.constraints)) { + this.constraints = newConstraints; + this.shouldAcquireMedia = true; + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + return Promise.resolve().then(function () { + if (_this.shouldAcquireMedia) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return _this.createOfferOrAnswer(options.RTCOfferOptions, modifiers); }) + .then(function (description) { + if (description.sdp === undefined) { + throw new Exceptions_1.Exceptions.SessionDescriptionHandlerError("getDescription", undefined, "SDP undefined"); + } + _this.emit("getDescription", description); + return { + body: description.sdp, + contentType: _this.CONTENT_TYPE + }; + }); + }; + /** + * Check if the Session Description Handler can handle the Content-Type described by a SIP Message + * @param {String} contentType The content type that is in the SIP Message + * @returns {boolean} + */ + SessionDescriptionHandler.prototype.hasDescription = function (contentType) { + return contentType === this.CONTENT_TYPE; + }; + /** + * The modifier that should be used when the session would like to place the call on hold + * @param {String} [sdp] The description that will be modified + * @returns {Promise} Promise that resolves with modified SDP + */ + SessionDescriptionHandler.prototype.holdModifier = function (description) { + if (!description.sdp) { + return Promise.resolve(description); + } + if (!(/a=(sendrecv|sendonly|recvonly|inactive)/).test(description.sdp)) { + description.sdp = description.sdp.replace(/(m=[^\r]*\r\n)/g, "$1a=sendonly\r\n"); + } + else { + description.sdp = description.sdp.replace(/a=sendrecv\r\n/g, "a=sendonly\r\n"); + description.sdp = description.sdp.replace(/a=recvonly\r\n/g, "a=inactive\r\n"); + } + return Promise.resolve(description); + }; + /** + * Set the remote description to the underlying media implementation + * @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves once the description is set + */ + SessionDescriptionHandler.prototype.setDescription = function (sessionDescription, options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + var description = { + type: this.hasOffer("local") ? "answer" : "offer", + sdp: sessionDescription + }; + return Promise.resolve().then(function () { + // Media should be acquired in getDescription unless we need to do it sooner for some reason (FF61+) + if (_this.shouldAcquireMedia && _this.options.alwaysAcquireMediaFirst) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return Utils_1.Utils.reducePromises(modifiers, description); }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e, "The modifiers did not resolve successfully"); + _this.logger.error(error.message); + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function (modifiedDescription) { + _this.emit("setDescription", modifiedDescription); + return _this.peerConnection.setRemoteDescription(modifiedDescription); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + // Check the original SDP for video, and ensure that we have want to do audio fallback + if ((/^m=video.+$/gm).test(sessionDescription) && !options.disableAudioFallback) { + // Do not try to audio fallback again + options.disableAudioFallback = true; + // Remove video first, then do the other modifiers + return _this.setDescription(sessionDescription, options, [Modifiers.stripVideo].concat(modifiers)); + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e); + if (error.error) { + _this.logger.error(error.error); + } + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function () { + if (_this.peerConnection.getReceivers) { + _this.emit("setRemoteDescription", _this.peerConnection.getReceivers()); + } + else { + _this.emit("setRemoteDescription", _this.peerConnection.getRemoteStreams()); + } + _this.emit("confirmed", _this); + }); + }; + /** + * Send DTMF via RTP (RFC 4733) + * @param {String} tones A string containing DTMF digits + * @param {Object} [options] Options object to be used by sendDtmf + * @returns {boolean} true if DTMF send is successful, false otherwise + */ + SessionDescriptionHandler.prototype.sendDtmf = function (tones, options) { + if (options === void 0) { options = {}; } + if (!this.dtmfSender && this.hasBrowserGetSenderSupport()) { + var senders = this.peerConnection.getSenders(); + if (senders.length > 0) { + this.dtmfSender = senders[0].dtmf; + } + } + if (!this.dtmfSender && this.hasBrowserTrackSupport()) { + var streams = this.peerConnection.getLocalStreams(); + if (streams.length > 0) { + var audioTracks = streams[0].getAudioTracks(); + if (audioTracks.length > 0) { + this.dtmfSender = this.peerConnection.createDTMFSender(audioTracks[0]); + } + } + } + if (!this.dtmfSender) { + return false; + } + try { + this.dtmfSender.insertDTMF(tones, options.duration, options.interToneGap); + } + catch (e) { + if (e.type === "InvalidStateError" || e.type === "InvalidCharacterError") { + this.logger.error(e); + return false; + } + else { + throw e; + } + } + this.logger.log("DTMF sent via RTP: " + tones.toString()); + return true; + }; + /** + * Get the direction of the session description + * @returns {String} direction of the description + */ + SessionDescriptionHandler.prototype.getDirection = function () { + return this.direction; + }; + SessionDescriptionHandler.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + // Internal functions + SessionDescriptionHandler.prototype.createOfferOrAnswer = function (RTCOfferOptions, modifiers) { + var _this = this; + if (RTCOfferOptions === void 0) { RTCOfferOptions = {}; } + if (modifiers === void 0) { modifiers = []; } + var methodName = this.hasOffer("remote") ? "createAnswer" : "createOffer"; + var pc = this.peerConnection; + this.logger.log(methodName); + var method = this.hasOffer("remote") ? pc.createAnswer : pc.createOffer; + return method.apply(pc, RTCOfferOptions).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-" + methodName + "Failed"); + _this.emit("peerConnection-" + methodName + "Failed", error); + throw error; + }).then(function (sdp) { + return Utils_1.Utils.reducePromises(modifiers, _this.createRTCSessionDescriptionInit(sdp)); + }).then(function (sdp) { + _this.resetIceGatheringComplete(); + _this.logger.log("Setting local sdp."); + _this.logger.log("sdp is " + sdp.sdp || false); + return pc.setLocalDescription(sdp); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-SetLocalDescriptionFailed"); + _this.emit("peerConnection-SetLocalDescriptionFailed", error); + throw error; + }).then(function () { return _this.waitForIceGatheringComplete(); }) + .then(function () { + var localDescription = _this.createRTCSessionDescriptionInit(_this.peerConnection.localDescription); + return Utils_1.Utils.reducePromises(modifiers, localDescription); + }).then(function (localDescription) { + _this.setDirection(localDescription.sdp || ""); + return localDescription; + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e); + _this.logger.error(error.toString()); + throw error; + }); + }; + // Creates an RTCSessionDescriptionInit from an RTCSessionDescription + SessionDescriptionHandler.prototype.createRTCSessionDescriptionInit = function (RTCSessionDescription) { + return { + type: RTCSessionDescription.type, + sdp: RTCSessionDescription.sdp + }; + }; + SessionDescriptionHandler.prototype.addDefaultIceCheckingTimeout = function (peerConnectionOptions) { + if (peerConnectionOptions.iceCheckingTimeout === undefined) { + peerConnectionOptions.iceCheckingTimeout = 5000; + } + return peerConnectionOptions; + }; + SessionDescriptionHandler.prototype.addDefaultIceServers = function (rtcConfiguration) { + if (!rtcConfiguration.iceServers) { + rtcConfiguration.iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; + } + return rtcConfiguration; + }; + SessionDescriptionHandler.prototype.checkAndDefaultConstraints = function (constraints) { + var defaultConstraints = { audio: true, video: !this.options.alwaysAcquireMediaFirst }; + constraints = constraints || defaultConstraints; + // Empty object check + if (Object.keys(constraints).length === 0 && constraints.constructor === Object) { + return defaultConstraints; + } + return constraints; + }; + SessionDescriptionHandler.prototype.hasBrowserTrackSupport = function () { + return Boolean(this.peerConnection.addTrack); + }; + SessionDescriptionHandler.prototype.hasBrowserGetSenderSupport = function () { + return Boolean(this.peerConnection.getSenders); + }; + SessionDescriptionHandler.prototype.initPeerConnection = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + options = this.addDefaultIceCheckingTimeout(options); + options.rtcConfiguration = options.rtcConfiguration || {}; + options.rtcConfiguration = this.addDefaultIceServers(options.rtcConfiguration); + this.logger.log("initPeerConnection"); + if (this.peerConnection) { + this.logger.log("Already have a peer connection for this session. Tearing down."); + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + this.peerConnection = new this.WebRTC.RTCPeerConnection(options.rtcConfiguration); + this.logger.log("New peer connection created"); + if ("ontrack" in this.peerConnection) { + this.peerConnection.addEventListener("track", function (e) { + _this.logger.log("track added"); + _this.observer.trackAdded(); + _this.emit("addTrack", e); + }); + } + else { + this.logger.warn("Using onaddstream which is deprecated"); + this.peerConnection.onaddstream = function (e) { + _this.logger.log("stream added"); + _this.emit("addStream", e); + }; + } + this.peerConnection.onicecandidate = function (e) { + _this.emit("iceCandidate", e); + if (e.candidate) { + _this.logger.log("ICE candidate received: " + + (e.candidate.candidate === null ? null : e.candidate.candidate.trim())); + } + else if (e.candidate === null) { + // indicates the end of candidate gathering + _this.logger.log("ICE candidate gathering complete"); + _this.triggerIceGatheringComplete(); + } + }; + this.peerConnection.onicegatheringstatechange = function () { + _this.logger.log("RTCIceGatheringState changed: " + _this.peerConnection.iceGatheringState); + switch (_this.peerConnection.iceGatheringState) { + case "gathering": + _this.emit("iceGathering", _this); + if (!_this.iceGatheringTimer && options.iceCheckingTimeout) { + _this.iceGatheringTimeout = false; + _this.iceGatheringTimer = setTimeout(function () { + _this.logger.log("RTCIceChecking Timeout Triggered after " + options.iceCheckingTimeout + " milliseconds"); + _this.iceGatheringTimeout = true; + _this.triggerIceGatheringComplete(); + }, options.iceCheckingTimeout); + } + break; + case "complete": + _this.triggerIceGatheringComplete(); + break; + } + }; + this.peerConnection.oniceconnectionstatechange = function () { + var stateEvent; + switch (_this.peerConnection.iceConnectionState) { + case "new": + stateEvent = "iceConnection"; + break; + case "checking": + stateEvent = "iceConnectionChecking"; + break; + case "connected": + stateEvent = "iceConnectionConnected"; + break; + case "completed": + stateEvent = "iceConnectionCompleted"; + break; + case "failed": + stateEvent = "iceConnectionFailed"; + break; + case "disconnected": + stateEvent = "iceConnectionDisconnected"; + break; + case "closed": + stateEvent = "iceConnectionClosed"; + break; + default: + _this.logger.warn("Unknown iceConnection state: " + _this.peerConnection.iceConnectionState); + return; + } + _this.logger.log("ICE Connection State changed to " + stateEvent); + _this.emit(stateEvent, _this); + }; + }; + SessionDescriptionHandler.prototype.acquire = function (constraints) { + var _this = this; + // Default audio & video to true + constraints = this.checkAndDefaultConstraints(constraints); + return new Promise(function (resolve, reject) { + /* + * Make the call asynchronous, so that ICCs have a chance + * to define callbacks to `userMediaRequest` + */ + _this.logger.log("acquiring local media"); + _this.emit("userMediaRequest", constraints); + if (constraints.audio || constraints.video) { + _this.WebRTC.getUserMedia(constraints).then(function (streams) { + _this.observer.trackAdded(); + _this.emit("userMedia", streams); + resolve(streams); + }).catch(function (e) { + _this.emit("userMediaFailed", e); + reject(e); + }); + } + else { + // Local streams were explicitly excluded. + resolve([]); + } + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "unable to acquire streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + _this.logger.log("acquired local media streams"); + try { + // Remove old tracks + if (_this.peerConnection.removeTrack) { + _this.peerConnection.getSenders().forEach(function (sender) { + _this.peerConnection.removeTrack(sender); + }); + } + return streams; + } + catch (e) { + return Promise.reject(e); + } + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error removing streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + try { + streams = [].concat(streams); + streams.forEach(function (stream) { + if (_this.peerConnection.addTrack) { + stream.getTracks().forEach(function (track) { + _this.peerConnection.addTrack(track, stream); + }); + } + else { + // Chrome 59 does not support addTrack + _this.peerConnection.addStream(stream); + } + }); + } + catch (e) { + return Promise.reject(e); + } + return Promise.resolve(); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error adding stream"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }); + }; + SessionDescriptionHandler.prototype.hasOffer = function (where) { + var offerState = "have-" + where + "-offer"; + return this.peerConnection.signalingState === offerState; + }; + // ICE gathering state handling + SessionDescriptionHandler.prototype.isIceGatheringComplete = function () { + return this.peerConnection.iceGatheringState === "complete" || this.iceGatheringTimeout; + }; + SessionDescriptionHandler.prototype.resetIceGatheringComplete = function () { + this.iceGatheringTimeout = false; + this.logger.log("resetIceGatheringComplete"); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.reject(); + this.iceGatheringDeferred = undefined; + } + }; + SessionDescriptionHandler.prototype.setDirection = function (sdp) { + var match = sdp.match(/a=(sendrecv|sendonly|recvonly|inactive)/); + if (match === null) { + this.direction = this.C.DIRECTION.NULL; + this.observer.directionChanged(); + return; + } + var direction = match[1]; + switch (direction) { + case this.C.DIRECTION.SENDRECV: + case this.C.DIRECTION.SENDONLY: + case this.C.DIRECTION.RECVONLY: + case this.C.DIRECTION.INACTIVE: + this.direction = direction; + break; + default: + this.direction = this.C.DIRECTION.NULL; + break; + } + this.observer.directionChanged(); + }; + SessionDescriptionHandler.prototype.triggerIceGatheringComplete = function () { + if (this.isIceGatheringComplete()) { + this.emit("iceGatheringComplete", this); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.resolve(); + this.iceGatheringDeferred = undefined; + } + } + }; + SessionDescriptionHandler.prototype.waitForIceGatheringComplete = function () { + this.logger.log("waitForIceGatheringComplete"); + if (this.isIceGatheringComplete()) { + this.logger.log("ICE is already complete. Return resolved."); + return Promise.resolve(); + } + else if (!this.iceGatheringDeferred) { + this.iceGatheringDeferred = Utils_1.Utils.defer(); + } + this.logger.log("ICE is not complete. Returning promise"); + return this.iceGatheringDeferred ? this.iceGatheringDeferred.promise : Promise.resolve(); + }; + return SessionDescriptionHandler; +}(events_1.EventEmitter)); +exports.SessionDescriptionHandler = SessionDescriptionHandler; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }), +/* 95 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var stripPayload = function (sdp, payload) { + var mediaDescs = []; + var lines = sdp.split(/\r\n/); + var currentMediaDesc; + for (var i = 0; i < lines.length;) { + var line = lines[i]; + if (/^m=(?:audio|video)/.test(line)) { + currentMediaDesc = { + index: i, + stripped: [] + }; + mediaDescs.push(currentMediaDesc); + } + else if (currentMediaDesc) { + var rtpmap = /^a=rtpmap:(\d+) ([^/]+)\//.exec(line); + if (rtpmap && payload === rtpmap[2]) { + lines.splice(i, 1); + currentMediaDesc.stripped.push(rtpmap[1]); + continue; // Don't increment 'i' + } + } + i++; + } + for (var _i = 0, mediaDescs_1 = mediaDescs; _i < mediaDescs_1.length; _i++) { + var mediaDesc = mediaDescs_1[_i]; + var mline = lines[mediaDesc.index].split(" "); + // Ignore the first 3 parameters of the mline. The codec information is after that + for (var j = 3; j < mline.length;) { + if (mediaDesc.stripped.indexOf(mline[j]) !== -1) { + mline.splice(j, 1); + continue; + } + j++; + } + lines[mediaDesc.index] = mline.join(" "); + } + return lines.join("\r\n"); +}; +var stripMediaDescription = function (sdp, description) { + var descriptionRegExp = new RegExp("m=" + description + ".*$", "gm"); + var groupRegExp = new RegExp("^a=group:.*$", "gm"); + if (descriptionRegExp.test(sdp)) { + var midLineToRemove_1; + sdp = sdp.split(/^m=/gm).filter(function (section) { + if (section.substr(0, description.length) === description) { + midLineToRemove_1 = section.match(/^a=mid:.*$/gm); + if (midLineToRemove_1) { + var step = midLineToRemove_1[0].match(/:.+$/g); + if (step) { + midLineToRemove_1 = step[0].substr(1); + } + } + return false; + } + return true; + }).join("m="); + var groupLine = sdp.match(groupRegExp); + if (groupLine && groupLine.length === 1) { + var groupLinePortion = groupLine[0]; + var groupRegExpReplace = new RegExp("\ *" + midLineToRemove_1 + "[^\ ]*", "g"); + groupLinePortion = groupLinePortion.replace(groupRegExpReplace, ""); + sdp = sdp.split(groupRegExp).join(groupLinePortion); + } + } + return sdp; +}; +function stripTcpCandidates(description) { + description.sdp = (description.sdp || "").replace(/^a=candidate:\d+ \d+ tcp .*?\r\n/img, ""); + return Promise.resolve(description); +} +exports.stripTcpCandidates = stripTcpCandidates; +function stripTelephoneEvent(description) { + description.sdp = stripPayload(description.sdp || "", "telephone-event"); + return Promise.resolve(description); +} +exports.stripTelephoneEvent = stripTelephoneEvent; +function cleanJitsiSdpImageattr(description) { + description.sdp = (description.sdp || "").replace(/^(a=imageattr:.*?)(x|y)=\[0-/gm, "$1$2=[1:"); + return Promise.resolve(description); +} +exports.cleanJitsiSdpImageattr = cleanJitsiSdpImageattr; +function stripG722(description) { + description.sdp = stripPayload(description.sdp || "", "G722"); + return Promise.resolve(description); +} +exports.stripG722 = stripG722; +function stripRtpPayload(payload) { + return function (description) { + description.sdp = stripPayload(description.sdp || "", payload); + return Promise.resolve(description); + }; +} +exports.stripRtpPayload = stripRtpPayload; +function stripVideo(description) { + description.sdp = stripMediaDescription(description.sdp || "", "video"); + return Promise.resolve(description); +} +exports.stripVideo = stripVideo; +function addMidLines(description) { + var sdp = description.sdp || ""; + if (sdp.search(/^a=mid.*$/gm) === -1) { + var mlines_1 = sdp.match(/^m=.*$/gm); + var sdpArray_1 = sdp.split(/^m=.*$/gm); + if (mlines_1) { + mlines_1.forEach(function (elem, idx) { + mlines_1[idx] = elem + "\na=mid:" + idx; + }); + } + sdpArray_1.forEach(function (elem, idx) { + if (mlines_1 && mlines_1[idx]) { + sdpArray_1[idx] = elem + mlines_1[idx]; + } + }); + sdp = sdpArray_1.join(""); + description.sdp = sdp; + } + return Promise.resolve(description); +} +exports.addMidLines = addMidLines; + + +/***/ }), +/* 96 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var Enums_1 = __webpack_require__(81); +/* SessionDescriptionHandlerObserver + * @class SessionDescriptionHandler Observer Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandlerObserver = /** @class */ (function () { + function SessionDescriptionHandlerObserver(session, options) { + this.type = Enums_1.TypeStrings.SessionDescriptionHandlerObserver; + this.session = session; + this.options = options; + } + SessionDescriptionHandlerObserver.prototype.trackAdded = function () { + this.session.emit("trackAdded"); + }; + SessionDescriptionHandlerObserver.prototype.directionChanged = function () { + this.session.emit("directionChanged"); + }; + return SessionDescriptionHandlerObserver; +}()); +exports.SessionDescriptionHandlerObserver = SessionDescriptionHandlerObserver; + + +/***/ }), +/* 97 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var core_1 = __webpack_require__(2); +var Enums_1 = __webpack_require__(81); +var Exceptions_1 = __webpack_require__(83); +var Utils_1 = __webpack_require__(82); +var TransportStatus; +(function (TransportStatus) { + TransportStatus[TransportStatus["STATUS_CONNECTING"] = 0] = "STATUS_CONNECTING"; + TransportStatus[TransportStatus["STATUS_OPEN"] = 1] = "STATUS_OPEN"; + TransportStatus[TransportStatus["STATUS_CLOSING"] = 2] = "STATUS_CLOSING"; + TransportStatus[TransportStatus["STATUS_CLOSED"] = 3] = "STATUS_CLOSED"; +})(TransportStatus = exports.TransportStatus || (exports.TransportStatus = {})); +/** + * Compute an amount of time in seconds to wait before sending another + * keep-alive. + * @returns {Number} + */ +var computeKeepAliveTimeout = function (upperBound) { + var lowerBound = upperBound * 0.8; + return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound); +}; +/** + * @class Transport + * @param {Object} options + */ +var Transport = /** @class */ (function (_super) { + tslib_1.__extends(Transport, _super); + function Transport(logger, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this, logger, options) || this; + _this.WebSocket = (global.window || global).WebSocket; + _this.type = Enums_1.TypeStrings.Transport; + _this.reconnectionAttempts = 0; + _this.status = TransportStatus.STATUS_CONNECTING; + _this.configuration = _this.loadConfig(options); + _this.server = _this.configuration.wsServers[0]; + return _this; + } + /** + * @returns {Boolean} + */ + Transport.prototype.isConnected = function () { + return this.status === TransportStatus.STATUS_OPEN; + }; + /** + * Send a message. + * @param {SIP.OutgoingRequest|String} msg + * @param {Object} [options] + * @returns {Promise} + */ + Transport.prototype.sendPromise = function (msg, options) { + if (options === void 0) { options = {}; } + if (!this.statusAssert(TransportStatus.STATUS_OPEN, options.force)) { + this.onError("unable to send message - WebSocket not open"); + return Promise.reject(); + } + var message = msg.toString(); + if (this.ws) { + if (this.configuration.traceSip === true) { + this.logger.log("sending WebSocket message:\n\n" + message + "\n"); + } + this.ws.send(message); + return Promise.resolve({ msg: message }); + } + else { + this.onError("unable to send message - WebSocket does not exist"); + return Promise.reject(); + } + }; + /** + * Disconnect socket. + */ + Transport.prototype.disconnectPromise = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.disconnectionPromise) { // Already disconnecting. Just return this. + return this.disconnectionPromise; + } + options.code = options.code || 1000; + if (!this.statusTransition(TransportStatus.STATUS_CLOSING, options.force)) { + if (this.status === TransportStatus.STATUS_CLOSED) { // Websocket is already closed + return Promise.resolve({ overrideEvent: true }); + } + else if (this.connectionPromise) { // Websocket is connecting, cannot move to disconneting yet + return this.connectionPromise.then(function () { return Promise.reject("The websocket did not disconnect"); }) + .catch(function () { return Promise.resolve({ overrideEvent: true }); }); + } + else { + // Cannot move to disconnecting, but not in connecting state. + return Promise.reject("The websocket did not disconnect"); + } + } + this.emit("disconnecting"); + this.disconnectionPromise = new Promise(function (resolve, reject) { + _this.disconnectDeferredResolve = resolve; + if (_this.reconnectTimer) { + clearTimeout(_this.reconnectTimer); + _this.reconnectTimer = undefined; + } + if (_this.ws) { + _this.stopSendingKeepAlives(); + _this.logger.log("closing WebSocket " + _this.server.wsUri); + _this.ws.close(options.code, options.reason); + } + else { + reject("Attempted to disconnect but the websocket doesn't exist"); + } + }); + return this.disconnectionPromise; + }; + /** + * Connect socket. + */ + Transport.prototype.connectPromise = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.status === TransportStatus.STATUS_CLOSING && !options.force) { + return Promise.reject("WebSocket " + this.server.wsUri + " is closing"); + } + if (this.connectionPromise) { + return this.connectionPromise; + } + this.server = this.server || this.getNextWsServer(options.force); + this.connectionPromise = new Promise(function (resolve, reject) { + if ((_this.status === TransportStatus.STATUS_OPEN || _this.status === TransportStatus.STATUS_CLOSING) + && !options.force) { + _this.logger.warn("WebSocket " + _this.server.wsUri + " is already connected"); + reject("Failed status check - attempted to open a connection but already open/closing"); + return; + } + _this.connectDeferredResolve = resolve; + _this.status = TransportStatus.STATUS_CONNECTING; + _this.emit("connecting"); + _this.logger.log("connecting to WebSocket " + _this.server.wsUri); + _this.disposeWs(); + try { + _this.ws = new WebSocket(_this.server.wsUri, "sip"); + } + catch (e) { + _this.ws = null; + _this.statusTransition(TransportStatus.STATUS_CLOSED, true); + _this.onError("error connecting to WebSocket " + _this.server.wsUri + ":" + e); + reject("Failed to create a websocket"); + return; + } + if (!_this.ws) { + reject("Unexpected instance websocket not set"); + return; + } + _this.connectionTimeout = setTimeout(function () { + _this.statusTransition(TransportStatus.STATUS_CLOSED); + _this.logger.warn("took too long to connect - exceeded time set in configuration.connectionTimeout: " + + _this.configuration.connectionTimeout + "s"); + _this.emit("disconnected", { code: 1000 }); + _this.connectionPromise = undefined; + reject("Connection timeout"); + }, _this.configuration.connectionTimeout * 1000); + _this.boundOnOpen = _this.onOpen.bind(_this); + _this.boundOnMessage = _this.onMessage.bind(_this); + _this.boundOnClose = _this.onClose.bind(_this); + _this.boundOnError = _this.onWebsocketError.bind(_this); + _this.ws.addEventListener("open", _this.boundOnOpen); + _this.ws.addEventListener("message", _this.boundOnMessage); + _this.ws.addEventListener("close", _this.boundOnClose); + _this.ws.addEventListener("error", _this.boundOnError); + }); + return this.connectionPromise; + }; + /** + * @event + * @param {event} e + */ + Transport.prototype.onMessage = function (e) { + var data = e.data; + var finishedData; + // CRLF Keep Alive response from server. Clear our keep alive timeout. + if (/^(\r\n)+$/.test(data)) { + this.clearKeepAliveTimeout(); + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket message with CRLF Keep Alive response"); + } + return; + } + else if (!data) { + this.logger.warn("received empty message, message discarded"); + return; + } + else if (typeof data !== "string") { // WebSocket binary message. + try { + // the UInt8Data was here prior to types, and doesn't check + finishedData = String.fromCharCode.apply(null, new Uint8Array(data)); + } + catch (err) { + this.logger.warn("received WebSocket binary message failed to be converted into string, message discarded"); + return; + } + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket binary message:\n\n" + data + "\n"); + } + } + else { // WebSocket text message. + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket text message:\n\n" + data + "\n"); + } + finishedData = data; + } + this.emit("message", finishedData); + }; + // Transport Event Handlers + /** + * @event + * @param {event} e + */ + Transport.prototype.onOpen = function () { + if (this.status === TransportStatus.STATUS_CLOSED) { // Indicated that the transport thinks the ws is dead already + var ws = this.ws; + this.disposeWs(); + ws.close(1000); + return; + } + this.statusTransition(TransportStatus.STATUS_OPEN, true); + this.emit("connected"); + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + this.connectionTimeout = undefined; + } + this.logger.log("WebSocket " + this.server.wsUri + " connected"); + // Clear reconnectTimer since we are not disconnected + if (this.reconnectTimer !== undefined) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + // Reset reconnectionAttempts + this.reconnectionAttempts = 0; + // Reset disconnection promise so we can disconnect from a fresh state + this.disconnectionPromise = undefined; + this.disconnectDeferredResolve = undefined; + // Start sending keep-alives + this.startSendingKeepAlives(); + if (this.connectDeferredResolve) { + this.connectDeferredResolve({ overrideEvent: true }); + } + else { + this.logger.warn("Unexpected websocket.onOpen with no connectDeferredResolve"); + } + }; + /** + * @event + * @param {event} e + */ + Transport.prototype.onClose = function (e) { + this.logger.log("WebSocket disconnected (code: " + e.code + (e.reason ? "| reason: " + e.reason : "") + ")"); + if (this.status !== TransportStatus.STATUS_CLOSING) { + this.logger.warn("WebSocket closed without SIP.js requesting it"); + this.emit("transportError"); + } + this.stopSendingKeepAlives(); + // Clean up connection variables so we can connect again from a fresh state + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + } + this.connectionTimeout = undefined; + this.connectionPromise = undefined; + this.connectDeferredResolve = undefined; + // Check whether the user requested to close. + if (this.disconnectDeferredResolve) { + this.disconnectDeferredResolve({ overrideEvent: true }); + this.statusTransition(TransportStatus.STATUS_CLOSED); + this.disconnectDeferredResolve = undefined; + return; + } + this.statusTransition(TransportStatus.STATUS_CLOSED, true); + this.emit("disconnected", { code: e.code, reason: e.reason }); + this.reconnect(); + }; + /** + * Removes event listeners and clears the instance ws + */ + Transport.prototype.disposeWs = function () { + if (this.ws) { + this.ws.removeEventListener("open", this.boundOnOpen); + this.ws.removeEventListener("message", this.boundOnMessage); + this.ws.removeEventListener("close", this.boundOnClose); + this.ws.removeEventListener("error", this.boundOnError); + this.ws = undefined; + } + }; + /** + * @event + * @param {string} e + */ + Transport.prototype.onError = function (e) { + this.logger.warn("Transport error: " + e); + this.emit("transportError"); + }; + /** + * @event + * @private + */ + Transport.prototype.onWebsocketError = function () { + this.onError("The Websocket had an error"); + }; + /** + * Reconnection attempt logic. + */ + Transport.prototype.reconnect = function () { + var _this = this; + if (this.reconnectionAttempts > 0) { + this.logger.log("Reconnection attempt " + this.reconnectionAttempts + " failed"); + } + if (this.noAvailableServers()) { + this.logger.warn("attempted to get next ws server but there are no available ws servers left"); + this.logger.warn("no available ws servers left - going to closed state"); + this.statusTransition(TransportStatus.STATUS_CLOSED, true); + this.emit("closed"); + this.resetServerErrorStatus(); + return; + } + if (this.isConnected()) { + this.logger.warn("attempted to reconnect while connected - forcing disconnect"); + this.disconnect({ force: true }); + } + this.reconnectionAttempts += 1; + if (this.reconnectionAttempts > this.configuration.maxReconnectionAttempts) { + this.logger.warn("maximum reconnection attempts for WebSocket " + this.server.wsUri); + this.logger.log("transport " + this.server.wsUri + " failed | connection state set to 'error'"); + this.server.isError = true; + this.emit("transportError"); + if (!this.noAvailableServers()) { + this.server = this.getNextWsServer(); + } + // When there are no available servers, the reconnect function ends on the next recursive call + // after checking for no available servers again. + this.reconnectionAttempts = 0; + this.reconnect(); + } + else { + this.logger.log("trying to reconnect to WebSocket " + + this.server.wsUri + " (reconnection attempt " + this.reconnectionAttempts + ")"); + this.reconnectTimer = setTimeout(function () { + _this.connect(); + _this.reconnectTimer = undefined; + }, (this.reconnectionAttempts === 1) ? 0 : this.configuration.reconnectionTimeout * 1000); + } + }; + /** + * Resets the error state of all servers in the configuration + */ + Transport.prototype.resetServerErrorStatus = function () { + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var websocket = _a[_i]; + websocket.isError = false; + } + }; + /** + * Retrieve the next server to which connect. + * @param {Boolean} force allows bypass of server error status checking + * @returns {Object} WsServer + */ + Transport.prototype.getNextWsServer = function (force) { + if (force === void 0) { force = false; } + if (this.noAvailableServers()) { + this.logger.warn("attempted to get next ws server but there are no available ws servers left"); + throw new Error("Attempted to get next ws server, but there are no available ws servers left."); + } + // Order servers by weight + var candidates = []; + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var wsServer = _a[_i]; + if (wsServer.isError && !force) { + continue; + } + else if (candidates.length === 0) { + candidates.push(wsServer); + } + else if (wsServer.weight > candidates[0].weight) { + candidates = [wsServer]; + } + else if (wsServer.weight === candidates[0].weight) { + candidates.push(wsServer); + } + } + var idx = Math.floor(Math.random() * candidates.length); + return candidates[idx]; + }; + /** + * Checks all configuration servers, returns true if all of them have isError: true and false otherwise + * @returns {Boolean} + */ + Transport.prototype.noAvailableServers = function () { + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var server = _a[_i]; + if (!server.isError) { + return false; + } + } + return true; + }; + // ============================== + // KeepAlive Stuff + // ============================== + /** + * Send a keep-alive (a double-CRLF sequence). + * @returns {Boolean} + */ + Transport.prototype.sendKeepAlive = function () { + var _this = this; + if (this.keepAliveDebounceTimeout) { + // We already have an outstanding keep alive, do not send another. + return; + } + this.keepAliveDebounceTimeout = setTimeout(function () { + _this.emit("keepAliveDebounceTimeout"); + _this.clearKeepAliveTimeout(); + }, this.configuration.keepAliveDebounce * 1000); + return this.send("\r\n\r\n"); + }; + Transport.prototype.clearKeepAliveTimeout = function () { + if (this.keepAliveDebounceTimeout) { + clearTimeout(this.keepAliveDebounceTimeout); + } + this.keepAliveDebounceTimeout = undefined; + }; + /** + * Start sending keep-alives. + */ + Transport.prototype.startSendingKeepAlives = function () { + var _this = this; + if (this.configuration.keepAliveInterval && !this.keepAliveInterval) { + this.keepAliveInterval = setInterval(function () { + _this.sendKeepAlive(); + _this.startSendingKeepAlives(); + }, computeKeepAliveTimeout(this.configuration.keepAliveInterval)); + } + }; + /** + * Stop sending keep-alives. + */ + Transport.prototype.stopSendingKeepAlives = function () { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + } + if (this.keepAliveDebounceTimeout) { + clearTimeout(this.keepAliveDebounceTimeout); + } + this.keepAliveInterval = undefined; + this.keepAliveDebounceTimeout = undefined; + }; + // ============================== + // Status Stuff + // ============================== + /** + * Checks given status against instance current status. Returns true if they match + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + Transport.prototype.statusAssert = function (status, force) { + if (status === this.status) { + return true; + } + else { + if (force) { + this.logger.warn("Attempted to assert " + + Object.keys(TransportStatus)[this.status] + " as " + + Object.keys(TransportStatus)[status] + "- continuing with option: 'force'"); + return true; + } + else { + this.logger.warn("Tried to assert " + + Object.keys(TransportStatus)[status] + " but is currently " + + Object.keys(TransportStatus)[this.status]); + return false; + } + } + }; + /** + * Transitions the status. Checks for legal transition via assertion beforehand + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + Transport.prototype.statusTransition = function (status, force) { + if (force === void 0) { force = false; } + this.logger.log("Attempting to transition status from " + + Object.keys(TransportStatus)[this.status] + " to " + + Object.keys(TransportStatus)[status]); + if ((status === TransportStatus.STATUS_CONNECTING && this.statusAssert(TransportStatus.STATUS_CLOSED, force)) || + (status === TransportStatus.STATUS_OPEN && this.statusAssert(TransportStatus.STATUS_CONNECTING, force)) || + (status === TransportStatus.STATUS_CLOSING && this.statusAssert(TransportStatus.STATUS_OPEN, force)) || + (status === TransportStatus.STATUS_CLOSED)) { + this.status = status; + return true; + } + else { + this.logger.warn("Status transition failed - result: no-op - reason:" + + " either gave an nonexistent status or attempted illegal transition"); + return false; + } + }; + // ============================== + // Configuration Handling + // ============================== + /** + * Configuration load. + * returns {Configuration} + */ + Transport.prototype.loadConfig = function (configuration) { + var settings = { + wsServers: [{ + scheme: "WSS", + sipUri: "", + weight: 0, + wsUri: "wss://edge.sip.onsip.com", + isError: false + }], + connectionTimeout: 5, + maxReconnectionAttempts: 3, + reconnectionTimeout: 4, + keepAliveInterval: 0, + keepAliveDebounce: 10, + // Logging + traceSip: false + }; + var configCheck = this.getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if ((value instanceof Array && value.length === 0) || + (value === null || value === "" || value === undefined) || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + var skeleton = {}; // Fill the value of the configuration_skeleton + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + skeleton[parameter] = { + value: settings[parameter], + }; + } + } + var returnConfiguration = Object.defineProperties({}, skeleton); + this.logger.log("configuration parameters after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + return returnConfiguration; + }; + /** + * Configuration checker. + * @return {Boolean} + */ + Transport.prototype.getConfigurationCheck = function () { + return { + mandatory: {}, + optional: { + // Note: this function used to call 'this.logger.error' but calling 'this' with anything here is invalid + wsServers: function (wsServers) { + /* Allow defining wsServers parameter as: + * String: "host" + * Array of Strings: ["host1", "host2"] + * Array of Objects: [{wsUri:"host1", weight:1}, {wsUri:"host2", weight:0}] + * Array of Objects and Strings: [{wsUri:"host1"}, "host2"] + */ + if (typeof wsServers === "string") { + wsServers = [{ wsUri: wsServers }]; + } + else if (wsServers instanceof Array) { + for (var idx = 0; idx < wsServers.length; idx++) { + if (typeof wsServers[idx] === "string") { + wsServers[idx] = { wsUri: wsServers[idx] }; + } + } + } + else { + return; + } + if (wsServers.length === 0) { + return false; + } + for (var _i = 0, wsServers_1 = wsServers; _i < wsServers_1.length; _i++) { + var wsServer = wsServers_1[_i]; + if (!wsServer.wsUri) { + return; + } + if (wsServer.weight && !Number(wsServer.weight)) { + return; + } + var url = core_1.Grammar.parse(wsServer.wsUri, "absoluteURI"); + if (url === -1) { + return; + } + else if (["wss", "ws", "udp"].indexOf(url.scheme) < 0) { + return; + } + else { + wsServer.sipUri = ""; + if (!wsServer.weight) { + wsServer.weight = 0; + } + wsServer.isError = false; + wsServer.scheme = url.scheme.toUpperCase(); + } + } + return wsServers; + }, + keepAliveInterval: function (keepAliveInterval) { + if (Utils_1.Utils.isDecimal(keepAliveInterval)) { + var value = Number(keepAliveInterval); + if (value > 0) { + return value; + } + } + }, + keepAliveDebounce: function (keepAliveDebounce) { + if (Utils_1.Utils.isDecimal(keepAliveDebounce)) { + var value = Number(keepAliveDebounce); + if (value > 0) { + return value; + } + } + }, + traceSip: function (traceSip) { + if (typeof traceSip === "boolean") { + return traceSip; + } + }, + connectionTimeout: function (connectionTimeout) { + if (Utils_1.Utils.isDecimal(connectionTimeout)) { + var value = Number(connectionTimeout); + if (value > 0) { + return value; + } + } + }, + maxReconnectionAttempts: function (maxReconnectionAttempts) { + if (Utils_1.Utils.isDecimal(maxReconnectionAttempts)) { + var value = Number(maxReconnectionAttempts); + if (value >= 0) { + return value; + } + } + }, + reconnectionTimeout: function (reconnectionTimeout) { + if (Utils_1.Utils.isDecimal(reconnectionTimeout)) { + var value = Number(reconnectionTimeout); + if (value > 0) { + return value; + } + } + } + } + }; + }; + Transport.C = TransportStatus; + return Transport; +}(core_1.Transport)); +exports.Transport = Transport; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }), +/* 98 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var Modifiers = tslib_1.__importStar(__webpack_require__(95)); +exports.Modifiers = Modifiers; +var Simple_1 = __webpack_require__(99); +exports.Simple = Simple_1.Simple; +var SessionDescriptionHandler_1 = __webpack_require__(94); +exports.SessionDescriptionHandler = SessionDescriptionHandler_1.SessionDescriptionHandler; +var Transport_1 = __webpack_require__(97); +exports.Transport = Transport_1.Transport; + + +/***/ }), +/* 99 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) { +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(1); +var events_1 = __webpack_require__(30); +var UA_1 = __webpack_require__(92); +var Modifiers = tslib_1.__importStar(__webpack_require__(95)); +/* Simple + * @class Simple + */ +var SimpleStatus; +(function (SimpleStatus) { + SimpleStatus[SimpleStatus["STATUS_NULL"] = 0] = "STATUS_NULL"; + SimpleStatus[SimpleStatus["STATUS_NEW"] = 1] = "STATUS_NEW"; + SimpleStatus[SimpleStatus["STATUS_CONNECTING"] = 2] = "STATUS_CONNECTING"; + SimpleStatus[SimpleStatus["STATUS_CONNECTED"] = 3] = "STATUS_CONNECTED"; + SimpleStatus[SimpleStatus["STATUS_COMPLETED"] = 4] = "STATUS_COMPLETED"; +})(SimpleStatus = exports.SimpleStatus || (exports.SimpleStatus = {})); +var Simple = /** @class */ (function (_super) { + tslib_1.__extends(Simple, _super); + function Simple(options) { + var _this = _super.call(this) || this; + /* + * { + * media: { + * remote: { + * audio: , + * video: + * }, + * local: { + * video: + * } + * }, + * ua: { + * + * } + * } + */ + if (options.media.remote.video) { + _this.video = true; + } + else { + _this.video = false; + } + if (options.media.remote.audio) { + _this.audio = true; + } + else { + _this.audio = false; + } + if (!_this.audio && !_this.video) { + // Need to do at least audio or video + // Error + throw new Error("At least one remote audio or video element is required for Simple."); + } + _this.options = options; + // https://stackoverflow.com/questions/7944460/detect-safari-browser + var browserUa = global.navigator.userAgent.toLowerCase(); + var isSafari = false; + var isFirefox = false; + if (browserUa.indexOf("safari") > -1 && browserUa.indexOf("chrome") < 0) { + isSafari = true; + } + else if (browserUa.indexOf("firefox") > -1 && browserUa.indexOf("chrome") < 0) { + isFirefox = true; + } + var sessionDescriptionHandlerFactoryOptions = {}; + if (isSafari) { + sessionDescriptionHandlerFactoryOptions.modifiers = [Modifiers.stripG722]; + } + if (isFirefox) { + sessionDescriptionHandlerFactoryOptions.alwaysAcquireMediaFirst = true; + } + if (!_this.options.ua.uri) { + _this.anonymous = true; + } + else { + _this.anonymous = false; + } + _this.ua = new UA_1.UA({ + // User Configurable Options + uri: _this.options.ua.uri, + authorizationUser: _this.options.ua.authorizationUser, + password: _this.options.ua.password, + displayName: _this.options.ua.displayName, + // Undocumented "Advanced" Options + userAgentString: _this.options.ua.userAgentString, + // Fixed Options + register: true, + sessionDescriptionHandlerFactoryOptions: sessionDescriptionHandlerFactoryOptions, + transportOptions: { + traceSip: _this.options.ua.traceSip, + wsServers: _this.options.ua.wsServers + } + }); + _this.state = SimpleStatus.STATUS_NULL; + _this.logger = _this.ua.getLogger("sip.simple"); + _this.ua.on("registered", function () { + _this.emit("registered", _this.ua); + }); + _this.ua.on("unregistered", function () { + _this.emit("unregistered", _this.ua); + }); + _this.ua.on("registrationFailed", function () { + _this.emit("unregistered", _this.ua); + }); + _this.ua.on("invite", function (session) { + // If there is already an active session reject the incoming session + if (_this.state !== SimpleStatus.STATUS_NULL && _this.state !== SimpleStatus.STATUS_COMPLETED) { + _this.logger.warn("Rejecting incoming call. Simple only supports 1 call at a time"); + session.reject(); + return; + } + _this.session = session; + _this.setupSession(); + _this.emit("ringing", _this.session); + }); + _this.ua.on("message", function (message) { + _this.emit("message", message); + }); + return _this; + } + Simple.prototype.call = function (destination) { + if (!this.ua || !this.checkRegistration()) { + this.logger.warn("A registered UA is required for calling"); + return; + } + if (this.state !== SimpleStatus.STATUS_NULL && this.state !== SimpleStatus.STATUS_COMPLETED) { + this.logger.warn("Cannot make more than a single call with Simple"); + return; + } + // Safari hack, because you cannot call .play() from a non user action + if (this.options.media.remote.audio) { + this.options.media.remote.audio.autoplay = true; + } + if (this.options.media.remote.video) { + this.options.media.remote.video.autoplay = true; + } + if (this.options.media.local && this.options.media.local.video) { + this.options.media.local.video.autoplay = true; + this.options.media.local.video.volume = 0; + } + this.session = this.ua.invite(destination, { + sessionDescriptionHandlerOptions: { + constraints: { + audio: this.audio, + video: this.video + } + } + }); + this.setupSession(); + return this.session; + }; + Simple.prototype.answer = function () { + if (this.state !== SimpleStatus.STATUS_NEW && this.state !== SimpleStatus.STATUS_CONNECTING) { + this.logger.warn("No call to answer"); + return; + } + // Safari hack, because you cannot call .play() from a non user action + if (this.options.media.remote.audio) { + this.options.media.remote.audio.autoplay = true; + } + if (this.options.media.remote.video) { + this.options.media.remote.video.autoplay = true; + } + return this.session.accept({ + sessionDescriptionHandlerOptions: { + constraints: { + audio: this.audio, + video: this.video + } + } + }); + // emit call is active + }; + Simple.prototype.reject = function () { + if (this.state !== SimpleStatus.STATUS_NEW && this.state !== SimpleStatus.STATUS_CONNECTING) { + this.logger.warn("Call is already answered"); + return; + } + return this.session.reject(); + }; + Simple.prototype.hangup = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED && + this.state !== SimpleStatus.STATUS_CONNECTING && + this.state !== SimpleStatus.STATUS_NEW) { + this.logger.warn("No active call to hang up on"); + return; + } + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + return this.session.cancel(); + } + else if (this.session) { + return this.session.bye(); + } + }; + Simple.prototype.hold = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session || this.session.localHold) { + this.logger.warn("Cannot put call on hold"); + return; + } + this.mute(); + this.logger.log("Placing session on hold"); + return this.session.hold(); + }; + Simple.prototype.unhold = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session || !this.session.localHold) { + this.logger.warn("Cannot unhold a call that is not on hold"); + return; + } + this.unmute(); + this.logger.log("Placing call off hold"); + return this.session.unhold(); + }; + Simple.prototype.mute = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + this.logger.warn("An acitve call is required to mute audio"); + return; + } + this.logger.log("Muting Audio"); + this.toggleMute(true); + this.emit("mute", this); + }; + Simple.prototype.unmute = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + this.logger.warn("An active call is required to unmute audio"); + return; + } + this.logger.log("Unmuting Audio"); + this.toggleMute(false); + this.emit("unmute", this); + }; + Simple.prototype.sendDTMF = function (tone) { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session) { + this.logger.warn("An active call is required to send a DTMF tone"); + return; + } + this.logger.log("Sending DTMF tone: " + tone); + this.session.dtmf(tone); + }; + Simple.prototype.message = function (destination, message) { + if (!this.ua || !this.checkRegistration()) { + this.logger.warn("A registered UA is required to send a message"); + return; + } + if (!destination || !message) { + this.logger.warn("A destination and message are required to send a message"); + return; + } + this.ua.message(destination, message); + }; + // Private Helpers + Simple.prototype.checkRegistration = function () { + return (this.anonymous || (this.ua && this.ua.isRegistered())); + }; + Simple.prototype.setupRemoteMedia = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session to set remote media on"); + return; + } + // If there is a video track, it will attach the video and audio to the same element + var pc = this.session.sessionDescriptionHandler.peerConnection; + var remoteStream; + if (pc.getReceivers) { + remoteStream = new global.window.MediaStream(); + pc.getReceivers().forEach(function (receiver) { + var track = receiver.track; + if (track) { + remoteStream.addTrack(track); + } + }); + } + else { + remoteStream = pc.getRemoteStreams()[0]; + } + if (this.video) { + this.options.media.remote.video.srcObject = remoteStream; + this.options.media.remote.video.play().catch(function () { + _this.logger.log("play was rejected"); + }); + } + else if (this.audio) { + this.options.media.remote.audio.srcObject = remoteStream; + this.options.media.remote.audio.play().catch(function () { + _this.logger.log("play was rejected"); + }); + } + }; + Simple.prototype.setupLocalMedia = function () { + if (!this.session) { + this.logger.warn("No session to set local media on"); + return; + } + if (this.video && this.options.media.local && this.options.media.local.video) { + var pc = this.session.sessionDescriptionHandler.peerConnection; + var localStream_1; + if (pc.getSenders) { + localStream_1 = new global.window.MediaStream(); + pc.getSenders().forEach(function (sender) { + var track = sender.track; + if (track && track.kind === "video") { + localStream_1.addTrack(track); + } + }); + } + else { + localStream_1 = pc.getLocalStreams()[0]; + } + this.options.media.local.video.srcObject = localStream_1; + this.options.media.local.video.volume = 0; + this.options.media.local.video.play(); + } + }; + Simple.prototype.cleanupMedia = function () { + if (this.video) { + this.options.media.remote.video.srcObject = null; + this.options.media.remote.video.pause(); + if (this.options.media.local && this.options.media.local.video) { + this.options.media.local.video.srcObject = null; + this.options.media.local.video.pause(); + } + } + if (this.audio) { + this.options.media.remote.audio.srcObject = null; + this.options.media.remote.audio.pause(); + } + }; + Simple.prototype.setupSession = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session to set up"); + return; + } + this.state = SimpleStatus.STATUS_NEW; + this.emit("new", this.session); + this.session.on("progress", function () { return _this.onProgress(); }); + this.session.on("accepted", function () { return _this.onAccepted(); }); + this.session.on("rejected", function () { return _this.onEnded(); }); + this.session.on("failed", function () { return _this.onFailed(); }); + this.session.on("terminated", function () { return _this.onEnded(); }); + }; + Simple.prototype.destroyMedia = function () { + if (this.session && this.session.sessionDescriptionHandler) { + this.session.sessionDescriptionHandler.close(); + } + }; + Simple.prototype.toggleMute = function (mute) { + if (!this.session) { + this.logger.warn("No session to toggle mute"); + return; + } + var pc = this.session.sessionDescriptionHandler.peerConnection; + if (pc.getSenders) { + pc.getSenders().forEach(function (sender) { + if (sender.track) { + sender.track.enabled = !mute; + } + }); + } + else { + pc.getLocalStreams().forEach(function (stream) { + stream.getAudioTracks().forEach(function (track) { + track.enabled = !mute; + }); + stream.getVideoTracks().forEach(function (track) { + track.enabled = !mute; + }); + }); + } + }; + Simple.prototype.onAccepted = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session for accepting"); + return; + } + this.state = SimpleStatus.STATUS_CONNECTED; + this.emit("connected", this.session); + this.setupLocalMedia(); + this.setupRemoteMedia(); + if (this.session.sessionDescriptionHandler) { + this.session.sessionDescriptionHandler.on("addTrack", function () { + _this.logger.log("A track has been added, triggering new remoteMedia setup"); + _this.setupRemoteMedia(); + }); + this.session.sessionDescriptionHandler.on("addStream", function () { + _this.logger.log("A stream has been added, trigger new remoteMedia setup"); + _this.setupRemoteMedia(); + }); + } + this.session.on("dtmf", function (request, dtmf) { + _this.emit("dtmf", dtmf.tone); + }); + this.session.on("bye", function () { return _this.onEnded(); }); + }; + Simple.prototype.onProgress = function () { + this.state = SimpleStatus.STATUS_CONNECTING; + this.emit("connecting", this.session); + }; + Simple.prototype.onFailed = function () { + this.onEnded(); + }; + Simple.prototype.onEnded = function () { + this.state = SimpleStatus.STATUS_COMPLETED; + this.emit("ended", this.session); + this.cleanupMedia(); + }; + Simple.C = SimpleStatus; + return Simple; +}(events_1.EventEmitter)); +exports.Simple = Simple; + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(93))) + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/dist/sip.min.js b/dist/sip.min.js new file mode 100644 index 000000000..04a944ef7 --- /dev/null +++ b/dist/sip.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SIP=t():e.SIP=t()}(this,function(){return function(e){var t={};function r(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(i,n,function(t){return e[t]}.bind(null,n));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=63)}([function(e,t,r){"use strict";r.r(t),r.d(t,"__extends",function(){return n}),r.d(t,"__assign",function(){return s}),r.d(t,"__rest",function(){return o}),r.d(t,"__decorate",function(){return a}),r.d(t,"__param",function(){return c}),r.d(t,"__metadata",function(){return u}),r.d(t,"__awaiter",function(){return d}),r.d(t,"__generator",function(){return p}),r.d(t,"__exportStar",function(){return h}),r.d(t,"__values",function(){return l}),r.d(t,"__read",function(){return g}),r.d(t,"__spread",function(){return f}),r.d(t,"__await",function(){return m}),r.d(t,"__asyncGenerator",function(){return v}),r.d(t,"__asyncDelegator",function(){return S}),r.d(t,"__asyncValues",function(){return T}),r.d(t,"__makeTemplateObject",function(){return y}),r.d(t,"__importStar",function(){return E}),r.d(t,"__importDefault",function(){return b});var i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function n(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}var s=function(){return(s=Object.assign||function(e){for(var t,r=1,i=arguments.length;r=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,r,o):n(t,r))||o);return s>3&&o&&Object.defineProperty(t,r,o),o}function c(e,t){return function(r,i){t(r,i,e)}}function u(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function d(e,t,r,i){return new(r||(r=Promise))(function(n,s){function o(e){try{c(i.next(e))}catch(e){s(e)}}function a(e){try{c(i.throw(e))}catch(e){s(e)}}function c(e){e.done?n(e.value):new r(function(t){t(e.value)}).then(o,a)}c((i=i.apply(e,t||[])).next())})}function p(e,t){var r,i,n,s,o={label:0,sent:function(){if(1&n[0])throw n[1];return n[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(r)throw new TypeError("Generator is already executing.");for(;o;)try{if(r=1,i&&(n=2&s[0]?i.return:s[0]?i.throw||((n=i.return)&&n.call(i),0):i.next)&&!(n=n.call(i,s[1])).done)return n;switch(i=0,n&&(s=[2&s[0],n.value]),s[0]){case 0:case 1:n=s;break;case 4:return o.label++,{value:s[1],done:!1};case 5:o.label++,i=s[1],s=[0];continue;case 7:s=o.ops.pop(),o.trys.pop();continue;default:if(!(n=(n=o.trys).length>0&&n[n.length-1])&&(6===s[0]||2===s[0])){o=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}function g(e,t){var r="function"==typeof Symbol&&e[Symbol.iterator];if(!r)return e;var i,n,s=r.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(i=s.next()).done;)o.push(i.value)}catch(e){n={error:e}}finally{try{i&&!i.done&&(r=s.return)&&r.call(s)}finally{if(n)throw n.error}}return o}function f(){for(var e=[],t=0;t1||a(e,t)})})}function a(e,t){try{(r=n[e](t)).value instanceof m?Promise.resolve(r.value.v).then(c,u):d(s[0][2],r)}catch(e){d(s[0][3],e)}var r}function c(e){a("next",e)}function u(e){a("throw",e)}function d(e,t){e(t),s.shift(),s.length&&a(s[0][0],s[0][1])}}function S(e){var t,r;return t={},i("next"),i("throw",function(e){throw e}),i("return"),t[Symbol.iterator]=function(){return this},t;function i(i,n){t[i]=e[i]?function(t){return(r=!r)?{value:m(e[i](t)),done:"return"===i}:n?n(t):t}:n}}function T(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,r=e[Symbol.asyncIterator];return r?r.call(e):(e=l(e),t={},i("next"),i("throw"),i("return"),t[Symbol.asyncIterator]=function(){return this},t);function i(r){t[r]=e[r]&&function(t){return new Promise(function(i,n){(function(e,t,r,i){Promise.resolve(i).then(function(t){e({value:t,done:r})},t)})(i,n,(t=e[r](t)).done,t.value)})}}}function y(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}function E(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}function b(e){return e&&e.__esModule?e:{default:e}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(27),t),i.__exportStar(r(39),t),i.__exportStar(r(76),t),i.__exportStar(r(77),t),i.__exportStar(r(78),t),i.__exportStar(r(39),t),i.__exportStar(r(30),t),i.__exportStar(r(14),t),i.__exportStar(r(28),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(64),t),i.__exportStar(r(66),t),i.__exportStar(r(68),t),i.__exportStar(r(22),t),i.__exportStar(r(21),t),i.__exportStar(r(36),t),i.__exportStar(r(37),t),i.__exportStar(r(23),t),i.__exportStar(r(38),t),i.__exportStar(r(71),t),i.__exportStar(r(24),t),i.__exportStar(r(25),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(2),n=r(1),s=function(){function e(e,t,r,i){this.transactionConstructor=e,this.core=t,this.message=r,this.delegate=i,this.challenged=!1,this.stale=!1,this.logger=this.loggerFactory.getLogger("sip.user-agent-client"),this.init()}return e.prototype.dispose=function(){this.transaction.dispose()},Object.defineProperty(e.prototype,"loggerFactory",{get:function(){return this.core.loggerFactory},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"transaction",{get:function(){if(!this._transaction)throw new Error("Transaction undefined.");return this._transaction},enumerable:!0,configurable:!0}),e.prototype.cancel=function(t,r){var s=this;if(void 0===r&&(r={}),!this.transaction)throw new Error("Transaction undefined.");if(!this.message.to)throw new Error("To undefined.");if(!this.message.from)throw new Error("From undefined.");var o=this.core.makeOutgoingRequestMessage(i.C.CANCEL,this.message.ruri,this.message.from.uri,this.message.to.uri,{toTag:this.message.toTag,fromTag:this.message.fromTag,callId:this.message.callId,cseq:this.message.cseq},r.extraHeaders);if(o.branch=this.message.branch,this.message.headers.Route&&(o.headers.Route=this.message.headers.Route),t&&o.setHeader("Reason",t),this.transaction.state===n.TransactionState.Proceeding)new e(n.NonInviteClientTransaction,this.core,o);else this.transaction.once("stateChanged",function(){if(s.transaction&&s.transaction.state===n.TransactionState.Proceeding)new e(n.NonInviteClientTransaction,s.core,o)});return o},e.prototype.authenticationGuard=function(e){var t,r,i=e.statusCode;if(!i)throw new Error("Response status code undefined.");if(401!==i&&407!==i)return!0;if(401===i?(t=e.parseHeader("www-authenticate"),r="authorization"):(t=e.parseHeader("proxy-authenticate"),r="proxy-authorization"),!t)return this.logger.warn(i+" with wrong or missing challenge, cannot authenticate"),!0;if(this.challenged&&(this.stale||!0!==t.stale))return this.logger.warn(i+" apparently in authentication loop, cannot authenticate"),!0;if(!this.credentials&&(this.credentials=this.core.configuration.authenticationFactory(),!this.credentials))return this.logger.warn("Unable to obtain credentials, cannot authenticate"),!0;if(!this.credentials.authenticate(this.message,t))return!0;this.challenged=!0,t.stale&&(this.stale=!0);var n=this.message.cseq+=1;return this.message.setHeader("cseq",n+" "+this.message.method),this.message.setHeader(r,this.credentials.toString()),this.init(),!1},e.prototype.receiveResponse=function(e){if(this.authenticationGuard(e)){var t=e.statusCode?e.statusCode.toString():"";if(!t)throw new Error("Response status code undefined.");switch(!0){case/^100$/.test(t):this.delegate&&this.delegate.onTrying&&this.delegate.onTrying({message:e});break;case/^1[0-9]{2}$/.test(t):this.delegate&&this.delegate.onProgress&&this.delegate.onProgress({message:e});break;case/^2[0-9]{2}$/.test(t):this.delegate&&this.delegate.onAccept&&this.delegate.onAccept({message:e});break;case/^3[0-9]{2}$/.test(t):this.delegate&&this.delegate.onRedirect&&this.delegate.onRedirect({message:e});break;case/^[4-6][0-9]{2}$/.test(t):this.delegate&&this.delegate.onReject&&this.delegate.onReject({message:e});break;default:throw new Error("Invalid status code "+t)}}},e.prototype.init=function(){var e=this,t={loggerFactory:this.loggerFactory,onRequestTimeout:function(){return e.onRequestTimeout()},onStateChange:function(t){t===n.TransactionState.Terminated&&(e.core.userAgentClients.delete(i),r===e._transaction&&e.dispose())},onTransportError:function(t){return e.onTransportError(t)},receiveResponse:function(t){return e.receiveResponse(t)}},r=new this.transactionConstructor(this.message,this.core.transport,t);this._transaction=r;var i=r.id+r.request.method;this.core.userAgentClients.set(i,this)},e.prototype.onRequestTimeout=function(){this.logger.warn("User agent client request timed out. Generating internal 408 Request Timeout.");var e=new i.IncomingResponseMessage;e.statusCode=408,e.reasonPhrase="Request Timeout",this.receiveResponse(e)},e.prototype.onTransportError=function(e){this.logger.error(e.message),this.logger.error("User agent client request transport error. Generating internal 503 Service Unavailable.");var t=new i.IncomingResponseMessage;t.statusCode=503,t.reasonPhrase="Service Unavailable",this.receiveResponse(t)},e}();t.UserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e[e.STATUS_EARLY=1]="STATUS_EARLY",e[e.STATUS_CONFIRMED=2]="STATUS_CONFIRMED"}(t.DialogStatus||(t.DialogStatus={})),function(e){e[e.STATUS_NULL=0]="STATUS_NULL",e[e.STATUS_INVITE_SENT=1]="STATUS_INVITE_SENT",e[e.STATUS_1XX_RECEIVED=2]="STATUS_1XX_RECEIVED",e[e.STATUS_INVITE_RECEIVED=3]="STATUS_INVITE_RECEIVED",e[e.STATUS_WAITING_FOR_ANSWER=4]="STATUS_WAITING_FOR_ANSWER",e[e.STATUS_ANSWERED=5]="STATUS_ANSWERED",e[e.STATUS_WAITING_FOR_PRACK=6]="STATUS_WAITING_FOR_PRACK",e[e.STATUS_WAITING_FOR_ACK=7]="STATUS_WAITING_FOR_ACK",e[e.STATUS_CANCELED=8]="STATUS_CANCELED",e[e.STATUS_TERMINATED=9]="STATUS_TERMINATED",e[e.STATUS_ANSWERED_WAITING_FOR_PRACK=10]="STATUS_ANSWERED_WAITING_FOR_PRACK",e[e.STATUS_EARLY_MEDIA=11]="STATUS_EARLY_MEDIA",e[e.STATUS_CONFIRMED=12]="STATUS_CONFIRMED"}(t.SessionStatus||(t.SessionStatus={})),function(e){e[e.ClientContext=0]="ClientContext",e[e.ConfigurationError=1]="ConfigurationError",e[e.Dialog=2]="Dialog",e[e.DigestAuthentication=3]="DigestAuthentication",e[e.DTMF=4]="DTMF",e[e.IncomingMessage=5]="IncomingMessage",e[e.IncomingRequest=6]="IncomingRequest",e[e.IncomingResponse=7]="IncomingResponse",e[e.InvalidStateError=8]="InvalidStateError",e[e.InviteClientContext=9]="InviteClientContext",e[e.InviteServerContext=10]="InviteServerContext",e[e.Logger=11]="Logger",e[e.LoggerFactory=12]="LoggerFactory",e[e.MethodParameterError=13]="MethodParameterError",e[e.NameAddrHeader=14]="NameAddrHeader",e[e.NotSupportedError=15]="NotSupportedError",e[e.OutgoingRequest=16]="OutgoingRequest",e[e.Parameters=17]="Parameters",e[e.PublishContext=18]="PublishContext",e[e.ReferClientContext=19]="ReferClientContext",e[e.ReferServerContext=20]="ReferServerContext",e[e.RegisterContext=21]="RegisterContext",e[e.RenegotiationError=22]="RenegotiationError",e[e.RequestSender=23]="RequestSender",e[e.ServerContext=24]="ServerContext",e[e.Session=25]="Session",e[e.SessionDescriptionHandler=26]="SessionDescriptionHandler",e[e.SessionDescriptionHandlerError=27]="SessionDescriptionHandlerError",e[e.SessionDescriptionHandlerObserver=28]="SessionDescriptionHandlerObserver",e[e.Subscription=29]="Subscription",e[e.Transport=30]="Transport",e[e.UA=31]="UA",e[e.URI=32]="URI"}(t.TypeStrings||(t.TypeStrings={})),function(e){e[e.STATUS_INIT=0]="STATUS_INIT",e[e.STATUS_STARTING=1]="STATUS_STARTING",e[e.STATUS_READY=2]="STATUS_READY",e[e.STATUS_USER_CLOSED=3]="STATUS_USER_CLOSED",e[e.STATUS_NOT_READY=4]="STATUS_NOT_READY"}(t.UAStatus||(t.UAStatus={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(19),t),i.__exportStar(r(15),t),i.__exportStar(r(81),t),i.__exportStar(r(2),t),i.__exportStar(r(26),t),i.__exportStar(r(32),t),i.__exportStar(r(1),t),i.__exportStar(r(83),t),i.__exportStar(r(53),t),i.__exportStar(r(11),t),i.__exportStar(r(95),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(15),n=r(2),s=r(13),o=r(1),a=function(){function e(e,t,r,i){this.transactionConstructor=e,this.core=t,this.message=r,this.delegate=i,this.logger=this.loggerFactory.getLogger("sip.user-agent-server"),this.toTag=r.toTag?r.toTag:s.newTag(),this.init()}return e.prototype.dispose=function(){this.transaction.dispose()},Object.defineProperty(e.prototype,"loggerFactory",{get:function(){return this.core.loggerFactory},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"transaction",{get:function(){if(!this._transaction)throw new Error("Transaction undefined.");return this._transaction},enumerable:!0,configurable:!0}),e.prototype.accept=function(e){if(void 0===e&&(e={statusCode:200}),!this.acceptable)throw new i.TransactionStateError(this.message.method+" not acceptable in state "+this.transaction.state+".");var t=e.statusCode;if(t<200||t>299)throw new TypeError("Invalid statusCode: "+t);return this.reply(e)},e.prototype.progress=function(e){if(void 0===e&&(e={statusCode:180}),!this.progressable)throw new i.TransactionStateError(this.message.method+" not progressable in state "+this.transaction.state+".");var t=e.statusCode;if(t<101||t>199)throw new TypeError("Invalid statusCode: "+t);return this.reply(e)},e.prototype.redirect=function(e,t){if(void 0===t&&(t={statusCode:302}),!this.redirectable)throw new i.TransactionStateError(this.message.method+" not redirectable in state "+this.transaction.state+".");var r=t.statusCode;if(r<300||r>399)throw new TypeError("Invalid statusCode: "+r);var n=new Array;return e.forEach(function(e){return n.push("Contact: "+e.toString())}),t.extraHeaders=(t.extraHeaders||[]).concat(n),this.reply(t)},e.prototype.reject=function(e){if(void 0===e&&(e={statusCode:480}),!this.rejectable)throw new i.TransactionStateError(this.message.method+" not rejectable in state "+this.transaction.state+".");var t=e.statusCode;if(t<400||t>699)throw new TypeError("Invalid statusCode: "+t);return this.reply(e)},e.prototype.trying=function(e){if(!this.tryingable)throw new i.TransactionStateError(this.message.method+" not tryingable in state "+this.transaction.state+".");return this.reply({statusCode:100})},e.prototype.receiveCancel=function(e){this.delegate&&this.delegate.onCancel&&this.delegate.onCancel(e)},Object.defineProperty(e.prototype,"acceptable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding||this.transaction.state===o.TransactionState.Accepted;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying||this.transaction.state===o.TransactionState.Proceeding;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"progressable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return!1;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"redirectable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying||this.transaction.state===o.TransactionState.Proceeding;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"rejectable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying||this.transaction.state===o.TransactionState.Proceeding;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"tryingable",{get:function(){if(this.transaction instanceof o.InviteServerTransaction)return this.transaction.state===o.TransactionState.Proceeding;if(this.transaction instanceof o.NonInviteServerTransaction)return this.transaction.state===o.TransactionState.Trying;throw new Error("Unknown transaction type.")},enumerable:!0,configurable:!0}),e.prototype.reply=function(e){e.toTag||100===e.statusCode||(e.toTag=this.toTag),e.userAgent=e.userAgent||this.core.configuration.userAgentHeaderFieldValue,e.supported=e.supported||this.core.configuration.supportedOptionTagsResponse;var t=n.constructOutgoingResponse(this.message,e);return this.transaction.receiveResponse(e.statusCode,t.message),t},e.prototype.init=function(){var e=this,t={loggerFactory:this.loggerFactory,onStateChange:function(t){t===o.TransactionState.Terminated&&(e.core.userAgentServers.delete(i),e.dispose())},onTransportError:function(t){e.logger.error(t.message),e.delegate&&e.delegate.onTransportError?e.delegate.onTransportError(t):e.logger.error("User agent server response transport error.")}},r=new this.transactionConstructor(this.message,this.core.transport,t);this._transaction=r;var i=r.id;this.core.userAgentServers.set(r.id,this)},e}();t.UserAgentServer=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(54);!function(e){e.USER_AGENT=i.title+"/"+i.version,e.SIP="sip",e.SIPS="sips",function(e){e.CONNECTION_ERROR="Connection Error",e.INTERNAL_ERROR="Internal Error",e.REQUEST_TIMEOUT="Request Timeout",e.SIP_FAILURE_CODE="SIP Failure Code",e.ADDRESS_INCOMPLETE="Address Incomplete",e.AUTHENTICATION_ERROR="Authentication Error",e.BUSY="Busy",e.DIALOG_ERROR="Dialog Error",e.INCOMPATIBLE_SDP="Incompatible SDP",e.NOT_FOUND="Not Found",e.REDIRECTED="Redirected",e.REJECTED="Rejected",e.UNAVAILABLE="Unavailable",e.BAD_MEDIA_DESCRIPTION="Bad Media Description",e.CANCELED="Canceled",e.EXPIRES="Expires",e.NO_ACK="No ACK",e.NO_ANSWER="No Answer",e.NO_PRACK="No PRACK",e.RTP_TIMEOUT="RTP Timeout",e.USER_DENIED_MEDIA_ACCESS="User Denied Media Access",e.WEBRTC_ERROR="WebRTC Error",e.WEBRTC_NOT_SUPPORTED="WebRTC Not Supported"}(e.causes||(e.causes={})),function(e){e.REQUIRED="required",e.SUPPORTED="supported",e.UNSUPPORTED="none"}(e.supported||(e.supported={})),e.SIP_ERROR_CAUSES={ADDRESS_INCOMPLETE:[484],AUTHENTICATION_ERROR:[401,407],BUSY:[486,600],INCOMPATIBLE_SDP:[488,606],NOT_FOUND:[404,604],REDIRECTED:[300,301,302,305,380],REJECTED:[403,603],UNAVAILABLE:[480,410,408,430]},e.ACK="ACK",e.BYE="BYE",e.CANCEL="CANCEL",e.INFO="INFO",e.INVITE="INVITE",e.MESSAGE="MESSAGE",e.NOTIFY="NOTIFY",e.OPTIONS="OPTIONS",e.REGISTER="REGISTER",e.UPDATE="UPDATE",e.SUBSCRIBE="SUBSCRIBE",e.PUBLISH="PUBLISH",e.REFER="REFER",e.PRACK="PRACK",e.REASON_PHRASE={100:"Trying",180:"Ringing",181:"Call Is Being Forwarded",182:"Queued",183:"Session Progress",199:"Early Dialog Terminated",200:"OK",202:"Accepted",204:"No Notification",300:"Multiple Choices",301:"Moved Permanently",302:"Moved Temporarily",305:"Use Proxy",380:"Alternative Service",400:"Bad Request",401:"Unauthorized",402:"Payment Required",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",406:"Not Acceptable",407:"Proxy Authentication Required",408:"Request Timeout",410:"Gone",412:"Conditional Request Failed",413:"Request Entity Too Large",414:"Request-URI Too Long",415:"Unsupported Media Type",416:"Unsupported URI Scheme",417:"Unknown Resource-Priority",420:"Bad Extension",421:"Extension Required",422:"Session Interval Too Small",423:"Interval Too Brief",428:"Use Identity Header",429:"Provide Referrer Identity",430:"Flow Failed",433:"Anonymity Disallowed",436:"Bad Identity-Info",437:"Unsupported Certificate",438:"Invalid Identity Header",439:"First Hop Lacks Outbound Support",440:"Max-Breadth Exceeded",469:"Bad Info Package",470:"Consent Needed",478:"Unresolvable Destination",480:"Temporarily Unavailable",481:"Call/Transaction Does Not Exist",482:"Loop Detected",483:"Too Many Hops",484:"Address Incomplete",485:"Ambiguous",486:"Busy Here",487:"Request Terminated",488:"Not Acceptable Here",489:"Bad Event",491:"Request Pending",493:"Undecipherable",494:"Security Agreement Required",500:"Internal Server Error",501:"Not Implemented",502:"Bad Gateway",503:"Service Unavailable",504:"Server Time-out",505:"Version Not Supported",513:"Message Too Large",580:"Precondition Failure",600:"Busy Everywhere",603:"Decline",604:"Does Not Exist Anywhere",606:"Not Acceptable"},e.OPTION_TAGS={"100rel":!0,199:!0,answermode:!0,"early-session":!0,eventlist:!0,explicitsub:!0,"from-change":!0,"geolocation-http":!0,"geolocation-sip":!0,gin:!0,gruu:!0,histinfo:!0,ice:!0,join:!0,"multiple-refer":!0,norefersub:!0,nosub:!0,outbound:!0,path:!0,policy:!0,precondition:!0,pref:!0,privacy:!0,"recipient-list-invite":!0,"recipient-list-message":!0,"recipient-list-subscribe":!0,replaces:!0,"resource-priority":!0,"sdp-anat":!0,"sec-agree":!0,tdialog:!0,timer:!0,uui:!0},function(e){e.INFO="info",e.RTP="rtp"}(e.dtmfType||(e.dtmfType={}))}(t.C||(t.C={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(7),n=r(22),s=r(25);!function(e){e.defer=function(){var e={};return e.promise=new Promise(function(t,r){e.resolve=t,e.reject=r}),e},e.reducePromises=function(e,t){return e.reduce(function(e,t){return e=e.then(t)},Promise.resolve(t))},e.str_utf8_length=function(e){return encodeURIComponent(e).replace(/%[A-F\d]{2}/g,"U").length},e.generateFakeSDP=function(e){if(e){var t=e.indexOf("o="),r=e.indexOf("\r\n",t);return"v=0\r\n"+e.slice(t,r)+"\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0"}},e.isDecimal=function(e){var t=parseInt(e,10);return!isNaN(t)&&parseFloat(e)===t},e.createRandomToken=function(e,t){void 0===t&&(t=32);for(var r="",i=0;i699)throw new TypeError("Invalid statusCode: "+t);if(t)return e.getReasonHeaderValue(t,r)},e.buildStatusLine=function(t,r){if(!t||t<100||t>699)throw new TypeError("Invalid statusCode: "+t);if(r&&"string"!=typeof r&&!(r instanceof String))throw new TypeError("Invalid reason: "+r);return"SIP/2.0 "+t+" "+(r=e.getReasonPhrase(t,r))+"\r\n"},e.fromBodyObj=function(e){var t=e.body,r=e.contentType;return{contentDisposition:function(e){return"application/sdp"===e?"session":"render"}(r),contentType:r,content:t}},e.toBodyObj=function(e){return{body:e.content,contentType:e.contentType}}}(t.Utils||(t.Utils={}))},function(e,t,r){"use strict";var i,n="object"==typeof Reflect?Reflect:null,s=n&&"function"==typeof n.apply?n.apply:function(e,t,r){return Function.prototype.apply.call(e,t,r)};i=n&&"function"==typeof n.ownKeys?n.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var o=Number.isNaN||function(e){return e!=e};function a(){a.init.call(this)}e.exports=a,a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var c=10;function u(e){return void 0===e._maxListeners?a.defaultMaxListeners:e._maxListeners}function d(e,t,r,i){var n,s,o,a;if("function"!=typeof r)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof r);if(void 0===(s=e._events)?(s=e._events=Object.create(null),e._eventsCount=0):(void 0!==s.newListener&&(e.emit("newListener",t,r.listener?r.listener:r),s=e._events),o=s[t]),void 0===o)o=s[t]=r,++e._eventsCount;else if("function"==typeof o?o=s[t]=i?[r,o]:[o,r]:i?o.unshift(r):o.push(r),(n=u(e))>0&&o.length>n&&!o.warned){o.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=o.length,a=c,console&&console.warn&&console.warn(a)}return e}function p(e,t,r){var i={fired:!1,wrapFn:void 0,target:e,type:t,listener:r},n=function(){for(var e=[],t=0;t0&&(o=t[0]),o instanceof Error)throw o;var a=new Error("Unhandled error."+(o?" ("+o.message+")":""));throw a.context=o,a}var c=n[e];if(void 0===c)return!1;if("function"==typeof c)s(c,this,t);else{var u=c.length,d=g(c,u);for(r=0;r=0;s--)if(r[s]===t||r[s].listener===t){o=r[s].listener,n=s;break}if(n<0)return this;0===n?r.shift():function(e,t){for(;t+1=0;i--)this.removeListener(e,t[i]);return this},a.prototype.listeners=function(e){return h(this,e,!0)},a.prototype.rawListeners=function(e){return h(this,e,!1)},a.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):l.call(e,t)},a.prototype.listenerCount=l,a.prototype.eventNames=function(){return this._eventsCount>0?i(this._events):[]}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(5),s=r(4);!function(e){var t=function(e){function t(){return e.call(this,"The session description handler has closed.")||this}return i.__extends(t,e),t}(n.Exception);e.ClosedSessionDescriptionHandlerError=t;var r=function(e){function t(){return e.call(this,"The session has terminated.")||this}return i.__extends(t,e),t}(n.Exception);e.TerminatedSessionError=r;var s=function(e){function t(t){return e.call(this,t||"Unsupported session description content type.")||this}return i.__extends(t,e),t}(n.Exception);e.UnsupportedSessionDescriptionContentTypeError=s}(t.Exceptions||(t.Exceptions={}));var o=function(e){function t(t,r,i){var n=e.call(this,i)||this;return n.code=t,n.name=r,n.message=i,n}return i.__extends(t,e),t}(n.Exception);!function(e){var t=function(e){function t(t,r){var i=e.call(this,1,"CONFIGURATION_ERROR",r?"Invalid value "+JSON.stringify(r)+" for parameter '"+t+"'":"Missing parameter: "+t)||this;return i.type=s.TypeStrings.ConfigurationError,i.parameter=t,i.value=r,i}return i.__extends(t,e),t}(o);e.ConfigurationError=t;var r=function(e){function t(t){var r=e.call(this,2,"INVALID_STATE_ERROR","Invalid status: "+t)||this;return r.type=s.TypeStrings.InvalidStateError,r.status=t,r}return i.__extends(t,e),t}(o);e.InvalidStateError=r;var n=function(e){function t(t){var r=e.call(this,3,"NOT_SUPPORTED_ERROR",t)||this;return r.type=s.TypeStrings.NotSupportedError,r}return i.__extends(t,e),t}(o);e.NotSupportedError=n;var a=function(e){function t(t){var r=e.call(this,5,"RENEGOTIATION_ERROR",t)||this;return r.type=s.TypeStrings.RenegotiationError,r}return i.__extends(t,e),t}(o);e.RenegotiationError=a;var c=function(e){function t(t,r,i){var n=e.call(this,6,"METHOD_PARAMETER_ERROR",i?"Invalid value "+JSON.stringify(i)+" for parameter '"+r+"'":"Missing parameter: "+r)||this;return n.type=s.TypeStrings.MethodParameterError,n.method=t,n.parameter=r,n.value=i,n}return i.__extends(t,e),t}(o);e.MethodParameterError=c;var u=function(e){function t(t,r,i){var n=e.call(this,8,"SESSION_DESCRIPTION_HANDLER_ERROR",i||"Error with Session Description Handler")||this;return n.type=s.TypeStrings.SessionDescriptionHandlerError,n.method=t,n.error=r,n}return i.__extends(t,e),t}(o);e.SessionDescriptionHandlerError=u}(t.Exceptions||(t.Exceptions={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=500;t.Timers={T1:i,T2:4e3,T4:5e3,TIMER_B:32e3,TIMER_D:0,TIMER_F:32e3,TIMER_H:32e3,TIMER_I:0,TIMER_J:0,TIMER_K:0,TIMER_L:32e3,TIMER_M:32e3,TIMER_N:32e3,PROVISIONAL_RESPONSE_INTERVAL:6e4}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(7),o=r(4),a=r(8),c=function(e){function t(r,i,n,s){var o=e.call(this)||this;return o.data={},t.initializer(o,r,i,n,s),o}return i.__extends(t,e),t.initializer=function(e,t,r,i,n){if(e.type=o.TypeStrings.ClientContext,void 0===i)throw new TypeError("Not enough arguments");e.ua=t,e.logger=t.getLogger("sip.clientcontext"),e.method=r;var s=t.normalizeTarget(i);if(!s)throw new TypeError("Invalid target: "+i);var c,u,d=((n=Object.create(n||Object.prototype)).extraHeaders||[]).slice(),p=n.params||{};n.body&&(c={body:n.body,contentType:n.contentType?n.contentType:"application/sdp"},e.body=c),c&&(u=a.Utils.fromBodyObj(c)),e.request=t.userAgentCore.makeOutgoingRequestMessage(r,s,p.fromUri?p.fromUri:t.userAgentCore.configuration.aor,p.toUri?p.toUri:s,p,d,u),e.request.from&&(e.localIdentity=e.request.from),e.request.to&&(e.remoteIdentity=e.request.to)},t.prototype.send=function(){var e=this;return this.ua.userAgentCore.request(this.request,{onAccept:function(t){return e.receiveResponse(t.message)},onProgress:function(t){return e.receiveResponse(t.message)},onRedirect:function(t){return e.receiveResponse(t.message)},onReject:function(t){return e.receiveResponse(t.message)},onTrying:function(t){return e.receiveResponse(t.message)}}),this},t.prototype.receiveResponse=function(e){var t=e.statusCode||0,r=a.Utils.getReasonPhrase(t);switch(!0){case/^1[0-9]{2}$/.test(t.toString()):this.emit("progress",e,r);break;case/^2[0-9]{2}$/.test(t.toString()):this.ua.applicants[this.toString()]&&delete this.ua.applicants[this.toString()],this.emit("accepted",e,r);break;default:this.ua.applicants[this.toString()]&&delete this.ua.applicants[this.toString()],this.emit("rejected",e,r),this.emit("failed",e,r)}},t.prototype.onRequestTimeout=function(){this.emit("failed",void 0,s.C.causes.REQUEST_TIMEOUT)},t.prototype.onTransportError=function(){this.emit("failed",void 0,s.C.causes.CONNECTION_ERROR)},t}(n.EventEmitter);t.ClientContext=c},function(e,t,r){"use strict";function i(e,t){void 0===t&&(t=32);for(var r="",i=0;in)throw new TypeError("Invalid statusCode: "+r);var p,h={statusCode:r,reasonPhrase:s,extraHeaders:a,body:u},l=r.toString();switch(!0){case/^100$/.test(l):p=this.incomingRequest.trying(h).message;break;case/^1[0-9]{2}$/.test(l):p=this.incomingRequest.progress(h).message;break;case/^2[0-9]{2}$/.test(l):p=this.incomingRequest.accept(h).message;break;case/^3[0-9]{2}$/.test(l):p=this.incomingRequest.redirect([],h).message;break;case/^[4-6][0-9]{2}$/.test(l):p=this.incomingRequest.reject(h).message;break;default:throw new Error("Invalid status code "+r)}return d.forEach(function(e){t.emit(e,p,s)}),this},t.prototype.onRequestTimeout=function(){this.emit("failed",void 0,s.C.causes.REQUEST_TIMEOUT)},t.prototype.onTransportError=function(){this.emit("failed",void 0,s.C.causes.CONNECTION_ERROR)},t}(n.EventEmitter);t.ServerContext=u},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(20),t),i.__exportStar(r(72),t),i.__exportStar(r(50),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(2),n=function(){function e(e,t){this.core=e,this.dialogState=t,this.core.dialogs.set(this.id,this)}return e.initialDialogStateForUserAgentClient=function(e,t){var r=t.getHeaders("record-route").reverse(),n=t.parseHeader("contact");if(!n)throw new Error("Contact undefined.");if(!(n instanceof i.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");var s=n.uri,o=e.cseq,a=e.callId,c=e.fromTag,u=t.toTag;if(!a)throw new Error("Call id undefined.");if(!c)throw new Error("From tag undefined.");if(!u)throw new Error("To tag undefined.");if(!e.from)throw new Error("From undefined.");if(!e.to)throw new Error("To undefined.");var d=e.from.uri,p=e.to.uri;if(!t.statusCode)throw new Error("Incoming response status code undefined.");return{id:a+c+u,early:t.statusCode<200,callId:a,localTag:c,remoteTag:u,localSequenceNumber:o,remoteSequenceNumber:void 0,localURI:d,remoteURI:p,remoteTarget:s,routeSet:r,secure:!1}},e.initialDialogStateForUserAgentServer=function(e,t,r){void 0===r&&(r=!1);var n=e.getHeaders("record-route"),s=e.parseHeader("contact");if(!s)throw new Error("Contact undefined.");if(!(s instanceof i.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");var o=s.uri,a=e.cseq,c=e.callId,u=t,d=e.fromTag,p=e.from.uri;return{id:c+u+d,early:r,callId:c,localTag:u,remoteTag:d,localSequenceNumber:void 0,remoteSequenceNumber:a,localURI:e.to.uri,remoteURI:p,remoteTarget:o,routeSet:n,secure:!1}},e.prototype.dispose=function(){this.core.dialogs.delete(this.id)},Object.defineProperty(e.prototype,"id",{get:function(){return this.dialogState.id},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"early",{get:function(){return this.dialogState.early},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"callId",{get:function(){return this.dialogState.callId},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"localTag",{get:function(){return this.dialogState.localTag},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteTag",{get:function(){return this.dialogState.remoteTag},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"localSequenceNumber",{get:function(){return this.dialogState.localSequenceNumber},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteSequenceNumber",{get:function(){return this.dialogState.remoteSequenceNumber},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"localURI",{get:function(){return this.dialogState.localURI},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteURI",{get:function(){return this.dialogState.remoteURI},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"remoteTarget",{get:function(){return this.dialogState.remoteTarget},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"routeSet",{get:function(){return this.dialogState.routeSet},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"secure",{get:function(){return this.dialogState.secure},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"userAgentCore",{get:function(){return this.core},enumerable:!0,configurable:!0}),e.prototype.confirm=function(){this.dialogState.early=!1},e.prototype.receiveRequest=function(e){if(e.method!==i.C.ACK){if(this.remoteSequenceNumber){if(e.cseq<=this.remoteSequenceNumber)throw new Error("Out of sequence in dialog request. Did you forget to call sequenceGuard()?");this.dialogState.remoteSequenceNumber=e.cseq}this.remoteSequenceNumber||(this.dialogState.remoteSequenceNumber=e.cseq)}},e.prototype.recomputeRouteSet=function(e){this.dialogState.routeSet=e.getHeaders("record-route").reverse()},e.prototype.createOutgoingRequestMessage=function(e,t){var r,i=this.remoteURI,n=this.remoteTag,s=this.localURI,o=this.localTag,a=this.callId;r=t&&t.cseq?t.cseq:this.dialogState.localSequenceNumber?this.dialogState.localSequenceNumber+=1:this.dialogState.localSequenceNumber=1;var c=this.remoteTarget,u=this.routeSet,d=t&&t.extraHeaders,p=t&&t.body;return this.userAgentCore.makeOutgoingRequestMessage(e,c,s,i,{callId:a,cseq:r,fromTag:o,toTag:n,routeSet:u},d,p)},e.prototype.sequenceGuard=function(e){return e.method===i.C.ACK||(!(this.remoteSequenceNumber&&e.cseq<=this.remoteSequenceNumber)||(this.core.replyStateless(e,{statusCode:500}),!1))},e}();t.Dialog=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(22),n=r(13),s=function(){function e(){this.headers={}}return e.prototype.addHeader=function(e,t){var r={raw:t};e=n.headerize(e),this.headers[e]?this.headers[e].push(r):this.headers[e]=[r]},e.prototype.getHeader=function(e){var t=this.headers[n.headerize(e)];if(t)return t[0]?t[0].raw:void 0},e.prototype.getHeaders=function(e){var t=this.headers[n.headerize(e)],r=[];if(!t)return[];for(var i=0,s=t;i=this.headers[e].length)){var r=this.headers[e][t],s=r.raw;if(r.parsed)return r.parsed;var o=i.Grammar.parse(s,e.replace(/-/g,"_"));return-1===o?void this.headers[e].splice(t,1):(r.parsed=o,o)}},e.prototype.s=function(e,t){return void 0===t&&(t=0),this.parseHeader(e,t)},e.prototype.setHeader=function(e,t){this.headers[n.headerize(e)]=[{raw:t}]},e.prototype.toString=function(){return this.data},e}();t.IncomingMessage=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0).__importStar(r(67));!function(e){e.parse=function(e,t){var r={startRule:t};try{i.parse(e,r)}catch(e){r.data=-1}return r.data},e.nameAddrHeaderParse=function(t){var r=e.parse(t,"Name_Addr_Header");return-1!==r?r:void 0},e.URIParse=function(t){var r=e.parse(t,"SIP_URI");return-1!==r?r:void 0}}(t.Grammar||(t.Grammar={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t,r,i){var n=e.call(this,i)||this;return n.uri=t,n._displayName=r,n}return i.__extends(t,e),Object.defineProperty(t.prototype,"friendlyName",{get:function(){return this.displayName||this.uri.aor},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"displayName",{get:function(){return this._displayName},set:function(e){this._displayName=e},enumerable:!0,configurable:!0}),t.prototype.clone=function(){return new t(this.uri.clone(),this._displayName,JSON.parse(JSON.stringify(this.parameters)))},t.prototype.toString=function(){var e=this.displayName||"0"===this.displayName?'"'+this.displayName+'" ':"";for(var t in e+="<"+this.uri.toString()+">",this.parameters)this.parameters.hasOwnProperty(t)&&(e+=";"+t,null!==this.parameters[t]&&(e+="="+this.parameters[t]));return e},t}(r(24).Parameters);t.NameAddrHeader=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e){for(var t in this.parameters={},e)e.hasOwnProperty(t)&&this.setParam(t,e[t])}return e.prototype.setParam=function(e,t){e&&(this.parameters[e.toLowerCase()]=null==t?null:t.toString())},e.prototype.getParam=function(e){if(e)return this.parameters[e.toLowerCase()]},e.prototype.hasParam=function(e){return!!e&&!!this.parameters.hasOwnProperty(e.toLowerCase())},e.prototype.deleteParam=function(e){if(e=e.toLowerCase(),this.parameters.hasOwnProperty(e)){var t=this.parameters[e];return delete this.parameters[e],t}},e.prototype.clearParams=function(){this.parameters={}},e}();t.Parameters=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t,r,i,n,s,o){var a=e.call(this,s)||this;if(a.headers={},!i)throw new TypeError('missing or invalid "host" parameter');for(var c in t=t||"sip",o)o.hasOwnProperty(c)&&a.setHeader(c,o[c]);return a.raw={scheme:t,user:r,host:i,port:n},a.normal={scheme:t.toLowerCase(),user:r,host:i.toLowerCase(),port:n},a}return i.__extends(t,e),Object.defineProperty(t.prototype,"scheme",{get:function(){return this.normal.scheme},set:function(e){this.raw.scheme=e,this.normal.scheme=e.toLowerCase()},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"user",{get:function(){return this.normal.user},set:function(e){this.normal.user=this.raw.user=e},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"host",{get:function(){return this.normal.host},set:function(e){this.raw.host=e,this.normal.host=e.toLowerCase()},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"aor",{get:function(){return this.normal.user+"@"+this.normal.host},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"port",{get:function(){return this.normal.port},set:function(e){this.normal.port=this.raw.port=e},enumerable:!0,configurable:!0}),t.prototype.setHeader=function(e,t){this.headers[this.headerize(e)]=t instanceof Array?t:[t]},t.prototype.getHeader=function(e){if(e)return this.headers[this.headerize(e)]},t.prototype.hasHeader=function(e){return!!e&&!!this.headers.hasOwnProperty(this.headerize(e))},t.prototype.deleteHeader=function(e){if(e=this.headerize(e),this.headers.hasOwnProperty(e)){var t=this.headers[e];return delete this.headers[e],t}},t.prototype.clearHeaders=function(){this.headers={}},t.prototype.clone=function(){return new t(this._raw.scheme,this._raw.user||"",this._raw.host,this._raw.port,JSON.parse(JSON.stringify(this.parameters)),JSON.parse(JSON.stringify(this.headers)))},t.prototype.toRaw=function(){return this._toString(this._raw)},t.prototype.toString=function(){return this._toString(this._normal)},Object.defineProperty(t.prototype,"_normal",{get:function(){return this.normal},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"_raw",{get:function(){return this.raw},enumerable:!0,configurable:!0}),t.prototype._toString=function(e){var t=e.scheme+":";for(var r in e.scheme.toLowerCase().match("^sips?$")||(t+="//"),e.user&&(t+=this.escapeUser(e.user)+"@"),t+=e.host,(e.port||0===e.port)&&(t+=":"+e.port),this.parameters)this.parameters.hasOwnProperty(r)&&(t+=";"+r,null!==this.parameters[r]&&(t+="="+this.parameters[r]));var i=[];for(var n in this.headers)if(this.headers.hasOwnProperty(n))for(var s in this.headers[n])this.headers[n].hasOwnProperty(s)&&i.push(n+"="+this.headers[n][s]);return i.length>0&&(t+="?"+i.join("&")),t},t.prototype.escapeUser=function(e){return encodeURIComponent(decodeURIComponent(e)).replace(/%3A/gi,":").replace(/%2B/gi,"+").replace(/%3F/gi,"?").replace(/%2F/gi,"/")},t.prototype.headerize=function(e){for(var t={"Call-Id":"Call-ID",Cseq:"CSeq","Min-Se":"Min-SE",Rack:"RAck",Rseq:"RSeq","Www-Authenticate":"WWW-Authenticate"},r=e.toLowerCase().replace(/_/g,"-").split("-"),i=r.length,n="",s=0;s"),o.extraHeaders.push("Contact: "+r.contact),o.extraHeaders.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),o.extraHeaders.push("Refer-To: "+o.target),o.errorListener=o.onTransportError.bind(o),t.transport&&t.transport.on("transportError",o.errorListener),o}return i.__extends(t,e),t.prototype.refer=function(e){var t=this;void 0===e&&(e={});var r=(this.extraHeaders||[]).slice();return e.extraHeaders&&r.concat(e.extraHeaders),this.applicant.sendRequest(s.C.REFER,{extraHeaders:this.extraHeaders,receiveResponse:function(r){var i=r&&r.statusCode?r.statusCode.toString():"";/^1[0-9]{2}$/.test(i)?t.emit("referRequestProgress",t):/^2[0-9]{2}$/.test(i)?t.emit("referRequestAccepted",t):/^[4-6][0-9]{2}$/.test(i)&&t.emit("referRequestRejected",t),e.receiveResponse&&e.receiveResponse(r)}}),this},t.prototype.receiveNotify=function(e){var t=e.message.hasHeader("Content-Type")?e.message.getHeader("Content-Type"):void 0;if(t&&-1!==t.search(/^message\/sipfrag/)){var r=o.Grammar.parse(e.message.body,"sipfrag");if(-1===r)return void e.reject({statusCode:489,reasonPhrase:"Bad Event"});switch(!0){case/^1[0-9]{2}$/.test(r.status_code):this.emit("referProgress",this);break;case/^2[0-9]{2}$/.test(r.status_code):this.emit("referAccepted",this),!this.options.activeAfterTransfer&&this.applicant.terminate&&this.applicant.terminate();break;default:this.emit("referRejected",this)}return e.accept(),void this.emit("notify",e.message)}e.reject({statusCode:489,reasonPhrase:"Bad Event"})},t.prototype.initReferTo=function(e){var t;if("string"==typeof e){var r=o.Grammar.parse(e,"Refer_To");t=r&&r.uri?r.uri:e;var i=this.ua.normalizeTarget(e);if(!i)throw new TypeError("Invalid target: "+e);t=i}else{if(!e.session)throw new Error("Session undefined.");var n=e.remoteIdentity.friendlyName,s=e.session.remoteTarget.toString(),a=e.session.callId,c=e.session.remoteTag,u=e.session.localTag;t='"'+n+'" <'+s+"?Replaces="+encodeURIComponent(a+";to-tag="+c+";from-tag="+u)+">"}return t},t}(n.ClientContext);t.ReferClientContext=d;var p=function(e){function t(t,r,i){var n=e.call(this,t,r)||this;return n.session=i,n.type=a.TypeStrings.ReferServerContext,n.ua=t,n.status=a.SessionStatus.STATUS_INVITE_RECEIVED,n.fromTag=n.request.fromTag,n.id=n.request.callId+n.fromTag,n.contact=n.ua.contact.toString(),n.logger=t.getLogger("sip.referservercontext",n.id),n.cseq=Math.floor(1e4*Math.random()),n.callId=n.request.callId,n.fromUri=n.request.to.uri,n.fromTag=n.request.to.parameters.tag,n.remoteTarget=n.request.headers.Contact[0].parsed.uri,n.toUri=n.request.from.uri,n.toTag=n.request.fromTag,n.routeSet=n.request.getHeaders("record-route"),n.request.hasHeader("refer-to")?(n.referTo=n.request.parseHeader("refer-to"),n.referredSession=n.ua.findSession(n.request),n.request.hasHeader("referred-by")&&(n.referredBy=n.request.getHeader("referred-by")),n.referTo.uri.hasHeader("replaces")&&(n.replaces=n.referTo.uri.getHeader("replaces")),n.errorListener=n.onTransportError.bind(n),t.transport&&t.transport.on("transportError",n.errorListener),n.status=a.SessionStatus.STATUS_WAITING_FOR_ANSWER,n):(n.logger.warn("Invalid REFER packet. A refer-to header is required. Rejecting refer."),n.reject(),n)}return i.__extends(t,e),t.prototype.progress=function(){if(this.status!==a.SessionStatus.STATUS_WAITING_FOR_ANSWER)throw new c.Exceptions.InvalidStateError(this.status);this.incomingRequest.trying()},t.prototype.reject=function(t){if(void 0===t&&(t={}),this.status===a.SessionStatus.STATUS_TERMINATED)throw new c.Exceptions.InvalidStateError(this.status);this.logger.log("Rejecting refer"),this.status=a.SessionStatus.STATUS_TERMINATED,e.prototype.reject.call(this,t),this.emit("referRequestRejected",this)},t.prototype.accept=function(e,t){var r=this;if(void 0===e&&(e={}),this.status!==a.SessionStatus.STATUS_WAITING_FOR_ANSWER)throw new c.Exceptions.InvalidStateError(this.status);if(this.status=a.SessionStatus.STATUS_ANSWERED,this.incomingRequest.accept({statusCode:202,reasonPhrase:"Accepted"}),this.emit("referRequestAccepted",this),e.followRefer){this.logger.log("Accepted refer, attempting to automatically follow it");var i=this.referTo.uri;if(!i.scheme||!i.scheme.match("^sips?$"))return this.logger.error("SIP.js can only automatically follow SIP refer target"),void this.reject();var n=e.inviteOptions||{},s=(n.extraHeaders||[]).slice();if(this.replaces&&s.push("Replaces: "+decodeURIComponent(this.replaces)),this.referredBy&&s.push("Referred-By: "+this.referredBy),n.extraHeaders=s,i.clearHeaders(),this.targetSession=this.ua.invite(i.toString(),n,t),this.emit("referInviteSent",this),this.targetSession){this.targetSession.once("progress",function(e){var t=e.statusCode||100,i=e.reasonPhrase;r.sendNotify(("SIP/2.0 "+t+" "+i).trim()),r.emit("referProgress",r),r.referredSession&&r.referredSession.emit("referProgress",r)}),this.targetSession.once("accepted",function(){r.logger.log("Successfully followed the refer"),r.sendNotify("SIP/2.0 200 OK"),r.emit("referAccepted",r),r.referredSession&&r.referredSession.emit("referAccepted",r)});var o=function(e){if(r.status!==a.SessionStatus.STATUS_TERMINATED){if(r.logger.log("Refer was not successful. Resuming session"),e&&429===e.statusCode)return r.logger.log("Alerting referrer that identity is required."),void r.sendNotify("SIP/2.0 429 Provide Referrer Identity");r.sendNotify("SIP/2.0 603 Declined"),r.status=a.SessionStatus.STATUS_TERMINATED,r.emit("referRejected",r),r.referredSession&&r.referredSession.emit("referRejected")}};this.targetSession.once("rejected",o),this.targetSession.once("failed",o)}}else this.logger.log("Accepted refer, but did not automatically follow it"),this.sendNotify("SIP/2.0 200 OK"),this.emit("referAccepted",this),this.referredSession&&this.referredSession.emit("referAccepted",this)},t.prototype.sendNotify=function(e){if(this.status!==a.SessionStatus.STATUS_ANSWERED)throw new c.Exceptions.InvalidStateError(this.status);if(-1===o.Grammar.parse(e,"sipfrag"))throw new Error("sipfrag body is required to send notify for refer");var t={contentDisposition:"render",contentType:"message/sipfrag",content:e};if(this.session)this.session.notify(void 0,{extraHeaders:["Event: refer","Subscription-State: terminated"],body:t});else{var r=this.ua.userAgentCore.makeOutgoingRequestMessage(s.C.NOTIFY,this.remoteTarget,this.fromUri,this.toUri,{cseq:this.cseq+=1,callId:this.callId,fromTag:this.fromTag,toTag:this.toTag,routeSet:this.routeSet},["Event: refer","Subscription-State: terminated","Content-Type: message/sipfrag"],t),i=this.ua.transport;if(!i)throw new Error("Transport undefined.");var n={loggerFactory:this.ua.getLoggerFactory()};new o.NonInviteClientTransaction(r,i,n)}},t.prototype.on=function(t,r){return e.prototype.on.call(this,t,r)},t}(u.ServerContext);t.ReferServerContext=p},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=function(e,t){for(var r,i=[],n=e.split(/\r\n/),s=0;s699)throw new Error("Invalid status code "+r);switch(this.state){case o.TransactionState.Calling:if(r>=100&&r<=199)return this.stateTransition(o.TransactionState.Proceeding),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=200&&r<=299)return this.ackRetransmissionCache.set(e.toTag,void 0),this.stateTransition(o.TransactionState.Accepted),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=300&&r<=699)return this.stateTransition(o.TransactionState.Completed),this.ack(e),void(this.user.receiveResponse&&this.user.receiveResponse(e));break;case o.TransactionState.Proceeding:if(r>=100&&r<=199)return void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=200&&r<=299)return this.ackRetransmissionCache.set(e.toTag,void 0),this.stateTransition(o.TransactionState.Accepted),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(r>=300&&r<=699)return this.stateTransition(o.TransactionState.Completed),this.ack(e),void(this.user.receiveResponse&&this.user.receiveResponse(e));break;case o.TransactionState.Accepted:if(r>=200&&r<=299){if(!this.ackRetransmissionCache.has(e.toTag))return this.ackRetransmissionCache.set(e.toTag,void 0),void(this.user.receiveResponse&&this.user.receiveResponse(e));var i=this.ackRetransmissionCache.get(e.toTag);return i?void this.send(i.toString()).catch(function(e){t.logTransportError(e,"Failed to send retransmission of ACK to 2xx response.")}):void 0}break;case o.TransactionState.Completed:if(r>=300&&r<=699)return void this.ack(e);break;case o.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}var n="Received unexpected "+r+" response while in state "+this.state+".";this.logger.warn(n)},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e),this.stateTransition(o.TransactionState.Terminated,!0)},t.prototype.typeToString=function(){return"INVITE client transaction"},t.prototype.ack=function(e){var t=this,r=this.request.ruri,i=this.request.callId,n=this.request.cseq,s=this.request.getHeader("from"),o=e.getHeader("to"),a=this.request.getHeader("via"),c=this.request.getHeader("route");if(!s)throw new Error("From undefined.");if(!o)throw new Error("To undefined.");if(!a)throw new Error("Via undefined.");var u="ACK "+r+" SIP/2.0\r\n";c&&(u+="Route: "+c+"\r\n"),u+="Via: "+a+"\r\n",u+="To: "+o+"\r\n",u+="From: "+s+"\r\n",u+="Call-ID: "+i+"\r\n",u+="CSeq: "+n+" ACK\r\n",u+="Max-Forwards: 70\r\n",u+="Content-Length: 0\r\n\r\n",this.send(u).catch(function(e){t.logTransportError(e,"Failed to send ACK to non-2xx response.")})},t.prototype.stateTransition=function(e,t){var r=this;void 0===t&&(t=!1);var i=function(){throw new Error("Invalid state transition from "+r.state+" to "+e)};switch(e){case o.TransactionState.Calling:i();break;case o.TransactionState.Proceeding:this.state!==o.TransactionState.Calling&&i();break;case o.TransactionState.Accepted:case o.TransactionState.Completed:this.state!==o.TransactionState.Calling&&this.state!==o.TransactionState.Proceeding&&i();break;case o.TransactionState.Terminated:this.state!==o.TransactionState.Calling&&this.state!==o.TransactionState.Accepted&&this.state!==o.TransactionState.Completed&&(t||i());break;default:i()}this.B&&(clearTimeout(this.B),this.B=void 0),o.TransactionState.Proceeding,e===o.TransactionState.Completed&&(this.D=setTimeout(function(){return r.timer_D()},n.Timers.TIMER_D)),e===o.TransactionState.Accepted&&(this.M=setTimeout(function(){return r.timer_M()},n.Timers.TIMER_M)),e===o.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.timer_A=function(){},t.prototype.timer_B=function(){this.logger.debug("Timer B expired for INVITE client transaction "+this.id+"."),this.state===o.TransactionState.Calling&&(this.onRequestTimeout(),this.stateTransition(o.TransactionState.Terminated))},t.prototype.timer_D=function(){this.logger.debug("Timer D expired for INVITE client transaction "+this.id+"."),this.state===o.TransactionState.Completed&&this.stateTransition(o.TransactionState.Terminated)},t.prototype.timer_M=function(){this.logger.debug("Timer M expired for INVITE client transaction "+this.id+"."),this.state===o.TransactionState.Accepted&&this.stateTransition(o.TransactionState.Terminated)},t}(s.ClientTransaction);t.InviteClientTransaction=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o,a=t.createOutgoingRequestMessage(n.C.BYE,i);return o=e.call(this,s.NonInviteClientTransaction,t.userAgentCore,a,r)||this,t.dispose(),o}return i.__extends(t,e),t}(r(3).UserAgentClient);t.ByeUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.ByeUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.InfoUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=t.createOutgoingRequestMessage(n.C.NOTIFY,i);return e.call(this,s.NonInviteClientTransaction,t.userAgentCore,o,r)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.NotifyUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o,a=t.createOutgoingRequestMessage(n.C.PRACK,i);return o=e.call(this,s.NonInviteClientTransaction,t.userAgentCore,a,r)||this,t.signalingStateTransition(a),o}return i.__extends(t,e),t}(r(3).UserAgentClient);t.PrackUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this;return t.signalingStateTransition(r),s.dialog=t,s}return i.__extends(t,e),t.prototype.accept=function(t){return void 0===t&&(t={statusCode:200}),t.body&&this.dialog.signalingStateTransition(t.body),e.prototype.accept.call(this,t)},t}(r(6).UserAgentServer);t.PrackUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=this,a=t.createOutgoingRequestMessage(n.C.INVITE,i);return(o=e.call(this,s.InviteClientTransaction,t.userAgentCore,a,r)||this).delegate=r,t.signalingStateTransition(a),t.reinviteUserAgentClient=o,o.dialog=t,o}return i.__extends(t,e),t.prototype.receiveResponse=function(e){var t=this,r=e.statusCode?e.statusCode.toString():"";if(!r)throw new Error("Response status code undefined.");switch(!0){case/^100$/.test(r):this.delegate&&this.delegate.onTrying&&this.delegate.onTrying({message:e});break;case/^1[0-9]{2}$/.test(r):this.delegate&&this.delegate.onProgress&&this.delegate.onProgress({message:e,session:this.dialog,prack:function(e){throw new Error("Unimplemented.")}});break;case/^2[0-9]{2}$/.test(r):this.dialog.signalingStateTransition(e),this.delegate&&this.delegate.onAccept&&this.delegate.onAccept({message:e,session:this.dialog,ack:function(e){return t.dialog.ack(e)}});break;case/^3[0-9]{2}$/.test(r):this.delegate&&this.delegate.onRedirect&&this.delegate.onRedirect({message:e});break;case/^[4-6][0-9]{2}$/.test(r):this.delegate&&this.delegate.onReject&&this.delegate.onReject({message:e});break;default:throw new Error("Invalid status code "+r)}},t}(r(3).UserAgentClient);t.ReInviteUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.InviteServerTransaction,t.userAgentCore,r,i)||this;return t.reinviteUserAgentServer=s,s.dialog=t,s}return i.__extends(t,e),t.prototype.accept=function(t){void 0===t&&(t={statusCode:200}),t.extraHeaders=t.extraHeaders||[],t.extraHeaders=t.extraHeaders.concat(this.dialog.routeSet.map(function(e){return"Record-Route: "+e}));var r=e.prototype.accept.call(this,t),n=this.dialog,s=i.__assign({},r,{session:n});return t.body&&this.dialog.signalingStateTransition(t.body),this.dialog.reConfirm(),s},t.prototype.progress=function(t){void 0===t&&(t={statusCode:180});var r=e.prototype.progress.call(this,t),n=this.dialog,s=i.__assign({},r,{session:n});return t.body&&this.dialog.signalingStateTransition(t.body),s},t}(r(6).UserAgentServer);t.ReInviteUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=t.createOutgoingRequestMessage(n.C.REFER,i);return e.call(this,s.NonInviteClientTransaction,t.userAgentCore,o,r)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.ReferUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=void 0!==t.userAgentCore?t.userAgentCore:t;return e.call(this,n.NonInviteServerTransaction,s,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.ReferUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(32),o=r(11),a=r(16),c=r(31),u=r(51),d=function(e){function t(t,r,i,n,s,o){var a=e.call(this,n,s)||this;return a.delegate=o,a._autoRefresh=!1,a._subscriptionEvent=t,a._subscriptionExpires=r,a._subscriptionExpiresInitial=r,a._subscriptionExpiresLastSet=Math.floor(Date.now()/1e3),a._subscriptionRefresh=void 0,a._subscriptionRefreshLastSet=void 0,a._subscriptionState=i,a.logger=n.loggerFactory.getLogger("sip.subscribe-dialog"),a.logger.log("SUBSCRIBE dialog "+a.id+" constructed"),a}return i.__extends(t,e),t.initialDialogStateForSubscription=function(e,t){var r=t.getHeaders("record-route"),i=t.parseHeader("contact");if(!i)throw new Error("Contact undefined.");if(!(i instanceof n.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");var s=i.uri,o=e.cseq,a=e.callId,c=e.fromTag,u=t.fromTag;if(!a)throw new Error("Call id undefined.");if(!c)throw new Error("From tag undefined.");if(!u)throw new Error("To tag undefined.");if(!e.from)throw new Error("From undefined.");if(!e.to)throw new Error("To undefined.");return{id:a+c+u,early:!1,callId:a,localTag:c,remoteTag:u,localSequenceNumber:o,remoteSequenceNumber:void 0,localURI:e.from.uri,remoteURI:e.to.uri,remoteTarget:s,routeSet:r,secure:!1}},t.prototype.dispose=function(){e.prototype.dispose.call(this),this.N&&(clearTimeout(this.N),this.N=void 0),this.refreshTimerClear(),this.logger.log("SUBSCRIBE dialog "+this.id+" destroyed")},Object.defineProperty(t.prototype,"autoRefresh",{get:function(){return this._autoRefresh},set:function(e){this._autoRefresh=!0,this.refreshTimerSet()},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionEvent",{get:function(){return this._subscriptionEvent},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionExpires",{get:function(){var e=Math.floor(Date.now()/1e3)-this._subscriptionExpiresLastSet,t=this._subscriptionExpires-e;return Math.max(t,0)},set:function(e){if(e<0)throw new Error("Expires must be greater than or equal to zero.");if(this._subscriptionExpires=e,this._subscriptionExpiresLastSet=Math.floor(Date.now()/1e3),this.autoRefresh){var t=this.subscriptionRefresh;(void 0===t||t>=e)&&this.refreshTimerSet()}},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionExpiresInitial",{get:function(){return this._subscriptionExpiresInitial},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionRefresh",{get:function(){if(void 0!==this._subscriptionRefresh&&void 0!==this._subscriptionRefreshLastSet){var e=Math.floor(Date.now()/1e3)-this._subscriptionRefreshLastSet,t=this._subscriptionRefresh-e;return Math.max(t,0)}},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"subscriptionState",{get:function(){return this._subscriptionState},enumerable:!0,configurable:!0}),t.prototype.receiveRequest=function(t){if(this.logger.log("SUBSCRIBE dialog "+this.id+" received "+t.method+" request"),this.sequenceGuard(t))switch(e.prototype.receiveRequest.call(this,t),t.method){case n.C.NOTIFY:this.onNotify(t);break;default:this.logger.log("SUBSCRIBE dialog "+this.id+" received unimplemented "+t.method+" request"),this.core.replyStateless(t,{statusCode:501})}else this.logger.log("SUBSCRIBE dialog "+this.id+" rejected out of order "+t.method+" request.")},t.prototype.refresh=function(){var e="Allow: "+a.AllowedMethods.toString(),t={};return t.extraHeaders=(t.extraHeaders||[]).slice(),t.extraHeaders.push(e),t.extraHeaders.push("Event: "+this.subscriptionEvent),t.extraHeaders.push("Expires: "+this.subscriptionExpiresInitial),t.extraHeaders.push("Contact: "+this.core.configuration.contact.toString()),this.subscribe(void 0,t)},t.prototype.subscribe=function(e,t){var r=this;if(void 0===t&&(t={}),this.subscriptionState!==s.SubscriptionState.Pending&&this.subscriptionState!==s.SubscriptionState.Active)throw new Error("Invalid state "+this.subscriptionState+'. May only re-subscribe while in state "pending" or "active".');this.logger.log("SUBSCRIBE dialog "+this.id+" sending SUBSCRIBE request");var i=new u.ReSubscribeUserAgentClient(this,e,t);return this.N=setTimeout(function(){return r.timer_N()},o.Timers.TIMER_N),i},t.prototype.terminate=function(){this.stateTransition(s.SubscriptionState.Terminated),this.onTerminated()},t.prototype.unsubscribe=function(){var e="Allow: "+a.AllowedMethods.toString(),t={};return t.extraHeaders=(t.extraHeaders||[]).slice(),t.extraHeaders.push(e),t.extraHeaders.push("Event: "+this.subscriptionEvent),t.extraHeaders.push("Expires: 0"),t.extraHeaders.push("Contact: "+this.core.configuration.contact.toString()),this.subscribe(void 0,t)},t.prototype.onNotify=function(e){var t=e.parseHeader("Event").event;if(t&&t===this.subscriptionEvent){this.N&&(clearTimeout(this.N),this.N=void 0);var r=e.parseHeader("Subscription-State");if(r&&r.state){var i=r.state,n=r.expires?Math.max(r.expires,0):void 0;switch(i){case"pending":this.stateTransition(s.SubscriptionState.Pending,n);break;case"active":this.stateTransition(s.SubscriptionState.Active,n);break;case"terminated":this.stateTransition(s.SubscriptionState.Terminated,n);break;default:this.logger.warn("Unrecognized subscription state.")}var o=new c.NotifyUserAgentServer(this,e);this.delegate&&this.delegate.onNotify?this.delegate.onNotify(o):o.accept()}else this.core.replyStateless(e,{statusCode:489})}else this.core.replyStateless(e,{statusCode:489})},t.prototype.onRefresh=function(e){this.delegate&&this.delegate.onRefresh&&this.delegate.onRefresh(e)},t.prototype.onTerminated=function(){this.delegate&&this.delegate.onTerminated&&this.delegate.onTerminated()},t.prototype.refreshTimerClear=function(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=void 0)},t.prototype.refreshTimerSet=function(){var e=this;if(this.refreshTimerClear(),this.autoRefresh&&this.subscriptionExpires>0){var t=900*this.subscriptionExpires;this._subscriptionRefresh=Math.floor(t/1e3),this._subscriptionRefreshLastSet=Math.floor(Date.now()/1e3),this.refreshTimer=setTimeout(function(){e.refreshTimer=void 0,e._subscriptionRefresh=void 0,e._subscriptionRefreshLastSet=void 0,e.onRefresh(e.refresh())},t)}},t.prototype.stateTransition=function(e,t){var r=this,i=function(){r.logger.warn("Invalid subscription state transition from "+r.subscriptionState+" to "+e)};switch(e){case s.SubscriptionState.Initial:case s.SubscriptionState.NotifyWait:return void i();case s.SubscriptionState.Pending:if(this.subscriptionState!==s.SubscriptionState.NotifyWait&&this.subscriptionState!==s.SubscriptionState.Pending)return void i();break;case s.SubscriptionState.Active:case s.SubscriptionState.Terminated:if(this.subscriptionState!==s.SubscriptionState.NotifyWait&&this.subscriptionState!==s.SubscriptionState.Pending&&this.subscriptionState!==s.SubscriptionState.Active)return void i();break;default:return void i()}e===s.SubscriptionState.Pending&&t&&(this.subscriptionExpires=t),e===s.SubscriptionState.Active&&t&&(this.subscriptionExpires=t),e===s.SubscriptionState.Terminated&&this.dispose(),this._subscriptionState=e},t.prototype.timer_N=function(){this.subscriptionState!==s.SubscriptionState.Terminated&&(this.stateTransition(s.SubscriptionState.Terminated),this.onTerminated())},t}(r(20).Dialog);t.SubscriptionDialog=d},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=this,a=t.createOutgoingRequestMessage(n.C.SUBSCRIBE,i);return(o=e.call(this,s.NonInviteClientTransaction,t.userAgentCore,a,r)||this).dialog=t,o}return i.__extends(t,e),t.prototype.waitNotifyStop=function(){},t.prototype.receiveResponse=function(t){if(t.statusCode&&t.statusCode>=200&&t.statusCode<300){var r=t.getHeader("Expires");if(r){var i=Number(r);this.dialog.subscriptionExpires>i&&(this.dialog.subscriptionExpires=i)}else this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE")}if(t.statusCode&&t.statusCode>=400&&t.statusCode<700){[404,405,410,416,480,481,482,483,484,485,489,501,604].includes(t.statusCode)&&this.dialog.terminate()}e.prototype.receiveResponse.call(this,t)},t}(r(3).UserAgentClient);t.ReSubscribeUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(33),n=function(){function e(e,t,r){this.logger=e,this.category=t,this.label=r}return e.prototype.error=function(e){this.genericLog(i.Levels.error,e)},e.prototype.warn=function(e){this.genericLog(i.Levels.warn,e)},e.prototype.log=function(e){this.genericLog(i.Levels.log,e)},e.prototype.debug=function(e){this.genericLog(i.Levels.debug,e)},e.prototype.genericLog=function(e,t){this.logger.genericLog(e,this.category,this.label,t)},e}();t.Logger=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(40),t),i.__exportStar(r(41),t),i.__exportStar(r(85),t),i.__exportStar(r(42),t),i.__exportStar(r(86),t),i.__exportStar(r(87),t),i.__exportStar(r(88),t),i.__exportStar(r(89),t),i.__exportStar(r(43),t),i.__exportStar(r(31),t),i.__exportStar(r(90),t),i.__exportStar(r(44),t),i.__exportStar(r(45),t),i.__exportStar(r(46),t),i.__exportStar(r(47),t),i.__exportStar(r(51),t),i.__exportStar(r(91),t),i.__exportStar(r(48),t),i.__exportStar(r(49),t),i.__exportStar(r(92),t),i.__exportStar(r(93),t),i.__exportStar(r(94),t),i.__exportStar(r(3),t),i.__exportStar(r(6),t)},function(e){e.exports={name:"sip.js",title:"SIP.js",description:"A simple, intuitive, and powerful JavaScript signaling library",version:"0.14.2",license:"MIT",main:"./lib/index.js",types:"./lib/index.d.ts",homepage:"https://sipjs.com",author:"OnSIP (https://sipjs.com/aboutus/)",contributors:[{url:"https://github.com/onsip/SIP.js/blob/master/THANKS.md"}],repository:{type:"git",url:"https://github.com/onsip/SIP.js.git"},keywords:["sip","webrtc","library","websocket","javascript","typescript"],dependencies:{"crypto-js":"^3.1.9-1"},devDependencies:{"@types/crypto-js":"^3.1.43","@types/jasmine":"^3.3.13","@types/node":"^12.0.4","circular-dependency-plugin":"^5.0.2","jasmine-core":"^3.4.0",karma:"^4.1.0","karma-chrome-launcher":"^2.2.0","karma-cli":"^2.0.0","karma-jasmine":"^2.0.1","karma-jasmine-html-reporter":"^1.4.2","karma-mocha-reporter":"^2.2.5","karma-sourcemap-loader":"^0.3.7","karma-webpack":"^3.0.5",pegjs:"^0.10.0","ts-loader":"^6.0.2","ts-pegjs":"0.2.5",tslint:"^5.17.0",typescript:"^3.5.1",webpack:"^4.32.2","webpack-cli":"^3.3.2"},engines:{node:">=8.0"},scripts:{prebuild:"tslint -p tsconfig-base.json -c tslint.json","generate-grammar":"node build/grammarGenerator.js","build-reg-bundle":"webpack --progress --config build/webpack.config.js --env.buildType reg","build-min-bundle":"webpack --progress --config build/webpack.config.js --env.buildType min","build-bundles":"npm run build-reg-bundle && npm run build-min-bundle","build-lib":"tsc -p src","build-test":"tsc -p test","copy-dist-files":"cp dist/sip.js dist/sip-$npm_package_version.js && cp dist/sip.min.js dist/sip-$npm_package_version.min.js",build:"npm run generate-grammar && npm run build-lib && npm run build-reg-bundle && npm run build-min-bundle && npm run copy-dist-files",browserTest:"npm run build-test && sleep 2 && open http://0.0.0.0:9876/debug.html & karma start --reporters kjhtml --no-single-run",commandLineTest:"npm run build-test && karma start --reporters mocha --browsers ChromeHeadless --single-run",buildAndTest:"npm run build && npm run commandLineTest",buildAndBrowserTest:"npm run build && npm run browserTest"}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(5);!function(e){function t(e,t){var r=t,i=0,n=0;if(e.substring(r,r+2).match(/(^\r\n)/))return-2;for(;0===i;){if(-1===(n=e.indexOf("\r\n",r)))return n;!e.substring(n+2,n+4).match(/(^\r\n)/)&&e.charAt(n+2).match(/(^\s+)/)?r=n+2:i=n}return i}function r(e,t,r,n){var s,o=t.indexOf(":",r),a=t.substring(r,o).trim(),c=t.substring(o+1,n).trim();switch(a.toLowerCase()){case"via":case"v":e.addHeader("via",c),1===e.getHeaders("via").length?(s=e.parseHeader("Via"))&&(e.via=s,e.viaBranch=s.branch):s=0;break;case"from":case"f":e.setHeader("from",c),(s=e.parseHeader("from"))&&(e.from=s,e.fromTag=s.getParam("tag"));break;case"to":case"t":e.setHeader("to",c),(s=e.parseHeader("to"))&&(e.to=s,e.toTag=s.getParam("tag"));break;case"record-route":if(-1===(s=i.Grammar.parse(c,"Record_Route"))){s=void 0;break}for(var u in s)s[u]&&(e.addHeader("record-route",c.substring(s[u].position,s[u].offset)),e.headers["Record-Route"][e.getHeaders("record-route").length-1].parsed=s[u].parsed);break;case"call-id":case"i":e.setHeader("call-id",c),(s=e.parseHeader("call-id"))&&(e.callId=c);break;case"contact":case"m":if(-1===(s=i.Grammar.parse(c,"Contact"))){s=void 0;break}if(!(s instanceof Array)){s=void 0;break}s.forEach(function(t){e.addHeader("contact",c.substring(t.position,t.offset)),e.headers.Contact[e.getHeaders("contact").length-1].parsed=t.parsed});break;case"content-length":case"l":e.setHeader("content-length",c),s=e.parseHeader("content-length");break;case"content-type":case"c":e.setHeader("content-type",c),s=e.parseHeader("content-type");break;case"cseq":e.setHeader("cseq",c),(s=e.parseHeader("cseq"))&&(e.cseq=s.value),e instanceof i.IncomingResponseMessage&&(e.method=s.method);break;case"max-forwards":e.setHeader("max-forwards",c),s=e.parseHeader("max-forwards");break;case"www-authenticate":e.setHeader("www-authenticate",c),s=e.parseHeader("www-authenticate");break;case"proxy-authenticate":e.setHeader("proxy-authenticate",c),s=e.parseHeader("proxy-authenticate");break;case"refer-to":case"r":e.setHeader("refer-to",c),(s=e.parseHeader("refer-to"))&&(e.referTo=s);break;default:e.setHeader(a,c),s=0}return void 0!==s||{error:"error parsing header '"+a+"'"}}e.getHeader=t,e.parseHeader=r,e.parseMessage=function(e,n){var s=0,o=e.indexOf("\r\n");if(-1!==o){var a,c=e.substring(0,o),u=i.Grammar.parse(c,"Request_Response");if(-1!==u){var d;for(u.status_code?((a=new i.IncomingResponseMessage).statusCode=u.status_code,a.reasonPhrase=u.reason_phrase):((a=new i.IncomingRequestMessage).method=u.method,a.ruri=u.uri),a.data=e,s=o+2;;){if(-2===(o=t(e,s))){d=s+2;break}if(-1===o)return void n.error("malformed message");if(!0!==r(a,e,s,o))return void n.error(u.error);s=o+2}return a.hasHeader("content-length")?a.body=e.substr(d,Number(a.getHeader("content-length"))):a.body=e.substring(d),a}n.warn('error parsing first line of SIP message: "'+c+'"')}else n.warn("no CRLF found, not a SIP message, discarded")}}(t.Parser||(t.Parser={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(12),s=r(7),o=r(5),a=r(4),c=r(10),u=r(8),d=function(e){function t(t,r,i,n){void 0===n&&(n={});var o=this;if(n.extraHeaders=(n.extraHeaders||[]).slice(),n.contentType=n.contentType||"text/plain","number"!=typeof n.expires||n.expires%1!=0?n.expires=3600:n.expires=Number(n.expires),"boolean"!=typeof n.unpublishOnClose&&(n.unpublishOnClose=!0),null==r||""===r)throw new c.Exceptions.MethodParameterError("Publish","Target",r);if(void 0===(r=t.normalizeTarget(r)))throw new c.Exceptions.MethodParameterError("Publish","Target",r);if((o=e.call(this,t,s.C.PUBLISH,r,n)||this).type=a.TypeStrings.PublishContext,o.options=n,o.target=r,null==i||""===i)throw new c.Exceptions.MethodParameterError("Publish","Event",i);return o.event=i,o.logger=t.getLogger("sip.publish"),o.pubRequestExpires=o.options.expires,t.on("transportCreated",function(e){e.on("transportError",function(){return o.onTransportError()})}),o}return i.__extends(t,e),t.prototype.publish=function(e){this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.options.body=e,this.pubRequestBody=this.options.body,0===this.pubRequestExpires&&(this.pubRequestExpires=this.options.expires,this.pubRequestEtag=void 0),this.ua.publishers[this.target.toString()+":"+this.event]||(this.ua.publishers[this.target.toString()+":"+this.event]=this),this.sendPublishRequest()},t.prototype.unpublish=function(){this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,this.pubRequestExpires=0,void 0!==this.pubRequestEtag&&this.sendPublishRequest()},t.prototype.close=function(){this.options.unpublishOnClose?this.unpublish():(this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,this.pubRequestExpires=0,this.pubRequestEtag=void 0),this.ua.publishers[this.target.toString()+":"+this.event]&&delete this.ua.publishers[this.target.toString()+":"+this.event]},t.prototype.onRequestTimeout=function(){e.prototype.onRequestTimeout.call(this),this.emit("unpublished",void 0,s.C.causes.REQUEST_TIMEOUT)},t.prototype.onTransportError=function(){e.prototype.onTransportError.call(this),this.emit("unpublished",void 0,s.C.causes.CONNECTION_ERROR)},t.prototype.receiveResponse=function(e){var t=this,r=e.statusCode||0,i=u.Utils.getReasonPhrase(r);switch(!0){case/^1[0-9]{2}$/.test(r.toString()):this.emit("progress",e,i);break;case/^2[0-9]{2}$/.test(r.toString()):if(e.hasHeader("SIP-ETag")?this.pubRequestEtag=e.getHeader("SIP-ETag"):this.logger.warn("SIP-ETag header missing in a 200-class response to PUBLISH"),e.hasHeader("Expires")){var n=Number(e.getHeader("Expires"));"number"==typeof n&&n>=0&&n<=this.pubRequestExpires?this.pubRequestExpires=n:this.logger.warn("Bad Expires header in a 200-class response to PUBLISH")}else this.logger.warn("Expires header missing in a 200-class response to PUBLISH");0!==this.pubRequestExpires?(this.publishRefreshTimer=setTimeout(function(){return t.refreshRequest()},900*this.pubRequestExpires),this.emit("published",e,i)):this.emit("unpublished",e,i);break;case/^412$/.test(r.toString()):void 0!==this.pubRequestEtag&&0!==this.pubRequestExpires?(this.logger.warn("412 response to PUBLISH, recovering"),this.pubRequestEtag=void 0,this.emit("progress",e,i),this.publish(this.options.body)):(this.logger.warn("412 response to PUBLISH, recovery failed"),this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i));break;case/^423$/.test(r.toString()):if(0!==this.pubRequestExpires&&e.hasHeader("Min-Expires")){var s=Number(e.getHeader("Min-Expires"));"number"==typeof s||s>this.pubRequestExpires?(this.logger.warn("423 code in response to PUBLISH, adjusting the Expires value and trying to recover"),this.pubRequestExpires=s,this.emit("progress",e,i),this.publish(this.options.body)):(this.logger.warn("Bad 423 response Min-Expires header received for PUBLISH"),this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i))}else this.logger.warn("423 response to PUBLISH, recovery failed"),this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i);break;default:this.pubRequestExpires=0,this.emit("failed",e,i),this.emit("unpublished",e,i)}0===this.pubRequestExpires&&(this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,this.pubRequestEtag=void 0)},t.prototype.send=function(){var e=this;return this.ua.userAgentCore.publish(this.request,{onAccept:function(t){return e.receiveResponse(t.message)},onProgress:function(t){return e.receiveResponse(t.message)},onRedirect:function(t){return e.receiveResponse(t.message)},onReject:function(t){return e.receiveResponse(t.message)},onTrying:function(t){return e.receiveResponse(t.message)}}),this},t.prototype.refreshRequest=function(){if(this.publishRefreshTimer&&(clearTimeout(this.publishRefreshTimer),this.publishRefreshTimer=void 0),this.pubRequestBody=void 0,void 0===this.pubRequestEtag)throw new c.Exceptions.MethodParameterError("Publish","Body",void 0);if(0===this.pubRequestExpires)throw new c.Exceptions.MethodParameterError("Publish","Expire",this.pubRequestExpires);this.sendPublishRequest()},t.prototype.sendPublishRequest=function(){var e=Object.create(this.options||Object.prototype);e.extraHeaders=(this.options.extraHeaders||[]).slice(),e.extraHeaders.push("Event: "+this.event),e.extraHeaders.push("Expires: "+this.pubRequestExpires),void 0!==this.pubRequestEtag&&e.extraHeaders.push("SIP-If-Match: "+this.pubRequestEtag);var t=this.target instanceof o.URI?this.target:this.ua.normalizeTarget(this.target);if(!t)throw new Error("ruri undefined.");var r,i,n=this.options.params||{};void 0!==this.pubRequestBody&&(r={body:this.pubRequestBody,contentType:this.options.contentType}),r&&(i=u.Utils.fromBodyObj(r)),this.request=this.ua.userAgentCore.makeOutgoingRequestMessage(s.C.PUBLISH,t,n.fromUri?n.fromUri:this.ua.userAgentCore.configuration.aor,n.toUri?n.toUri:this.target,n,e.extraHeaders,i),this.send()},t}(n.ClientContext);t.PublishContext=d},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(12),s=r(7),o=r(5),a=r(4),c=r(10),u=r(8);function d(e){var t={expires:600,extraContactHeaderParams:[],instanceId:void 0,params:{},regId:void 0,registrar:void 0},r={mandatory:{},optional:{expires:function(e){if(u.Utils.isDecimal(e)){var t=Number(e);if(t>=0)return t}},extraContactHeaderParams:function(e){if(e instanceof Array)return e.filter(function(e){return"string"==typeof e})},instanceId:function(e){if("string"==typeof e)return/^uuid:/i.test(e)&&(e=e.substr(5)),-1===o.Grammar.parse(e,"uuid")?void 0:e},params:function(e){if("object"==typeof e)return e},regId:function(e){if(u.Utils.isDecimal(e)){var t=Number(e);if(t>=0)return t}},registrar:function(e){if("string"==typeof e){/^sip:/i.test(e)||(e=s.C.SIP+":"+e);var t=o.Grammar.URIParse(e);return t?t.user?void 0:t:void 0}}}};for(var i in r.mandatory){if(!e.hasOwnProperty(i))throw new c.Exceptions.ConfigurationError(i);var n=e[i];if(void 0===(a=r.mandatory[i](n)))throw new c.Exceptions.ConfigurationError(i,n);t[i]=a}for(var i in r.optional)if(e.hasOwnProperty(i)){var a;if((n=e[i])instanceof Array&&0===n.length)continue;if(null===n||""===n||void 0===n||"number"==typeof n&&isNaN(n))continue;if(void 0===(a=r.optional[i](n)))throw new c.Exceptions.ConfigurationError(i,n);t[i]=a}return t}var p=function(e){function t(t,r){void 0===r&&(r={});var i=this,n=d(r);if(n.regId&&!n.instanceId?n.instanceId=u.Utils.newUUID():!n.regId&&n.instanceId&&(n.regId=1),n.params.toUri=n.params.toUri||t.configuration.uri,n.params.toDisplayName=n.params.toDisplayName||t.configuration.displayName,n.params.callId=n.params.callId||u.Utils.createRandomToken(22),n.params.cseq=n.params.cseq||Math.floor(1e4*Math.random()),!n.registrar){var o={};"object"==typeof t.configuration.uri?(o=t.configuration.uri.clone()).user=void 0:o=t.configuration.uri,n.registrar=o}for(var c in(i=e.call(this,t,s.C.REGISTER,n.registrar,n)||this).type=a.TypeStrings.RegisterContext,i.options=n,i.logger=t.getLogger("sip.registercontext"),i.logger.log("configuration parameters for RegisterContext after validation:"),n)n.hasOwnProperty(c)&&i.logger.log("\xb7 "+c+": "+JSON.stringify(n[c]));return i.expires=n.expires,i.contact=t.contact.toString(),i.registered=!1,t.on("transportCreated",function(e){e.on("disconnected",function(){return i.onTransportDisconnected()})}),i}return i.__extends(t,e),t.prototype.register=function(e){var t=this;void 0===e&&(e={}),this.options=i.__assign({},this.options,e);var r=(this.options.extraHeaders||[]).slice();r.push("Contact: "+this.generateContactHeader(this.expires)),r.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),this.closeHeaders=this.options.closeWithHeaders?(this.options.extraHeaders||[]).slice():[],this.receiveResponse=function(e){if(e.cseq===t.request.cseq){void 0!==t.registrationTimer&&(clearTimeout(t.registrationTimer),t.registrationTimer=void 0);var r=(e.statusCode||0).toString();switch(!0){case/^1[0-9]{2}$/.test(r):t.emit("progress",e);break;case/^2[0-9]{2}$/.test(r):t.emit("accepted",e);var i=void 0;e.hasHeader("expires")&&(i=Number(e.getHeader("expires"))),void 0!==t.registrationExpiredTimer&&(clearTimeout(t.registrationExpiredTimer),t.registrationExpiredTimer=void 0);var n=e.getHeaders("contact").length;if(!n){t.logger.warn("no Contact header in response to REGISTER, response ignored");break}for(var a=void 0;n--;){if((a=e.parseHeader("contact",n)).uri.user===t.ua.contact.uri.user){i=a.getParam("expires");break}a=void 0}if(!a){t.logger.warn("no Contact header pointing to us, response ignored");break}void 0===i&&(i=t.expires),t.registrationTimer=setTimeout(function(){t.registrationTimer=void 0,t.register(t.options)},1e3*i-3e3),t.registrationExpiredTimer=setTimeout(function(){t.logger.warn("registration expired"),t.registered&&t.unregistered(void 0,s.C.causes.EXPIRES)},1e3*i),a.hasParam("temp-gruu")&&(t.ua.contact.tempGruu=o.Grammar.URIParse(a.getParam("temp-gruu").replace(/"/g,""))),a.hasParam("pub-gruu")&&(t.ua.contact.pubGruu=o.Grammar.URIParse(a.getParam("pub-gruu").replace(/"/g,""))),t.registered=!0,t.emit("registered",e||void 0);break;case/^423$/.test(r):e.hasHeader("min-expires")?(t.expires=Number(e.getHeader("min-expires")),t.register(t.options)):(t.logger.warn("423 response received for REGISTER without Min-Expires"),t.registrationFailure(e,s.C.causes.SIP_FAILURE_CODE));break;default:t.registrationFailure(e,u.Utils.sipErrorCause(e.statusCode||0))}}},this.onRequestTimeout=function(){t.registrationFailure(void 0,s.C.causes.REQUEST_TIMEOUT)},this.onTransportError=function(){t.registrationFailure(void 0,s.C.causes.CONNECTION_ERROR)},this.request.cseq++,this.request.setHeader("cseq",this.request.cseq+" REGISTER"),this.request.extraHeaders=r,this.send()},t.prototype.close=function(){var e={all:!1,extraHeaders:this.closeHeaders};this.registeredBefore=this.registered,this.registered&&this.unregister(e)},t.prototype.unregister=function(e){var t=this;void 0===e&&(e={}),this.registered||e.all||this.logger.warn("Already unregistered, but sending an unregister anyways.");var r=(e.extraHeaders||[]).slice();this.registered=!1,void 0!==this.registrationTimer&&(clearTimeout(this.registrationTimer),this.registrationTimer=void 0),e.all?(r.push("Contact: *"),r.push("Expires: 0")):r.push("Contact: "+this.generateContactHeader(0)),this.receiveResponse=function(e){var r=e&&e.statusCode?e.statusCode.toString():"";switch(!0){case/^1[0-9]{2}$/.test(r):t.emit("progress",e);break;case/^2[0-9]{2}$/.test(r):t.emit("accepted",e),void 0!==t.registrationExpiredTimer&&(clearTimeout(t.registrationExpiredTimer),t.registrationExpiredTimer=void 0),t.unregistered(e);break;default:t.unregistered(e,u.Utils.sipErrorCause(e.statusCode||0))}},this.onRequestTimeout=function(){},this.request.cseq++,this.request.setHeader("cseq",this.request.cseq+" REGISTER"),this.request.extraHeaders=r,this.send()},t.prototype.unregistered=function(e,t){this.registered=!1,this.emit("unregistered",e||void 0,t||void 0)},t.prototype.send=function(){var e=this;return this.ua.userAgentCore.register(this.request,{onAccept:function(t){return e.receiveResponse(t.message)},onProgress:function(t){return e.receiveResponse(t.message)},onRedirect:function(t){return e.receiveResponse(t.message)},onReject:function(t){return e.receiveResponse(t.message)},onTrying:function(t){return e.receiveResponse(t.message)}}),this},t.prototype.registrationFailure=function(e,t){this.emit("failed",e||void 0,t||void 0)},t.prototype.onTransportDisconnected=function(){this.registeredBefore=this.registered,void 0!==this.registrationTimer&&(clearTimeout(this.registrationTimer),this.registrationTimer=void 0),void 0!==this.registrationExpiredTimer&&(clearTimeout(this.registrationExpiredTimer),this.registrationExpiredTimer=void 0),this.registered&&this.unregistered(void 0,s.C.causes.CONNECTION_ERROR)},t.prototype.generateContactHeader=function(e){void 0===e&&(e=0);var t=this.contact;return this.options.regId&&this.options.instanceId&&(t+=";reg-id="+this.options.regId,t+=';+sip.instance=""'),this.options.extraContactHeaderParams&&this.options.extraContactHeaderParams.forEach(function(e){t+=";"+e}),t+=";expires="+e},t}(n.ClientContext);t.RegisterContext=p},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(12),o=r(7),a=r(5),c=r(4),u=r(10),d=r(34),p=r(17),h=r(96),l=r(8),g=function(e){function t(r){var i=e.call(this)||this;if(i.data={},i.type=c.TypeStrings.Session,!r)throw new u.Exceptions.SessionDescriptionHandlerError("A session description handler is required for the session to function");return i.status=t.C.STATUS_NULL,i.pendingReinvite=!1,i.sessionDescriptionHandlerFactory=r,i.hasOffer=!1,i.hasAnswer=!1,i.timers={ackTimer:void 0,expiresTimer:void 0,invite2xxTimer:void 0,userNoAnswerTimer:void 0,rel1xxTimer:void 0,prackTimer:void 0},i.startTime=void 0,i.endTime=void 0,i.tones=void 0,i.localHold=!1,i.earlySdp=void 0,i.rel100=o.C.supported.UNSUPPORTED,i}return i.__extends(t,e),t.prototype.dtmf=function(e,t){var r=this;if(void 0===t&&(t={}),this.status!==c.SessionStatus.STATUS_CONFIRMED&&this.status!==c.SessionStatus.STATUS_WAITING_FOR_ACK)throw new u.Exceptions.InvalidStateError(this.status);if(!e||!e.toString().match(/^[0-9A-D#*,]+$/i))throw new TypeError("Invalid tones: "+e);var i=function(){if(r.status!==c.SessionStatus.STATUS_TERMINATED&&r.tones&&0!==r.tones.length){var e,n=r.tones.shift();","===n.tone?e=2e3:(n.on("failed",function(){r.tones=void 0}),n.send(t),e=n.duration+n.interToneGap),setTimeout(i,e)}else r.tones=void 0};e=e.toString();var n=this.ua.configuration.dtmfType;this.sessionDescriptionHandler&&n===o.C.dtmfType.RTP&&(this.sessionDescriptionHandler.sendDtmf(e,t)||(this.logger.warn("Attempt to use dtmfType 'RTP' has failed, falling back to INFO packet method"),n=o.C.dtmfType.INFO));if(n===o.C.dtmfType.INFO){for(var s=[],a=e.split("");a.length>0;)s.push(new h.DTMF(this,a.shift(),t));if(this.tones)return this.tones=this.tones.concat(s),this;this.tones=s,i()}return this},t.prototype.bye=function(e){if(void 0===e&&(e={}),this.status===c.SessionStatus.STATUS_TERMINATED)return this.logger.error("Error: Attempted to send BYE in a terminated session."),this;this.logger.log("terminating Session");var t=e.statusCode;if(t&&(t<200||t>=700))throw new TypeError("Invalid statusCode: "+t);return e.receiveResponse=function(){},this.sendRequest(o.C.BYE,e).terminated()},t.prototype.refer=function(e,t){if(void 0===t&&(t={}),this.status!==c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);return this.referContext=new d.ReferClientContext(this.ua,this,e,t),this.emit("referRequested",this.referContext),this.referContext.refer(t),this.referContext},t.prototype.sendRequest=function(e,t){if(void 0===t&&(t={}),!this.session)throw new Error("Session undefined.");var r;t.body&&(t.body=l.Utils.fromBodyObj(t.body));var i,n=t.receiveResponse;n&&(r={onAccept:function(e){return n(e.message)},onProgress:function(e){return n(e.message)},onRedirect:function(e){return n(e.message)},onReject:function(e){return n(e.message)},onTrying:function(e){return n(e.message)}});var s=t;switch(e){case o.C.BYE:i=this.session.bye(r,s);break;case o.C.INVITE:i=this.session.invite(r,s);break;case o.C.REFER:i=this.session.refer(r,s);break;default:throw new Error("Unexpected "+e+". Method not implemented by user agent core.")}return this.emit(e.toLowerCase(),i.message),this},t.prototype.close=function(){if(this.status===c.SessionStatus.STATUS_TERMINATED)return this;for(var e in this.logger.log("closing INVITE session "+this.id),this.sessionDescriptionHandler&&this.sessionDescriptionHandler.close(),this.timers)this.timers[e]&&clearTimeout(this.timers[e]);return this.status=c.SessionStatus.STATUS_TERMINATED,this.ua.transport&&this.ua.transport.removeListener("transportError",this.errorListener),delete this.ua.sessions[this.id],this},t.prototype.hold=function(e,t){if(void 0===e&&(e={}),void 0===t&&(t=[]),this.status!==c.SessionStatus.STATUS_WAITING_FOR_ACK&&this.status!==c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);this.localHold?this.logger.log("Session is already on hold, cannot put it on hold again"):(e.modifiers=t,this.sessionDescriptionHandler&&e.modifiers.push(this.sessionDescriptionHandler.holdModifier),this.localHold=!0,this.sendReinvite(e))},t.prototype.unhold=function(e,t){if(void 0===e&&(e={}),void 0===t&&(t=[]),this.status!==c.SessionStatus.STATUS_WAITING_FOR_ACK&&this.status!==c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);this.localHold?(e.modifiers=t,this.localHold=!1,this.sendReinvite(e)):this.logger.log("Session is not on hold, cannot unhold it")},t.prototype.reinvite=function(e,t){return void 0===e&&(e={}),void 0===t&&(t=[]),e.modifiers=t,this.sendReinvite(e)},t.prototype.terminate=function(e){return this},t.prototype.onTransportError=function(){this.status!==c.SessionStatus.STATUS_CONFIRMED&&this.status!==c.SessionStatus.STATUS_TERMINATED&&this.failed(void 0,o.C.causes.CONNECTION_ERROR)},t.prototype.onRequestTimeout=function(){this.status===c.SessionStatus.STATUS_CONFIRMED?this.terminated(void 0,o.C.causes.REQUEST_TIMEOUT):this.status!==c.SessionStatus.STATUS_TERMINATED&&(this.failed(void 0,o.C.causes.REQUEST_TIMEOUT),this.terminated(void 0,o.C.causes.REQUEST_TIMEOUT))},t.prototype.onDialogError=function(e){this.status===c.SessionStatus.STATUS_CONFIRMED?this.terminated(e,o.C.causes.DIALOG_ERROR):this.status!==c.SessionStatus.STATUS_TERMINATED&&(this.failed(e,o.C.causes.DIALOG_ERROR),this.terminated(e,o.C.causes.DIALOG_ERROR))},t.prototype.on=function(t,r){return e.prototype.on.call(this,t,r)},t.prototype.onAck=function(e){var t=this,r=function(){clearTimeout(t.timers.ackTimer),clearTimeout(t.timers.invite2xxTimer),t.status=c.SessionStatus.STATUS_CONFIRMED;var r=e.message.getHeader("Content-Disposition");r&&"render"===r.type&&(t.renderbody=e.message.body,t.rendertype=e.message.getHeader("Content-Type")),t.emit("confirmed",e.message)};this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK&&(this.sessionDescriptionHandler&&this.sessionDescriptionHandler.hasDescription(e.message.getHeader("Content-Type")||"")?(this.hasAnswer=!0,this.sessionDescriptionHandler.setDescription(e.message.body,this.sessionDescriptionHandlerOptions,this.modifiers).catch(function(r){throw t.logger.warn(r),t.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),t.failed(e.message,o.C.causes.BAD_MEDIA_DESCRIPTION),t.terminated(e.message,o.C.causes.BAD_MEDIA_DESCRIPTION),r}).then(function(){return r()})):r())},t.prototype.receiveRequest=function(e){switch(e.message.method){case o.C.BYE:e.accept(),this.status===c.SessionStatus.STATUS_CONFIRMED&&(this.emit("bye",e.message),this.terminated(e.message,o.C.BYE));break;case o.C.INVITE:this.status===c.SessionStatus.STATUS_CONFIRMED&&(this.logger.log("re-INVITE received"),this.receiveReinvite(e));break;case o.C.INFO:if(this.status===c.SessionStatus.STATUS_CONFIRMED||this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK){if(this.onInfo)return this.onInfo(e.message);var t=e.message.getHeader("content-type");if(t)if(t.match(/^application\/dtmf-relay/i)){if(e.message.body){var r=e.message.body.split("\r\n",2);if(2===r.length){var i=void 0,n=void 0,s=/^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/;s.test(r[0])&&(i=r[0].replace(s,"$2"));var a=/^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/;a.test(r[1])&&(n=parseInt(r[1].replace(a,"$2"),10)),i&&n&&new h.DTMF(this,i,{duration:n}).init_incoming(e)}}}else e.reject({statusCode:415,extraHeaders:["Accept: application/dtmf-relay"]})}break;case o.C.REFER:if(this.status===c.SessionStatus.STATUS_CONFIRMED)if(this.logger.log("REFER received"),this.referContext=new d.ReferServerContext(this.ua,e,this.session),this.listeners("referRequested").length)this.emit("referRequested",this.referContext);else{this.logger.log("No referRequested listeners, automatically accepting and following the refer");var u={followRefer:!0};this.passedOptions&&(u.inviteOptions=this.passedOptions),this.referContext.accept(u,this.modifiers)}break;case o.C.NOTIFY:if(this.referContext&&this.referContext.type===c.TypeStrings.ReferClientContext&&e.message.hasHeader("event")&&/^refer(;.*)?$/.test(e.message.getHeader("event")))return void this.referContext.receiveNotify(e);e.accept(),this.emit("notify",e.message)}},t.prototype.receiveReinvite=function(e){var t,r=this;if(this.emit("reinvite",this,e.message),e.message.hasHeader("P-Asserted-Identity")&&(this.assertedIdentity=a.Grammar.nameAddrHeaderParse(e.message.getHeader("P-Asserted-Identity"))),this.sessionDescriptionHandler){if("0"!==e.message.getHeader("Content-Length")||e.message.getHeader("Content-Type")){if(!this.sessionDescriptionHandler.hasDescription(e.message.getHeader("Content-Type")||""))return e.reject({statusCode:415}),void this.emit("reinviteFailed",this);t=this.sessionDescriptionHandler.setDescription(e.message.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(this.sessionDescriptionHandler.getDescription.bind(this.sessionDescriptionHandler,this.sessionDescriptionHandlerOptions,this.modifiers))}else t=this.sessionDescriptionHandler.getDescription(this.sessionDescriptionHandlerOptions,this.modifiers);t.catch(function(t){var i;throw t.type===c.TypeStrings.SessionDescriptionHandlerError?i=500:t.type===c.TypeStrings.RenegotiationError?(r.emit("renegotiationError",t),r.logger.warn(t.toString()),i=488):(r.logger.error(t),i=488),e.reject({statusCode:i}),r.emit("reinviteFailed",r),t}).then(function(t){var i=["Contact: "+r.contact];e.accept({statusCode:200,extraHeaders:i,body:l.Utils.fromBodyObj(t)}),r.status=c.SessionStatus.STATUS_WAITING_FOR_ACK,r.emit("reinviteAccepted",r)})}else this.logger.warn("No SessionDescriptionHandler to reinvite")},t.prototype.sendReinvite=function(e){var t=this;if(void 0===e&&(e={}),this.pendingReinvite)this.logger.warn("Reinvite in progress. Please wait until complete, then try again.");else if(this.sessionDescriptionHandler){this.pendingReinvite=!0,e.modifiers=e.modifiers||[];var r=(e.extraHeaders||[]).slice();r.push("Contact: "+this.contact),r.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),this.sessionDescriptionHandler.getDescription(e.sessionDescriptionHandlerOptions,e.modifiers).then(function(e){if(!t.session)throw new Error("Session undefined.");var i={onAccept:function(e){if(t.status!==c.SessionStatus.STATUS_TERMINATED)if(t.pendingReinvite){if(t.status=c.SessionStatus.STATUS_CONFIRMED,t.emit("ack",e.ack()),t.pendingReinvite=!1,clearTimeout(t.timers.invite2xxTimer),!t.sessionDescriptionHandler||!t.sessionDescriptionHandler.hasDescription(e.message.getHeader("Content-Type")||""))return t.logger.error("2XX response received to re-invite but did not have a description"),t.emit("reinviteFailed",t),void t.emit("renegotiationError",new u.Exceptions.RenegotiationError("2XX response received to re-invite but did not have a description"));t.sessionDescriptionHandler.setDescription(e.message.body,t.sessionDescriptionHandlerOptions,t.modifiers).catch(function(e){throw t.logger.error("Could not set the description in 2XX response"),t.logger.error(e),t.emit("reinviteFailed",t),t.emit("renegotiationError",e),t.sendRequest(o.C.BYE,{extraHeaders:["Reason: "+l.Utils.getReasonHeaderValue(488,"Not Acceptable Here")]}),t.terminated(void 0,o.C.causes.INCOMPATIBLE_SDP),e}).then(function(){t.emit("reinviteAccepted",t)})}else t.logger.error("Received reinvite response, but have no pending reinvite");else t.logger.error("Received reinvite response, but in STATUS_TERMINATED")},onProgress:function(e){},onRedirect:function(e){t.pendingReinvite=!1,t.logger.log("Received a non 1XX or 2XX response to a re-invite"),t.emit("reinviteFailed",t),t.emit("renegotiationError",new u.Exceptions.RenegotiationError("Invalid response to a re-invite"))},onReject:function(e){t.pendingReinvite=!1,t.logger.log("Received a non 1XX or 2XX response to a re-invite"),t.emit("reinviteFailed",t),t.emit("renegotiationError",new u.Exceptions.RenegotiationError("Invalid response to a re-invite"))},onTrying:function(e){}},n={extraHeaders:r,body:l.Utils.fromBodyObj(e)};t.session.invite(i,n)}).catch(function(e){if(e.type===c.TypeStrings.RenegotiationError)throw t.pendingReinvite=!1,t.emit("renegotiationError",e),t.logger.warn("Renegotiation Error"),t.logger.warn(e.toString()),e;throw t.logger.error("sessionDescriptionHandler error"),t.logger.error(e),e})}else this.logger.warn("No SessionDescriptionHandler, can't reinvite..")},t.prototype.failed=function(e,t){return this.status===c.SessionStatus.STATUS_TERMINATED?this:(this.emit("failed",e,t),this)},t.prototype.rejected=function(e,t){return this.emit("rejected",e,t),this},t.prototype.canceled=function(){return this.sessionDescriptionHandler&&this.sessionDescriptionHandler.close(),this.emit("cancel"),this},t.prototype.accepted=function(e,t){return e instanceof String||(t=l.Utils.getReasonPhrase(e&&e.statusCode||0,t)),this.startTime=new Date,this.replacee&&(this.replacee.emit("replaced",this),this.replacee.terminate()),this.emit("accepted",e,t),this},t.prototype.terminated=function(e,t){return this.status===c.SessionStatus.STATUS_TERMINATED?this:(this.endTime=new Date,this.close(),this.emit("terminated",e,t),this)},t.prototype.connecting=function(e){return this.emit("connecting",{request:e}),this},t.C=c.SessionStatus,t}(n.EventEmitter);t.Session=g;var f=function(e){function t(t,r){var i=this;if(!t.configuration.sessionDescriptionHandlerFactory)throw t.logger.warn("Can't build ISC without SDH Factory"),new Error("ISC Constructor Failed");(i=e.call(this,t.configuration.sessionDescriptionHandlerFactory)||this)._canceled=!1,i.rseq=Math.floor(1e4*Math.random()),i.incomingRequest=r;var n=r.message;p.ServerContext.initializer(i,t,r),i.type=c.TypeStrings.InviteServerContext;var s=n.parseHeader("Content-Disposition");s&&"render"===s.type&&(i.renderbody=n.body,i.rendertype=n.getHeader("Content-Type")),i.status=c.SessionStatus.STATUS_INVITE_RECEIVED,i.fromTag=n.fromTag,i.id=n.callId+i.fromTag,i.request=n,i.contact=i.ua.contact.toString(),i.logger=t.getLogger("sip.inviteservercontext",i.id),i.ua.sessions[i.id]=i;var a=function(e,t){n.hasHeader(e)&&n.getHeader(e).toLowerCase().indexOf("100rel")>=0&&(i.rel100=t)};if(a("require",o.C.supported.REQUIRED),a("supported",o.C.supported.SUPPORTED),i.request.toTag=r.toTag,i.status=c.SessionStatus.STATUS_WAITING_FOR_ANSWER,i.timers.userNoAnswerTimer=setTimeout(function(){r.reject({statusCode:408}),i.failed(n,o.C.causes.NO_ANSWER),i.terminated(n,o.C.causes.NO_ANSWER)},i.ua.configuration.noAnswerTimeout||60),n.hasHeader("expires")){var u=1e3*Number(n.getHeader("expires")||0);i.timers.expiresTimer=setTimeout(function(){i.status===c.SessionStatus.STATUS_WAITING_FOR_ANSWER&&(r.reject({statusCode:487}),i.failed(n,o.C.causes.EXPIRES),i.terminated(n,o.C.causes.EXPIRES))},u)}return i.errorListener=i.onTransportError.bind(i),t.transport&&t.transport.on("transportError",i.errorListener),i}return i.__extends(t,e),Object.defineProperty(t.prototype,"autoSendAnInitialProvisionalResponse",{get:function(){return this.rel100!==o.C.supported.REQUIRED},enumerable:!0,configurable:!0}),t.prototype.reply=function(e){return void 0===e&&(e={}),this},t.prototype.reject=function(e){var t=this;if(void 0===e&&(e={}),this.status===c.SessionStatus.STATUS_TERMINATED)throw new u.Exceptions.InvalidStateError(this.status);this.logger.log("rejecting RTCSession");var r=e.statusCode||480,i=l.Utils.getReasonPhrase(r,e.reasonPhrase),n=e.extraHeaders||[];if(r<300||r>699)throw new TypeError("Invalid statusCode: "+r);var s=e.body?a.fromBodyLegacy(e.body):void 0,o=r<400?this.incomingRequest.redirect([],{statusCode:r,reasonPhrase:i,extraHeaders:n,body:s}):this.incomingRequest.reject({statusCode:r,reasonPhrase:i,extraHeaders:n,body:s});return["rejected","failed"].forEach(function(e){t.emit(e,o.message,i)}),this.terminated()},t.prototype.accept=function(e){var t=this;return void 0===e&&(e={}),this._accept(e).then(function(e){var r=e.message,i=e.session;i.delegate={onAck:function(e){return t.onAck(e)},onAckTimeout:function(){return t.onAckTimeout()},onBye:function(e){return t.receiveRequest(e)},onInfo:function(e){return t.receiveRequest(e)},onInvite:function(e){return t.receiveRequest(e)},onNotify:function(e){return t.receiveRequest(e)},onPrack:function(e){return t.receiveRequest(e)},onRefer:function(e){return t.receiveRequest(e)}},t.session=i,t.status=c.SessionStatus.STATUS_WAITING_FOR_ACK,t.accepted(r,l.Utils.getReasonPhrase(200))}).catch(function(e){if(t.onContextError(e),!t._canceled)throw e}),this},t.prototype.progress=function(e){var t=this;void 0===e&&(e={});var r=e.statusCode||180;if(r<100||r>199)throw new TypeError("Invalid statusCode: "+r);if(this.status===c.SessionStatus.STATUS_TERMINATED)return this.logger.warn("Unexpected call for progress while terminated, ignoring"),this;if(this.status===c.SessionStatus.STATUS_ANSWERED)return this.logger.warn("Unexpected call for progress while answered, ignoring"),this;if(this.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK)return this.logger.warn("Unexpected call for progress while answered (waiting for prack), ignoring"),this;if(this.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK)return this.logger.warn("Unexpected call for progress while waiting for prack, ignoring"),this;if(100===e.statusCode){try{this.incomingRequest.trying()}catch(e){if(this.onContextError(e),!this._canceled)throw e}return this}return this.rel100===o.C.supported.REQUIRED||this.rel100===o.C.supported.SUPPORTED&&e.rel100||this.rel100===o.C.supported.SUPPORTED&&this.ua.configuration.rel100===o.C.supported.REQUIRED?(this._reliableProgressWaitForPrack(e).catch(function(e){if(t.onContextError(e),!t._canceled)throw e}),this):(this._progress(e).catch(function(e){if(t.onContextError(e),!t._canceled)throw e}),this)},t.prototype.terminate=function(e){var t=this;if(void 0===e&&(e={}),!this.session)return this.reject(e),this;switch(this.session.sessionState){case a.SessionState.Initial:case a.SessionState.Early:return this.reject(e),this;case a.SessionState.AckWait:return this.session.delegate={onAck:function(){t.sendRequest(o.C.BYE,e)},onAckTimeout:function(){t.sendRequest(o.C.BYE,e)}},this.emit("bye",this.request),this.terminated(),this;case a.SessionState.Confirmed:return this.bye(e),this;case a.SessionState.Terminated:default:return this}},t.prototype.onCancel=function(e){this.status!==c.SessionStatus.STATUS_WAITING_FOR_ANSWER&&this.status!==c.SessionStatus.STATUS_WAITING_FOR_PRACK&&this.status!==c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK&&this.status!==c.SessionStatus.STATUS_EARLY_MEDIA&&this.status!==c.SessionStatus.STATUS_ANSWERED||(this.status=c.SessionStatus.STATUS_CANCELED,this.incomingRequest.reject({statusCode:487}),this.canceled(),this.rejected(e,o.C.causes.CANCELED),this.failed(e,o.C.causes.CANCELED),this.terminated(e,o.C.causes.CANCELED))},t.prototype.receiveRequest=function(t){var r=this;switch(t.message.method){case o.C.PRACK:this.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK||this.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK?this.hasAnswer?(clearTimeout(this.timers.rel1xxTimer),clearTimeout(this.timers.prackTimer),t.accept(),this.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK&&(this.status=c.SessionStatus.STATUS_EARLY_MEDIA,this.accept()),this.status=c.SessionStatus.STATUS_EARLY_MEDIA):(this.sessionDescriptionHandler=this.setupSessionDescriptionHandler(),this.emit("SessionDescriptionHandler-created",this.sessionDescriptionHandler),this.sessionDescriptionHandler.hasDescription(t.message.getHeader("Content-Type")||"")?(this.hasAnswer=!0,this.sessionDescriptionHandler.setDescription(t.message.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){clearTimeout(r.timers.rel1xxTimer),clearTimeout(r.timers.prackTimer),t.accept(),r.status===c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK&&(r.status=c.SessionStatus.STATUS_EARLY_MEDIA,r.accept()),r.status=c.SessionStatus.STATUS_EARLY_MEDIA},function(e){r.logger.warn(e),r.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),r.failed(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION),r.terminated(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION)})):(this.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),this.failed(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION),this.terminated(t.message,o.C.causes.BAD_MEDIA_DESCRIPTION))):this.status===c.SessionStatus.STATUS_EARLY_MEDIA&&t.accept();break;default:e.prototype.receiveRequest.call(this,t)}},t.prototype.setupSessionDescriptionHandler=function(){return this.sessionDescriptionHandler?this.sessionDescriptionHandler:this.sessionDescriptionHandlerFactory(this,this.ua.configuration.sessionDescriptionHandlerFactoryOptions)},t.prototype.generateResponseOfferAnswer=function(e){if(!this.session){var t=a.getBody(this.incomingRequest.message);return t&&"session"===t.contentDisposition?this.setOfferAndGetAnswer(t,e):this.getOffer(e)}switch(this.session.signalingState){case a.SignalingState.Initial:return this.getOffer(e);case a.SignalingState.Stable:case a.SignalingState.HaveLocalOffer:return Promise.resolve(void 0);case a.SignalingState.HaveRemoteOffer:if(!this.session.offer)throw new Error("Session offer undefined");return this.setOfferAndGetAnswer(this.session.offer,e);case a.SignalingState.Closed:default:throw new Error("Invalid signaling state "+this.session.signalingState+".")}},t.prototype.handlePrackOfferAnswer=function(e,t){if(!this.session)throw new Error("Session undefined.");var r=a.getBody(e.message);if(!r||"session"!==r.contentDisposition)return Promise.resolve(void 0);switch(this.session.signalingState){case a.SignalingState.Initial:throw new Error("Invalid signaling state "+this.session.signalingState+".");case a.SignalingState.Stable:return this.setAnswer(r,t).then(function(){});case a.SignalingState.HaveLocalOffer:throw new Error("Invalid signaling state "+this.session.signalingState+".");case a.SignalingState.HaveRemoteOffer:return this.setOfferAndGetAnswer(r,t);case a.SignalingState.Closed:default:throw new Error("Invalid signaling state "+this.session.signalingState+".")}},t.prototype.canceled=function(){return this._canceled=!0,e.prototype.canceled.call(this)},t.prototype.terminated=function(t,r){return this.prackNeverArrived(),e.prototype.terminated.call(this,t,r)},t.prototype._accept=function(e){var t=this;return void 0===e&&(e={}),this.onInfo=e.onInfo,this.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK?(this.status=c.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK,this.waitForArrivalOfPrack().then(function(){t.status=c.SessionStatus.STATUS_ANSWERED,clearTimeout(t.timers.userNoAnswerTimer)}).then(function(){return t.generateResponseOfferAnswer(e)}).then(function(e){return t.incomingRequest.accept({statusCode:200,body:e})})):this.status!==c.SessionStatus.STATUS_WAITING_FOR_ANSWER?Promise.reject(new u.Exceptions.InvalidStateError(this.status)):(this.status=c.SessionStatus.STATUS_ANSWERED,this.status=c.SessionStatus.STATUS_ANSWERED,clearTimeout(this.timers.userNoAnswerTimer),this.generateResponseOfferAnswer(e).then(function(e){return t.incomingRequest.accept({statusCode:200,body:e})}))},t.prototype._progress=function(e){void 0===e&&(e={});var t=e.statusCode||180,r=e.reasonPhrase,i=(e.extraHeaders||[]).slice(),n=e.body?a.fromBodyLegacy(e.body):void 0;try{var s=this.incomingRequest.progress({statusCode:t,reasonPhrase:r,extraHeaders:i,body:n});return this.emit("progress",s.message,r),this.session=s.session,Promise.resolve(s)}catch(e){return Promise.reject(e)}},t.prototype._reliableProgress=function(e){var t=this;void 0===e&&(e={});var r=e.statusCode||183,i=e.reasonPhrase,n=(e.extraHeaders||[]).slice();return n.push("Require: 100rel"),n.push("RSeq: "+Math.floor(1e4*Math.random())),this.generateResponseOfferAnswer(e).then(function(e){return t.incomingRequest.progress({statusCode:r,reasonPhrase:i,extraHeaders:n,body:e})}).then(function(e){return t.emit("progress",e.message,i),t.session=e.session,e})},t.prototype._reliableProgressWaitForPrack=function(e){var t=this;void 0===e&&(e={});var r,i=e.statusCode||183,n=e.reasonPhrase,s=(e.extraHeaders||[]).slice();return s.push("Require: 100rel"),s.push("RSeq: "+this.rseq++),this.status=c.SessionStatus.STATUS_WAITING_FOR_PRACK,new Promise(function(d,p){var h=!0;return t.generateResponseOfferAnswer(e).then(function(e){return r=e,t.incomingRequest.progress({statusCode:i,reasonPhrase:n,extraHeaders:s,body:r})}).then(function(l){var g,f;t.emit("progress",l.message,n),t.session=l.session,l.session.delegate={onPrack:function(r){g=r,clearTimeout(m),clearTimeout(T),h&&(h=!1,t.handlePrackOfferAnswer(g,e).then(function(e){try{f=g.accept({statusCode:200,body:e}),t.status===c.SessionStatus.STATUS_WAITING_FOR_PRACK&&(t.status=c.SessionStatus.STATUS_WAITING_FOR_ANSWER),t.prackArrived(),d({prackRequest:g,prackResponse:f,progressResponse:l})}catch(e){p(e)}}))}};var m=setTimeout(function(){if(h){h=!1,t.logger.warn("No PRACK received, rejecting INVITE."),clearTimeout(T);try{t.incomingRequest.reject({statusCode:504}),t.terminated(void 0,o.C.causes.NO_PRACK),p(new u.Exceptions.TerminatedSessionError)}catch(e){p(e)}}},64*a.Timers.T1),v=function(){try{t.incomingRequest.progress({statusCode:i,reasonPhrase:n,extraHeaders:s,body:r})}catch(e){return h=!1,void p(e)}T=setTimeout(v,S*=2)},S=a.Timers.T1,T=setTimeout(v,S)})})},t.prototype.onAckTimeout=function(){if(this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK){if(this.logger.log("no ACK received for an extended period of time, terminating the call"),!this.session)throw new Error("Session undefined.");this.session.bye(),this.terminated(void 0,o.C.causes.NO_ACK)}},t.prototype.onContextError=function(e){var t=480;e instanceof a.Exception?e instanceof u.Exceptions.SessionDescriptionHandlerError?(this.logger.error(e.message),e.error&&this.logger.error(e.error)):e instanceof u.Exceptions.TerminatedSessionError?this.logger.warn("Incoming session terminated while waiting for PRACK."):e instanceof u.Exceptions.UnsupportedSessionDescriptionContentTypeError?t=415:e instanceof a.Exception&&this.logger.error(e.message):e instanceof Error?this.logger.error(e.message):(this.logger.error("An error occurred in the session description handler."),this.logger.error(e));try{this.incomingRequest.reject({statusCode:t}),this.failed(this.incomingRequest.message,e.message),this.terminated(this.incomingRequest.message,e.message)}catch(e){return}},t.prototype.prackArrived=function(){this.waitingForPrackResolve&&this.waitingForPrackResolve(),this.waitingForPrackPromise=void 0,this.waitingForPrackResolve=void 0,this.waitingForPrackReject=void 0},t.prototype.prackNeverArrived=function(){this.waitingForPrackReject&&this.waitingForPrackReject(new u.Exceptions.TerminatedSessionError),this.waitingForPrackPromise=void 0,this.waitingForPrackResolve=void 0,this.waitingForPrackReject=void 0},t.prototype.waitForArrivalOfPrack=function(){var e=this;if(this.waitingForPrackPromise)throw new Error("Already waiting for PRACK");return this.waitingForPrackPromise=new Promise(function(t,r){e.waitingForPrackResolve=t,e.waitingForPrackReject=r}),this.waitingForPrackPromise},t.prototype.getOffer=function(e){return this.hasOffer=!0,this.getSessionDescriptionHandler().getDescription(e.sessionDescriptionHandlerOptions,e.modifiers).then(function(e){return l.Utils.fromBodyObj(e)})},t.prototype.setAnswer=function(e,t){this.hasAnswer=!0;var r=this.getSessionDescriptionHandler();return r.hasDescription(e.contentType)?r.setDescription(e.content,t.sessionDescriptionHandlerOptions,t.modifiers):Promise.reject(new u.Exceptions.UnsupportedSessionDescriptionContentTypeError)},t.prototype.setOfferAndGetAnswer=function(e,t){this.hasOffer=!0,this.hasAnswer=!0;var r=this.getSessionDescriptionHandler();return r.hasDescription(e.contentType)?r.setDescription(e.content,t.sessionDescriptionHandlerOptions,t.modifiers).then(function(){return r.getDescription(t.sessionDescriptionHandlerOptions,t.modifiers)}).then(function(e){return l.Utils.fromBodyObj(e)}):Promise.reject(new u.Exceptions.UnsupportedSessionDescriptionContentTypeError)},t.prototype.getSessionDescriptionHandler=function(){var e=this.sessionDescriptionHandler=this.setupSessionDescriptionHandler();return this.emit("SessionDescriptionHandler-created",this.sessionDescriptionHandler),e},t}(g);t.InviteServerContext=f;var m=function(e){function t(t,r,i,n){void 0===i&&(i={}),void 0===n&&(n=[]);var a=this;if(!t.configuration.sessionDescriptionHandlerFactory)throw t.logger.warn("Can't build ISC without SDH Factory"),new Error("ICC Constructor Failed");i.params=i.params||{};var d=i.anonymous||!1,p=l.Utils.newTag();i.params.fromTag=p;var h=t.contact.toString({anonymous:d,outbound:d?!t.contact.tempGruu:!t.contact.pubGruu}),g=(i.extraHeaders||[]).slice();if(d&&t.configuration.uri&&(i.params.from_displayName="Anonymous",i.params.from_uri="sip:anonymous@anonymous.invalid",g.push("P-Preferred-Identity: "+t.configuration.uri.toString()),g.push("Privacy: id")),g.push("Contact: "+h),g.push("Allow: "+["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"].toString()),t.configuration.rel100===o.C.supported.REQUIRED&&g.push("Require: 100rel"),t.configuration.replaces===o.C.supported.REQUIRED&&g.push("Require: replaces"),i.extraHeaders=g,a=e.call(this,t.configuration.sessionDescriptionHandlerFactory)||this,s.ClientContext.initializer(a,t,o.C.INVITE,r,i),a.earlyMediaSessionDescriptionHandlers=new Map,a.type=c.TypeStrings.InviteClientContext,a.passedOptions=i,a.sessionDescriptionHandlerOptions=i.sessionDescriptionHandlerOptions||{},a.modifiers=n,a.inviteWithoutSdp=i.inviteWithoutSdp||!1,a.anonymous=i.anonymous||!1,a.renderbody=i.renderbody||void 0,a.rendertype=i.rendertype||"text/plain",a.fromTag=p,a.contact=h,a.status!==c.SessionStatus.STATUS_NULL)throw new u.Exceptions.InvalidStateError(a.status);return a.isCanceled=!1,a.received100=!1,a.method=o.C.INVITE,a.logger=t.getLogger("sip.inviteclientcontext"),t.applicants[a.toString()]=a,a.id=a.request.callId+a.fromTag,a.onInfo=i.onInfo,a.errorListener=a.onTransportError.bind(a),t.transport&&t.transport.on("transportError",a.errorListener),a}return i.__extends(t,e),t.prototype.receiveResponse=function(e){throw new Error("Unimplemented.")},t.prototype.send=function(){return this.sendInvite(),this},t.prototype.invite=function(){var e=this;return this.ua.sessions[this.id]=this,Promise.resolve().then(function(){e.isCanceled||e.status===c.SessionStatus.STATUS_TERMINATED||(e.inviteWithoutSdp?(e.renderbody&&e.rendertype&&(e.request.body={body:e.renderbody,contentType:e.rendertype}),e.status=c.SessionStatus.STATUS_INVITE_SENT,e.send()):(e.sessionDescriptionHandler=e.sessionDescriptionHandlerFactory(e,e.ua.configuration.sessionDescriptionHandlerFactoryOptions||{}),e.emit("SessionDescriptionHandler-created",e.sessionDescriptionHandler),e.sessionDescriptionHandler.getDescription(e.sessionDescriptionHandlerOptions,e.modifiers).then(function(t){e.isCanceled||e.status===c.SessionStatus.STATUS_TERMINATED||(e.hasOffer=!0,e.request.body=t,e.status=c.SessionStatus.STATUS_INVITE_SENT,e.send())},function(t){t.type===c.TypeStrings.SessionDescriptionHandlerError&&(e.logger.log(t.message),t.error&&e.logger.log(t.error)),e.status!==c.SessionStatus.STATUS_TERMINATED&&(e.failed(void 0,o.C.causes.WEBRTC_ERROR),e.terminated(void 0,o.C.causes.WEBRTC_ERROR))})))}),this},t.prototype.cancel=function(e){if(void 0===e&&(e={}),this.status===c.SessionStatus.STATUS_TERMINATED||this.status===c.SessionStatus.STATUS_CONFIRMED)throw new u.Exceptions.InvalidStateError(this.status);if(this.isCanceled)throw new u.Exceptions.InvalidStateError(c.SessionStatus.STATUS_CANCELED);this.isCanceled=!0,this.logger.log("Canceling session");var t=l.Utils.getCancelReason(e.statusCode,e.reasonPhrase);return e.extraHeaders=(e.extraHeaders||[]).slice(),this.outgoingInviteRequest&&(this.logger.warn("Canceling session before it was created"),this.outgoingInviteRequest.cancel(t,e)),this.canceled()},t.prototype.terminate=function(e){return this.status===c.SessionStatus.STATUS_TERMINATED?this:(this.status===c.SessionStatus.STATUS_WAITING_FOR_ACK||this.status===c.SessionStatus.STATUS_CONFIRMED?this.bye(e):this.cancel(e),this)},t.prototype.sendInvite=function(){var e=this;this.outgoingInviteRequest=this.ua.userAgentCore.invite(this.request,{onAccept:function(t){return e.onAccept(t)},onProgress:function(t){return e.onProgress(t)},onRedirect:function(t){return e.onRedirect(t)},onReject:function(t){return e.onReject(t)},onTrying:function(t){return e.onTrying(t)}})},t.prototype.ackAndBye=function(e,t,r,i){if(!this.ua.userAgentCore)throw new Error("Method requires user agent core.");var n=[];r&&n.push("Reason: "+l.Utils.getReasonHeaderValue(r,i));var s=e.ack();this.emit("ack",s.message);var o=t.bye(void 0,{extraHeaders:n});this.emit("bye",o.message)},t.prototype.disposeEarlyMedia=function(){if(!this.earlyMediaSessionDescriptionHandlers)throw new Error("Early media session description handlers undefined.");this.earlyMediaSessionDescriptionHandlers.forEach(function(e){e.close()})},t.prototype.onAccept=function(e){var t=this;if(!this.earlyMediaSessionDescriptionHandlers)throw new Error("Early media session description handlers undefined.");var r=e.message,i=e.session;if(this.session)this.ackAndBye(e,i);else{if(this.isCanceled)return this.ackAndBye(e,i),void this.emit("bye",this.request);switch(r.hasHeader("P-Asserted-Identity")&&(this.assertedIdentity=a.Grammar.nameAddrHeaderParse(r.getHeader("P-Asserted-Identity"))),this.session=i,this.session.delegate={onAck:function(e){return t.onAck(e)},onBye:function(e){return t.receiveRequest(e)},onInfo:function(e){return t.receiveRequest(e)},onInvite:function(e){return t.receiveRequest(e)},onNotify:function(e){return t.receiveRequest(e)},onPrack:function(e){return t.receiveRequest(e)},onRefer:function(e){return t.receiveRequest(e)}},i.signalingState){case a.SignalingState.Initial:case a.SignalingState.HaveLocalOffer:this.ackAndBye(e,i,400,"Missing session description"),this.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION);break;case a.SignalingState.HaveRemoteOffer:var n=this.sessionDescriptionHandlerFactory(this,this.ua.configuration.sessionDescriptionHandlerFactoryOptions||{});if(this.sessionDescriptionHandler=n,this.emit("SessionDescriptionHandler-created",this.sessionDescriptionHandler),!n.hasDescription(r.getHeader("Content-Type")||"")){this.ackAndBye(e,i,400,"Missing session description"),this.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION);break}this.hasOffer=!0,n.setDescription(r.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){return n.getDescription(t.sessionDescriptionHandlerOptions,t.modifiers)}).then(function(i){if(!t.isCanceled&&t.status!==c.SessionStatus.STATUS_TERMINATED){t.status=c.SessionStatus.STATUS_CONFIRMED,t.hasAnswer=!0;var n={contentDisposition:"session",contentType:i.contentType,content:i.body},s=e.ack({body:n});t.emit("ack",s.message),t.accepted(r)}}).catch(function(n){if(n.type!==c.TypeStrings.SessionDescriptionHandlerError)throw n;t.logger.warn("invalid description"),t.logger.warn(n.toString()),t.ackAndBye(e,i,488,"Invalid session description"),t.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION)});break;case a.SignalingState.Stable:var s;if(this.renderbody&&this.rendertype&&(s={body:{contentDisposition:"render",contentType:this.rendertype,content:this.renderbody}}),this.hasOffer&&!this.hasAnswer){if(!this.sessionDescriptionHandler)throw new Error("Session description handler undefined.");var u=i.answer;if(!u)throw new Error("Answer is undefined.");this.sessionDescriptionHandler.setDescription(u.content,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){t.hasAnswer=!0,t.status=c.SessionStatus.STATUS_CONFIRMED;var i=e.ack(s);t.emit("ack",i.message),t.accepted(r)}).catch(function(n){t.logger.error(n),t.ackAndBye(e,i,488,"Not Acceptable Here"),t.failed(r,o.C.causes.BAD_MEDIA_DESCRIPTION)})}else{if(this.sessionDescriptionHandler=this.earlyMediaSessionDescriptionHandlers.get(i.id),!this.sessionDescriptionHandler)throw new Error("Session description handler undefined.");this.earlyMediaSessionDescriptionHandlers.delete(i.id),this.hasOffer=!0,this.hasAnswer=!0,this.status=c.SessionStatus.STATUS_CONFIRMED;var d=e.ack();this.emit("ack",d.message),this.accepted(r)}break;case a.SignalingState.Closed:break;default:throw new Error("Unknown session signaling state.")}this.disposeEarlyMedia()}},t.prototype.onProgress=function(e){var t=this;if(!this.isCanceled){if(!this.outgoingInviteRequest)throw new Error("Outgoing INVITE request undefined.");if(!this.earlyMediaSessionDescriptionHandlers)throw new Error("Early media session description handlers undefined.");var r=e.message,i=e.session;if(this.status=c.SessionStatus.STATUS_1XX_RECEIVED,r.hasHeader("P-Asserted-Identity")&&(this.assertedIdentity=a.Grammar.nameAddrHeaderParse(r.getHeader("P-Asserted-Identity"))),!i)throw new Error("Session undefined.");var n=r.getHeader("require"),s=r.getHeader("rseq"),u=!!(n&&n.includes("100rel")&&s?Number(s):void 0),d=[];if(u&&d.push("RAck: "+r.getHeader("rseq")+" "+r.getHeader("cseq")),i.signalingState===a.SignalingState.Initial)return u&&(this.logger.warn("First reliable provisional response received MUST contain an offer when INVITE does not contain an offer."),e.prack({extraHeaders:d})),void this.emit("progress",r);if(i.signalingState===a.SignalingState.HaveLocalOffer)return u&&e.prack({extraHeaders:d}),void this.emit("progress",r);if(i.signalingState===a.SignalingState.HaveRemoteOffer){if(!u)return void this.logger.warn("Non-reliable provisional response MUST NOT contain an initial offer, discarding response.");var p=this.sessionDescriptionHandlerFactory(this,this.ua.configuration.sessionDescriptionHandlerFactoryOptions||{});return this.emit("SessionDescriptionHandler-created",p),this.earlyMediaSessionDescriptionHandlers.set(i.id,p),void p.setDescription(r.body,this.sessionDescriptionHandlerOptions,this.modifiers).then(function(){return p.getDescription(t.sessionDescriptionHandlerOptions,t.modifiers)}).then(function(i){var n={contentDisposition:"session",contentType:i.contentType,content:i.body};e.prack({extraHeaders:d,body:n}),t.status=c.SessionStatus.STATUS_EARLY_MEDIA,t.emit("progress",r)}).catch(function(e){t.status!==c.SessionStatus.STATUS_TERMINATED&&(t.failed(void 0,o.C.causes.WEBRTC_ERROR),t.terminated(void 0,o.C.causes.WEBRTC_ERROR))})}return i.signalingState===a.SignalingState.Stable?(u&&e.prack({extraHeaders:d}),void this.emit("progress",r)):void 0}},t.prototype.onRedirect=function(e){this.disposeEarlyMedia();var t=e.message,r=t.statusCode,i=l.Utils.sipErrorCause(r||0);this.rejected(t,i),this.failed(t,i),this.terminated(t,i)},t.prototype.onReject=function(e){this.disposeEarlyMedia();var t=e.message,r=t.statusCode,i=l.Utils.sipErrorCause(r||0);this.rejected(t,i),this.failed(t,i),this.terminated(t,i)},t.prototype.onTrying=function(e){this.received100=!0,this.emit("progress",e.message)},t}(g);t.InviteClientContext=m},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(7),o=r(5),a=r(16),c=r(4),u=r(8),d=function(e){function t(t,r,i,n){void 0===n&&(n={});var o=e.call(this)||this;o.data={},o.method=s.C.SUBSCRIBE,o.body=void 0,o.type=c.TypeStrings.Subscription,o.ua=t,o.logger=t.getLogger("sip.subscription"),n.body&&(o.body={body:n.body,contentType:n.contentType?n.contentType:"application/sdp"});var a=t.normalizeTarget(r);if(!a)throw new TypeError("Invalid target: "+r);if(o.uri=a,o.event=i,void 0===n.expires?o.expires=3600:"number"!=typeof n.expires?(t.logger.warn('Option "expires" must be a number. Using default of 3600.'),o.expires=3600):o.expires=n.expires,o.extraHeaders=(n.extraHeaders||[]).slice(),o.context=o.initContext(),o.disposed=!1,o.request=o.context.message,!o.request.from)throw new Error("From undefined.");if(!o.request.to)throw new Error("From undefined.");return o.localIdentity=o.request.from,o.remoteIdentity=o.request.to,o.id=o.request.callId+o.request.from.parameters.tag+o.event,o.ua.subscriptions[o.id]=o,o}return i.__extends(t,e),t.prototype.dispose=function(){this.disposed||(this.retryAfterTimer&&(clearTimeout(this.retryAfterTimer),this.retryAfterTimer=void 0),this.context.dispose(),this.disposed=!0,delete this.ua.subscriptions[this.id])},t.prototype.on=function(t,r){return e.prototype.on.call(this,t,r)},t.prototype.emit=function(t){for(var r=[],i=1;i0?(e.accept(),i.emit("notify",{request:e.message})):e.reject({statusCode:481})},onRefer:function(e){i.logger.log("Received an out of dialog refer"),i.configuration.allowOutOfDialogRefers||e.reject({statusCode:405}),i.logger.log("Allow out of dialog refers is enabled on the UA");var t=new h.ReferServerContext(i,e);i.listeners("outOfDialogReferRequested").length?i.emit("outOfDialogReferRequested",t):(i.logger.log("No outOfDialogReferRequest listeners, automatically accepting and following the out of dialog refer"),t.accept({followRefer:!0}))},onSubscribe:function(e){i.emit("subscribe",e)}};return i.userAgentCore=new a.UserAgentCore(u,d),i.registerContext=new l.RegisterContext(i,e.registerOptions),i.registerContext.on("failed",i.emit.bind(i,"registrationFailed")),i.registerContext.on("registered",i.emit.bind(i,"registered")),i.registerContext.on("unregistered",i.emit.bind(i,"unregistered")),i.configuration.autostart&&i.start(),i}return i.__extends(r,t),r.prototype.register=function(e){return void 0===e&&(e={}),e.register&&(this.configuration.register=!0),this.registerContext.register(e),this},r.prototype.unregister=function(e){var t=this;return this.configuration.register=!1,this.transport&&this.transport.afterConnected(function(){t.registerContext.unregister(e)}),this},r.prototype.isRegistered=function(){return this.registerContext.registered},r.prototype.invite=function(e,t,r){var i=this,n=new f.InviteClientContext(this,e,t,r);return this.transport&&this.transport.afterConnected(function(){n.invite(),i.emit("inviteSent",n)}),n},r.prototype.subscribe=function(e,t,r){var i=new m.Subscription(this,e,t,r);return this.transport&&this.transport.afterConnected(function(){return i.subscribe()}),i},r.prototype.publish=function(e,t,r,i){var n=new p.PublishContext(this,e,t,i);return this.transport&&this.transport.afterConnected(function(){n.publish(r)}),n},r.prototype.message=function(e,t,r){if(void 0===r&&(r={}),void 0===t)throw new TypeError("Not enough arguments");return r.contentType=r.contentType||"text/plain",r.body=t,this.request(o.C.MESSAGE,e,r)},r.prototype.request=function(e,t,r){var i=new s.ClientContext(this,e,t,r);return this.transport&&this.transport.afterConnected(function(){return i.send()}),i},r.prototype.stop=function(){if(this.logger.log("user requested closure..."),this.status===c.UAStatus.STATUS_USER_CLOSED)return this.logger.warn("UA already closed"),this;for(var t in this.logger.log("closing registerContext"),this.registerContext.close(),this.sessions)this.sessions[t]&&(this.logger.log("closing session "+t),this.sessions[t].terminate());for(var r in this.subscriptions)this.subscriptions[r]&&(this.logger.log("unsubscribe "+r),this.subscriptions[r].unsubscribe());for(var i in this.publishers)this.publishers[i]&&(this.logger.log("unpublish "+i),this.publishers[i].close());for(var n in this.applicants)this.applicants[n]&&this.applicants[n].close();return this.status=c.UAStatus.STATUS_USER_CLOSED,this.transport&&this.transport.disconnect(),this.userAgentCore.reset(),"function"==typeof y.removeEventListener&&(e.chrome&&e.chrome.app&&e.chrome.app.runtime||y.removeEventListener("unload",this.environListener)),this},r.prototype.start=function(){var t=this;if(this.logger.log("user requested startup..."),this.status===c.UAStatus.STATUS_INIT){if(this.status=c.UAStatus.STATUS_STARTING,!this.configuration.transportConstructor)throw new a.TransportError("Transport constructor not set");this.transport=new this.configuration.transportConstructor(this.getLogger("sip.transport"),this.configuration.transportOptions),this.setTransportListeners(),this.emit("transportCreated",this.transport),this.transport.connect()}else this.status===c.UAStatus.STATUS_USER_CLOSED?(this.logger.log("resuming"),this.status=c.UAStatus.STATUS_READY,this.transport&&this.transport.connect()):this.status===c.UAStatus.STATUS_STARTING?this.logger.log("UA is in STARTING status, not opening new connection"):this.status===c.UAStatus.STATUS_READY?this.logger.log("UA is in READY status, not resuming"):this.logger.error("Connection is down. Auto-Recovery system is trying to connect");return this.configuration.autostop&&"function"==typeof y.addEventListener&&(e.chrome&&e.chrome.app&&e.chrome.app.runtime||(this.environListener=this.stop,y.addEventListener("unload",function(){return t.environListener()}))),this},r.prototype.normalizeTarget=function(e){return v.Utils.normalizeTarget(e,this.configuration.hostportParams)},r.prototype.getLogger=function(e,t){return this.log.getLogger(e,t)},r.prototype.getLoggerFactory=function(){return this.log},r.prototype.getSupportedResponseOptions=function(){var e=[];(this.contact.pubGruu||this.contact.tempGruu)&&e.push("gruu"),this.configuration.rel100===o.C.supported.SUPPORTED&&e.push("100rel"),this.configuration.replaces===o.C.supported.SUPPORTED&&e.push("replaces"),e.push("outbound"),e=e.concat(this.configuration.extraSupported||[]);var t=this.configuration.hackAllowUnregisteredOptionTags||!1,r={};return e=e.filter(function(e){var i=o.C.OPTION_TAGS[e],n=!r[e];return r[e]=!0,(i||t)&&n})},r.prototype.findSession=function(e){return this.sessions[e.callId+e.fromTag]||this.sessions[e.callId+e.toTag]||void 0},r.prototype.on=function(e,r){return t.prototype.on.call(this,e,r)},r.prototype.onTransportError=function(){this.status!==c.UAStatus.STATUS_USER_CLOSED&&(this.error&&this.error===r.C.NETWORK_ERROR||(this.status=c.UAStatus.STATUS_NOT_READY,this.error=r.C.NETWORK_ERROR))},r.prototype.setTransportListeners=function(){var e=this;this.transport&&(this.transport.on("connected",function(){return e.onTransportConnected()}),this.transport.on("message",function(t){return e.onTransportReceiveMsg(t)}),this.transport.on("transportError",function(){return e.onTransportError()}))},r.prototype.onTransportConnected=function(){var e=this;this.configuration.register&&Promise.resolve().then(function(){return e.registerContext.register()})},r.prototype.onTransportReceiveMsg=function(e){var t=this,r=d.Parser.parseMessage(e,this.getLogger("sip.parser"));if(r)if(this.status===c.UAStatus.STATUS_USER_CLOSED&&r instanceof a.IncomingRequestMessage)this.logger.warn("UA received message when status = USER_CLOSED - aborting");else{var i=function(){for(var e=0,i=["from","to","call_id","cseq","via"];e1)return void this.logger.warn("More than one Via header field present in the response. Dropping.");if(r.via.host!==this.configuration.viaHost||void 0!==r.via.port)return void this.logger.warn("Via sent-by in the response does not match UA Via host value. Dropping.");var s;n=v.Utils.str_utf8_length(r.body);if((s=r.getHeader("content-length"))&&n"}};var h={};for(var n in r)r.hasOwnProperty(n)&&(h[n]=r[n]);for(var n in Object.assign(this.configuration,h),this.logger.log("configuration parameters after validation:"),r)if(r.hasOwnProperty(n))switch(n){case"uri":case"sessionDescriptionHandlerFactory":this.logger.log("\xb7 "+n+": "+r[n]);break;case"password":this.logger.log("\xb7 "+n+": NOT SHOWN");break;case"transportConstructor":this.logger.log("\xb7 "+n+": "+r[n].name);break;default:this.logger.log("\xb7 "+n+": "+JSON.stringify(r[n]))}},r.prototype.getConfigurationCheck=function(){return{mandatory:{},optional:{uri:function(e){/^sip:/i.test(e)||(e=o.C.SIP+":"+e);var t=a.Grammar.URIParse(e);return t&&t.user?t:void 0},transportConstructor:function(e){if(e instanceof Function)return e},transportOptions:function(e){if("object"==typeof e)return e},authorizationUser:function(e){return-1===a.Grammar.parse('"'+e+'"',"quoted_string")?void 0:e},displayName:function(e){return-1===a.Grammar.parse('"'+e+'"',"displayName")?void 0:e},dtmfType:function(e){switch(e){case o.C.dtmfType.RTP:return o.C.dtmfType.RTP;case o.C.dtmfType.INFO:default:return o.C.dtmfType.INFO}},hackViaTcp:function(e){if("boolean"==typeof e)return e},hackIpInContact:function(e){return"boolean"==typeof e?e:"string"==typeof e&&-1!==a.Grammar.parse(e,"host")?e:void 0},hackWssInTransport:function(e){if("boolean"==typeof e)return e},hackAllowUnregisteredOptionTags:function(e){if("boolean"==typeof e)return e},contactTransport:function(e){if("string"==typeof e)return e},extraSupported:function(e){if(e instanceof Array){for(var t=0,r=e;t0)return t}},password:function(e){return String(e)},rel100:function(e){return e===o.C.supported.REQUIRED?o.C.supported.REQUIRED:e===o.C.supported.SUPPORTED?o.C.supported.SUPPORTED:o.C.supported.UNSUPPORTED},replaces:function(e){return e===o.C.supported.REQUIRED?o.C.supported.REQUIRED:e===o.C.supported.SUPPORTED?o.C.supported.SUPPORTED:o.C.supported.UNSUPPORTED},register:function(e){if("boolean"==typeof e)return e},registerOptions:function(e){if("object"==typeof e)return e},userAgentString:function(e){if("string"==typeof e)return e},autostart:function(e){if("boolean"==typeof e)return e},autostop:function(e){if("boolean"==typeof e)return e},sessionDescriptionHandlerFactory:function(e){if(e instanceof Function)return e},sessionDescriptionHandlerFactoryOptions:function(e){if("object"==typeof e)return e},authenticationFactory:this.checkAuthenticationFactory,allowLegacyNotifications:function(e){if("boolean"==typeof e)return e},custom:function(e){if("object"==typeof e)return e},contactName:function(e){if("string"==typeof e)return e},experimentalFeatures:function(e){if("boolean"==typeof e)return e}}}},r.C={STATUS_INIT:0,STATUS_STARTING:1,STATUS_READY:2,STATUS_USER_CLOSED:3,STATUS_NOT_READY:4,CONFIGURATION_ERROR:1,NETWORK_ERROR:2,ALLOWED_METHODS:["ACK","CANCEL","INVITE","MESSAGE","BYE","OPTIONS","INFO","NOTIFY","REFER"],ACCEPTED_BODY_TYPES:["application/sdp","application/dtmf-relay"],MAX_FORWARDS:70,TAG_LENGTH:10},r}(n.EventEmitter);function b(e){if(!(e.configuration.uri instanceof a.URI))throw new Error("Configuration URI not instance of URI.");var t=e.configuration.uri,r=e.contact,i=e.configuration.displayName?e.configuration.displayName:"",n=!!e.configuration.hackViaTcp,s=e.configuration.usePreloadedRoute&&e.transport?[e.transport.server.sipUri]:[],c=e.configuration.sipjsId||v.Utils.createRandomToken(5),u=[];u.push("outbound"),e.configuration.rel100===o.C.supported.SUPPORTED&&u.push("100rel"),e.configuration.replaces===o.C.supported.SUPPORTED&&u.push("replaces"),e.configuration.extraSupported&&u.push.apply(u,e.configuration.extraSupported),e.configuration.hackAllowUnregisteredOptionTags||(u=u.filter(function(e){return o.C.OPTION_TAGS[e]})),u=Array.from(new Set(u));var d=e.getSupportedResponseOptions(),p=e.configuration.userAgentString||"sipjs";if(!e.configuration.viaHost)throw new Error("Configuration via host undefined");var h=!!e.configuration.forceRport,l=e.configuration.viaHost;return{aor:t,contact:r,displayName:i,hackViaTcp:n,loggerFactory:e.getLoggerFactory(),routeSet:s,sipjsId:c,supportedOptionTags:u,supportedOptionTagsResponse:d,userAgentHeaderFieldValue:p,viaForceRport:h,viaHost:l,authenticationFactory:function(){if(e.configuration.authenticationFactory)return e.configuration.authenticationFactory(e)},transportAccessor:function(){return e.transport}}}t.UA=E,function(e){!function(e){e.RTP="rtp",e.INFO="info"}(e.DtmfType||(e.DtmfType={}))}(E=t.UA||(t.UA={})),t.UA=E,t.makeUserAgentCoreConfigurationFromUA=b}).call(this,r(18))},function(e,t,r){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(4),o=r(10),a=r(8),c=i.__importStar(r(35)),u=r(97),d=function(t){function r(r,i,n){var o=t.call(this)||this;o.type=s.TypeStrings.SessionDescriptionHandler,o.options=n||{},o.logger=r,o.observer=i,o.dtmfSender=void 0,o.shouldAcquireMedia=!0,o.CONTENT_TYPE="application/sdp",o.C={DIRECTION:{NULL:null,SENDRECV:"sendrecv",SENDONLY:"sendonly",RECVONLY:"recvonly",INACTIVE:"inactive"}},o.logger.log("SessionDescriptionHandlerOptions: "+JSON.stringify(o.options)),o.direction=o.C.DIRECTION.NULL,o.modifiers=o.options.modifiers||[],Array.isArray(o.modifiers)||(o.modifiers=[o.modifiers]);var a=e.window||e;return o.WebRTC={MediaStream:a.MediaStream,getUserMedia:a.navigator.mediaDevices.getUserMedia.bind(a.navigator.mediaDevices),RTCPeerConnection:a.RTCPeerConnection},o.iceGatheringTimeout=!1,o.initPeerConnection(o.options.peerConnectionOptions),o.constraints=o.checkAndDefaultConstraints(o.options.constraints),o}return i.__extends(r,t),r.defaultFactory=function(e,t){return new r(e.ua.getLogger("sip.invitecontext.sessionDescriptionHandler",e.id),new u.SessionDescriptionHandlerObserver(e,t),t)},r.prototype.close=function(){this.logger.log("closing PeerConnection"),this.peerConnection&&"closed"!==this.peerConnection.signalingState&&(this.peerConnection.getSenders?this.peerConnection.getSenders().forEach(function(e){e.track&&e.track.stop()}):(this.logger.warn("Using getLocalStreams which is deprecated"),this.peerConnection.getLocalStreams().forEach(function(e){e.getTracks().forEach(function(e){e.stop()})})),this.peerConnection.getReceivers?this.peerConnection.getReceivers().forEach(function(e){e.track&&e.track.stop()}):(this.logger.warn("Using getRemoteStreams which is deprecated"),this.peerConnection.getRemoteStreams().forEach(function(e){e.getTracks().forEach(function(e){e.stop()})})),this.resetIceGatheringComplete(),this.peerConnection.close())},r.prototype.getDescription=function(e,t){var r=this;void 0===e&&(e={}),void 0===t&&(t=[]),e.peerConnectionOptions&&this.initPeerConnection(e.peerConnectionOptions);var i=Object.assign({},this.constraints,e.constraints);return i=this.checkAndDefaultConstraints(i),JSON.stringify(i)!==JSON.stringify(this.constraints)&&(this.constraints=i,this.shouldAcquireMedia=!0),Array.isArray(t)||(t=[t]),t=t.concat(this.modifiers),Promise.resolve().then(function(){if(r.shouldAcquireMedia)return r.acquire(r.constraints).then(function(){r.shouldAcquireMedia=!1})}).then(function(){return r.createOfferOrAnswer(e.RTCOfferOptions,t)}).then(function(e){if(void 0===e.sdp)throw new o.Exceptions.SessionDescriptionHandlerError("getDescription",void 0,"SDP undefined");return r.emit("getDescription",e),{body:e.sdp,contentType:r.CONTENT_TYPE}})},r.prototype.hasDescription=function(e){return e===this.CONTENT_TYPE},r.prototype.holdModifier=function(e){return e.sdp?(/a=(sendrecv|sendonly|recvonly|inactive)/.test(e.sdp)?(e.sdp=e.sdp.replace(/a=sendrecv\r\n/g,"a=sendonly\r\n"),e.sdp=e.sdp.replace(/a=recvonly\r\n/g,"a=inactive\r\n")):e.sdp=e.sdp.replace(/(m=[^\r]*\r\n)/g,"$1a=sendonly\r\n"),Promise.resolve(e)):Promise.resolve(e)},r.prototype.setDescription=function(e,t,r){var i=this;void 0===t&&(t={}),void 0===r&&(r=[]),t.peerConnectionOptions&&this.initPeerConnection(t.peerConnectionOptions),Array.isArray(r)||(r=[r]),r=r.concat(this.modifiers);var n={type:this.hasOffer("local")?"answer":"offer",sdp:e};return Promise.resolve().then(function(){if(i.shouldAcquireMedia&&i.options.alwaysAcquireMediaFirst)return i.acquire(i.constraints).then(function(){i.shouldAcquireMedia=!1})}).then(function(){return a.Utils.reducePromises(r,n)}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("setDescription",e,"The modifiers did not resolve successfully");throw i.logger.error(t.message),i.emit("peerConnection-setRemoteDescriptionFailed",t),t}).then(function(e){return i.emit("setDescription",e),i.peerConnection.setRemoteDescription(e)}).catch(function(n){if(n.type===s.TypeStrings.SessionDescriptionHandlerError)throw n;if(/^m=video.+$/gm.test(e)&&!t.disableAudioFallback)return t.disableAudioFallback=!0,i.setDescription(e,t,[c.stripVideo].concat(r));var a=new o.Exceptions.SessionDescriptionHandlerError("setDescription",n);throw a.error&&i.logger.error(a.error),i.emit("peerConnection-setRemoteDescriptionFailed",a),a}).then(function(){i.peerConnection.getReceivers?i.emit("setRemoteDescription",i.peerConnection.getReceivers()):i.emit("setRemoteDescription",i.peerConnection.getRemoteStreams()),i.emit("confirmed",i)})},r.prototype.sendDtmf=function(e,t){if(void 0===t&&(t={}),!this.dtmfSender&&this.hasBrowserGetSenderSupport()){var r=this.peerConnection.getSenders();r.length>0&&(this.dtmfSender=r[0].dtmf)}if(!this.dtmfSender&&this.hasBrowserTrackSupport()){var i=this.peerConnection.getLocalStreams();if(i.length>0){var n=i[0].getAudioTracks();n.length>0&&(this.dtmfSender=this.peerConnection.createDTMFSender(n[0]))}}if(!this.dtmfSender)return!1;try{this.dtmfSender.insertDTMF(e,t.duration,t.interToneGap)}catch(e){if("InvalidStateError"===e.type||"InvalidCharacterError"===e.type)return this.logger.error(e),!1;throw e}return this.logger.log("DTMF sent via RTP: "+e.toString()),!0},r.prototype.getDirection=function(){return this.direction},r.prototype.on=function(e,r){return t.prototype.on.call(this,e,r)},r.prototype.createOfferOrAnswer=function(e,t){var r=this;void 0===e&&(e={}),void 0===t&&(t=[]);var i=this.hasOffer("remote")?"createAnswer":"createOffer",n=this.peerConnection;return this.logger.log(i),(this.hasOffer("remote")?n.createAnswer:n.createOffer).apply(n,e).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer",e,"peerConnection-"+i+"Failed");throw r.emit("peerConnection-"+i+"Failed",t),t}).then(function(e){return a.Utils.reducePromises(t,r.createRTCSessionDescriptionInit(e))}).then(function(e){return r.resetIceGatheringComplete(),r.logger.log("Setting local sdp."),r.logger.log("sdp is "+e.sdp||!1),n.setLocalDescription(e)}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer",e,"peerConnection-SetLocalDescriptionFailed");throw r.emit("peerConnection-SetLocalDescriptionFailed",t),t}).then(function(){return r.waitForIceGatheringComplete()}).then(function(){var e=r.createRTCSessionDescriptionInit(r.peerConnection.localDescription);return a.Utils.reducePromises(t,e)}).then(function(e){return r.setDirection(e.sdp||""),e}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var t=new o.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer",e);throw r.logger.error(t.toString()),t})},r.prototype.createRTCSessionDescriptionInit=function(e){return{type:e.type,sdp:e.sdp}},r.prototype.addDefaultIceCheckingTimeout=function(e){return void 0===e.iceCheckingTimeout&&(e.iceCheckingTimeout=5e3),e},r.prototype.addDefaultIceServers=function(e){return e.iceServers||(e.iceServers=[{urls:"stun:stun.l.google.com:19302"}]),e},r.prototype.checkAndDefaultConstraints=function(e){var t={audio:!0,video:!this.options.alwaysAcquireMediaFirst};return e=e||t,0===Object.keys(e).length&&e.constructor===Object?t:e},r.prototype.hasBrowserTrackSupport=function(){return Boolean(this.peerConnection.addTrack)},r.prototype.hasBrowserGetSenderSupport=function(){return Boolean(this.peerConnection.getSenders)},r.prototype.initPeerConnection=function(e){var t=this;void 0===e&&(e={}),(e=this.addDefaultIceCheckingTimeout(e)).rtcConfiguration=e.rtcConfiguration||{},e.rtcConfiguration=this.addDefaultIceServers(e.rtcConfiguration),this.logger.log("initPeerConnection"),this.peerConnection&&(this.logger.log("Already have a peer connection for this session. Tearing down."),this.resetIceGatheringComplete(),this.peerConnection.close()),this.peerConnection=new this.WebRTC.RTCPeerConnection(e.rtcConfiguration),this.logger.log("New peer connection created"),"ontrack"in this.peerConnection?this.peerConnection.addEventListener("track",function(e){t.logger.log("track added"),t.observer.trackAdded(),t.emit("addTrack",e)}):(this.logger.warn("Using onaddstream which is deprecated"),this.peerConnection.onaddstream=function(e){t.logger.log("stream added"),t.emit("addStream",e)}),this.peerConnection.onicecandidate=function(e){t.emit("iceCandidate",e),e.candidate?t.logger.log("ICE candidate received: "+(null===e.candidate.candidate?null:e.candidate.candidate.trim())):null===e.candidate&&(t.logger.log("ICE candidate gathering complete"),t.triggerIceGatheringComplete())},this.peerConnection.onicegatheringstatechange=function(){switch(t.logger.log("RTCIceGatheringState changed: "+t.peerConnection.iceGatheringState),t.peerConnection.iceGatheringState){case"gathering":t.emit("iceGathering",t),!t.iceGatheringTimer&&e.iceCheckingTimeout&&(t.iceGatheringTimeout=!1,t.iceGatheringTimer=setTimeout(function(){t.logger.log("RTCIceChecking Timeout Triggered after "+e.iceCheckingTimeout+" milliseconds"),t.iceGatheringTimeout=!0,t.triggerIceGatheringComplete()},e.iceCheckingTimeout));break;case"complete":t.triggerIceGatheringComplete()}},this.peerConnection.oniceconnectionstatechange=function(){var e;switch(t.peerConnection.iceConnectionState){case"new":e="iceConnection";break;case"checking":e="iceConnectionChecking";break;case"connected":e="iceConnectionConnected";break;case"completed":e="iceConnectionCompleted";break;case"failed":e="iceConnectionFailed";break;case"disconnected":e="iceConnectionDisconnected";break;case"closed":e="iceConnectionClosed";break;default:return void t.logger.warn("Unknown iceConnection state: "+t.peerConnection.iceConnectionState)}t.logger.log("ICE Connection State changed to "+e),t.emit(e,t)}},r.prototype.acquire=function(e){var t=this;return e=this.checkAndDefaultConstraints(e),new Promise(function(r,i){t.logger.log("acquiring local media"),t.emit("userMediaRequest",e),e.audio||e.video?t.WebRTC.getUserMedia(e).then(function(e){t.observer.trackAdded(),t.emit("userMedia",e),r(e)}).catch(function(e){t.emit("userMediaFailed",e),i(e)}):r([])}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var r=new o.Exceptions.SessionDescriptionHandlerError("acquire",e,"unable to acquire streams");throw t.logger.error(r.message),r.error&&t.logger.error(r.error),r}).then(function(e){t.logger.log("acquired local media streams");try{return t.peerConnection.removeTrack&&t.peerConnection.getSenders().forEach(function(e){t.peerConnection.removeTrack(e)}),e}catch(e){return Promise.reject(e)}}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var r=new o.Exceptions.SessionDescriptionHandlerError("acquire",e,"error removing streams");throw t.logger.error(r.message),r.error&&t.logger.error(r.error),r}).then(function(e){try{(e=[].concat(e)).forEach(function(e){t.peerConnection.addTrack?e.getTracks().forEach(function(r){t.peerConnection.addTrack(r,e)}):t.peerConnection.addStream(e)})}catch(e){return Promise.reject(e)}return Promise.resolve()}).catch(function(e){if(e.type===s.TypeStrings.SessionDescriptionHandlerError)throw e;var r=new o.Exceptions.SessionDescriptionHandlerError("acquire",e,"error adding stream");throw t.logger.error(r.message),r.error&&t.logger.error(r.error),r})},r.prototype.hasOffer=function(e){var t="have-"+e+"-offer";return this.peerConnection.signalingState===t},r.prototype.isIceGatheringComplete=function(){return"complete"===this.peerConnection.iceGatheringState||this.iceGatheringTimeout},r.prototype.resetIceGatheringComplete=function(){this.iceGatheringTimeout=!1,this.logger.log("resetIceGatheringComplete"),this.iceGatheringTimer&&(clearTimeout(this.iceGatheringTimer),this.iceGatheringTimer=void 0),this.iceGatheringDeferred&&(this.iceGatheringDeferred.reject(),this.iceGatheringDeferred=void 0)},r.prototype.setDirection=function(e){var t=e.match(/a=(sendrecv|sendonly|recvonly|inactive)/);if(null===t)return this.direction=this.C.DIRECTION.NULL,void this.observer.directionChanged();var r=t[1];switch(r){case this.C.DIRECTION.SENDRECV:case this.C.DIRECTION.SENDONLY:case this.C.DIRECTION.RECVONLY:case this.C.DIRECTION.INACTIVE:this.direction=r;break;default:this.direction=this.C.DIRECTION.NULL}this.observer.directionChanged()},r.prototype.triggerIceGatheringComplete=function(){this.isIceGatheringComplete()&&(this.emit("iceGatheringComplete",this),this.iceGatheringTimer&&(clearTimeout(this.iceGatheringTimer),this.iceGatheringTimer=void 0),this.iceGatheringDeferred&&(this.iceGatheringDeferred.resolve(),this.iceGatheringDeferred=void 0))},r.prototype.waitForIceGatheringComplete=function(){return this.logger.log("waitForIceGatheringComplete"),this.isIceGatheringComplete()?(this.logger.log("ICE is already complete. Return resolved."),Promise.resolve()):(this.iceGatheringDeferred||(this.iceGatheringDeferred=a.Utils.defer()),this.logger.log("ICE is not complete. Returning promise"),this.iceGatheringDeferred?this.iceGatheringDeferred.promise:Promise.resolve())},r}(n.EventEmitter);t.SessionDescriptionHandler=d}).call(this,r(18))},function(e,t,r){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var i,n=r(0),s=r(5),o=r(4),a=r(10),c=r(8);!function(e){e[e.STATUS_CONNECTING=0]="STATUS_CONNECTING",e[e.STATUS_OPEN=1]="STATUS_OPEN",e[e.STATUS_CLOSING=2]="STATUS_CLOSING",e[e.STATUS_CLOSED=3]="STATUS_CLOSED"}(i=t.TransportStatus||(t.TransportStatus={}));var u=function(t){function r(r,n){void 0===n&&(n={});var s=t.call(this,r,n)||this;return s.WebSocket=(e.window||e).WebSocket,s.type=o.TypeStrings.Transport,s.reconnectionAttempts=0,s.status=i.STATUS_CONNECTING,s.configuration=s.loadConfig(n),s.server=s.configuration.wsServers[0],s}return n.__extends(r,t),r.prototype.isConnected=function(){return this.status===i.STATUS_OPEN},r.prototype.sendPromise=function(e,t){if(void 0===t&&(t={}),!this.statusAssert(i.STATUS_OPEN,t.force))return this.onError("unable to send message - WebSocket not open"),Promise.reject();var r=e.toString();return this.ws?(!0===this.configuration.traceSip&&this.logger.log("sending WebSocket message:\n\n"+r+"\n"),this.ws.send(r),Promise.resolve({msg:r})):(this.onError("unable to send message - WebSocket does not exist"),Promise.reject())},r.prototype.disconnectPromise=function(e){var t=this;return void 0===e&&(e={}),this.disconnectionPromise?this.disconnectionPromise:(e.code=e.code||1e3,this.statusTransition(i.STATUS_CLOSING,e.force)?(this.emit("disconnecting"),this.disconnectionPromise=new Promise(function(r,i){t.disconnectDeferredResolve=r,t.reconnectTimer&&(clearTimeout(t.reconnectTimer),t.reconnectTimer=void 0),t.ws?(t.stopSendingKeepAlives(),t.logger.log("closing WebSocket "+t.server.wsUri),t.ws.close(e.code,e.reason)):i("Attempted to disconnect but the websocket doesn't exist")}),this.disconnectionPromise):this.status===i.STATUS_CLOSED?Promise.resolve({overrideEvent:!0}):this.connectionPromise?this.connectionPromise.then(function(){return Promise.reject("The websocket did not disconnect")}).catch(function(){return Promise.resolve({overrideEvent:!0})}):Promise.reject("The websocket did not disconnect"))},r.prototype.connectPromise=function(e){var t=this;return void 0===e&&(e={}),this.status!==i.STATUS_CLOSING||e.force?this.connectionPromise?this.connectionPromise:(this.server=this.server||this.getNextWsServer(e.force),this.connectionPromise=new Promise(function(r,n){if((t.status===i.STATUS_OPEN||t.status===i.STATUS_CLOSING)&&!e.force)return t.logger.warn("WebSocket "+t.server.wsUri+" is already connected"),void n("Failed status check - attempted to open a connection but already open/closing");t.connectDeferredResolve=r,t.status=i.STATUS_CONNECTING,t.emit("connecting"),t.logger.log("connecting to WebSocket "+t.server.wsUri),t.disposeWs();try{t.ws=new WebSocket(t.server.wsUri,"sip")}catch(e){return t.ws=null,t.statusTransition(i.STATUS_CLOSED,!0),t.onError("error connecting to WebSocket "+t.server.wsUri+":"+e),void n("Failed to create a websocket")}t.ws?(t.connectionTimeout=setTimeout(function(){t.statusTransition(i.STATUS_CLOSED),t.logger.warn("took too long to connect - exceeded time set in configuration.connectionTimeout: "+t.configuration.connectionTimeout+"s"),t.emit("disconnected",{code:1e3}),t.connectionPromise=void 0,n("Connection timeout")},1e3*t.configuration.connectionTimeout),t.boundOnOpen=t.onOpen.bind(t),t.boundOnMessage=t.onMessage.bind(t),t.boundOnClose=t.onClose.bind(t),t.boundOnError=t.onWebsocketError.bind(t),t.ws.addEventListener("open",t.boundOnOpen),t.ws.addEventListener("message",t.boundOnMessage),t.ws.addEventListener("close",t.boundOnClose),t.ws.addEventListener("error",t.boundOnError)):n("Unexpected instance websocket not set")}),this.connectionPromise):Promise.reject("WebSocket "+this.server.wsUri+" is closing")},r.prototype.onMessage=function(e){var t,r=e.data;if(/^(\r\n)+$/.test(r))return this.clearKeepAliveTimeout(),void(!0===this.configuration.traceSip&&this.logger.log("received WebSocket message with CRLF Keep Alive response"));if(r){if("string"!=typeof r){try{t=String.fromCharCode.apply(null,new Uint8Array(r))}catch(e){return void this.logger.warn("received WebSocket binary message failed to be converted into string, message discarded")}!0===this.configuration.traceSip&&this.logger.log("received WebSocket binary message:\n\n"+r+"\n")}else!0===this.configuration.traceSip&&this.logger.log("received WebSocket text message:\n\n"+r+"\n"),t=r;this.emit("message",t)}else this.logger.warn("received empty message, message discarded")},r.prototype.onOpen=function(){if(this.status===i.STATUS_CLOSED){var e=this.ws;return this.disposeWs(),void e.close(1e3)}this.statusTransition(i.STATUS_OPEN,!0),this.emit("connected"),this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0),this.logger.log("WebSocket "+this.server.wsUri+" connected"),void 0!==this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0),this.reconnectionAttempts=0,this.disconnectionPromise=void 0,this.disconnectDeferredResolve=void 0,this.startSendingKeepAlives(),this.connectDeferredResolve?this.connectDeferredResolve({overrideEvent:!0}):this.logger.warn("Unexpected websocket.onOpen with no connectDeferredResolve")},r.prototype.onClose=function(e){if(this.logger.log("WebSocket disconnected (code: "+e.code+(e.reason?"| reason: "+e.reason:"")+")"),this.status!==i.STATUS_CLOSING&&(this.logger.warn("WebSocket closed without SIP.js requesting it"),this.emit("transportError")),this.stopSendingKeepAlives(),this.connectionTimeout&&clearTimeout(this.connectionTimeout),this.connectionTimeout=void 0,this.connectionPromise=void 0,this.connectDeferredResolve=void 0,this.disconnectDeferredResolve)return this.disconnectDeferredResolve({overrideEvent:!0}),this.statusTransition(i.STATUS_CLOSED),void(this.disconnectDeferredResolve=void 0);this.statusTransition(i.STATUS_CLOSED,!0),this.emit("disconnected",{code:e.code,reason:e.reason}),this.reconnect()},r.prototype.disposeWs=function(){this.ws&&(this.ws.removeEventListener("open",this.boundOnOpen),this.ws.removeEventListener("message",this.boundOnMessage),this.ws.removeEventListener("close",this.boundOnClose),this.ws.removeEventListener("error",this.boundOnError),this.ws=void 0)},r.prototype.onError=function(e){this.logger.warn("Transport error: "+e),this.emit("transportError")},r.prototype.onWebsocketError=function(){this.onError("The Websocket had an error")},r.prototype.reconnect=function(){var e=this;if(this.reconnectionAttempts>0&&this.logger.log("Reconnection attempt "+this.reconnectionAttempts+" failed"),this.noAvailableServers())return this.logger.warn("attempted to get next ws server but there are no available ws servers left"),this.logger.warn("no available ws servers left - going to closed state"),this.statusTransition(i.STATUS_CLOSED,!0),this.emit("closed"),void this.resetServerErrorStatus();this.isConnected()&&(this.logger.warn("attempted to reconnect while connected - forcing disconnect"),this.disconnect({force:!0})),this.reconnectionAttempts+=1,this.reconnectionAttempts>this.configuration.maxReconnectionAttempts?(this.logger.warn("maximum reconnection attempts for WebSocket "+this.server.wsUri),this.logger.log("transport "+this.server.wsUri+" failed | connection state set to 'error'"),this.server.isError=!0,this.emit("transportError"),this.noAvailableServers()||(this.server=this.getNextWsServer()),this.reconnectionAttempts=0,this.reconnect()):(this.logger.log("trying to reconnect to WebSocket "+this.server.wsUri+" (reconnection attempt "+this.reconnectionAttempts+")"),this.reconnectTimer=setTimeout(function(){e.connect(),e.reconnectTimer=void 0},1===this.reconnectionAttempts?0:1e3*this.configuration.reconnectionTimeout))},r.prototype.resetServerErrorStatus=function(){for(var e=0,t=this.configuration.wsServers;et[0].weight?t=[n]:n.weight===t[0].weight&&t.push(n))}return t[Math.floor(Math.random()*t.length)]},r.prototype.noAvailableServers=function(){for(var e=0,t=this.configuration.wsServers;e",weight:0,wsUri:"wss://edge.sip.onsip.com",isError:!1}],connectionTimeout:5,maxReconnectionAttempts:3,reconnectionTimeout:4,keepAliveInterval:0,keepAliveDebounce:10,traceSip:!1},r=this.getConfigurationCheck();for(var i in r.mandatory){if(!e.hasOwnProperty(i))throw new a.Exceptions.ConfigurationError(i);var n=e[i];if(void 0===(s=r.mandatory[i](n)))throw new a.Exceptions.ConfigurationError(i,n);t[i]=s}for(var i in r.optional)if(e.hasOwnProperty(i)){var s;if((n=e[i])instanceof Array&&0===n.length||null===n||""===n||void 0===n||"number"==typeof n&&isNaN(n))continue;if(void 0===(s=r.optional[i](n)))throw new a.Exceptions.ConfigurationError(i,n);t[i]=s}var o={};for(var i in t)t.hasOwnProperty(i)&&(o[i]={value:t[i]});var c=Object.defineProperties({},o);for(var i in this.logger.log("configuration parameters after validation:"),t)t.hasOwnProperty(i)&&this.logger.log("\xb7 "+i+": "+JSON.stringify(t[i]));return c},r.prototype.getConfigurationCheck=function(){return{mandatory:{},optional:{wsServers:function(e){if("string"==typeof e)e=[{wsUri:e}];else{if(!(e instanceof Array))return;for(var t=0;t",n.weight||(n.weight=0),n.isError=!1,n.scheme=o.scheme.toUpperCase()}return e},keepAliveInterval:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}},keepAliveDebounce:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}},traceSip:function(e){if("boolean"==typeof e)return e},connectionTimeout:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}},maxReconnectionAttempts:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>=0)return t}},reconnectionTimeout:function(e){if(c.Utils.isDecimal(e)){var t=Number(e);if(t>0)return t}}}}},r.C=i,r}(s.Transport);t.Transport=u}).call(this,r(18))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(5);t.DigestAuthentication=n.DigestAuthentication,t.Grammar=n.Grammar,t.IncomingRequest=n.IncomingRequestMessage,t.IncomingResponse=n.IncomingResponseMessage,t.LoggerFactory=n.LoggerFactory,t.NameAddrHeader=n.NameAddrHeader,t.OutgoingRequest=n.OutgoingRequestMessage,t.Timers=n.Timers,t.Transport=n.Transport,t.URI=n.URI;var s=r(12);t.ClientContext=s.ClientContext;var o=r(7);t.C=o.C;var a=r(4);t.DialogStatus=a.DialogStatus,t.SessionStatus=a.SessionStatus,t.TypeStrings=a.TypeStrings,t.UAStatus=a.UAStatus;var c=r(10);t.Exceptions=c.Exceptions;var u=r(55);t.Parser=u.Parser;var d=r(56);t.PublishContext=d.PublishContext;var p=r(34);t.ReferClientContext=p.ReferClientContext,t.ReferServerContext=p.ReferServerContext;var h=r(57);t.RegisterContext=h.RegisterContext;var l=r(17);t.ServerContext=l.ServerContext;var g=r(58);t.InviteClientContext=g.InviteClientContext,t.InviteServerContext=g.InviteServerContext,t.Session=g.Session;var f=r(59);t.Subscription=f.Subscription;var m=r(1),v={InviteClientTransaction:m.InviteClientTransaction,InviteServerTransaction:m.InviteServerTransaction,NonInviteClientTransaction:m.NonInviteClientTransaction,NonInviteServerTransaction:m.NonInviteServerTransaction};t.Transactions=v;var S=r(60);t.makeUserAgentCoreConfigurationFromUA=S.makeUserAgentCoreConfigurationFromUA,t.UA=S.UA;var T=r(8);t.Utils=T.Utils;var y=i.__importStar(r(98));t.Web=y;var E=r(54),b=E.title;t.name=b;var C=E.version;t.version=C;var _=i.__importStar(r(5));t.Core=_},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(0).__exportStar(r(65),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.ACK="ACK",e.BYE="BYE",e.CANCEL="CANCEL",e.INFO="INFO",e.INVITE="INVITE",e.MESSAGE="MESSAGE",e.NOTIFY="NOTIFY",e.OPTIONS="OPTIONS",e.REGISTER="REGISTER",e.UPDATE="UPDATE",e.SUBSCRIBE="SUBSCRIBE",e.PUBLISH="PUBLISH",e.REFER="REFER",e.PRACK="PRACK"}(t.C||(t.C={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(36),n=r(37),s=r(38);function o(e){return!(!e||"string"!=typeof e.content||"string"!=typeof e.contentType||void 0!==e.contentDisposition)||"string"==typeof e.contentDisposition}function a(e){return"application/sdp"===e?"session":"render"}t.fromBodyLegacy=function(e){var t="string"==typeof e?e:e.body,r="string"==typeof e?"application/sdp":e.contentType;return{contentDisposition:a(r),contentType:r,content:t}},t.getBody=function(e){var t,r,c,u;if(e instanceof i.IncomingRequestMessage&&e.body&&(t=(u=e.parseHeader("Content-Disposition"))?u.type:void 0,r=e.parseHeader("Content-Type"),c=e.body),e instanceof n.IncomingResponseMessage&&e.body&&(t=(u=e.parseHeader("Content-Disposition"))?u.type:void 0,r=e.parseHeader("Content-Type"),c=e.body),e instanceof s.OutgoingRequestMessage&&e.body)if(t=e.getHeader("Content-Disposition"),r=e.getHeader("Content-Type"),"string"==typeof e.body){if(!r)throw new Error("Header content type header does not equal body content type.");c=e.body}else{if(r&&r!==e.body.contentType)throw new Error("Header content type header does not equal body content type.");r=e.body.contentType,c=e.body.body}if(o(e)&&(t=e.contentDisposition,r=e.contentType,c=e.content),c){if(r&&!t&&(t=a(r)),!t)throw new Error("Content disposition undefined.");if(!r)throw new Error("Content type undefined.");return{contentDisposition:t,contentType:r,content:c}}},t.isBody=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(23),s=r(25),o=function(e){function t(r,i,n,s){var o=e.call(this)||this;return o.message=r,o.expected=i,o.found=n,o.location=s,o.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(o,t),o}return i.__extends(t,e),t.buildMessage=function(e,t){function r(e){return e.charCodeAt(0).toString(16).toUpperCase()}function i(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,function(e){return"\\x0"+r(e)}).replace(/[\x10-\x1F\x7F-\x9F]/g,function(e){return"\\x"+r(e)})}function n(e){return e.replace(/\\/g,"\\\\").replace(/\]/g,"\\]").replace(/\^/g,"\\^").replace(/-/g,"\\-").replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,function(e){return"\\x0"+r(e)}).replace(/[\x10-\x1F\x7F-\x9F]/g,function(e){return"\\x"+r(e)})}function s(e){switch(e.type){case"literal":return'"'+i(e.text)+'"';case"class":var t=e.parts.map(function(e){return Array.isArray(e)?n(e[0])+"-"+n(e[1]):n(e)});return"["+(e.inverted?"^":"")+t+"]";case"any":return"any character";case"end":return"end of input";case"other":return e.description}}return"Expected "+function(e){var t,r,i=e.map(s);if(i.sort(),i.length>0){for(t=1,r=1;t",T(">",!1),"\\",T("\\",!1),"[",T("[",!1),"]",T("]",!1),"{",T("{",!1),"}",T("}",!1),function(){return"*"},function(){return"/"},function(){return"="},function(){return"("},function(){return")"},function(){return">"},function(){return"<"},function(){return","},function(){return";"},function(){return":"},function(){return'"'},/^[!-']/,y([["!","'"]],!1,!1),/^[*-[]/,y([["*","["]],!1,!1),/^[\]-~]/,y([["]","~"]],!1,!1),function(e){return e},/^[#-[]/,y([["#","["]],!1,!1),/^[\0-\t]/,y([["\0","\t"]],!1,!1),/^[\x0B-\f]/,y([["\v","\f"]],!1,!1),/^[\x0E-\x7F]/,y([["\x0e","\x7f"]],!1,!1),function(){(t=t||{data:{}}).data.uri=new s.URI(t.data.scheme,t.data.user,t.data.host,t.data.port),delete t.data.scheme,delete t.data.user,delete t.data.host,delete t.data.host_type,delete t.data.port},function(){(t=t||{data:{}}).data.uri=new s.URI(t.data.scheme,t.data.user,t.data.host,t.data.port,t.data.uri_params,t.data.uri_headers),delete t.data.scheme,delete t.data.user,delete t.data.host,delete t.data.host_type,delete t.data.port,delete t.data.uri_params,"SIP_URI"===t.startRule&&(t.data=t.data.uri)},"sips",T("sips",!0),"sip",T("sip",!0),function(e){(t=t||{data:{}}).data.scheme=e},function(){(t=t||{data:{}}).data.user=decodeURIComponent(v().slice(0,-1))},function(){(t=t||{data:{}}).data.password=v()},function(){return(t=t||{data:{}}).data.host=v(),t.data.host},function(){return(t=t||{data:{}}).data.host_type="domain",v()},/^[a-zA-Z0-9_\-]/,y([["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),/^[a-zA-Z0-9\-]/,y([["a","z"],["A","Z"],["0","9"],"-"],!1,!1),function(){return(t=t||{data:{}}).data.host_type="IPv6",v()},"::",T("::",!1),function(){return(t=t||{data:{}}).data.host_type="IPv6",v()},function(){return(t=t||{data:{}}).data.host_type="IPv4",v()},"25",T("25",!1),/^[0-5]/,y([["0","5"]],!1,!1),"2",T("2",!1),/^[0-4]/,y([["0","4"]],!1,!1),"1",T("1",!1),/^[1-9]/,y([["1","9"]],!1,!1),function(e){return t=t||{data:{}},e=parseInt(e.join("")),t.data.port=e,e},"transport=",T("transport=",!0),"udp",T("udp",!0),"tcp",T("tcp",!0),"sctp",T("sctp",!0),"tls",T("tls",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.transport=e.toLowerCase()},"user=",T("user=",!0),"phone",T("phone",!0),"ip",T("ip",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.user=e.toLowerCase()},"method=",T("method=",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.method=e},"ttl=",T("ttl=",!0),function(e){(t=t||{data:{}}).data.params||(t.data.params={}),t.data.params.ttl=e},"maddr=",T("maddr=",!0),function(e){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.maddr=e},"lr",T("lr",!0),function(){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),t.data.uri_params.lr=void 0},function(e,r){(t=t||{data:{}}).data.uri_params||(t.data.uri_params={}),r=null===r?void 0:r[1],t.data.uri_params[e.toLowerCase()]=r},function(e,r){e=e.join("").toLowerCase(),r=r.join(""),(t=t||{data:{}}).data.uri_headers||(t.data.uri_headers={}),t.data.uri_headers[e]?t.data.uri_headers[e].push(r):t.data.uri_headers[e]=[r]},function(){"Refer_To"===(t=t||{data:{}}).startRule&&(t.data.uri=new s.URI(t.data.scheme,t.data.user,t.data.host,t.data.port,t.data.uri_params,t.data.uri_headers),delete t.data.scheme,delete t.data.user,delete t.data.host,delete t.data.host_type,delete t.data.port,delete t.data.uri_params)},"//",T("//",!1),function(){(t=t||{data:{}}).data.scheme=v()},T("SIP",!0),function(){(t=t||{data:{}}).data.sip_version=v()},"INVITE",T("INVITE",!1),"ACK",T("ACK",!1),"VXACH",T("VXACH",!1),"OPTIONS",T("OPTIONS",!1),"BYE",T("BYE",!1),"CANCEL",T("CANCEL",!1),"REGISTER",T("REGISTER",!1),"SUBSCRIBE",T("SUBSCRIBE",!1),"NOTIFY",T("NOTIFY",!1),"REFER",T("REFER",!1),"PUBLISH",T("PUBLISH",!1),function(){return(t=t||{data:{}}).data.method=v(),t.data.method},function(e){(t=t||{data:{}}).data.status_code=parseInt(e.join(""))},function(){(t=t||{data:{}}).data.reason_phrase=v()},function(){(t=t||{data:{}}).data=v()},function(){var e,r;for(r=(t=t||{data:{}}).data.multi_header.length,e=0;e""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E'),R(";).# &;,"),R('2F""6F7G.} &2H""6H7I.q &2J""6J7K.e &2L""6L7M.Y &2N""6N7O.M &2P""6P7Q.A &2R""6R7S.5 &2T""6T7U.) &2V""6V7W'),R('%%2X""6X7Y/5#;#/,$;#/#$+#)(#\'#("\'#&\'#/"!&,)'),R('%%$;$0#*;$&/,#; /#$+")("\'#&\'#." &"/=#$;$/�#*;$&&&#/\'$8":Z" )("\'#&\'#'),R(';.." &"'),R("%$;'.# &;(0)*;'.# &;(&/?#28\"\"6879/0$;//'$8#:[# )(#'#(\"'#&'#"),R('%%$;2/�#*;2&&&#/g#$%$;.0#*;.&/,#;2/#$+")("\'#&\'#0=*%$;.0#*;.&/,#;2/#$+")("\'#&\'#&/#$+")("\'#&\'#/"!&,)'),R('4\\""5!7].# &;3'),R('4^""5!7_'),R('4`""5!7a'),R(';!.) &4b""5!7c'),R('%$;).\x95 &2F""6F7G.\x89 &2J""6J7K.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O/\x9e#0\x9b*;).\x95 &2F""6F7G.\x89 &2J""6J7K.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O&&&#/"!&,)'),R('%$;).\x89 &2F""6F7G.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O/\x92#0\x8f*;).\x89 &2F""6F7G.} &2L""6L7M.q &2X""6X7Y.e &2P""6P7Q.Y &2H""6H7I.M &2@""6@7A.A &2d""6d7e.5 &2R""6R7S.) &2N""6N7O&&&#/"!&,)'),R('2T""6T7U.\xe3 &2V""6V7W.\xd7 &2f""6f7g.\xcb &2h""6h7i.\xbf &2:""6:7;.\xb3 &2D""6D7E.\xa7 &22""6273.\x9b &28""6879.\x8f &2j""6j7k.\x83 &;&.} &24""6475.q &2l""6l7m.e &2n""6n7o.Y &26""6677.M &2>""6>7?.A &2p""6p7q.5 &2r""6r7s.) &;\'.# &;('),R('%$;).\u012b &2F""6F7G.\u011f &2J""6J7K.\u0113 &2L""6L7M.\u0107 &2X""6X7Y.\xfb &2P""6P7Q.\xef &2H""6H7I.\xe3 &2@""6@7A.\xd7 &2d""6d7e.\xcb &2R""6R7S.\xbf &2N""6N7O.\xb3 &2T""6T7U.\xa7 &2V""6V7W.\x9b &2f""6f7g.\x8f &2h""6h7i.\x83 &28""6879.w &2j""6j7k.k &;&.e &24""6475.Y &2l""6l7m.M &2n""6n7o.A &26""6677.5 &2p""6p7q.) &2r""6r7s/\u0134#0\u0131*;).\u012b &2F""6F7G.\u011f &2J""6J7K.\u0113 &2L""6L7M.\u0107 &2X""6X7Y.\xfb &2P""6P7Q.\xef &2H""6H7I.\xe3 &2@""6@7A.\xd7 &2d""6d7e.\xcb &2R""6R7S.\xbf &2N""6N7O.\xb3 &2T""6T7U.\xa7 &2V""6V7W.\x9b &2f""6f7g.\x8f &2h""6h7i.\x83 &28""6879.w &2j""6j7k.k &;&.e &24""6475.Y &2l""6l7m.M &2n""6n7o.A &26""6677.5 &2p""6p7q.) &2r""6r7s&&&#/"!&,)'),R("%;//?#2P\"\"6P7Q/0$;//'$8#:t# )(#'#(\"'#&'#"),R("%;//?#24\"\"6475/0$;//'$8#:u# )(#'#(\"'#&'#"),R("%;//?#2>\"\"6>7?/0$;//'$8#:v# )(#'#(\"'#&'#"),R("%;//?#2T\"\"6T7U/0$;//'$8#:w# )(#'#(\"'#&'#"),R("%;//?#2V\"\"6V7W/0$;//'$8#:x# )(#'#(\"'#&'#"),R('%2h""6h7i/0#;//\'$8":y" )("\'#&\'#'),R('%;//6#2f""6f7g/\'$8":z" )("\'#&\'#'),R("%;//?#2D\"\"6D7E/0$;//'$8#:{# )(#'#(\"'#&'#"),R("%;//?#22\"\"6273/0$;//'$8#:|# )(#'#(\"'#&'#"),R("%;//?#28\"\"6879/0$;//'$8#:}# )(#'#(\"'#&'#"),R("%;//0#;&/'$8\":~\" )(\"'#&'#"),R("%;&/0#;//'$8\":~\" )(\"'#&'#"),R("%;=/T#$;G.) &;K.# &;F0/*;G.) &;K.# &;F&/,$;>/#$+#)(#'#(\"'#&'#"),R('4\x7f""5!7\x80.A &4\x81""5!7\x82.5 &4\x83""5!7\x84.) &;3.# &;.'),R("%%;//Q#;&/H$$;J.# &;K0)*;J.# &;K&/,$;&/#$+$)($'#(#'#(\"'#&'#/\"!&,)"),R("%;//]#;&/T$%$;J.# &;K0)*;J.# &;K&/\"!&,)/1$;&/($8$:\x85$!!)($'#(#'#(\"'#&'#"),R(';..G &2L""6L7M.; &4\x86""5!7\x87./ &4\x83""5!7\x84.# &;3'),R('%2j""6j7k/J#4\x88""5!7\x89.5 &4\x8a""5!7\x8b.) &4\x8c""5!7\x8d/#$+")("\'#&\'#'),R("%;N/M#28\"\"6879/>$;O.\" &\"/0$;S/'$8$:\x8e$ )($'#(#'#(\"'#&'#"),R("%;N/d#28\"\"6879/U$;O.\" &\"/G$;S/>$;_/5$;l.\" &\"/'$8&:\x8f& )(&'#(%'#($'#(#'#(\"'#&'#"),R('%3\x90""5$7\x91.) &3\x92""5#7\x93/\' 8!:\x94!! )'),R('%;P/]#%28""6879/,#;R/#$+")("\'#&\'#." &"/6$2:""6:7;/\'$8#:\x95# )(#\'#("\'#&\'#'),R("$;+.) &;-.# &;Q/2#0/*;+.) &;-.# &;Q&&&#"),R('2<""6<7=.q &2>""6>7?.e &2@""6@7A.Y &2B""6B7C.M &2D""6D7E.A &22""6273.5 &26""6677.) &24""6475'),R('%$;+._ &;-.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E0e*;+._ &;-.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E&/& 8!:\x96! )'),R('%;T/J#%28""6879/,#;^/#$+")("\'#&\'#." &"/#$+")("\'#&\'#'),R("%;U.) &;\\.# &;X/& 8!:\x97! )"),R('%$%;V/2#2J""6J7K/#$+")("\'#&\'#0<*%;V/2#2J""6J7K/#$+")("\'#&\'#&/D#;W/;$2J""6J7K." &"/\'$8#:\x98# )(#\'#("\'#&\'#'),R('$4\x99""5!7\x9a/,#0)*4\x99""5!7\x9a&&&#'),R('%4$""5!7%/?#$4\x9b""5!7\x9c0)*4\x9b""5!7\x9c&/#$+")("\'#&\'#'),R('%2l""6l7m/?#;Y/6$2n""6n7o/\'$8#:\x9d# )(#\'#("\'#&\'#'),R('%%;Z/\xb3#28""6879/\xa4$;Z/\x9b$28""6879/\x8c$;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+-)(-\'#(,\'#(+\'#(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0790 &%2\x9e""6\x9e7\x9f/\xa4#;Z/\x9b$28""6879/\x8c$;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+,)(,\'#(+\'#(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u06f9 &%2\x9e""6\x9e7\x9f/\x8c#;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+*)(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u067a &%2\x9e""6\x9e7\x9f/t#;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0613 &%2\x9e""6\x9e7\x9f/\\#;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+&)(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u05c4 &%2\x9e""6\x9e7\x9f/D#;Z/;$28""6879/,$;[/#$+$)($\'#(#\'#("\'#&\'#.\u058d &%2\x9e""6\x9e7\x9f/,#;[/#$+")("\'#&\'#.\u056e &%2\x9e""6\x9e7\x9f/,#;Z/#$+")("\'#&\'#.\u054f &%;Z/\x9b#2\x9e""6\x9e7\x9f/\x8c$;Z/\x83$28""6879/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$++)(+\'#(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u04c7 &%;Z/\xaa#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x83$2\x9e""6\x9e7\x9f/t$;Z/k$28""6879/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+*)(*\'#()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0430 &%;Z/\xb9#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x92$%28""6879/,#;Z/#$+")("\'#&\'#." &"/k$2\x9e""6\x9e7\x9f/\\$;Z/S$28""6879/D$;Z/;$28""6879/,$;[/#$+))()\'#((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u038a &%;Z/\xc8#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xa1$%28""6879/,#;Z/#$+")("\'#&\'#." &"/z$%28""6879/,#;Z/#$+")("\'#&\'#." &"/S$2\x9e""6\x9e7\x9f/D$;Z/;$28""6879/,$;[/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u02d5 &%;Z/\xd7#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xb0$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x89$%28""6879/,#;Z/#$+")("\'#&\'#." &"/b$%28""6879/,#;Z/#$+")("\'#&\'#." &"/;$2\x9e""6\x9e7\x9f/,$;[/#$+\')(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0211 &%;Z/\xfe#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xd7$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xb0$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x89$%28""6879/,#;Z/#$+")("\'#&\'#." &"/b$%28""6879/,#;Z/#$+")("\'#&\'#." &"/;$2\x9e""6\x9e7\x9f/,$;Z/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#.\u0126 &%;Z/\u011c#%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xf5$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xce$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\xa7$%28""6879/,#;Z/#$+")("\'#&\'#." &"/\x80$%28""6879/,#;Z/#$+")("\'#&\'#." &"/Y$%28""6879/,#;Z/#$+")("\'#&\'#." &"/2$2\x9e""6\x9e7\x9f/#$+()((\'#(\'\'#(&\'#(%\'#($\'#(#\'#("\'#&\'#/& 8!:\xa0! )'),R('%;#/M#;#." &"/?$;#." &"/1$;#." &"/#$+$)($\'#(#\'#("\'#&\'#'),R("%;Z/;#28\"\"6879/,$;Z/#$+#)(#'#(\"'#&'#.# &;\\"),R("%;]/o#2J\"\"6J7K/`$;]/W$2J\"\"6J7K/H$;]/?$2J\"\"6J7K/0$;]/'$8':\xa1' )(''#(&'#(%'#($'#(#'#(\"'#&'#"),R('%2\xa2""6\xa27\xa3/2#4\xa4""5!7\xa5/#$+")("\'#&\'#.\x98 &%2\xa6""6\xa67\xa7/;#4\xa8""5!7\xa9/,$;!/#$+#)(#\'#("\'#&\'#.j &%2\xaa""6\xaa7\xab/5#;!/,$;!/#$+#)(#\'#("\'#&\'#.B &%4\xac""5!7\xad/,#;!/#$+")("\'#&\'#.# &;!'),R('%%;!." &"/[#;!." &"/M$;!." &"/?$;!." &"/1$;!." &"/#$+%)(%\'#($\'#(#\'#("\'#&\'#/\' 8!:\xae!! )'),R('$%22""6273/,#;`/#$+")("\'#&\'#0<*%22""6273/,#;`/#$+")("\'#&\'#&'),R(";a.A &;b.; &;c.5 &;d./ &;e.) &;f.# &;g"),R('%3\xaf""5*7\xb0/a#3\xb1""5#7\xb2.G &3\xb3""5#7\xb4.; &3\xb5""5$7\xb6./ &3\xb7""5#7\xb8.# &;6/($8":\xb9"! )("\'#&\'#'),R('%3\xba""5%7\xbb/I#3\xbc""5%7\xbd./ &3\xbe""5"7\xbf.# &;6/($8":\xc0"! )("\'#&\'#'),R('%3\xc1""5\'7\xc2/1#;\x90/($8":\xc3"! )("\'#&\'#'),R('%3\xc4""5$7\xc5/1#;\xf0/($8":\xc6"! )("\'#&\'#'),R('%3\xc7""5&7\xc8/1#;T/($8":\xc9"! )("\'#&\'#'),R('%3\xca""5"7\xcb/N#%2>""6>7?/,#;6/#$+")("\'#&\'#." &"/\'$8":\xcc" )("\'#&\'#'),R('%;h/P#%2>""6>7?/,#;i/#$+")("\'#&\'#." &"/)$8":\xcd""! )("\'#&\'#'),R('%$;j/�#*;j&&&#/"!&,)'),R('%$;j/�#*;j&&&#/"!&,)'),R(";k.) &;+.# &;-"),R('2l""6l7m.e &2n""6n7o.Y &24""6475.M &28""6879.A &2<""6<7=.5 &2@""6@7A.) &2B""6B7C'),R('%26""6677/n#;m/e$$%2<""6<7=/,#;m/#$+")("\'#&\'#0<*%2<""6<7=/,#;m/#$+")("\'#&\'#&/#$+#)(#\'#("\'#&\'#'),R('%;n/A#2>""6>7?/2$;o/)$8#:\xce#"" )(#\'#("\'#&\'#'),R("$;p.) &;+.# &;-/2#0/*;p.) &;+.# &;-&&&#"),R("$;p.) &;+.# &;-0/*;p.) &;+.# &;-&"),R('2l""6l7m.e &2n""6n7o.Y &24""6475.M &26""6677.A &28""6879.5 &2@""6@7A.) &2B""6B7C'),R(";\x91.# &;r"),R("%;\x90/G#;'/>$;s/5$;'/,$;\x84/#$+%)(%'#($'#(#'#(\"'#&'#"),R(";M.# &;t"),R("%;\x7f/E#28\"\"6879/6$;u.# &;x/'$8#:\xcf# )(#'#(\"'#&'#"),R('%;v.# &;w/J#%26""6677/,#;\x83/#$+")("\'#&\'#." &"/#$+")("\'#&\'#'),R('%2\xd0""6\xd07\xd1/:#;\x80/1$;w." &"/#$+#)(#\'#("\'#&\'#'),R('%24""6475/,#;{/#$+")("\'#&\'#'),R("%;z/3#$;y0#*;y&/#$+\")(\"'#&'#"),R(";*.) &;+.# &;-"),R(';+.\x8f &;-.\x89 &22""6273.} &26""6677.q &28""6879.e &2:""6:7;.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E'),R('%;|/e#$%24""6475/,#;|/#$+")("\'#&\'#0<*%24""6475/,#;|/#$+")("\'#&\'#&/#$+")("\'#&\'#'),R('%$;~0#*;~&/e#$%22""6273/,#;}/#$+")("\'#&\'#0<*%22""6273/,#;}/#$+")("\'#&\'#&/#$+")("\'#&\'#'),R("$;~0#*;~&"),R(';+.w &;-.q &28""6879.e &2:""6:7;.Y &2<""6<7=.M &2>""6>7?.A &2@""6@7A.5 &2B""6B7C.) &2D""6D7E'),R('%%;"/\x87#$;".G &;!.A &2@""6@7A.5 &2F""6F7G.) &2J""6J7K0M*;".G &;!.A &2@""6@7A.5 &2F""6F7G.) &2J""6J7K&/#$+")("\'#&\'#/& 8!:\xd2! )'),R(";\x81.# &;\x82"),R('%%;O/2#2:""6:7;/#$+")("\'#&\'#." &"/,#;S/#$+")("\'#&\'#." &"'),R('$;+.\x83 &;-.} &2B""6B7C.q &2D""6D7E.e &22""6273.Y &28""6879.M &2:""6:7;.A &2<""6<7=.5 &2>""6>7?.) &2@""6@7A/\x8c#0\x89*;+.\x83 &;-.} &2B""6B7C.q &2D""6D7E.e &22""6273.Y &28""6879.M &2:""6:7;.A &2<""6<7=.5 &2>""6>7?.) &2@""6@7A&&&#'),R("$;y0#*;y&"),R('%3\x92""5#7\xd3/q#24""6475/b$$;!/�#*;!&&&#/L$2J""6J7K/=$$;!/�#*;!&&&#/\'$8%:\xd4% )(%\'#($\'#(#\'#("\'#&\'#'),R('2\xd5""6\xd57\xd6'),R('2\xd7""6\xd77\xd8'),R('2\xd9""6\xd97\xda'),R('2\xdb""6\xdb7\xdc'),R('2\xdd""6\xdd7\xde'),R('2\xdf""6\xdf7\xe0'),R('2\xe1""6\xe17\xe2'),R('2\xe3""6\xe37\xe4'),R('2\xe5""6\xe57\xe6'),R('2\xe7""6\xe77\xe8'),R('2\xe9""6\xe97\xea'),R("%;\x85.Y &;\x86.S &;\x88.M &;\x89.G &;\x8a.A &;\x8b.; &;\x8c.5 &;\x8f./ &;\x8d.) &;\x8e.# &;6/& 8!:\xeb! )"),R("%;\x84/G#;'/>$;\x92/5$;'/,$;\x94/#$+%)(%'#($'#(#'#(\"'#&'#"),R("%;\x93/' 8!:\xec!! )"),R("%;!/5#;!/,$;!/#$+#)(#'#(\"'#&'#"),R("%$;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(0G*;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(&/& 8!:\xed! )"),R("%;\xb6/Y#$%;A/,#;\xb6/#$+\")(\"'#&'#06*%;A/,#;\xb6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),R('%;9/N#%2:""6:7;/,#;9/#$+")("\'#&\'#." &"/\'$8":\xee" )("\'#&\'#'),R("%;:.c &%;\x98/Y#$%;A/,#;\x98/#$+\")(\"'#&'#06*%;A/,#;\x98/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/& 8!:\xef! )"),R("%;L.# &;\x99/]#$%;B/,#;\x9b/#$+\")(\"'#&'#06*%;B/,#;\x9b/#$+\")(\"'#&'#&/'$8\":\xf0\" )(\"'#&'#"),R("%;\x9a.\" &\"/>#;@/5$;M/,$;?/#$+$)($'#(#'#(\"'#&'#"),R("%%;6/Y#$%;./,#;6/#$+\")(\"'#&'#06*%;./,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#.# &;H/' 8!:\xf1!! )"),R(";\x9c.) &;\x9d.# &;\xa0"),R("%3\xf2\"\"5!7\xf3/:#;$;\xcf/5$;./,$;\x90/#$+%)(%'#($'#(#'#(\"'#&'#"),R("%$;!/�#*;!&&&#/' 8!:\u014b!! )"),R("%;\xd1/]#$%;A/,#;\xd1/#$+\")(\"'#&'#06*%;A/,#;\xd1/#$+\")(\"'#&'#&/'$8\":\u014c\" )(\"'#&'#"),R("%;\x99/]#$%;B/,#;\xa0/#$+\")(\"'#&'#06*%;B/,#;\xa0/#$+\")(\"'#&'#&/'$8\":\u014d\" )(\"'#&'#"),R('%;L.O &;\x99.I &%;@." &"/:#;t/1$;?." &"/#$+#)(#\'#("\'#&\'#/]#$%;B/,#;\xa0/#$+")("\'#&\'#06*%;B/,#;\xa0/#$+")("\'#&\'#&/\'$8":\u014e" )("\'#&\'#'),R("%;\xd4/]#$%;B/,#;\xd5/#$+\")(\"'#&'#06*%;B/,#;\xd5/#$+\")(\"'#&'#&/'$8\":\u014f\" )(\"'#&'#"),R("%;\x96/& 8!:\u0150! )"),R('%3\u0151""5(7\u0152/:#;$;6/5$;;/,$;\xec/#$+%)(%'#($'#(#'#(\"'#&'#"),R('%3\x92""5#7\xd3.# &;6/\' 8!:\u018b!! )'),R('%3\xb1""5#7\u018c.G &3\xb3""5#7\u018d.; &3\xb7""5#7\u018e./ &3\xb5""5$7\u018f.# &;6/\' 8!:\u0190!! )'),R('%;\xee/D#%;C/,#;\xef/#$+")("\'#&\'#." &"/#$+")("\'#&\'#'),R("%;U.) &;\\.# &;X/& 8!:\u0191! )"),R('%%;!." &"/[#;!." &"/M$;!." &"/?$;!." &"/1$;!." &"/#$+%)(%\'#($\'#(#\'#("\'#&\'#/\' 8!:\u0192!! )'),R('%%;!/?#;!." &"/1$;!." &"/#$+#)(#\'#("\'#&\'#/\' 8!:\u0193!! )'),R(";\xbe"),R('%;\x9e/^#$%;B/,#;\xf3/#$+")("\'#&\'#06*%;B/,#;\xf3/#$+")("\'#&\'#&/($8":\u0194"!!)("\'#&\'#'),R(";\xf4.# &;\xa0"),R('%2\u0195""6\u01957\u0196/L#;""6>7?'),R('%;\u0100/b#28""6879/S$;\xfb/J$%2\u01a3""6\u01a37\u01a4/,#;\xec/#$+")("\'#&\'#." &"/#$+$)($\'#(#\'#("\'#&\'#'),R('%3\u01a5""5%7\u01a6.) &3\u01a7""5$7\u01a8/\' 8!:\u01a1!! )'),R('%3\xb1""5#7\xb2.6 &3\xb3""5#7\xb4.* &$;+0#*;+&/\' 8!:\u01a9!! )'),R("%;\u0104/\x87#2F\"\"6F7G/x$;\u0103/o$2F\"\"6F7G/`$;\u0103/W$2F\"\"6F7G/H$;\u0103/?$2F\"\"6F7G/0$;\u0105/'$8):\u01aa) )()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#"),R("%;#/>#;#/5$;#/,$;#/#$+$)($'#(#'#(\"'#&'#"),R("%;\u0103/,#;\u0103/#$+\")(\"'#&'#"),R("%;\u0103/5#;\u0103/,$;\u0103/#$+#)(#'#(\"'#&'#"),R("%;q/T#$;m0#*;m&/D$%; /,#;\xf8/#$+\")(\"'#&'#.\" &\"/#$+#)(#'#(\"'#&'#"),R('%2\u01ab""6\u01ab7\u01ac.) &2\u01ad""6\u01ad7\u01ae/w#;0/n$;\u0108/e$$%;B/2#;\u0109.# &;\xa0/#$+")("\'#&\'#0<*%;B/2#;\u0109.# &;\xa0/#$+")("\'#&\'#&/#$+$)($\'#(#\'#("\'#&\'#'),R(";\x99.# &;L"),R("%2\u01af\"\"6\u01af7\u01b0/5#;g&&(g=p,f=[]),f.push(e))}function _(e,t,r){return new o(o.buildMessage(e,t),e,t,r)}function R(e){return e.split("").map(function(e){return e.charCodeAt(0)-32})}if(t.data={},(r=function t(r){for(var n,s=d[r],o=0,a=[],c=s.length,l=[],g=[];;){for(;op?(c=o+3+s[o+1],o+=3):(c=o+3+s[o+1]+s[o+2],o+=3+s[o+1]);break;case 18:l.push(c),a.push(o+4+s[o+2]+s[o+3]),e.substr(p,u[s[o+1]].length)===u[s[o+1]]?(c=o+4+s[o+2],o+=4):(c=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 19:l.push(c),a.push(o+4+s[o+2]+s[o+3]),e.substr(p,u[s[o+1]].length).toLowerCase()===u[s[o+1]]?(c=o+4+s[o+2],o+=4):(c=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 20:l.push(c),a.push(o+4+s[o+2]+s[o+3]),u[s[o+1]].test(e.charAt(p))?(c=o+4+s[o+2],o+=4):(c=o+4+s[o+2]+s[o+3],o+=4+s[o+2]);break;case 21:g.push(e.substr(p,s[o+1])),p+=s[o+1],o+=2;break;case 22:g.push(u[s[o+1]]),p+=u[s[o+1]].length,o+=2;break;case 23:g.push(i),0===m&&C(u[s[o+1]]),o+=2;break;case 24:h=g[g.length-1-s[o+1]],o+=2;break;case 25:h=p,o++;break;case 26:n=s.slice(o+4,o+4+s[o+3]).map(function(e){return g[g.length-1-e]}),g.splice(g.length-s[o+2],s[o+2],u[s[o+1]].apply(null,n)),o+=4+s[o+3];break;case 27:g.push(t(s[o+1])),o+=2;break;case 28:m++,o++;break;case 29:m--,o++;break;default:throw new Error("Invalid opcode: "+s[o]+".")}if(!(l.length>0))break;c=l.pop(),o=a.pop()}return g[0]}(c))!==i&&p===e.length)return r;throw r!==i&&p-1)this.qop="auth";else{if(!(t.qop.indexOf("auth-int")>-1))return this.logger.warn("challenge without Digest qop different than 'auth' or 'auth-int', authentication aborted"),!1;this.qop="auth-int"}else this.qop=void 0;return this.method=e.method,this.uri=e.ruri,this.cnonce=n.createRandomToken(12),this.nc+=1,this.updateNcHex(),4294967296===this.nc&&(this.nc=1,this.ncHex="00000001"),this.calculateResponse(r),!0},e.prototype.toString=function(){var e=[];if(!this.response)throw new Error("response field does not exist, cannot generate Authorization header");return e.push("algorithm="+this.algorithm),e.push('username="'+this.username+'"'),e.push('realm="'+this.realm+'"'),e.push('nonce="'+this.nonce+'"'),e.push('uri="'+this.uri+'"'),e.push('response="'+this.response+'"'),this.opaque&&e.push('opaque="'+this.opaque+'"'),this.qop&&(e.push("qop="+this.qop),e.push('cnonce="'+this.cnonce+'"'),e.push("nc="+this.ncHex)),"Digest "+e.join(", ")},e.prototype.updateNcHex=function(){var e=Number(this.nc).toString(16);this.ncHex="00000000".substr(0,8-e.length)+e},e.prototype.calculateResponse=function(e){var t,r=i.default(this.username+":"+this.realm+":"+this.password);"auth"===this.qop?(t=i.default(this.method+":"+this.uri),this.response=i.default(r+":"+this.nonce+":"+this.ncHex+":"+this.cnonce+":auth:"+t)):"auth-int"===this.qop?(t=i.default(this.method+":"+this.uri+":"+i.default(e||"")),this.response=i.default(r+":"+this.nonce+":"+this.ncHex+":"+this.cnonce+":auth-int:"+t)):void 0===this.qop&&(t=i.default(this.method+":"+this.uri),this.response=i.default(r+":"+this.nonce+":"+t))},e}();t.DigestAuthentication=s},function(e,t,r){var i;e.exports=(i=r(70),function(e){var t=i,r=t.lib,n=r.WordArray,s=r.Hasher,o=t.algo,a=[];!function(){for(var t=0;t<64;t++)a[t]=4294967296*e.abs(e.sin(t+1))|0}();var c=o.MD5=s.extend({_doReset:function(){this._hash=new n.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(e,t){for(var r=0;r<16;r++){var i=t+r,n=e[i];e[i]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8)}var s=this._hash.words,o=e[t+0],c=e[t+1],l=e[t+2],g=e[t+3],f=e[t+4],m=e[t+5],v=e[t+6],S=e[t+7],T=e[t+8],y=e[t+9],E=e[t+10],b=e[t+11],C=e[t+12],_=e[t+13],R=e[t+14],w=e[t+15],A=s[0],I=s[1],x=s[2],N=s[3];A=u(A,I,x,N,o,7,a[0]),N=u(N,A,I,x,c,12,a[1]),x=u(x,N,A,I,l,17,a[2]),I=u(I,x,N,A,g,22,a[3]),A=u(A,I,x,N,f,7,a[4]),N=u(N,A,I,x,m,12,a[5]),x=u(x,N,A,I,v,17,a[6]),I=u(I,x,N,A,S,22,a[7]),A=u(A,I,x,N,T,7,a[8]),N=u(N,A,I,x,y,12,a[9]),x=u(x,N,A,I,E,17,a[10]),I=u(I,x,N,A,b,22,a[11]),A=u(A,I,x,N,C,7,a[12]),N=u(N,A,I,x,_,12,a[13]),x=u(x,N,A,I,R,17,a[14]),A=d(A,I=u(I,x,N,A,w,22,a[15]),x,N,c,5,a[16]),N=d(N,A,I,x,v,9,a[17]),x=d(x,N,A,I,b,14,a[18]),I=d(I,x,N,A,o,20,a[19]),A=d(A,I,x,N,m,5,a[20]),N=d(N,A,I,x,E,9,a[21]),x=d(x,N,A,I,w,14,a[22]),I=d(I,x,N,A,f,20,a[23]),A=d(A,I,x,N,y,5,a[24]),N=d(N,A,I,x,R,9,a[25]),x=d(x,N,A,I,g,14,a[26]),I=d(I,x,N,A,T,20,a[27]),A=d(A,I,x,N,_,5,a[28]),N=d(N,A,I,x,l,9,a[29]),x=d(x,N,A,I,S,14,a[30]),A=p(A,I=d(I,x,N,A,C,20,a[31]),x,N,m,4,a[32]),N=p(N,A,I,x,T,11,a[33]),x=p(x,N,A,I,b,16,a[34]),I=p(I,x,N,A,R,23,a[35]),A=p(A,I,x,N,c,4,a[36]),N=p(N,A,I,x,f,11,a[37]),x=p(x,N,A,I,S,16,a[38]),I=p(I,x,N,A,E,23,a[39]),A=p(A,I,x,N,_,4,a[40]),N=p(N,A,I,x,o,11,a[41]),x=p(x,N,A,I,g,16,a[42]),I=p(I,x,N,A,v,23,a[43]),A=p(A,I,x,N,y,4,a[44]),N=p(N,A,I,x,C,11,a[45]),x=p(x,N,A,I,w,16,a[46]),A=h(A,I=p(I,x,N,A,l,23,a[47]),x,N,o,6,a[48]),N=h(N,A,I,x,S,10,a[49]),x=h(x,N,A,I,R,15,a[50]),I=h(I,x,N,A,m,21,a[51]),A=h(A,I,x,N,C,6,a[52]),N=h(N,A,I,x,g,10,a[53]),x=h(x,N,A,I,E,15,a[54]),I=h(I,x,N,A,c,21,a[55]),A=h(A,I,x,N,T,6,a[56]),N=h(N,A,I,x,w,10,a[57]),x=h(x,N,A,I,v,15,a[58]),I=h(I,x,N,A,_,21,a[59]),A=h(A,I,x,N,f,6,a[60]),N=h(N,A,I,x,b,10,a[61]),x=h(x,N,A,I,l,15,a[62]),I=h(I,x,N,A,y,21,a[63]),s[0]=s[0]+A|0,s[1]=s[1]+I|0,s[2]=s[2]+x|0,s[3]=s[3]+N|0},_doFinalize:function(){var t=this._data,r=t.words,i=8*this._nDataBytes,n=8*t.sigBytes;r[n>>>5]|=128<<24-n%32;var s=e.floor(i/4294967296),o=i;r[15+(n+64>>>9<<4)]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),r[14+(n+64>>>9<<4)]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),t.sigBytes=4*(r.length+1),this._process();for(var a=this._hash,c=a.words,u=0;u<4;u++){var d=c[u];c[u]=16711935&(d<<8|d>>>24)|4278255360&(d<<24|d>>>8)}return a},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}});function u(e,t,r,i,n,s,o){var a=e+(t&r|~t&i)+n+o;return(a<>>32-s)+t}function d(e,t,r,i,n,s,o){var a=e+(t&i|r&~i)+n+o;return(a<>>32-s)+t}function p(e,t,r,i,n,s,o){var a=e+(t^r^i)+n+o;return(a<>>32-s)+t}function h(e,t,r,i,n,s,o){var a=e+(r^(t|~i))+n+o;return(a<>>32-s)+t}t.MD5=s._createHelper(c),t.HmacMD5=s._createHmacHelper(c)}(Math),i.MD5)},function(e,t,r){var i;e.exports=(i=i||function(e,t){var r=Object.create||function(){function e(){}return function(t){var r;return e.prototype=t,r=new e,e.prototype=null,r}}(),i={},n=i.lib={},s=n.Base={extend:function(e){var t=r(this);return e&&t.mixIn(e),t.hasOwnProperty("init")&&this.init!==t.init||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},o=n.WordArray=s.extend({init:function(e,t){e=this.words=e||[],this.sigBytes=null!=t?t:4*e.length},toString:function(e){return(e||c).stringify(this)},concat:function(e){var t=this.words,r=e.words,i=this.sigBytes,n=e.sigBytes;if(this.clamp(),i%4)for(var s=0;s>>2]>>>24-s%4*8&255;t[i+s>>>2]|=o<<24-(i+s)%4*8}else for(var s=0;s>>2]=r[s>>>2];return this.sigBytes+=n,this},clamp:function(){var t=this.words,r=this.sigBytes;t[r>>>2]&=4294967295<<32-r%4*8,t.length=e.ceil(r/4)},clone:function(){var e=s.clone.call(this);return e.words=this.words.slice(0),e},random:function(t){for(var r,i=[],n=function(t){var t=t,r=987654321,i=4294967295;return function(){var n=((r=36969*(65535&r)+(r>>16)&i)<<16)+(t=18e3*(65535&t)+(t>>16)&i)&i;return n/=4294967296,(n+=.5)*(e.random()>.5?1:-1)}},s=0;s>>2]>>>24-n%4*8&255;i.push((s>>>4).toString(16)),i.push((15&s).toString(16))}return i.join("")},parse:function(e){for(var t=e.length,r=[],i=0;i>>3]|=parseInt(e.substr(i,2),16)<<24-i%8*4;return new o.init(r,t/2)}},u=a.Latin1={stringify:function(e){for(var t=e.words,r=e.sigBytes,i=[],n=0;n>>2]>>>24-n%4*8&255;i.push(String.fromCharCode(s))}return i.join("")},parse:function(e){for(var t=e.length,r=[],i=0;i>>2]|=(255&e.charCodeAt(i))<<24-i%4*8;return new o.init(r,t)}},d=a.Utf8={stringify:function(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function(e){return u.parse(unescape(encodeURIComponent(e)))}},p=n.BufferedBlockAlgorithm=s.extend({reset:function(){this._data=new o.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=d.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var r=this._data,i=r.words,n=r.sigBytes,s=this.blockSize,a=4*s,c=n/a,u=(c=t?e.ceil(c):e.max((0|c)-this._minBufferSize,0))*s,d=e.min(4*u,n);if(u){for(var p=0;p699)throw new TypeError("Invalid statusCode: "+t.statusCode);var n=t.reasonPhrase?t.reasonPhrase:i.getReasonPhrase(t.statusCode),s="SIP/2.0 "+t.statusCode+" "+n+r;t.statusCode>=100&&t.statusCode,t.statusCode;var o="From: "+e.getHeader("From")+r,a="Call-ID: "+e.callId+r,c="CSeq: "+e.cseq+" "+e.method+r,u=e.getHeaders("via").reduce(function(e,t){return e+"Via: "+t+r},""),d="To: "+e.getHeader("to");if(t.statusCode>100&&!e.parseHeader("to").hasParam("tag")){var p=t.toTag;p||(p=i.newTag()),d+=";tag="+p}d+=r;var h="";t.supported&&(h="Supported: "+t.supported.join(", ")+r);var l="";t.userAgent&&(l="User-Agent: "+t.userAgent+r);var g="";return t.extraHeaders&&(g=t.extraHeaders.reduce(function(e,t){return e+t.trim()+r},"")),s+=u,s+=o,s+=d,s+=c,s+=a,s+=h,s+=l,s+=g,t.body?(s+="Content-Type: "+t.body.contentType+r,s+="Content-Length: "+i.str_utf8_length(t.body.content)+r+r,s+=t.body.content):s+="Content-Length: 0\r\n\r\n",{message:s}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(26),o=r(11),a=r(1),c=r(40),u=r(41),d=r(79),p=r(42),h=r(43),l=r(31),g=r(44),f=r(45),m=r(46),v=r(47),S=r(48),T=r(49),y=function(e){function t(t,r,i,n){var o=e.call(this,r,i)||this;return o.initialTransaction=t,o._signalingState=s.SignalingState.Initial,o.ackWait=!1,o.delegate=n,t instanceof a.InviteServerTransaction&&(o.ackWait=!0),o.early||o.start2xxRetransmissionTimer(),o.signalingStateTransition(t.request),o.logger=r.loggerFactory.getLogger("sip.invite-dialog"),o.logger.log("INVITE dialog "+o.id+" constructed"),o}return i.__extends(t,e),t.prototype.dispose=function(){e.prototype.dispose.call(this),this._signalingState=s.SignalingState.Closed,this._offer=void 0,this._answer=void 0,this.invite2xxTimer&&(clearTimeout(this.invite2xxTimer),this.invite2xxTimer=void 0),this.logger.log("INVITE dialog "+this.id+" destroyed")},Object.defineProperty(t.prototype,"sessionState",{get:function(){return this.early?s.SessionState.Early:this.ackWait?s.SessionState.AckWait:this._signalingState===s.SignalingState.Closed?s.SessionState.Terminated:s.SessionState.Confirmed},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"signalingState",{get:function(){return this._signalingState},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"offer",{get:function(){return this._offer},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"answer",{get:function(){return this._answer},enumerable:!0,configurable:!0}),t.prototype.confirm=function(){this.early&&this.start2xxRetransmissionTimer(),e.prototype.confirm.call(this)},t.prototype.reConfirm=function(){this.reinviteUserAgentServer&&this.startReInvite2xxRetransmissionTimer()},t.prototype.ack=function(e){var t;if(void 0===e&&(e={}),this.logger.log("INVITE dialog "+this.id+" sending ACK request"),this.reinviteUserAgentClient){if(!(this.reinviteUserAgentClient.transaction instanceof a.InviteClientTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");t=this.reinviteUserAgentClient.transaction,this.reinviteUserAgentClient=void 0}else{if(!(this.initialTransaction instanceof a.InviteClientTransaction))throw new Error("Initial transaction not instance of InviteClientTransaction.");t=this.initialTransaction}e.cseq=t.request.cseq;var r=this.createOutgoingRequestMessage(n.C.ACK,e);return t.ackResponse(r),this.signalingStateTransition(r),{message:r}},t.prototype.bye=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending BYE request"),this.initialTransaction instanceof a.InviteServerTransaction){if(this.early)throw new Error("UAS MUST NOT send a BYE on early dialogs.");if(this.ackWait&&this.initialTransaction.state!==a.TransactionState.Terminated)throw new Error("UAS MUST NOT send a BYE on a confirmed dialog until it has received an ACK for its 2xx response or until the server transaction times out.")}return new c.ByeUserAgentClient(this,e,t)},t.prototype.info=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending INFO request"),this.early)throw new Error("Dialog not confirmed.");return new d.InfoUserAgentClient(this,e,t)},t.prototype.invite=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending INVITE request"),this.early)throw new Error("Dialog not confirmed.");if(this.reinviteUserAgentClient)throw new Error("There is an ongoing re-INVITE client transaction.");if(this.reinviteUserAgentServer)throw new Error("There is an ongoing re-INVITE server transaction.");return new m.ReInviteUserAgentClient(this,e,t)},t.prototype.notify=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending NOTIFY request"),this.early)throw new Error("Dialog not confirmed.");return new h.NotifyUserAgentClient(this,e,t)},t.prototype.prack=function(e,t){return this.logger.log("INVITE dialog "+this.id+" sending PRACK request"),new g.PrackUserAgentClient(this,e,t)},t.prototype.refer=function(e,t){if(this.logger.log("INVITE dialog "+this.id+" sending REFER request"),this.early)throw new Error("Dialog not confirmed.");return new S.ReferUserAgentClient(this,e,t)},t.prototype.receiveRequest=function(t){if(this.logger.log("INVITE dialog "+this.id+" received "+t.method+" request"),t.method===n.C.ACK){if(this.ackWait){if(this.initialTransaction instanceof a.InviteClientTransaction)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");if(this.initialTransaction.request.cseq!==t.cseq)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");this.ackWait=!1}else{if(!this.reinviteUserAgentServer)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");if(this.reinviteUserAgentServer.transaction.request.cseq!==t.cseq)return void this.logger.warn("INVITE dialog "+this.id+" received unexpected "+t.method+" request, dropping.");this.reinviteUserAgentServer=void 0}return this.signalingStateTransition(t),void(this.delegate&&this.delegate.onAck&&this.delegate.onAck({message:t}))}if(this.sequenceGuard(t)){if(t.method===n.C.INVITE){if(this.reinviteUserAgentServer){var r=["Retry-After: "+(Math.floor(10*Math.random())+1)];return void this.core.replyStateless(t,{statusCode:500,extraHeaders:r})}if(this.reinviteUserAgentClient)return void this.core.replyStateless(t,{statusCode:491})}if(e.prototype.receiveRequest.call(this,t),t.method===n.C.INVITE){var i=t.parseHeader("contact");if(!i)throw new Error("Contact undefined.");if(!(i instanceof n.NameAddrHeader))throw new Error("Contact not instance of NameAddrHeader.");this.dialogState.remoteTarget=i.uri}switch(t.method){case n.C.BYE:var s=new u.ByeUserAgentServer(this,t);this.delegate&&this.delegate.onBye?this.delegate.onBye(s):s.accept(),this.dispose();break;case n.C.INFO:s=new p.InfoUserAgentServer(this,t);this.delegate&&this.delegate.onInfo?this.delegate.onInfo(s):s.reject({statusCode:469,extraHeaders:["Recv-Info :"]});break;case n.C.INVITE:s=new v.ReInviteUserAgentServer(this,t);this.delegate&&this.delegate.onInvite?this.delegate.onInvite(s):s.reject({statusCode:488});break;case n.C.NOTIFY:s=new l.NotifyUserAgentServer(this,t);this.delegate&&this.delegate.onNotify?this.delegate.onNotify(s):s.accept();break;case n.C.PRACK:s=new f.PrackUserAgentServer(this,t);this.delegate&&this.delegate.onPrack?this.delegate.onPrack(s):s.accept();break;case n.C.REFER:s=new T.ReferUserAgentServer(this,t);this.delegate&&this.delegate.onRefer?this.delegate.onRefer(s):s.reject();break;default:this.logger.log("INVITE dialog "+this.id+" received unimplemented "+t.method+" request"),this.core.replyStateless(t,{statusCode:501})}}else this.logger.log("INVITE dialog "+this.id+" rejected out of order "+t.method+" request.")},t.prototype.reliableSequenceGuard=function(e){var t=e.statusCode;if(!t)throw new Error("Status code undefined");if(t>100&&t<200){var r=e.getHeader("require"),i=e.getHeader("rseq"),n=r&&r.includes("100rel")&&i?Number(i):void 0;if(n){if(this.rseq&&this.rseq+1!==n)return!1;this.rseq||(this.rseq=n)}}return!0},t.prototype.signalingStateTransition=function(e){var t=n.getBody(e);if(t&&"session"===t.contentDisposition){if(e instanceof n.IncomingRequestMessage)switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveRemoteOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.HaveRemoteOffer:case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}if(e instanceof n.IncomingResponseMessage)switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveRemoteOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.HaveRemoteOffer:case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}if(e instanceof n.OutgoingRequestMessage)switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveLocalOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:break;case s.SignalingState.HaveRemoteOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}if(n.isBody(e))switch(this._signalingState){case s.SignalingState.Initial:case s.SignalingState.Stable:this._signalingState=s.SignalingState.HaveLocalOffer,this._offer=t,this._answer=void 0;break;case s.SignalingState.HaveLocalOffer:break;case s.SignalingState.HaveRemoteOffer:this._signalingState=s.SignalingState.Stable,this._answer=t;break;case s.SignalingState.Closed:break;default:throw new Error("Unexpected signaling state.")}}},t.prototype.start2xxRetransmissionTimer=function(){var e=this;if(this.initialTransaction instanceof a.InviteServerTransaction){var t=this.initialTransaction,r=o.Timers.T1,i=function(){e.ackWait?(e.logger.log("No ACK for 2xx response received, attempting retransmission"),t.retransmitAcceptedResponse(),r=Math.min(2*r,o.Timers.T2),e.invite2xxTimer=setTimeout(i,r)):e.invite2xxTimer=void 0};this.invite2xxTimer=setTimeout(i,r);var n=function(){t.state===a.TransactionState.Terminated&&(t.removeListener("stateChanged",n),e.invite2xxTimer&&(clearTimeout(e.invite2xxTimer),e.invite2xxTimer=void 0),e.ackWait&&(e.delegate&&e.delegate.onAckTimeout?e.delegate.onAckTimeout():e.bye()))};t.addListener("stateChanged",n)}},t.prototype.startReInvite2xxRetransmissionTimer=function(){var e=this;if(this.reinviteUserAgentServer&&this.reinviteUserAgentServer.transaction instanceof a.InviteServerTransaction){var t=this.reinviteUserAgentServer.transaction,r=o.Timers.T1,i=function(){e.reinviteUserAgentServer?(e.logger.log("No ACK for 2xx response received, attempting retransmission"),t.retransmitAcceptedResponse(),r=Math.min(2*r,o.Timers.T2),e.invite2xxTimer=setTimeout(i,r)):e.invite2xxTimer=void 0};this.invite2xxTimer=setTimeout(i,r);var n=function(){t.state===a.TransactionState.Terminated&&(t.removeListener("stateChanged",n),e.invite2xxTimer&&(clearTimeout(e.invite2xxTimer),e.invite2xxTimer=void 0),e.reinviteUserAgentServer)};t.addListener("stateChanged",n)}},t}(r(20).Dialog);t.SessionDialog=y},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Initial="Initial",e.Early="Early",e.AckWait="AckWait",e.Confirmed="Confirmed",e.Terminated="Terminated"}(t.SessionState||(t.SessionState={})),function(e){e.Initial="Initial",e.HaveLocalOffer="HaveLocalOffer",e.HaveRemoteOffer="HaveRemoteOffer",e.Stable="Stable",e.Closed="Closed"}(t.SignalingState||(t.SignalingState={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t){return e.call(this,t||"Transaction state error.")||this}return i.__extends(t,e),t}(r(29).Exception);t.TransactionStateError=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t){return e.call(this,t||"Unspecified transport error.")||this}return i.__extends(t,e),t}(r(29).Exception);t.TransportError=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(11),o=r(30),a=r(14),c=function(e){function t(t,r,i){return e.call(this,t,r,i,a.TransactionState.Proceeding,"sip.transaction.ist")||this}return i.__extends(t,e),t.prototype.dispose=function(){this.stopProgressExtensionTimer(),this.H&&(clearTimeout(this.H),this.H=void 0),this.I&&(clearTimeout(this.I),this.I=void 0),this.L&&(clearTimeout(this.L),this.L=void 0),e.prototype.dispose.call(this)},Object.defineProperty(t.prototype,"kind",{get:function(){return"ist"},enumerable:!0,configurable:!0}),t.prototype.receiveRequest=function(e){var t=this;switch(this.state){case a.TransactionState.Proceeding:if(e.method===n.C.INVITE)return void(this.lastProvisionalResponse&&this.send(this.lastProvisionalResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of provisional response.")}));break;case a.TransactionState.Accepted:if(e.method===n.C.INVITE)return;break;case a.TransactionState.Completed:if(e.method===n.C.INVITE){if(!this.lastFinalResponse)throw new Error("Last final response undefined.");return void this.send(this.lastFinalResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of final response.")})}if(e.method===n.C.ACK)return void this.stateTransition(a.TransactionState.Confirmed);break;case a.TransactionState.Confirmed:case a.TransactionState.Terminated:if(e.method===n.C.INVITE||e.method===n.C.ACK)return;break;default:throw new Error("Invalid state "+this.state)}var r="INVITE server transaction received unexpected "+e.method+" request while in state "+this.state+".";this.logger.warn(r)},t.prototype.receiveResponse=function(e,t){var r=this;if(e<100||e>699)throw new Error("Invalid status code "+e);switch(this.state){case a.TransactionState.Proceeding:if(e>=100&&e<=199)return this.lastProvisionalResponse=t,e>100&&this.startProgressExtensionTimer(),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send 1xx response.")});if(e>=200&&e<=299)return this.lastFinalResponse=t,this.stateTransition(a.TransactionState.Accepted),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send 2xx response.")});if(e>=300&&e<=699)return this.lastFinalResponse=t,this.stateTransition(a.TransactionState.Completed),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send non-2xx final response.")});break;case a.TransactionState.Accepted:if(e>=200&&e<=299)return void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send 2xx response.")});break;case a.TransactionState.Completed:case a.TransactionState.Confirmed:case a.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}var i="INVITE server transaction received unexpected "+e+" response from TU while in state "+this.state+".";throw this.logger.error(i),new Error(i)},t.prototype.retransmitAcceptedResponse=function(){var e=this;this.state===a.TransactionState.Accepted&&this.lastFinalResponse&&this.send(this.lastFinalResponse).catch(function(t){e.logTransportError(t,"Failed to send 2xx response.")})},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e)},t.prototype.typeToString=function(){return"INVITE server transaction"},t.prototype.stateTransition=function(e){var t=this,r=function(){throw new Error("Invalid state transition from "+t.state+" to "+e)};switch(e){case a.TransactionState.Proceeding:r();break;case a.TransactionState.Accepted:case a.TransactionState.Completed:this.state!==a.TransactionState.Proceeding&&r();break;case a.TransactionState.Confirmed:this.state!==a.TransactionState.Completed&&r();break;case a.TransactionState.Terminated:this.state!==a.TransactionState.Accepted&&this.state!==a.TransactionState.Completed&&this.state!==a.TransactionState.Confirmed&&r();break;default:r()}this.stopProgressExtensionTimer(),e===a.TransactionState.Accepted&&(this.L=setTimeout(function(){return t.timer_L()},s.Timers.TIMER_L)),e===a.TransactionState.Completed&&(this.H=setTimeout(function(){return t.timer_H()},s.Timers.TIMER_H)),e===a.TransactionState.Confirmed&&(this.I=setTimeout(function(){return t.timer_I()},s.Timers.TIMER_I)),e===a.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.startProgressExtensionTimer=function(){var e=this;void 0===this.progressExtensionTimer&&(this.progressExtensionTimer=setInterval(function(){if(e.logger.debug("Progress extension timer expired for INVITE server transaction "+e.id+"."),!e.lastProvisionalResponse)throw new Error("Last provisional response undefined.");e.send(e.lastProvisionalResponse).catch(function(t){e.logTransportError(t,"Failed to send retransmission of provisional response.")})},s.Timers.PROVISIONAL_RESPONSE_INTERVAL))},t.prototype.stopProgressExtensionTimer=function(){void 0!==this.progressExtensionTimer&&(clearInterval(this.progressExtensionTimer),this.progressExtensionTimer=void 0)},t.prototype.timer_G=function(){},t.prototype.timer_H=function(){this.logger.debug("Timer H expired for INVITE server transaction "+this.id+"."),this.state===a.TransactionState.Completed&&(this.logger.warn("ACK to negative final response was never received, terminating transaction."),this.stateTransition(a.TransactionState.Terminated))},t.prototype.timer_I=function(){this.logger.debug("Timer I expired for INVITE server transaction "+this.id+"."),this.stateTransition(a.TransactionState.Terminated)},t.prototype.timer_L=function(){this.logger.debug("Timer L expired for INVITE server transaction "+this.id+"."),this.state===a.TransactionState.Accepted&&this.stateTransition(a.TransactionState.Terminated)},t}(o.ServerTransaction);t.InviteServerTransaction=c},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(11),s=r(27),o=r(14),a=function(e){function t(t,r,i){var s=e.call(this,t,r,i,o.TransactionState.Trying,"sip.transaction.nict")||this;return s.F=setTimeout(function(){return s.timer_F()},n.Timers.TIMER_F),s.send(t.toString()).catch(function(e){s.logTransportError(e,"Failed to send initial outgoing request.")}),s}return i.__extends(t,e),t.prototype.dispose=function(){this.F&&(clearTimeout(this.F),this.F=void 0),this.K&&(clearTimeout(this.K),this.K=void 0),e.prototype.dispose.call(this)},Object.defineProperty(t.prototype,"kind",{get:function(){return"nict"},enumerable:!0,configurable:!0}),t.prototype.receiveResponse=function(e){var t=e.statusCode;if(!t||t<100||t>699)throw new Error("Invalid status code "+t);switch(this.state){case o.TransactionState.Trying:if(t>=100&&t<=199)return this.stateTransition(o.TransactionState.Proceeding),void(this.user.receiveResponse&&this.user.receiveResponse(e));if(t>=200&&t<=699)return this.stateTransition(o.TransactionState.Completed),408===t?void this.onRequestTimeout():void(this.user.receiveResponse&&this.user.receiveResponse(e));break;case o.TransactionState.Proceeding:if(t>=100&&t<=199&&this.user.receiveResponse)return this.user.receiveResponse(e);if(t>=200&&t<=699)return this.stateTransition(o.TransactionState.Completed),408===t?void this.onRequestTimeout():void(this.user.receiveResponse&&this.user.receiveResponse(e));case o.TransactionState.Completed:case o.TransactionState.Terminated:return;default:throw new Error("Invalid state "+this.state)}var r="Non-INVITE client transaction received unexpected "+t+" response while in state "+this.state+".";this.logger.warn(r)},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e),this.stateTransition(o.TransactionState.Terminated,!0)},t.prototype.typeToString=function(){return"non-INVITE client transaction"},t.prototype.stateTransition=function(e,t){var r=this;void 0===t&&(t=!1);var i=function(){throw new Error("Invalid state transition from "+r.state+" to "+e)};switch(e){case o.TransactionState.Trying:i();break;case o.TransactionState.Proceeding:this.state!==o.TransactionState.Trying&&i();break;case o.TransactionState.Completed:this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding&&i();break;case o.TransactionState.Terminated:this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding&&this.state!==o.TransactionState.Completed&&(t||i());break;default:i()}e===o.TransactionState.Completed&&(this.F&&(clearTimeout(this.F),this.F=void 0),this.K=setTimeout(function(){return r.timer_K()},n.Timers.TIMER_K)),e===o.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.timer_F=function(){this.logger.debug("Timer F expired for non-INVITE client transaction "+this.id+"."),this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding||(this.onRequestTimeout(),this.stateTransition(o.TransactionState.Terminated))},t.prototype.timer_K=function(){this.state===o.TransactionState.Completed&&this.stateTransition(o.TransactionState.Terminated)},t}(s.ClientTransaction);t.NonInviteClientTransaction=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(11),s=r(30),o=r(14),a=function(e){function t(t,r,i){return e.call(this,t,r,i,o.TransactionState.Trying,"sip.transaction.nist")||this}return i.__extends(t,e),t.prototype.dispose=function(){this.J&&(clearTimeout(this.J),this.J=void 0),e.prototype.dispose.call(this)},Object.defineProperty(t.prototype,"kind",{get:function(){return"nist"},enumerable:!0,configurable:!0}),t.prototype.receiveRequest=function(e){var t=this;switch(this.state){case o.TransactionState.Trying:break;case o.TransactionState.Proceeding:if(!this.lastResponse)throw new Error("Last response undefined.");this.send(this.lastResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of provisional response.")});break;case o.TransactionState.Completed:if(!this.lastResponse)throw new Error("Last response undefined.");this.send(this.lastResponse).catch(function(e){t.logTransportError(e,"Failed to send retransmission of final response.")});break;case o.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}},t.prototype.receiveResponse=function(e,t){var r=this;if(e<100||e>699)throw new Error("Invalid status code "+e);if(e>100&&e<=199)throw new Error("Provisional response other than 100 not allowed.");switch(this.state){case o.TransactionState.Trying:if(this.lastResponse=t,e>=100&&e<200)return this.stateTransition(o.TransactionState.Proceeding),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send provisional response.")});if(e>=200&&e<=699)return this.stateTransition(o.TransactionState.Completed),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send final response.")});break;case o.TransactionState.Proceeding:if(this.lastResponse=t,e>=200&&e<=699)return this.stateTransition(o.TransactionState.Completed),void this.send(t).catch(function(e){r.logTransportError(e,"Failed to send final response.")});break;case o.TransactionState.Completed:return;case o.TransactionState.Terminated:break;default:throw new Error("Invalid state "+this.state)}var i="Non-INVITE server transaction received unexpected "+e+" response from TU while in state "+this.state+".";throw this.logger.error(i),new Error(i)},t.prototype.onTransportError=function(e){this.user.onTransportError&&this.user.onTransportError(e),this.stateTransition(o.TransactionState.Terminated,!0)},t.prototype.typeToString=function(){return"non-INVITE server transaction"},t.prototype.stateTransition=function(e,t){var r=this;void 0===t&&(t=!1);var i=function(){throw new Error("Invalid state transition from "+r.state+" to "+e)};switch(e){case o.TransactionState.Trying:i();break;case o.TransactionState.Proceeding:this.state!==o.TransactionState.Trying&&i();break;case o.TransactionState.Completed:this.state!==o.TransactionState.Trying&&this.state!==o.TransactionState.Proceeding&&i();break;case o.TransactionState.Terminated:this.state!==o.TransactionState.Proceeding&&this.state!==o.TransactionState.Completed&&(t||i());break;default:i()}e===o.TransactionState.Completed&&(this.J=setTimeout(function(){return r.timer_J()},n.Timers.TIMER_J)),e===o.TransactionState.Terminated&&this.dispose(),this.setState(e)},t.prototype.timer_J=function(){this.logger.debug("Timer J expired for NON-INVITE server transaction "+this.id+"."),this.state===o.TransactionState.Completed&&this.stateTransition(o.TransactionState.Terminated)},t}(s.ServerTransaction);t.NonInviteServerTransaction=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=function(e){function t(t,r,i){var o=t.createOutgoingRequestMessage(n.C.INFO,i);return e.call(this,s.NonInviteClientTransaction,t.userAgentCore,o,r)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.InfoUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Initial="Initial",e.NotifyWait="NotifyWait",e.Pending="Pending",e.Active="Active",e.Terminated="Terminated"}(t.SubscriptionState||(t.SubscriptionState={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0);i.__exportStar(r(33),t),i.__exportStar(r(82),t),i.__exportStar(r(52),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(33),n=r(52),s=function(){function e(){this.builtinEnabled=!0,this._level=i.Levels.log,this.loggers={},this.logger=this.getLogger("sip:loggerfactory")}return Object.defineProperty(e.prototype,"level",{get:function(){return this._level},set:function(e){e>=0&&e<=3?this._level=e:e>3?this._level=3:i.Levels.hasOwnProperty(e)?this._level=e:this.logger.error("invalid 'level' parameter value: "+JSON.stringify(e))},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"connector",{get:function(){return this._connector},set:function(e){e?"function"==typeof e?this._connector=e:this.logger.error("invalid 'connector' parameter value: "+JSON.stringify(e)):this._connector=void 0},enumerable:!0,configurable:!0}),e.prototype.getLogger=function(e,t){if(t&&3===this.level)return new n.Logger(this,e,t);if(this.loggers[e])return this.loggers[e];var r=new n.Logger(this,e);return this.loggers[e]=r,r},e.prototype.genericLog=function(e,t,r,n){this.level>=e&&this.builtinEnabled&&this.print(e,t,r,n),this.connector&&this.connector(i.Levels[e],t,r,n)},e.prototype.print=function(e,t,r,n){if("string"==typeof n){var s=[new Date,t];r&&s.push(r),n=s.concat(n).join(" | ")}switch(e){case i.Levels.error:console.error(n);break;case i.Levels.warn:console.warn(n);break;case i.Levels.log:console.log(n);break;case i.Levels.debug:console.debug(n)}},e}();t.LoggerFactory=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(0).__exportStar(r(84),t)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(2),s=r(1),o=r(53),a=r(16),c=["application/sdp","application/dtmf-relay"],u=function(){function e(e,t){void 0===t&&(t={}),this.userAgentClients=new Map,this.userAgentServers=new Map,this.configuration=e,this.delegate=t,this.dialogs=new Map,this.subscribers=new Map,this.logger=e.loggerFactory.getLogger("sip.user-agent-core")}return e.prototype.dispose=function(){this.reset()},e.prototype.reset=function(){this.dialogs.forEach(function(e){return e.dispose()}),this.dialogs.clear(),this.subscribers.forEach(function(e){return e.dispose()}),this.subscribers.clear(),this.userAgentClients.forEach(function(e){return e.dispose()}),this.userAgentClients.clear(),this.userAgentServers.forEach(function(e){return e.dispose()}),this.userAgentServers.clear()},Object.defineProperty(e.prototype,"loggerFactory",{get:function(){return this.configuration.loggerFactory},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"transport",{get:function(){var e=this.configuration.transportAccessor();if(!e)throw new Error("Transport undefined.");return e},enumerable:!0,configurable:!0}),e.prototype.invite=function(e,t){return new o.InviteUserAgentClient(this,e,t)},e.prototype.message=function(e,t){return new o.MessageUserAgentClient(this,e,t)},e.prototype.publish=function(e,t){return new o.PublishUserAgentClient(this,e,t)},e.prototype.register=function(e,t){return new o.RegisterUserAgentClient(this,e,t)},e.prototype.subscribe=function(e,t){return new o.SubscribeUserAgentClient(this,e,t)},e.prototype.request=function(e,t){return new o.UserAgentClient(s.NonInviteClientTransaction,this,e,t)},e.prototype.makeOutgoingRequestMessage=function(e,t,r,s,o,a,c){var u=this.configuration.sipjsId,d=this.configuration.displayName,p=this.configuration.viaForceRport,h=this.configuration.hackViaTcp,l=this.configuration.supportedOptionTags.slice();e===n.C.REGISTER&&l.push("path","gruu"),e===n.C.INVITE&&(this.configuration.contact.pubGruu||this.configuration.contact.tempGruu)&&l.push("gruu");var g={callIdPrefix:u,forceRport:p,fromDisplayName:d,hackViaTcp:h,optionTags:l,routeSet:this.configuration.routeSet,userAgentString:this.configuration.userAgentHeaderFieldValue,viaHost:this.configuration.viaHost},f=i.__assign({},g,o);return new n.OutgoingRequestMessage(e,t,r,s,f,a,c)},e.prototype.receiveIncomingRequestFromTransport=function(e){this.receiveRequestFromTransport(e)},e.prototype.receiveIncomingResponseFromTransport=function(e){this.receiveResponseFromTransport(e)},e.prototype.replyStateless=function(e,t){var r=this.configuration.userAgentHeaderFieldValue,s=this.configuration.supportedOptionTagsResponse;t=i.__assign({},t,{userAgent:r,supported:s});var o=n.constructOutgoingResponse(e,t);return this.transport.send(o.message),o},e.prototype.receiveRequestFromTransport=function(e){var t=e.viaBranch,r=this.userAgentServers.get(t);e.method===n.C.ACK&&r&&r.transaction.state===s.TransactionState.Accepted&&r instanceof o.InviteUserAgentServer?this.logger.warn("Discarding out of dialog ACK after 2xx response sent on transaction "+t+"."):e.method!==n.C.CANCEL?r?r.transaction.receiveRequest(e):this.receiveRequest(e):r?(this.replyStateless(e,{statusCode:200}),r.transaction instanceof s.InviteServerTransaction&&r.transaction.state===s.TransactionState.Proceeding&&r instanceof o.InviteUserAgentServer&&r.receiveCancel(e)):this.replyStateless(e,{statusCode:481})},e.prototype.receiveRequest=function(e){if(a.AllowedMethods.includes(e.method)){if(!e.ruri)throw new Error("Request-URI undefined.");if("sip"===e.ruri.scheme){var t=e.ruri,r=function(e){return!!e&&e.user===t.user};if(!r(this.configuration.aor)&&!(r(this.configuration.contact.uri)||r(this.configuration.contact.pubGruu)||r(this.configuration.contact.tempGruu)))return this.logger.warn("Request-URI does not point to us."),void(e.method!==n.C.ACK&&this.replyStateless(e,{statusCode:404}));if(e.method!==n.C.INVITE||e.hasHeader("Contact")){if(!e.toTag){var i=e.viaBranch;if(!this.userAgentServers.has(i))if(Array.from(this.userAgentServers.values()).some(function(t){return t.transaction.request.fromTag===e.fromTag&&t.transaction.request.callId===e.callId&&t.transaction.request.cseq===e.cseq}))return void this.replyStateless(e,{statusCode:482})}e.toTag?this.receiveInsideDialogRequest(e):this.receiveOutsideDialogRequest(e)}else this.replyStateless(e,{statusCode:400,reasonPhrase:"Missing Contact Header"})}else this.replyStateless(e,{statusCode:416})}else{var s="Allow: "+a.AllowedMethods.toString();this.replyStateless(e,{statusCode:405,extraHeaders:[s]})}},e.prototype.receiveInsideDialogRequest=function(e){if(e.method===n.C.NOTIFY){var t=e.parseHeader("Event");if(!t||!t.event)return void this.replyStateless(e,{statusCode:489});var r=e.callId+e.toTag+t.event,i=this.subscribers.get(r);if(i){var s=new o.NotifyUserAgentServer(this,e);return void i.onNotify(s)}}var u=e.callId+e.toTag+e.fromTag,d=this.dialogs.get(u);if(d){if(e.method===n.C.OPTIONS){var p="Allow: "+a.AllowedMethods.toString(),h="Accept: "+c.toString();return void this.replyStateless(e,{statusCode:200,extraHeaders:[p,h]})}d.receiveRequest(e)}else e.method!==n.C.ACK&&this.replyStateless(e,{statusCode:481})},e.prototype.receiveOutsideDialogRequest=function(e){switch(e.method){case n.C.ACK:break;case n.C.BYE:this.replyStateless(e,{statusCode:481});break;case n.C.CANCEL:throw new Error("Unexpected out of dialog request method "+e.method+".");case n.C.INFO:this.replyStateless(e,{statusCode:405});break;case n.C.INVITE:var t=new o.InviteUserAgentServer(this,e);this.delegate.onInvite?this.delegate.onInvite(t):t.reject();break;case n.C.MESSAGE:t=new o.MessageUserAgentServer(this,e);this.delegate.onMessage?this.delegate.onMessage(t):t.accept();break;case n.C.NOTIFY:t=new o.NotifyUserAgentServer(this,e);this.delegate.onNotify?this.delegate.onNotify(t):this.replyStateless(e,{statusCode:405});break;case n.C.OPTIONS:var r="Allow: "+a.AllowedMethods.toString(),i="Accept: "+c.toString();this.replyStateless(e,{statusCode:200,extraHeaders:[r,i]});break;case n.C.REFER:t=new o.ReferUserAgentServer(this,e);this.delegate.onRefer?this.delegate.onRefer(t):this.replyStateless(e,{statusCode:405});break;case n.C.SUBSCRIBE:t=new o.SubscribeUserAgentServer(this,e);this.delegate.onSubscribe?this.delegate.onSubscribe(t):t.reject();break;default:throw new Error("Unexpected out of dialog request method "+e.method+".")}},e.prototype.receiveResponseFromTransport=function(e){if(e.getHeaders("via").length>1)this.logger.warn("More than one Via header field present in the response, dropping");else{var t=e.viaBranch+e.method,r=this.userAgentClients.get(t);r?r.transaction.receiveResponse(e):this.logger.warn("Discarding unmatched "+e.statusCode+" response to "+e.method+" "+t+".")}},e}();t.UserAgentCore=u},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.CancelUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(19),s=r(1),o=function(e){function t(t,r,i){var n=e.call(this,s.InviteClientTransaction,t,r,i)||this;return n.confirmedDialogAcks=new Map,n.confirmedDialogs=new Map,n.earlyDialogs=new Map,n.delegate=i,n}return i.__extends(t,e),t.prototype.dispose=function(){this.earlyDialogs.forEach(function(e){return e.dispose()}),this.earlyDialogs.clear(),e.prototype.dispose.call(this)},t.prototype.receiveResponse=function(e){var t=this;if(this.authenticationGuard(e)){var r=e.statusCode?e.statusCode.toString():"";if(!r)throw new Error("Response status code undefined.");switch(!0){case/^100$/.test(r):return void(this.delegate&&this.delegate.onTrying&&this.delegate.onTrying({message:e}));case/^1[0-9]{2}$/.test(r):if(!e.toTag)return void this.logger.warn("Non-100 1xx INVITE response received without a to tag, dropping.");var i=n.Dialog.initialDialogStateForUserAgentClient(this.message,e),o=this.earlyDialogs.get(i.id);if(!o){if(!((u=this.transaction)instanceof s.InviteClientTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");o=new n.SessionDialog(u,this.core,i),this.earlyDialogs.set(o.id,o)}if(!o.reliableSequenceGuard(e))return void this.logger.warn("1xx INVITE reliable response received out of order, dropping.");o.signalingStateTransition(e);var a=o;return void(this.delegate&&this.delegate.onProgress&&this.delegate.onProgress({message:e,session:a,prack:function(e){return a.prack(void 0,e)}}));case/^2[0-9]{2}$/.test(r):i=n.Dialog.initialDialogStateForUserAgentClient(this.message,e);var c=this.confirmedDialogs.get(i.id);if(c){if(p=this.confirmedDialogAcks.get(i.id)){if(!((u=this.transaction)instanceof s.InviteClientTransaction))throw new Error("Client transaction not instance of InviteClientTransaction.");u.ackResponse(p.message)}return}if(c=this.earlyDialogs.get(i.id))c.confirm(),c.recomputeRouteSet(e),this.earlyDialogs.delete(c.id),this.confirmedDialogs.set(c.id,c);else{var u;if(!((u=this.transaction)instanceof s.InviteClientTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");c=new n.SessionDialog(u,this.core,i),this.confirmedDialogs.set(c.id,c)}c.signalingStateTransition(e);var d=c;if(this.delegate&&this.delegate.onAccept)this.delegate.onAccept({message:e,session:d,ack:function(e){var r=d.ack(e);return t.confirmedDialogAcks.set(d.id,r),r}});else{var p=d.ack();this.confirmedDialogAcks.set(d.id,p)}return;case/^3[0-9]{2}$/.test(r):return this.earlyDialogs.forEach(function(e){return e.dispose()}),this.earlyDialogs.clear(),void(this.delegate&&this.delegate.onRedirect&&this.delegate.onRedirect({message:e}));case/^[4-6][0-9]{2}$/.test(r):return this.earlyDialogs.forEach(function(e){return e.dispose()}),this.earlyDialogs.clear(),void(this.delegate&&this.delegate.onReject&&this.delegate.onReject({message:e}));default:throw new Error("Invalid status code "+r)}throw new Error("Executing what should be an unreachable code path receiving "+r+" response.")}},t}(r(3).UserAgentClient);t.InviteUserAgentClient=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(19),s=r(15),o=r(26),a=r(1),c=r(16),u=function(e){function t(t,r,i){var n=e.call(this,a.InviteServerTransaction,t,r,i)||this;return n.core=t,n}return i.__extends(t,e),t.prototype.dispose=function(){this.earlyDialog&&this.earlyDialog.dispose(),e.prototype.dispose.call(this)},t.prototype.accept=function(t){if(void 0===t&&(t={statusCode:200}),!this.acceptable)throw new s.TransactionStateError(this.message.method+" not acceptable in state "+this.transaction.state+".");if(!this.confirmedDialog)if(this.earlyDialog)this.earlyDialog.confirm(),this.confirmedDialog=this.earlyDialog,this.earlyDialog=void 0;else{var r=this.transaction;if(!(r instanceof a.InviteServerTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");var u=n.Dialog.initialDialogStateForUserAgentServer(this.message,this.toTag);this.confirmedDialog=new n.SessionDialog(r,this.core,u)}var d=this.message.getHeaders("record-route").map(function(e){return"Record-Route: "+e}),p="Contact: "+this.core.configuration.contact.toString(),h="Allow: "+c.AllowedMethods.toString();if(!t.body&&(this.confirmedDialog.signalingState===o.SignalingState.Initial||this.confirmedDialog.signalingState===o.SignalingState.HaveRemoteOffer))throw new Error("Response must have a body.");t.statusCode=t.statusCode||200,t.extraHeaders=t.extraHeaders||[],t.extraHeaders=t.extraHeaders.concat(d),t.extraHeaders.push(h),t.extraHeaders.push(p);var l=e.prototype.accept.call(this,t),g=this.confirmedDialog,f=i.__assign({},l,{session:g});return t.body&&this.confirmedDialog.signalingStateTransition(t.body),f},t.prototype.progress=function(t){if(void 0===t&&(t={statusCode:180}),!this.progressable)throw new s.TransactionStateError(this.message.method+" not progressable in state "+this.transaction.state+".");if(!this.earlyDialog){var r=this.transaction;if(!(r instanceof a.InviteServerTransaction))throw new Error("Transaction not instance of InviteClientTransaction.");var o=n.Dialog.initialDialogStateForUserAgentServer(this.message,this.toTag,!0);this.earlyDialog=new n.SessionDialog(r,this.core,o)}var c=this.message.getHeaders("record-route").map(function(e){return"Record-Route: "+e}),u="Contact: "+this.core.configuration.contact;t.extraHeaders=t.extraHeaders||[],t.extraHeaders=t.extraHeaders.concat(c),t.extraHeaders.push(u);var d=e.prototype.progress.call(this,t),p=this.earlyDialog,h=i.__assign({},d,{session:p});return t.body&&this.earlyDialog.signalingStateTransition(t.body),h},t.prototype.redirect=function(t,r){return void 0===r&&(r={statusCode:302}),e.prototype.redirect.call(this,t,r)},t.prototype.reject=function(t){return void 0===t&&(t={statusCode:486}),e.prototype.reject.call(this,t)},t}(r(6).UserAgentServer);t.InviteUserAgentServer=u},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.MessageUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.NonInviteServerTransaction,t,r,i)||this;return s.core=t,s}return i.__extends(t,e),t}(r(6).UserAgentServer);t.MessageUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.PublishUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteServerTransaction,t.userAgentCore,r,i)||this}return i.__extends(t,e),t}(r(6).UserAgentServer);t.ReSubscribeUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){return e.call(this,n.NonInviteClientTransaction,t,r,i)||this}return i.__extends(t,e),t}(r(3).UserAgentClient);t.RegisterUserAgentClient=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(50),s=r(32),o=r(11),a=r(1),c=function(e){function t(t,r,i){var n=this,o=r.getHeader("Event");if(!o)throw new Error("Event undefined");var c=r.getHeader("Expires");if(!c)throw new Error("Expires undefined");return(n=e.call(this,a.NonInviteClientTransaction,t,r,i)||this).delegate=i,n.subscriberId=r.callId+r.fromTag+o,n.subscriptionExpiresRequested=n.subscriptionExpires=Number(c),n.subscriptionEvent=o,n.subscriptionState=s.SubscriptionState.NotifyWait,n.waitNotifyStart(),n}return i.__extends(t,e),t.prototype.dispose=function(){e.prototype.dispose.call(this)},t.prototype.onNotify=function(e){var t=e.message.parseHeader("Event").event;if(!t||t!==this.subscriptionEvent)return this.logger.warn("Failed to parse event."),void e.reject({statusCode:489});var r=e.message.parseHeader("Subscription-State");if(!r||!r.state)return this.logger.warn("Failed to parse subscription state."),void e.reject({statusCode:489});var i=r.state;switch(i){case"pending":case"active":case"terminated":break;default:return this.logger.warn("Invalid subscription state "+i),void e.reject({statusCode:489})}if("terminated"!==i&&!e.message.parseHeader("contact"))return this.logger.warn("Failed to parse contact."),void e.reject({statusCode:489});if(this.dialog)throw new Error("Dialog already created. This implementation only supports install of single subscriptions.");switch(this.waitNotifyStop(),this.subscriptionExpires=r.expires?Math.min(this.subscriptionExpires,Math.max(r.expires,0)):this.subscriptionExpires,i){case"pending":this.subscriptionState=s.SubscriptionState.Pending;break;case"active":this.subscriptionState=s.SubscriptionState.Active;break;case"terminated":this.subscriptionState=s.SubscriptionState.Terminated;break;default:throw new Error("Unrecognized state "+i+".")}if(this.subscriptionState!==s.SubscriptionState.Terminated){var o=n.SubscriptionDialog.initialDialogStateForSubscription(this.message,e.message);this.dialog=new n.SubscriptionDialog(this.subscriptionEvent,this.subscriptionExpires,this.subscriptionState,this.core,o)}if(this.delegate&&this.delegate.onNotify){var a=e,c=this.dialog;this.delegate.onNotify({request:a,subscription:c})}else e.accept()},t.prototype.waitNotifyStart=function(){var e=this;this.N||(this.core.subscribers.set(this.subscriberId,this),this.N=setTimeout(function(){return e.timer_N()},o.Timers.TIMER_N))},t.prototype.waitNotifyStop=function(){this.N&&(this.core.subscribers.delete(this.subscriberId),clearTimeout(this.N),this.N=void 0)},t.prototype.receiveResponse=function(t){if(this.authenticationGuard(t)){if(t.statusCode&&t.statusCode>=200&&t.statusCode<300){var r=t.getHeader("Expires");if(r){var i=Number(r);i>this.subscriptionExpiresRequested&&this.logger.warn("Expires header in a 200-class response to SUBSCRIBE with a higher value than the one in the request"),ithis.subscriptionExpires&&(this.dialog.subscriptionExpires=this.subscriptionExpires)}t.statusCode&&t.statusCode>=300&&t.statusCode<700&&this.waitNotifyStop(),e.prototype.receiveResponse.call(this,t)}},t.prototype.timer_N=function(){this.logger.warn("Timer N expired for SUBSCRIBE user agent client. Timed out waiting for NOTIFY."),this.waitNotifyStop(),this.delegate&&this.delegate.onNotifyTimeout&&this.delegate.onNotifyTimeout()},t}(r(3).UserAgentClient);t.SubscribeUserAgentClient=c},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(1),s=function(e){function t(t,r,i){var s=e.call(this,n.NonInviteServerTransaction,t,r,i)||this;return s.core=t,s}return i.__extends(t,e),t}(r(6).UserAgentServer);t.SubscribeUserAgentServer=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=function(e){function t(t,r){var i=e.call(this)||this;return i.logger=t,i}return i.__extends(t,e),t.prototype.connect=function(e){var t=this;return void 0===e&&(e={}),this.connectPromise(e).then(function(e){e.overrideEvent||t.emit("connected")})},t.prototype.send=function(e,t){var r=this;return void 0===t&&(t={}),this.sendPromise(e).then(function(e){e.overrideEvent||r.emit("messageSent",e.msg)})},t.prototype.disconnect=function(e){var t=this;return void 0===e&&(e={}),this.disconnectPromise(e).then(function(e){e.overrideEvent||t.emit("disconnected")})},t.prototype.afterConnected=function(e){this.isConnected()?e():this.once("connected",e)},t.prototype.waitForConnected=function(){var e=this;return console.warn("DEPRECATION WARNING Transport.waitForConnected(): use afterConnected() instead"),new Promise(function(t){e.afterConnected(t)})},t}(r(9).EventEmitter);t.Transport=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=r(0),n=r(9),s=r(7),o=r(4),a=r(10),c=r(8),u=function(e){function t(t,r,i){void 0===i&&(i={});var n=e.call(this)||this;if(n.C={MIN_DURATION:70,MAX_DURATION:6e3,DEFAULT_DURATION:100,MIN_INTER_TONE_GAP:50,DEFAULT_INTER_TONE_GAP:500},n.type=o.TypeStrings.DTMF,void 0===r)throw new TypeError("Not enough arguments");if(n.logger=t.ua.getLogger("sip.invitecontext.dtmf",t.id),n.owner=t,"string"==typeof r)r=r.toUpperCase();else{if("number"!=typeof r)throw new TypeError("Invalid tone: "+r);r=r.toString()}if(!r.match(/^[0-9A-D#*]$/))throw new TypeError("Invalid tone: "+r);n.tone=r;var s=i.duration,a=i.interToneGap;if(s&&!c.Utils.isDecimal(s))throw new TypeError("Invalid tone duration: "+s);if(s?sn.C.MAX_DURATION?(n.logger.warn("'duration' value is greater than the maximum allowed, setting it to "+n.C.MAX_DURATION+" milliseconds"),s=n.C.MAX_DURATION):s=Math.abs(s):s=n.C.DEFAULT_DURATION,n.duration=s,a&&!c.Utils.isDecimal(a))throw new TypeError("Invalid interToneGap: "+a);return a?a-1&&s.indexOf("chrome")<0?c=!0:s.indexOf("firefox")>-1&&s.indexOf("chrome")<0&&(u=!0);var d={};return c&&(d.modifiers=[a.stripG722]),u&&(d.alwaysAcquireMediaFirst=!0),n.options.ua.uri?n.anonymous=!1:n.anonymous=!0,n.ua=new o.UA({uri:n.options.ua.uri,authorizationUser:n.options.ua.authorizationUser,password:n.options.ua.password,displayName:n.options.ua.displayName,userAgentString:n.options.ua.userAgentString,register:!0,sessionDescriptionHandlerFactoryOptions:d,transportOptions:{traceSip:n.options.ua.traceSip,wsServers:n.options.ua.wsServers}}),n.state=i.STATUS_NULL,n.logger=n.ua.getLogger("sip.simple"),n.ua.on("registered",function(){n.emit("registered",n.ua)}),n.ua.on("unregistered",function(){n.emit("unregistered",n.ua)}),n.ua.on("registrationFailed",function(){n.emit("unregistered",n.ua)}),n.ua.on("invite",function(e){if(n.state!==i.STATUS_NULL&&n.state!==i.STATUS_COMPLETED)return n.logger.warn("Rejecting incoming call. Simple only supports 1 call at a time"),void e.reject();n.session=e,n.setupSession(),n.emit("ringing",n.session)}),n.ua.on("message",function(e){n.emit("message",e)}),n}return n.__extends(r,t),r.prototype.call=function(e){if(this.ua&&this.checkRegistration()){if(this.state===i.STATUS_NULL||this.state===i.STATUS_COMPLETED)return this.options.media.remote.audio&&(this.options.media.remote.audio.autoplay=!0),this.options.media.remote.video&&(this.options.media.remote.video.autoplay=!0),this.options.media.local&&this.options.media.local.video&&(this.options.media.local.video.autoplay=!0,this.options.media.local.video.volume=0),this.session=this.ua.invite(e,{sessionDescriptionHandlerOptions:{constraints:{audio:this.audio,video:this.video}}}),this.setupSession(),this.session;this.logger.warn("Cannot make more than a single call with Simple")}else this.logger.warn("A registered UA is required for calling")},r.prototype.answer=function(){if(this.state===i.STATUS_NEW||this.state===i.STATUS_CONNECTING)return this.options.media.remote.audio&&(this.options.media.remote.audio.autoplay=!0),this.options.media.remote.video&&(this.options.media.remote.video.autoplay=!0),this.session.accept({sessionDescriptionHandlerOptions:{constraints:{audio:this.audio,video:this.video}}});this.logger.warn("No call to answer")},r.prototype.reject=function(){if(this.state===i.STATUS_NEW||this.state===i.STATUS_CONNECTING)return this.session.reject();this.logger.warn("Call is already answered")},r.prototype.hangup=function(){if(this.state===i.STATUS_CONNECTED||this.state===i.STATUS_CONNECTING||this.state===i.STATUS_NEW)return this.state!==i.STATUS_CONNECTED?this.session.cancel():this.session?this.session.bye():void 0;this.logger.warn("No active call to hang up on")},r.prototype.hold=function(){if(this.state===i.STATUS_CONNECTED&&this.session&&!this.session.localHold)return this.mute(),this.logger.log("Placing session on hold"),this.session.hold();this.logger.warn("Cannot put call on hold")},r.prototype.unhold=function(){if(this.state===i.STATUS_CONNECTED&&this.session&&this.session.localHold)return this.unmute(),this.logger.log("Placing call off hold"),this.session.unhold();this.logger.warn("Cannot unhold a call that is not on hold")},r.prototype.mute=function(){this.state===i.STATUS_CONNECTED?(this.logger.log("Muting Audio"),this.toggleMute(!0),this.emit("mute",this)):this.logger.warn("An acitve call is required to mute audio")},r.prototype.unmute=function(){this.state===i.STATUS_CONNECTED?(this.logger.log("Unmuting Audio"),this.toggleMute(!1),this.emit("unmute",this)):this.logger.warn("An active call is required to unmute audio")},r.prototype.sendDTMF=function(e){this.state===i.STATUS_CONNECTED&&this.session?(this.logger.log("Sending DTMF tone: "+e),this.session.dtmf(e)):this.logger.warn("An active call is required to send a DTMF tone")},r.prototype.message=function(e,t){this.ua&&this.checkRegistration()?e&&t?this.ua.message(e,t):this.logger.warn("A destination and message are required to send a message"):this.logger.warn("A registered UA is required to send a message")},r.prototype.checkRegistration=function(){return this.anonymous||this.ua&&this.ua.isRegistered()},r.prototype.setupRemoteMedia=function(){var t=this;if(this.session){var r,i=this.session.sessionDescriptionHandler.peerConnection;i.getReceivers?(r=new e.window.MediaStream,i.getReceivers().forEach(function(e){var t=e.track;t&&r.addTrack(t)})):r=i.getRemoteStreams()[0],this.video?(this.options.media.remote.video.srcObject=r,this.options.media.remote.video.play().catch(function(){t.logger.log("play was rejected")})):this.audio&&(this.options.media.remote.audio.srcObject=r,this.options.media.remote.audio.play().catch(function(){t.logger.log("play was rejected")}))}else this.logger.warn("No session to set remote media on")},r.prototype.setupLocalMedia=function(){if(this.session){if(this.video&&this.options.media.local&&this.options.media.local.video){var t,r=this.session.sessionDescriptionHandler.peerConnection;r.getSenders?(t=new e.window.MediaStream,r.getSenders().forEach(function(e){var r=e.track;r&&"video"===r.kind&&t.addTrack(r)})):t=r.getLocalStreams()[0],this.options.media.local.video.srcObject=t,this.options.media.local.video.volume=0,this.options.media.local.video.play()}}else this.logger.warn("No session to set local media on")},r.prototype.cleanupMedia=function(){this.video&&(this.options.media.remote.video.srcObject=null,this.options.media.remote.video.pause(),this.options.media.local&&this.options.media.local.video&&(this.options.media.local.video.srcObject=null,this.options.media.local.video.pause())),this.audio&&(this.options.media.remote.audio.srcObject=null,this.options.media.remote.audio.pause())},r.prototype.setupSession=function(){var e=this;this.session?(this.state=i.STATUS_NEW,this.emit("new",this.session),this.session.on("progress",function(){return e.onProgress()}),this.session.on("accepted",function(){return e.onAccepted()}),this.session.on("rejected",function(){return e.onEnded()}),this.session.on("failed",function(){return e.onFailed()}),this.session.on("terminated",function(){return e.onEnded()})):this.logger.warn("No session to set up")},r.prototype.destroyMedia=function(){this.session&&this.session.sessionDescriptionHandler&&this.session.sessionDescriptionHandler.close()},r.prototype.toggleMute=function(e){if(this.session){var t=this.session.sessionDescriptionHandler.peerConnection;t.getSenders?t.getSenders().forEach(function(t){t.track&&(t.track.enabled=!e)}):t.getLocalStreams().forEach(function(t){t.getAudioTracks().forEach(function(t){t.enabled=!e}),t.getVideoTracks().forEach(function(t){t.enabled=!e})})}else this.logger.warn("No session to toggle mute")},r.prototype.onAccepted=function(){var e=this;this.session?(this.state=i.STATUS_CONNECTED,this.emit("connected",this.session),this.setupLocalMedia(),this.setupRemoteMedia(),this.session.sessionDescriptionHandler&&(this.session.sessionDescriptionHandler.on("addTrack",function(){e.logger.log("A track has been added, triggering new remoteMedia setup"),e.setupRemoteMedia()}),this.session.sessionDescriptionHandler.on("addStream",function(){e.logger.log("A stream has been added, trigger new remoteMedia setup"),e.setupRemoteMedia()})),this.session.on("dtmf",function(t,r){e.emit("dtmf",r.tone)}),this.session.on("bye",function(){return e.onEnded()})):this.logger.warn("No session for accepting")},r.prototype.onProgress=function(){this.state=i.STATUS_CONNECTING,this.emit("connecting",this.session)},r.prototype.onFailed=function(){this.onEnded()},r.prototype.onEnded=function(){this.state=i.STATUS_COMPLETED,this.emit("ended",this.session),this.cleanupMedia()},r.C=i,r}(s.EventEmitter);t.Simple=c}).call(this,r(18))}])}); \ No newline at end of file diff --git a/lib/ClientContext.d.ts b/lib/ClientContext.d.ts new file mode 100644 index 000000000..96396d773 --- /dev/null +++ b/lib/ClientContext.d.ts @@ -0,0 +1,23 @@ +/// +import { EventEmitter } from "events"; +import { IncomingResponseMessage, Logger, NameAddrHeader, OutgoingRequestMessage, URI } from "./core"; +import { TypeStrings } from "./Enums"; +import { BodyObj } from "./session-description-handler"; +import { UA } from "./UA"; +export declare class ClientContext extends EventEmitter { + static initializer(objToConstruct: ClientContext, ua: UA, method: string, originalTarget: string | URI, options?: any): void; + type: TypeStrings; + data: any; + ua: UA; + logger: Logger; + request: OutgoingRequestMessage; + method: string; + body: BodyObj | undefined; + localIdentity: NameAddrHeader; + remoteIdentity: NameAddrHeader; + constructor(ua: UA, method: string, target: string | URI, options?: any); + send(): this; + receiveResponse(response: IncomingResponseMessage): void; + onRequestTimeout(): void; + onTransportError(): void; +} diff --git a/lib/ClientContext.js b/lib/ClientContext.js new file mode 100644 index 000000000..f294391c8 --- /dev/null +++ b/lib/ClientContext.js @@ -0,0 +1,101 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var Constants_1 = require("./Constants"); +var Enums_1 = require("./Enums"); +var Utils_1 = require("./Utils"); +var ClientContext = /** @class */ (function (_super) { + tslib_1.__extends(ClientContext, _super); + function ClientContext(ua, method, target, options) { + var _this = _super.call(this) || this; + _this.data = {}; + ClientContext.initializer(_this, ua, method, target, options); + return _this; + } + ClientContext.initializer = function (objToConstruct, ua, method, originalTarget, options) { + objToConstruct.type = Enums_1.TypeStrings.ClientContext; + // Validate arguments + if (originalTarget === undefined) { + throw new TypeError("Not enough arguments"); + } + objToConstruct.ua = ua; + objToConstruct.logger = ua.getLogger("sip.clientcontext"); + objToConstruct.method = method; + var target = ua.normalizeTarget(originalTarget); + if (!target) { + throw new TypeError("Invalid target: " + originalTarget); + } + /* Options + * - extraHeaders + * - params + * - contentType + * - body + */ + options = Object.create(options || Object.prototype); + var extraHeaders = (options.extraHeaders || []).slice(); + var params = options.params || {}; + var bodyObj; + if (options.body) { + bodyObj = { + body: options.body, + contentType: options.contentType ? options.contentType : "application/sdp" + }; + objToConstruct.body = bodyObj; + } + var body; + if (bodyObj) { + body = Utils_1.Utils.fromBodyObj(bodyObj); + } + // Build the request + objToConstruct.request = ua.userAgentCore.makeOutgoingRequestMessage(method, target, params.fromUri ? params.fromUri : ua.userAgentCore.configuration.aor, params.toUri ? params.toUri : target, params, extraHeaders, body); + /* Set other properties from the request */ + if (objToConstruct.request.from) { + objToConstruct.localIdentity = objToConstruct.request.from; + } + if (objToConstruct.request.to) { + objToConstruct.remoteIdentity = objToConstruct.request.to; + } + }; + ClientContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.request(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + ClientContext.prototype.receiveResponse = function (response) { + var statusCode = response.statusCode || 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + this.emit("progress", response, cause); + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + if (this.ua.applicants[this.toString()]) { + delete this.ua.applicants[this.toString()]; + } + this.emit("accepted", response, cause); + break; + default: + if (this.ua.applicants[this.toString()]) { + delete this.ua.applicants[this.toString()]; + } + this.emit("rejected", response, cause); + this.emit("failed", response, cause); + break; + } + }; + ClientContext.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + ClientContext.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + return ClientContext; +}(events_1.EventEmitter)); +exports.ClientContext = ClientContext; diff --git a/lib/Constants.d.ts b/lib/Constants.d.ts new file mode 100644 index 000000000..63b1c5938 --- /dev/null +++ b/lib/Constants.d.ts @@ -0,0 +1,62 @@ +export declare namespace C { + const USER_AGENT: string; + const SIP = "sip"; + const SIPS = "sips"; + enum causes { + CONNECTION_ERROR = "Connection Error", + INTERNAL_ERROR = "Internal Error", + REQUEST_TIMEOUT = "Request Timeout", + SIP_FAILURE_CODE = "SIP Failure Code", + ADDRESS_INCOMPLETE = "Address Incomplete", + AUTHENTICATION_ERROR = "Authentication Error", + BUSY = "Busy", + DIALOG_ERROR = "Dialog Error", + INCOMPATIBLE_SDP = "Incompatible SDP", + NOT_FOUND = "Not Found", + REDIRECTED = "Redirected", + REJECTED = "Rejected", + UNAVAILABLE = "Unavailable", + BAD_MEDIA_DESCRIPTION = "Bad Media Description", + CANCELED = "Canceled", + EXPIRES = "Expires", + NO_ACK = "No ACK", + NO_ANSWER = "No Answer", + NO_PRACK = "No PRACK", + RTP_TIMEOUT = "RTP Timeout", + USER_DENIED_MEDIA_ACCESS = "User Denied Media Access", + WEBRTC_ERROR = "WebRTC Error", + WEBRTC_NOT_SUPPORTED = "WebRTC Not Supported" + } + enum supported { + REQUIRED = "required", + SUPPORTED = "supported", + UNSUPPORTED = "none" + } + const SIP_ERROR_CAUSES: { + [name: string]: Array; + }; + const ACK = "ACK"; + const BYE = "BYE"; + const CANCEL = "CANCEL"; + const INFO = "INFO"; + const INVITE = "INVITE"; + const MESSAGE = "MESSAGE"; + const NOTIFY = "NOTIFY"; + const OPTIONS = "OPTIONS"; + const REGISTER = "REGISTER"; + const UPDATE = "UPDATE"; + const SUBSCRIBE = "SUBSCRIBE"; + const PUBLISH = "PUBLISH"; + const REFER = "REFER"; + const PRACK = "PRACK"; + const REASON_PHRASE: { + [code: number]: string; + }; + const OPTION_TAGS: { + [option: string]: boolean; + }; + enum dtmfType { + INFO = "info", + RTP = "rtp" + } +} diff --git a/lib/Constants.js b/lib/Constants.js new file mode 100644 index 000000000..08f9e5c7c --- /dev/null +++ b/lib/Constants.js @@ -0,0 +1,192 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +// tslint:disable-next-line:no-var-requires +var pkg = require("../package.json"); +var C; +(function (C) { + C.USER_AGENT = pkg.title + "/" + pkg.version; + // SIP scheme + C.SIP = "sip"; + C.SIPS = "sips"; + // End and Failure causes + var causes; + (function (causes) { + // Generic error causes + causes["CONNECTION_ERROR"] = "Connection Error"; + causes["INTERNAL_ERROR"] = "Internal Error"; + causes["REQUEST_TIMEOUT"] = "Request Timeout"; + causes["SIP_FAILURE_CODE"] = "SIP Failure Code"; + // SIP error causes + causes["ADDRESS_INCOMPLETE"] = "Address Incomplete"; + causes["AUTHENTICATION_ERROR"] = "Authentication Error"; + causes["BUSY"] = "Busy"; + causes["DIALOG_ERROR"] = "Dialog Error"; + causes["INCOMPATIBLE_SDP"] = "Incompatible SDP"; + causes["NOT_FOUND"] = "Not Found"; + causes["REDIRECTED"] = "Redirected"; + causes["REJECTED"] = "Rejected"; + causes["UNAVAILABLE"] = "Unavailable"; + // Session error causes + causes["BAD_MEDIA_DESCRIPTION"] = "Bad Media Description"; + causes["CANCELED"] = "Canceled"; + causes["EXPIRES"] = "Expires"; + causes["NO_ACK"] = "No ACK"; + causes["NO_ANSWER"] = "No Answer"; + causes["NO_PRACK"] = "No PRACK"; + causes["RTP_TIMEOUT"] = "RTP Timeout"; + causes["USER_DENIED_MEDIA_ACCESS"] = "User Denied Media Access"; + causes["WEBRTC_ERROR"] = "WebRTC Error"; + causes["WEBRTC_NOT_SUPPORTED"] = "WebRTC Not Supported"; + })(causes = C.causes || (C.causes = {})); + var supported; + (function (supported) { + supported["REQUIRED"] = "required"; + supported["SUPPORTED"] = "supported"; + supported["UNSUPPORTED"] = "none"; + })(supported = C.supported || (C.supported = {})); + C.SIP_ERROR_CAUSES = { + ADDRESS_INCOMPLETE: [484], + AUTHENTICATION_ERROR: [401, 407], + BUSY: [486, 600], + INCOMPATIBLE_SDP: [488, 606], + NOT_FOUND: [404, 604], + REDIRECTED: [300, 301, 302, 305, 380], + REJECTED: [403, 603], + UNAVAILABLE: [480, 410, 408, 430] + }; + // SIP Methods + C.ACK = "ACK"; + C.BYE = "BYE"; + C.CANCEL = "CANCEL"; + C.INFO = "INFO"; + C.INVITE = "INVITE"; + C.MESSAGE = "MESSAGE"; + C.NOTIFY = "NOTIFY"; + C.OPTIONS = "OPTIONS"; + C.REGISTER = "REGISTER"; + C.UPDATE = "UPDATE"; + C.SUBSCRIBE = "SUBSCRIBE"; + C.PUBLISH = "PUBLISH"; + C.REFER = "REFER"; + C.PRACK = "PRACK"; + /* SIP Response Reasons + * DOC: http://www.iana.org/assignments/sip-parameters + * Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7 + */ + C.REASON_PHRASE = { + 100: "Trying", + 180: "Ringing", + 181: "Call Is Being Forwarded", + 182: "Queued", + 183: "Session Progress", + 199: "Early Dialog Terminated", + 200: "OK", + 202: "Accepted", + 204: "No Notification", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Moved Temporarily", + 305: "Use Proxy", + 380: "Alternative Service", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 410: "Gone", + 412: "Conditional Request Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Unsupported URI Scheme", + 417: "Unknown Resource-Priority", + 420: "Bad Extension", + 421: "Extension Required", + 422: "Session Interval Too Small", + 423: "Interval Too Brief", + 428: "Use Identity Header", + 429: "Provide Referrer Identity", + 430: "Flow Failed", + 433: "Anonymity Disallowed", + 436: "Bad Identity-Info", + 437: "Unsupported Certificate", + 438: "Invalid Identity Header", + 439: "First Hop Lacks Outbound Support", + 440: "Max-Breadth Exceeded", + 469: "Bad Info Package", + 470: "Consent Needed", + 478: "Unresolvable Destination", + 480: "Temporarily Unavailable", + 481: "Call/Transaction Does Not Exist", + 482: "Loop Detected", + 483: "Too Many Hops", + 484: "Address Incomplete", + 485: "Ambiguous", + 486: "Busy Here", + 487: "Request Terminated", + 488: "Not Acceptable Here", + 489: "Bad Event", + 491: "Request Pending", + 493: "Undecipherable", + 494: "Security Agreement Required", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Server Time-out", + 505: "Version Not Supported", + 513: "Message Too Large", + 580: "Precondition Failure", + 600: "Busy Everywhere", + 603: "Decline", + 604: "Does Not Exist Anywhere", + 606: "Not Acceptable" + }; + /* SIP Option Tags + * DOC: http://www.iana.org/assignments/sip-parameters/sip-parameters.xhtml#sip-parameters-4 + */ + C.OPTION_TAGS = { + "100rel": true, + "199": true, + "answermode": true, + "early-session": true, + "eventlist": true, + "explicitsub": true, + "from-change": true, + "geolocation-http": true, + "geolocation-sip": true, + "gin": true, + "gruu": true, + "histinfo": true, + "ice": true, + "join": true, + "multiple-refer": true, + "norefersub": true, + "nosub": true, + "outbound": true, + "path": true, + "policy": true, + "precondition": true, + "pref": true, + "privacy": true, + "recipient-list-invite": true, + "recipient-list-message": true, + "recipient-list-subscribe": true, + "replaces": true, + "resource-priority": true, + "sdp-anat": true, + "sec-agree": true, + "tdialog": true, + "timer": true, + "uui": true // RFC 7433 + }; + var dtmfType; + (function (dtmfType) { + dtmfType["INFO"] = "info"; + dtmfType["RTP"] = "rtp"; + })(dtmfType = C.dtmfType || (C.dtmfType = {})); +})(C = exports.C || (exports.C = {})); diff --git a/lib/Enums.d.ts b/lib/Enums.d.ts new file mode 100644 index 000000000..a55b876e1 --- /dev/null +++ b/lib/Enums.d.ts @@ -0,0 +1,61 @@ +export declare enum DialogStatus { + STATUS_EARLY = 1, + STATUS_CONFIRMED = 2 +} +export declare enum SessionStatus { + STATUS_NULL = 0, + STATUS_INVITE_SENT = 1, + STATUS_1XX_RECEIVED = 2, + STATUS_INVITE_RECEIVED = 3, + STATUS_WAITING_FOR_ANSWER = 4, + STATUS_ANSWERED = 5, + STATUS_WAITING_FOR_PRACK = 6, + STATUS_WAITING_FOR_ACK = 7, + STATUS_CANCELED = 8, + STATUS_TERMINATED = 9, + STATUS_ANSWERED_WAITING_FOR_PRACK = 10, + STATUS_EARLY_MEDIA = 11, + STATUS_CONFIRMED = 12 +} +export declare enum TypeStrings { + ClientContext = 0, + ConfigurationError = 1, + Dialog = 2, + DigestAuthentication = 3, + DTMF = 4, + IncomingMessage = 5, + IncomingRequest = 6, + IncomingResponse = 7, + InvalidStateError = 8, + InviteClientContext = 9, + InviteServerContext = 10, + Logger = 11, + LoggerFactory = 12, + MethodParameterError = 13, + NameAddrHeader = 14, + NotSupportedError = 15, + OutgoingRequest = 16, + Parameters = 17, + PublishContext = 18, + ReferClientContext = 19, + ReferServerContext = 20, + RegisterContext = 21, + RenegotiationError = 22, + RequestSender = 23, + ServerContext = 24, + Session = 25, + SessionDescriptionHandler = 26, + SessionDescriptionHandlerError = 27, + SessionDescriptionHandlerObserver = 28, + Subscription = 29, + Transport = 30, + UA = 31, + URI = 32 +} +export declare enum UAStatus { + STATUS_INIT = 0, + STATUS_STARTING = 1, + STATUS_READY = 2, + STATUS_USER_CLOSED = 3, + STATUS_NOT_READY = 4 +} diff --git a/lib/Enums.js b/lib/Enums.js new file mode 100644 index 000000000..920136fd0 --- /dev/null +++ b/lib/Enums.js @@ -0,0 +1,71 @@ +"use strict"; +// enums can't really be declared, so they are set here. +// pulled out of individual files to avoid circular dependencies +Object.defineProperty(exports, "__esModule", { value: true }); +var DialogStatus; +(function (DialogStatus) { + DialogStatus[DialogStatus["STATUS_EARLY"] = 1] = "STATUS_EARLY"; + DialogStatus[DialogStatus["STATUS_CONFIRMED"] = 2] = "STATUS_CONFIRMED"; +})(DialogStatus = exports.DialogStatus || (exports.DialogStatus = {})); +var SessionStatus; +(function (SessionStatus) { + // Session states + SessionStatus[SessionStatus["STATUS_NULL"] = 0] = "STATUS_NULL"; + SessionStatus[SessionStatus["STATUS_INVITE_SENT"] = 1] = "STATUS_INVITE_SENT"; + SessionStatus[SessionStatus["STATUS_1XX_RECEIVED"] = 2] = "STATUS_1XX_RECEIVED"; + SessionStatus[SessionStatus["STATUS_INVITE_RECEIVED"] = 3] = "STATUS_INVITE_RECEIVED"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_ANSWER"] = 4] = "STATUS_WAITING_FOR_ANSWER"; + SessionStatus[SessionStatus["STATUS_ANSWERED"] = 5] = "STATUS_ANSWERED"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_PRACK"] = 6] = "STATUS_WAITING_FOR_PRACK"; + SessionStatus[SessionStatus["STATUS_WAITING_FOR_ACK"] = 7] = "STATUS_WAITING_FOR_ACK"; + SessionStatus[SessionStatus["STATUS_CANCELED"] = 8] = "STATUS_CANCELED"; + SessionStatus[SessionStatus["STATUS_TERMINATED"] = 9] = "STATUS_TERMINATED"; + SessionStatus[SessionStatus["STATUS_ANSWERED_WAITING_FOR_PRACK"] = 10] = "STATUS_ANSWERED_WAITING_FOR_PRACK"; + SessionStatus[SessionStatus["STATUS_EARLY_MEDIA"] = 11] = "STATUS_EARLY_MEDIA"; + SessionStatus[SessionStatus["STATUS_CONFIRMED"] = 12] = "STATUS_CONFIRMED"; +})(SessionStatus = exports.SessionStatus || (exports.SessionStatus = {})); +var TypeStrings; +(function (TypeStrings) { + TypeStrings[TypeStrings["ClientContext"] = 0] = "ClientContext"; + TypeStrings[TypeStrings["ConfigurationError"] = 1] = "ConfigurationError"; + TypeStrings[TypeStrings["Dialog"] = 2] = "Dialog"; + TypeStrings[TypeStrings["DigestAuthentication"] = 3] = "DigestAuthentication"; + TypeStrings[TypeStrings["DTMF"] = 4] = "DTMF"; + TypeStrings[TypeStrings["IncomingMessage"] = 5] = "IncomingMessage"; + TypeStrings[TypeStrings["IncomingRequest"] = 6] = "IncomingRequest"; + TypeStrings[TypeStrings["IncomingResponse"] = 7] = "IncomingResponse"; + TypeStrings[TypeStrings["InvalidStateError"] = 8] = "InvalidStateError"; + TypeStrings[TypeStrings["InviteClientContext"] = 9] = "InviteClientContext"; + TypeStrings[TypeStrings["InviteServerContext"] = 10] = "InviteServerContext"; + TypeStrings[TypeStrings["Logger"] = 11] = "Logger"; + TypeStrings[TypeStrings["LoggerFactory"] = 12] = "LoggerFactory"; + TypeStrings[TypeStrings["MethodParameterError"] = 13] = "MethodParameterError"; + TypeStrings[TypeStrings["NameAddrHeader"] = 14] = "NameAddrHeader"; + TypeStrings[TypeStrings["NotSupportedError"] = 15] = "NotSupportedError"; + TypeStrings[TypeStrings["OutgoingRequest"] = 16] = "OutgoingRequest"; + TypeStrings[TypeStrings["Parameters"] = 17] = "Parameters"; + TypeStrings[TypeStrings["PublishContext"] = 18] = "PublishContext"; + TypeStrings[TypeStrings["ReferClientContext"] = 19] = "ReferClientContext"; + TypeStrings[TypeStrings["ReferServerContext"] = 20] = "ReferServerContext"; + TypeStrings[TypeStrings["RegisterContext"] = 21] = "RegisterContext"; + TypeStrings[TypeStrings["RenegotiationError"] = 22] = "RenegotiationError"; + TypeStrings[TypeStrings["RequestSender"] = 23] = "RequestSender"; + TypeStrings[TypeStrings["ServerContext"] = 24] = "ServerContext"; + TypeStrings[TypeStrings["Session"] = 25] = "Session"; + TypeStrings[TypeStrings["SessionDescriptionHandler"] = 26] = "SessionDescriptionHandler"; + TypeStrings[TypeStrings["SessionDescriptionHandlerError"] = 27] = "SessionDescriptionHandlerError"; + TypeStrings[TypeStrings["SessionDescriptionHandlerObserver"] = 28] = "SessionDescriptionHandlerObserver"; + TypeStrings[TypeStrings["Subscription"] = 29] = "Subscription"; + TypeStrings[TypeStrings["Transport"] = 30] = "Transport"; + TypeStrings[TypeStrings["UA"] = 31] = "UA"; + TypeStrings[TypeStrings["URI"] = 32] = "URI"; +})(TypeStrings = exports.TypeStrings || (exports.TypeStrings = {})); +// UA status codes +var UAStatus; +(function (UAStatus) { + UAStatus[UAStatus["STATUS_INIT"] = 0] = "STATUS_INIT"; + UAStatus[UAStatus["STATUS_STARTING"] = 1] = "STATUS_STARTING"; + UAStatus[UAStatus["STATUS_READY"] = 2] = "STATUS_READY"; + UAStatus[UAStatus["STATUS_USER_CLOSED"] = 3] = "STATUS_USER_CLOSED"; + UAStatus[UAStatus["STATUS_NOT_READY"] = 4] = "STATUS_NOT_READY"; +})(UAStatus = exports.UAStatus || (exports.UAStatus = {})); diff --git a/lib/Exceptions.d.ts b/lib/Exceptions.d.ts new file mode 100644 index 000000000..156b42e06 --- /dev/null +++ b/lib/Exceptions.d.ts @@ -0,0 +1,67 @@ +import { Exception } from "./core"; +import { SessionStatus, TypeStrings } from "./Enums"; +export declare namespace Exceptions { + /** + * Indicates the session description handler has closed. + * Occurs when getDescription() or setDescription() are called after close() has been called. + * Occurs when close() is called while getDescription() or setDescription() are in progress. + */ + class ClosedSessionDescriptionHandlerError extends Exception { + constructor(); + } + /** + * Indicates the session terminated before the action completed. + */ + class TerminatedSessionError extends Exception { + constructor(); + } + /** + * Unsupported session description content type. + */ + class UnsupportedSessionDescriptionContentTypeError extends Exception { + constructor(message?: string); + } +} +/** + * DEPRECATED: The original implementation of exceptions in this library attempted to + * deal with the lack of type checking in JavaScript by adding a "type" attribute + * to objects and using that to discriminate. On top of that it layered allcoated + * "code" numbers and constant "name" strings. All of that is unnecessary when using + * TypeScript, inheriting from Error and properly setting up the prototype chain... + */ +declare abstract class LegacyException extends Exception { + type: TypeStrings; + name: string; + message: string; + code: number; + constructor(code: number, name: string, message: string); +} +export declare namespace Exceptions { + class ConfigurationError extends LegacyException { + parameter: string; + value: any; + constructor(parameter: string, value?: any); + } + class InvalidStateError extends LegacyException { + status: SessionStatus; + constructor(status: SessionStatus); + } + class NotSupportedError extends LegacyException { + constructor(message: string); + } + class RenegotiationError extends LegacyException { + constructor(message: string); + } + class MethodParameterError extends LegacyException { + method: string; + parameter: string; + value: any; + constructor(method: string, parameter: string, value: any); + } + class SessionDescriptionHandlerError extends LegacyException { + error: string | undefined; + method: string; + constructor(method: string, error?: string, message?: string); + } +} +export {}; diff --git a/lib/Exceptions.js b/lib/Exceptions.js new file mode 100644 index 000000000..7ba7fbcc8 --- /dev/null +++ b/lib/Exceptions.js @@ -0,0 +1,137 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var core_1 = require("./core"); +var Enums_1 = require("./Enums"); +// tslint:disable:max-classes-per-file +var Exceptions; +(function (Exceptions) { + /** + * Indicates the session description handler has closed. + * Occurs when getDescription() or setDescription() are called after close() has been called. + * Occurs when close() is called while getDescription() or setDescription() are in progress. + */ + var ClosedSessionDescriptionHandlerError = /** @class */ (function (_super) { + tslib_1.__extends(ClosedSessionDescriptionHandlerError, _super); + function ClosedSessionDescriptionHandlerError() { + return _super.call(this, "The session description handler has closed.") || this; + } + return ClosedSessionDescriptionHandlerError; + }(core_1.Exception)); + Exceptions.ClosedSessionDescriptionHandlerError = ClosedSessionDescriptionHandlerError; + /** + * Indicates the session terminated before the action completed. + */ + var TerminatedSessionError = /** @class */ (function (_super) { + tslib_1.__extends(TerminatedSessionError, _super); + function TerminatedSessionError() { + return _super.call(this, "The session has terminated.") || this; + } + return TerminatedSessionError; + }(core_1.Exception)); + Exceptions.TerminatedSessionError = TerminatedSessionError; + /** + * Unsupported session description content type. + */ + var UnsupportedSessionDescriptionContentTypeError = /** @class */ (function (_super) { + tslib_1.__extends(UnsupportedSessionDescriptionContentTypeError, _super); + function UnsupportedSessionDescriptionContentTypeError(message) { + return _super.call(this, message ? message : "Unsupported session description content type.") || this; + } + return UnsupportedSessionDescriptionContentTypeError; + }(core_1.Exception)); + Exceptions.UnsupportedSessionDescriptionContentTypeError = UnsupportedSessionDescriptionContentTypeError; +})(Exceptions = exports.Exceptions || (exports.Exceptions = {})); +/** + * DEPRECATED: The original implementation of exceptions in this library attempted to + * deal with the lack of type checking in JavaScript by adding a "type" attribute + * to objects and using that to discriminate. On top of that it layered allcoated + * "code" numbers and constant "name" strings. All of that is unnecessary when using + * TypeScript, inheriting from Error and properly setting up the prototype chain... + */ +var LegacyException = /** @class */ (function (_super) { + tslib_1.__extends(LegacyException, _super); + function LegacyException(code, name, message) { + var _this = _super.call(this, message) || this; + _this.code = code; + _this.name = name; + _this.message = message; + return _this; + } + return LegacyException; +}(core_1.Exception)); +(function (Exceptions) { + var ConfigurationError = /** @class */ (function (_super) { + tslib_1.__extends(ConfigurationError, _super); + function ConfigurationError(parameter, value) { + var _this = _super.call(this, 1, "CONFIGURATION_ERROR", (!value) ? "Missing parameter: " + parameter : + "Invalid value " + JSON.stringify(value) + " for parameter '" + parameter + "'") || this; + _this.type = Enums_1.TypeStrings.ConfigurationError; + _this.parameter = parameter; + _this.value = value; + return _this; + } + return ConfigurationError; + }(LegacyException)); + Exceptions.ConfigurationError = ConfigurationError; + var InvalidStateError = /** @class */ (function (_super) { + tslib_1.__extends(InvalidStateError, _super); + function InvalidStateError(status) { + var _this = _super.call(this, 2, "INVALID_STATE_ERROR", "Invalid status: " + status) || this; + _this.type = Enums_1.TypeStrings.InvalidStateError; + _this.status = status; + return _this; + } + return InvalidStateError; + }(LegacyException)); + Exceptions.InvalidStateError = InvalidStateError; + var NotSupportedError = /** @class */ (function (_super) { + tslib_1.__extends(NotSupportedError, _super); + function NotSupportedError(message) { + var _this = _super.call(this, 3, "NOT_SUPPORTED_ERROR", message) || this; + _this.type = Enums_1.TypeStrings.NotSupportedError; + return _this; + } + return NotSupportedError; + }(LegacyException)); + Exceptions.NotSupportedError = NotSupportedError; + // 4 was GetDescriptionError, which was deprecated and now removed + var RenegotiationError = /** @class */ (function (_super) { + tslib_1.__extends(RenegotiationError, _super); + function RenegotiationError(message) { + var _this = _super.call(this, 5, "RENEGOTIATION_ERROR", message) || this; + _this.type = Enums_1.TypeStrings.RenegotiationError; + return _this; + } + return RenegotiationError; + }(LegacyException)); + Exceptions.RenegotiationError = RenegotiationError; + var MethodParameterError = /** @class */ (function (_super) { + tslib_1.__extends(MethodParameterError, _super); + function MethodParameterError(method, parameter, value) { + var _this = _super.call(this, 6, "METHOD_PARAMETER_ERROR", (!value) ? + "Missing parameter: " + parameter : + "Invalid value " + JSON.stringify(value) + " for parameter '" + parameter + "'") || this; + _this.type = Enums_1.TypeStrings.MethodParameterError; + _this.method = method; + _this.parameter = parameter; + _this.value = value; + return _this; + } + return MethodParameterError; + }(LegacyException)); + Exceptions.MethodParameterError = MethodParameterError; + // 7 was TransportError, which was replaced + var SessionDescriptionHandlerError = /** @class */ (function (_super) { + tslib_1.__extends(SessionDescriptionHandlerError, _super); + function SessionDescriptionHandlerError(method, error, message) { + var _this = _super.call(this, 8, "SESSION_DESCRIPTION_HANDLER_ERROR", message || "Error with Session Description Handler") || this; + _this.type = Enums_1.TypeStrings.SessionDescriptionHandlerError; + _this.method = method; + _this.error = error; + return _this; + } + return SessionDescriptionHandlerError; + }(LegacyException)); + Exceptions.SessionDescriptionHandlerError = SessionDescriptionHandlerError; +})(Exceptions = exports.Exceptions || (exports.Exceptions = {})); diff --git a/lib/Parser.d.ts b/lib/Parser.d.ts new file mode 100644 index 000000000..2a3d039d6 --- /dev/null +++ b/lib/Parser.d.ts @@ -0,0 +1,18 @@ +import { IncomingRequestMessage, IncomingResponseMessage, Logger } from "./core"; +/** + * Extract and parse every header of a SIP message. + * @namespace + */ +export declare namespace Parser { + function getHeader(data: any, headerStart: number): number; + function parseHeader(message: IncomingRequestMessage | IncomingResponseMessage, data: any, headerStart: number, headerEnd: number): boolean | { + error: string; + }; + /** Parse SIP Message + * @function + * @param {String} message SIP message. + * @param {Object} logger object. + * @returns {SIP.IncomingRequest|SIP.IncomingResponse|undefined} + */ + function parseMessage(data: string, logger: Logger): IncomingRequestMessage | IncomingResponseMessage | undefined; +} diff --git a/lib/Parser.js b/lib/Parser.js new file mode 100644 index 000000000..275f0b3d8 --- /dev/null +++ b/lib/Parser.js @@ -0,0 +1,241 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var core_1 = require("./core"); +/** + * Extract and parse every header of a SIP message. + * @namespace + */ +var Parser; +(function (Parser) { + function getHeader(data, headerStart) { + // 'start' position of the header. + var start = headerStart; + // 'end' position of the header. + var end = 0; + // 'partial end' position of the header. + var partialEnd = 0; + // End of message. + if (data.substring(start, start + 2).match(/(^\r\n)/)) { + return -2; + } + while (end === 0) { + // Partial End of Header. + partialEnd = data.indexOf("\r\n", start); + // 'indexOf' returns -1 if the value to be found never occurs. + if (partialEnd === -1) { + return partialEnd; + } + if (!data.substring(partialEnd + 2, partialEnd + 4).match(/(^\r\n)/) && + data.charAt(partialEnd + 2).match(/(^\s+)/)) { + // Not the end of the message. Continue from the next position. + start = partialEnd + 2; + } + else { + end = partialEnd; + } + } + return end; + } + Parser.getHeader = getHeader; + function parseHeader(message, data, headerStart, headerEnd) { + var hcolonIndex = data.indexOf(":", headerStart); + var headerName = data.substring(headerStart, hcolonIndex).trim(); + var headerValue = data.substring(hcolonIndex + 1, headerEnd).trim(); + var parsed; + // If header-field is well-known, parse it. + switch (headerName.toLowerCase()) { + case "via": + case "v": + message.addHeader("via", headerValue); + if (message.getHeaders("via").length === 1) { + parsed = message.parseHeader("Via"); + if (parsed) { + message.via = parsed; + message.viaBranch = parsed.branch; + } + } + else { + parsed = 0; + } + break; + case "from": + case "f": + message.setHeader("from", headerValue); + parsed = message.parseHeader("from"); + if (parsed) { + message.from = parsed; + message.fromTag = parsed.getParam("tag"); + } + break; + case "to": + case "t": + message.setHeader("to", headerValue); + parsed = message.parseHeader("to"); + if (parsed) { + message.to = parsed; + message.toTag = parsed.getParam("tag"); + } + break; + case "record-route": + parsed = core_1.Grammar.parse(headerValue, "Record_Route"); + if (parsed === -1) { + parsed = undefined; + break; + } + for (var header in parsed) { + if (parsed[header]) { + message.addHeader("record-route", headerValue.substring(parsed[header].position, parsed[header].offset)); + message.headers["Record-Route"][message.getHeaders("record-route").length - 1].parsed = + parsed[header].parsed; + } + } + break; + case "call-id": + case "i": + message.setHeader("call-id", headerValue); + parsed = message.parseHeader("call-id"); + if (parsed) { + message.callId = headerValue; + } + break; + case "contact": + case "m": + parsed = core_1.Grammar.parse(headerValue, "Contact"); + if (parsed === -1) { + parsed = undefined; + break; + } + if (!(parsed instanceof Array)) { + parsed = undefined; + break; + } + parsed.forEach(function (header) { + message.addHeader("contact", headerValue.substring(header.position, header.offset)); + message.headers.Contact[message.getHeaders("contact").length - 1].parsed = header.parsed; + }); + break; + case "content-length": + case "l": + message.setHeader("content-length", headerValue); + parsed = message.parseHeader("content-length"); + break; + case "content-type": + case "c": + message.setHeader("content-type", headerValue); + parsed = message.parseHeader("content-type"); + break; + case "cseq": + message.setHeader("cseq", headerValue); + parsed = message.parseHeader("cseq"); + if (parsed) { + message.cseq = parsed.value; + } + if (message instanceof core_1.IncomingResponseMessage) { + message.method = parsed.method; + } + break; + case "max-forwards": + message.setHeader("max-forwards", headerValue); + parsed = message.parseHeader("max-forwards"); + break; + case "www-authenticate": + message.setHeader("www-authenticate", headerValue); + parsed = message.parseHeader("www-authenticate"); + break; + case "proxy-authenticate": + message.setHeader("proxy-authenticate", headerValue); + parsed = message.parseHeader("proxy-authenticate"); + break; + case "refer-to": + case "r": + message.setHeader("refer-to", headerValue); + parsed = message.parseHeader("refer-to"); + if (parsed) { + message.referTo = parsed; + } + break; + default: + // Do not parse this header. + message.setHeader(headerName, headerValue); + parsed = 0; + } + if (parsed === undefined) { + return { + error: "error parsing header '" + headerName + "'" + }; + } + else { + return true; + } + } + Parser.parseHeader = parseHeader; + /** Parse SIP Message + * @function + * @param {String} message SIP message. + * @param {Object} logger object. + * @returns {SIP.IncomingRequest|SIP.IncomingResponse|undefined} + */ + function parseMessage(data, logger) { + var headerStart = 0; + var headerEnd = data.indexOf("\r\n"); + if (headerEnd === -1) { + logger.warn("no CRLF found, not a SIP message, discarded"); + return; + } + // Parse first line. Check if it is a Request or a Reply. + var firstLine = data.substring(0, headerEnd); + var parsed = core_1.Grammar.parse(firstLine, "Request_Response"); + var message; + if (parsed === -1) { + logger.warn('error parsing first line of SIP message: "' + firstLine + '"'); + return; + } + else if (!parsed.status_code) { + message = new core_1.IncomingRequestMessage(); + message.method = parsed.method; + message.ruri = parsed.uri; + } + else { + message = new core_1.IncomingResponseMessage(); + message.statusCode = parsed.status_code; + message.reasonPhrase = parsed.reason_phrase; + } + message.data = data; + headerStart = headerEnd + 2; + /* Loop over every line in data. Detect the end of each header and parse + * it or simply add to the headers collection. + */ + var bodyStart; + while (true) { + headerEnd = getHeader(data, headerStart); + // The SIP message has normally finished. + if (headerEnd === -2) { + bodyStart = headerStart + 2; + break; + } + else if (headerEnd === -1) { + // data.indexOf returned -1 due to a malformed message. + logger.error("malformed message"); + return; + } + var parsedHeader = parseHeader(message, data, headerStart, headerEnd); + if (parsedHeader !== true) { + logger.error(parsed.error); + return; + } + headerStart = headerEnd + 2; + } + /* RFC3261 18.3. + * If there are additional bytes in the transport packet + * beyond the end of the body, they MUST be discarded. + */ + if (message.hasHeader("content-length")) { + message.body = data.substr(bodyStart, Number(message.getHeader("content-length"))); + } + else { + message.body = data.substring(bodyStart); + } + return message; + } + Parser.parseMessage = parseMessage; +})(Parser = exports.Parser || (exports.Parser = {})); diff --git a/lib/PublishContext.d.ts b/lib/PublishContext.d.ts new file mode 100644 index 000000000..21638a9f1 --- /dev/null +++ b/lib/PublishContext.d.ts @@ -0,0 +1,38 @@ +import { ClientContext } from "./ClientContext"; +import { IncomingResponseMessage, URI } from "./core"; +import { TypeStrings } from "./Enums"; +import { UA } from "./UA"; +/** + * SIP Publish (SIP Extension for Event State Publication RFC3903) + * @class Class creating a SIP PublishContext. + */ +export declare class PublishContext extends ClientContext { + type: TypeStrings; + private options; + private event; + private target; + private pubRequestBody; + private pubRequestExpires; + private pubRequestEtag; + private publishRefreshTimer; + constructor(ua: UA, target: string | URI, event: string, options?: any); + /** + * Publish + * @param {string} Event body to publish, optional + */ + publish(body: string): void; + /** + * Unpublish + */ + unpublish(): void; + /** + * Close + */ + close(): void; + onRequestTimeout(): void; + onTransportError(): void; + receiveResponse(response: IncomingResponseMessage): void; + send(): this; + private refreshRequest; + private sendPublishRequest; +} diff --git a/lib/PublishContext.js b/lib/PublishContext.js new file mode 100644 index 000000000..4409e5fcd --- /dev/null +++ b/lib/PublishContext.js @@ -0,0 +1,275 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var ClientContext_1 = require("./ClientContext"); +var Constants_1 = require("./Constants"); +var core_1 = require("./core"); +var Enums_1 = require("./Enums"); +var Exceptions_1 = require("./Exceptions"); +var Utils_1 = require("./Utils"); +/** + * SIP Publish (SIP Extension for Event State Publication RFC3903) + * @class Class creating a SIP PublishContext. + */ +var PublishContext = /** @class */ (function (_super) { + tslib_1.__extends(PublishContext, _super); + function PublishContext(ua, target, event, options) { + if (options === void 0) { options = {}; } + var _this = this; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.contentType = (options.contentType || "text/plain"); + if (typeof options.expires !== "number" || (options.expires % 1) !== 0) { + options.expires = 3600; + } + else { + options.expires = Number(options.expires); + } + if (typeof (options.unpublishOnClose) !== "boolean") { + options.unpublishOnClose = true; + } + if (target === undefined || target === null || target === "") { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Target", target); + } + else { + target = ua.normalizeTarget(target); + if (target === undefined) { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Target", target); + } + } + _this = _super.call(this, ua, Constants_1.C.PUBLISH, target, options) || this; + _this.type = Enums_1.TypeStrings.PublishContext; + _this.options = options; + _this.target = target; + if (event === undefined || event === null || event === "") { + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Event", event); + } + else { + _this.event = event; + } + _this.logger = ua.getLogger("sip.publish"); + _this.pubRequestExpires = _this.options.expires; + ua.on("transportCreated", function (transport) { + transport.on("transportError", function () { return _this.onTransportError(); }); + }); + return _this; + } + /** + * Publish + * @param {string} Event body to publish, optional + */ + PublishContext.prototype.publish = function (body) { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + // is Inital or Modify request + this.options.body = body; + this.pubRequestBody = this.options.body; + if (this.pubRequestExpires === 0) { + // This is Initial request after unpublish + this.pubRequestExpires = this.options.expires; + this.pubRequestEtag = undefined; + } + if (!(this.ua.publishers[this.target.toString() + ":" + this.event])) { + this.ua.publishers[this.target.toString() + ":" + this.event] = this; + } + this.sendPublishRequest(); + }; + /** + * Unpublish + */ + PublishContext.prototype.unpublish = function () { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestExpires = 0; + if (this.pubRequestEtag !== undefined) { + this.sendPublishRequest(); + } + }; + /** + * Close + */ + PublishContext.prototype.close = function () { + // Send unpublish, if requested + if (this.options.unpublishOnClose) { + this.unpublish(); + } + else { + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestExpires = 0; + this.pubRequestEtag = undefined; + } + if (this.ua.publishers[this.target.toString() + ":" + this.event]) { + delete this.ua.publishers[this.target.toString() + ":" + this.event]; + } + }; + PublishContext.prototype.onRequestTimeout = function () { + _super.prototype.onRequestTimeout.call(this); + this.emit("unpublished", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + PublishContext.prototype.onTransportError = function () { + _super.prototype.onTransportError.call(this); + this.emit("unpublished", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + PublishContext.prototype.receiveResponse = function (response) { + var _this = this; + var statusCode = response.statusCode || 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + this.emit("progress", response, cause); + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + // Set SIP-Etag + if (response.hasHeader("SIP-ETag")) { + this.pubRequestEtag = response.getHeader("SIP-ETag"); + } + else { + this.logger.warn("SIP-ETag header missing in a 200-class response to PUBLISH"); + } + // Update Expire + if (response.hasHeader("Expires")) { + var expires = Number(response.getHeader("Expires")); + if (typeof expires === "number" && expires >= 0 && expires <= this.pubRequestExpires) { + this.pubRequestExpires = expires; + } + else { + this.logger.warn("Bad Expires header in a 200-class response to PUBLISH"); + } + } + else { + this.logger.warn("Expires header missing in a 200-class response to PUBLISH"); + } + if (this.pubRequestExpires !== 0) { + // Schedule refresh + this.publishRefreshTimer = setTimeout(function () { return _this.refreshRequest(); }, this.pubRequestExpires * 900); + this.emit("published", response, cause); + } + else { + this.emit("unpublished", response, cause); + } + break; + case /^412$/.test(statusCode.toString()): + // 412 code means no matching ETag - possibly the PUBLISH expired + // Resubmit as new request, if the current request is not a "remove" + if (this.pubRequestEtag !== undefined && this.pubRequestExpires !== 0) { + this.logger.warn("412 response to PUBLISH, recovering"); + this.pubRequestEtag = undefined; + this.emit("progress", response, cause); + this.publish(this.options.body); + } + else { + this.logger.warn("412 response to PUBLISH, recovery failed"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + break; + case /^423$/.test(statusCode.toString()): + // 423 code means we need to adjust the Expires interval up + if (this.pubRequestExpires !== 0 && response.hasHeader("Min-Expires")) { + var minExpires = Number(response.getHeader("Min-Expires")); + if (typeof minExpires === "number" || minExpires > this.pubRequestExpires) { + this.logger.warn("423 code in response to PUBLISH, adjusting the Expires value and trying to recover"); + this.pubRequestExpires = minExpires; + this.emit("progress", response, cause); + this.publish(this.options.body); + } + else { + this.logger.warn("Bad 423 response Min-Expires header received for PUBLISH"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + } + else { + this.logger.warn("423 response to PUBLISH, recovery failed"); + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + } + break; + default: + this.pubRequestExpires = 0; + this.emit("failed", response, cause); + this.emit("unpublished", response, cause); + break; + } + // Do the cleanup + if (this.pubRequestExpires === 0) { + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + this.pubRequestBody = undefined; + this.pubRequestEtag = undefined; + } + }; + PublishContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.publish(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + PublishContext.prototype.refreshRequest = function () { + // Clean up before the run + if (this.publishRefreshTimer) { + clearTimeout(this.publishRefreshTimer); + this.publishRefreshTimer = undefined; + } + // This is Refresh request + this.pubRequestBody = undefined; + if (this.pubRequestEtag === undefined) { + // Request not valid + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Body", undefined); + } + if (this.pubRequestExpires === 0) { + // Request not valid + throw new Exceptions_1.Exceptions.MethodParameterError("Publish", "Expire", this.pubRequestExpires); + } + this.sendPublishRequest(); + }; + PublishContext.prototype.sendPublishRequest = function () { + var reqOptions = Object.create(this.options || Object.prototype); + reqOptions.extraHeaders = (this.options.extraHeaders || []).slice(); + reqOptions.extraHeaders.push("Event: " + this.event); + reqOptions.extraHeaders.push("Expires: " + this.pubRequestExpires); + if (this.pubRequestEtag !== undefined) { + reqOptions.extraHeaders.push("SIP-If-Match: " + this.pubRequestEtag); + } + var ruri = this.target instanceof core_1.URI ? this.target : this.ua.normalizeTarget(this.target); + if (!ruri) { + throw new Error("ruri undefined."); + } + var params = this.options.params || {}; + var bodyObj; + if (this.pubRequestBody !== undefined) { + bodyObj = { + body: this.pubRequestBody, + contentType: this.options.contentType + }; + } + var body; + if (bodyObj) { + body = Utils_1.Utils.fromBodyObj(bodyObj); + } + this.request = this.ua.userAgentCore.makeOutgoingRequestMessage(Constants_1.C.PUBLISH, ruri, params.fromUri ? params.fromUri : this.ua.userAgentCore.configuration.aor, params.toUri ? params.toUri : this.target, params, reqOptions.extraHeaders, body); + this.send(); + }; + return PublishContext; +}(ClientContext_1.ClientContext)); +exports.PublishContext = PublishContext; diff --git a/lib/React/SessionDescriptionHandler.d.ts b/lib/React/SessionDescriptionHandler.d.ts new file mode 100644 index 000000000..4f25ba1a6 --- /dev/null +++ b/lib/React/SessionDescriptionHandler.d.ts @@ -0,0 +1,93 @@ +/// +import { EventEmitter } from "events"; +import { Logger } from "../core"; +import { TypeStrings } from "../Enums"; +import { InviteClientContext, InviteServerContext } from "../Session"; +import { BodyObj, SessionDescriptionHandler as SessionDescriptionHandlerDefinition, SessionDescriptionHandlerModifiers } from "../session-description-handler"; +import { SessionDescriptionHandlerObserver } from "./SessionDescriptionHandlerObserver"; +export declare class SessionDescriptionHandler extends EventEmitter implements SessionDescriptionHandlerDefinition { + /** + * @param {SIP.Session} session + * @param {Object} [options] + */ + static defaultFactory(session: InviteClientContext | InviteServerContext, options: any): SessionDescriptionHandler; + type: TypeStrings; + private options; + private logger; + private observer; + private dtmfSender; + private shouldAcquireMedia; + private CONTENT_TYPE; + private direction; + private C; + private modifiers; + private WebRTC; + private iceGatheringDeferred; + private iceGatheringTimeout; + private iceGatheringTimer; + private constraints; + private peerConnection; + constructor(logger: Logger, observer: SessionDescriptionHandlerObserver, options: any); + /** + * Destructor + */ + close(): void; + /** + * Gets the local description from the underlying media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves with the local description to be used for the session + */ + getDescription(options?: any, modifiers?: SessionDescriptionHandlerModifiers): Promise; + /** + * Check if the Session Description Handler can handle the Content-Type described by a SIP Message + * @param {String} contentType The content type that is in the SIP Message + * @returns {boolean} + */ + hasDescription(contentType: string): boolean; + /** + * The modifier that should be used when the session would like to place the call on hold + * @param {String} [sdp] The description that will be modified + * @returns {Promise} Promise that resolves with modified SDP + */ + holdModifier(description: RTCSessionDescriptionInit): Promise; + /** + * Set the remote description to the underlying media implementation + * @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves once the description is set + */ + setDescription(sessionDescription: string, options?: any, modifiers?: SessionDescriptionHandlerModifiers): Promise; + /** + * Send DTMF via RTP (RFC 4733) + * @param {String} tones A string containing DTMF digits + * @param {Object} [options] Options object to be used by sendDtmf + * @returns {boolean} true if DTMF send is successful, false otherwise + */ + sendDtmf(tones: string, options: any): boolean; + getDirection(): string; + private createOfferOrAnswer; + private createRTCSessionDescriptionInit; + private addDefaultIceCheckingTimeout; + private addDefaultIceServers; + private checkAndDefaultConstraints; + private hasBrowserTrackSupport; + private hasBrowserGetSenderSupport; + private initPeerConnection; + private acquire; + private hasOffer; + private isIceGatheringComplete; + private resetIceGatheringComplete; + private setDirection; + private triggerIceGatheringComplete; + private waitForIceGatheringComplete; +} diff --git a/lib/React/SessionDescriptionHandler.js b/lib/React/SessionDescriptionHandler.js new file mode 100644 index 000000000..b6c064b07 --- /dev/null +++ b/lib/React/SessionDescriptionHandler.js @@ -0,0 +1,636 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var react_native_webrtc_1 = require("react-native-webrtc"); +var Enums_1 = require("../Enums"); +var Exceptions_1 = require("../Exceptions"); +var Utils_1 = require("../Utils"); +var Modifiers = tslib_1.__importStar(require("../Web/Modifiers")); +var SessionDescriptionHandlerObserver_1 = require("./SessionDescriptionHandlerObserver"); +/* SessionDescriptionHandler + * @class PeerConnection helper Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandler = /** @class */ (function (_super) { + tslib_1.__extends(SessionDescriptionHandler, _super); + function SessionDescriptionHandler(logger, observer, options) { + var _this = _super.call(this) || this; + _this.type = Enums_1.TypeStrings.SessionDescriptionHandler; + // TODO: Validate the options + _this.options = options || {}; + _this.logger = logger; + _this.observer = observer; + _this.dtmfSender = undefined; + _this.shouldAcquireMedia = true; + _this.CONTENT_TYPE = "application/sdp"; + _this.C = { + DIRECTION: { + NULL: null, + SENDRECV: "sendrecv", + SENDONLY: "sendonly", + RECVONLY: "recvonly", + INACTIVE: "inactive" + } + }; + _this.logger.log("SessionDescriptionHandlerOptions: " + JSON.stringify(_this.options)); + _this.direction = _this.C.DIRECTION.NULL; + _this.modifiers = _this.options.modifiers || []; + if (!Array.isArray(_this.modifiers)) { + _this.modifiers = [_this.modifiers]; + } + _this.WebRTC = { + MediaStream: react_native_webrtc_1.MediaStream, + getUserMedia: react_native_webrtc_1.mediaDevices.getUserMedia, + RTCPeerConnection: react_native_webrtc_1.RTCPeerConnection + }; + _this.iceGatheringTimeout = false; + _this.initPeerConnection(_this.options.peerConnectionOptions); + _this.constraints = _this.checkAndDefaultConstraints(_this.options.constraints); + return _this; + } + /** + * @param {SIP.Session} session + * @param {Object} [options] + */ + SessionDescriptionHandler.defaultFactory = function (session, options) { + var logger = session.ua.getLogger("sip.invitecontext.sessionDescriptionHandler", session.id); + var observer = new SessionDescriptionHandlerObserver_1.SessionDescriptionHandlerObserver(session, options); + return new SessionDescriptionHandler(logger, observer, options); + }; + // Functions the sesssion can use + /** + * Destructor + */ + SessionDescriptionHandler.prototype.close = function () { + this.logger.log("closing PeerConnection"); + // have to check signalingState since this.close() gets called multiple times + if (this.peerConnection && this.peerConnection.signalingState !== "closed") { + if (this.peerConnection.getSenders) { + this.peerConnection.getSenders().forEach(function (sender) { + if (sender.track) { + sender.track.stop(); + } + }); + } + else { + this.logger.warn("Using getLocalStreams which is deprecated"); + this.peerConnection.getLocalStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + if (this.peerConnection.getReceivers) { + this.peerConnection.getReceivers().forEach(function (receiver) { + if (receiver.track) { + receiver.track.stop(); + } + }); + } + else { + this.logger.warn("Using getRemoteStreams which is deprecated"); + this.peerConnection.getRemoteStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + }; + /** + * Gets the local description from the underlying media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves with the local description to be used for the session + */ + SessionDescriptionHandler.prototype.getDescription = function (options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + // Merge passed constraints with saved constraints and save + var newConstraints = Object.assign({}, this.constraints, options.constraints); + newConstraints = this.checkAndDefaultConstraints(newConstraints); + if (JSON.stringify(newConstraints) !== JSON.stringify(this.constraints)) { + this.constraints = newConstraints; + this.shouldAcquireMedia = true; + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + return Promise.resolve().then(function () { + if (_this.shouldAcquireMedia) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return _this.createOfferOrAnswer(options.RTCOfferOptions, modifiers); }) + .then(function (description) { + // Recreate offer containing the ICE Candidates + if (description.type === "offer") { + return _this.peerConnection.createOffer() + .then(function (sdp) { return _this.createRTCSessionDescriptionInit(sdp); }); + } + return description; + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOffer failed", e); + _this.logger.error(error.toString()); + throw error; + }).then(function (description) { + if (description.sdp === undefined) { + throw new Exceptions_1.Exceptions.SessionDescriptionHandlerError("getDescription", undefined, "SDP undefined"); + } + _this.emit("getDescription", description); + return { + body: description.sdp, + contentType: _this.CONTENT_TYPE + }; + }); + }; + /** + * Check if the Session Description Handler can handle the Content-Type described by a SIP Message + * @param {String} contentType The content type that is in the SIP Message + * @returns {boolean} + */ + SessionDescriptionHandler.prototype.hasDescription = function (contentType) { + return contentType === this.CONTENT_TYPE; + }; + /** + * The modifier that should be used when the session would like to place the call on hold + * @param {String} [sdp] The description that will be modified + * @returns {Promise} Promise that resolves with modified SDP + */ + SessionDescriptionHandler.prototype.holdModifier = function (description) { + if (!description.sdp) { + return Promise.resolve(description); + } + if (!(/a=(sendrecv|sendonly|recvonly|inactive)/).test(description.sdp)) { + description.sdp = description.sdp.replace(/(m=[^\r]*\r\n)/g, "$1a=sendonly\r\n"); + } + else { + description.sdp = description.sdp.replace(/a=sendrecv\r\n/g, "a=sendonly\r\n"); + description.sdp = description.sdp.replace(/a=recvonly\r\n/g, "a=inactive\r\n"); + } + return Promise.resolve(description); + }; + /** + * Set the remote description to the underlying media implementation + * @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves once the description is set + */ + SessionDescriptionHandler.prototype.setDescription = function (sessionDescription, options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + options = options || {}; + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + var description = { + type: this.hasOffer("local") ? "answer" : "offer", + sdp: sessionDescription + }; + return Promise.resolve().then(function () { + // Media should be acquired in getDescription unless we need to do it sooner for some reason (FF61+) + if (_this.shouldAcquireMedia && _this.options.alwaysAcquireMediaFirst) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return Utils_1.Utils.reducePromises(modifiers, description); }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e, "The modifiers did not resolve successfully"); + _this.logger.error(error.message); + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function (modifiedDescription) { + _this.emit("setDescription", modifiedDescription); + return _this.peerConnection.setRemoteDescription(modifiedDescription); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + // Check the original SDP for video, and ensure that we have want to do audio fallback + if ((/^m=video.+$/gm).test(sessionDescription) && !options.disableAudioFallback) { + // Do not try to audio fallback again + options.disableAudioFallback = true; + // Remove video first, then do the other modifiers + return _this.setDescription(sessionDescription, options, [Modifiers.stripVideo].concat(modifiers)); + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e); + if (error.error) { + _this.logger.error(error.error); + } + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function () { + if (_this.peerConnection.getReceivers) { + _this.emit("setRemoteDescription", _this.peerConnection.getReceivers()); + } + else { + _this.emit("setRemoteDescription", _this.peerConnection.getRemoteStreams()); + } + _this.emit("confirmed", _this); + }); + }; + /** + * Send DTMF via RTP (RFC 4733) + * @param {String} tones A string containing DTMF digits + * @param {Object} [options] Options object to be used by sendDtmf + * @returns {boolean} true if DTMF send is successful, false otherwise + */ + SessionDescriptionHandler.prototype.sendDtmf = function (tones, options) { + if (!this.dtmfSender && this.hasBrowserGetSenderSupport()) { + var senders = this.peerConnection.getSenders(); + if (senders.length > 0) { + this.dtmfSender = senders[0].dtmf; + } + } + if (!this.dtmfSender && this.hasBrowserTrackSupport()) { + var streams = this.peerConnection.getLocalStreams(); + if (streams.length > 0) { + var audioTracks = streams[0].getAudioTracks(); + if (audioTracks.length > 0) { + this.dtmfSender = this.peerConnection.createDTMFSender(audioTracks[0]); + } + } + } + if (!this.dtmfSender) { + return false; + } + try { + this.dtmfSender.insertDTMF(tones, options.duration, options.interToneGap); + } + catch (e) { + if (e.type === "InvalidStateError" || e.type === "InvalidCharacterError") { + this.logger.error(e); + return false; + } + else { + throw e; + } + } + this.logger.log("DTMF sent via RTP: " + tones.toString()); + return true; + }; + SessionDescriptionHandler.prototype.getDirection = function () { + return this.direction; + }; + // Internal functions + SessionDescriptionHandler.prototype.createOfferOrAnswer = function (RTCOfferOptions, modifiers) { + var _this = this; + if (RTCOfferOptions === void 0) { RTCOfferOptions = {}; } + RTCOfferOptions = RTCOfferOptions || {}; + var methodName = this.hasOffer("remote") ? "createAnswer" : "createOffer"; + var pc = this.peerConnection; + this.logger.log(methodName); + return pc[methodName](RTCOfferOptions).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-" + methodName + "Failed"); + _this.emit("peerConnection-" + methodName + "Failed", error); + throw error; + }).then(function (sdp) { return Utils_1.Utils.reducePromises(modifiers, _this.createRTCSessionDescriptionInit(sdp)); }) + .then(function (sdp) { + _this.resetIceGatheringComplete(); + _this.logger.log("Setting local sdp."); + _this.logger.log("sdp is " + sdp.sdp || "undefined"); + return pc.setLocalDescription(sdp); + }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-SetLocalDescriptionFailed"); + _this.emit("peerConnection-SetLocalDescriptionFailed", error); + throw error; + }).then(function () { return _this.waitForIceGatheringComplete(); }) + .then(function () { + var localDescription = _this.createRTCSessionDescriptionInit(_this.peerConnection.localDescription); + return Utils_1.Utils.reducePromises(modifiers, localDescription); + }).then(function (localDescription) { + _this.setDirection(localDescription.sdp || ""); + return localDescription; + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e); + _this.logger.error(error.toString()); + throw error; + }); + }; + // Creates an RTCSessionDescriptionInit from an RTCSessionDescription + SessionDescriptionHandler.prototype.createRTCSessionDescriptionInit = function (RTCSessionDescription) { + return { + type: RTCSessionDescription.type, + sdp: RTCSessionDescription.sdp + }; + }; + SessionDescriptionHandler.prototype.addDefaultIceCheckingTimeout = function (peerConnectionOptions) { + if (peerConnectionOptions.iceCheckingTimeout === undefined) { + peerConnectionOptions.iceCheckingTimeout = 5000; + } + return peerConnectionOptions; + }; + SessionDescriptionHandler.prototype.addDefaultIceServers = function (rtcConfiguration) { + if (!rtcConfiguration.iceServers) { + rtcConfiguration.iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; + } + return rtcConfiguration; + }; + SessionDescriptionHandler.prototype.checkAndDefaultConstraints = function (constraints) { + var defaultConstraints = { audio: true, video: !this.options.alwaysAcquireMediaFirst }; + constraints = constraints || defaultConstraints; + // Empty object check + if (Object.keys(constraints).length === 0 && constraints.constructor === Object) { + return defaultConstraints; + } + return constraints; + }; + SessionDescriptionHandler.prototype.hasBrowserTrackSupport = function () { + return Boolean(this.peerConnection.addTrack); + }; + SessionDescriptionHandler.prototype.hasBrowserGetSenderSupport = function () { + return Boolean(this.peerConnection.getSenders); + }; + SessionDescriptionHandler.prototype.initPeerConnection = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + options = this.addDefaultIceCheckingTimeout(options); + options.rtcConfiguration = options.rtcConfiguration || {}; + options.rtcConfiguration = this.addDefaultIceServers(options.rtcConfiguration); + this.logger.log("initPeerConnection"); + if (this.peerConnection) { + this.logger.log("Already have a peer connection for this session. Tearing down."); + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + this.peerConnection = new this.WebRTC.RTCPeerConnection(options.rtcConfiguration); + this.logger.log("New peer connection created"); + if ("ontrack" in this.peerConnection) { + this.peerConnection.addEventListener("track", function (e) { + _this.logger.log("track added"); + _this.observer.trackAdded(); + _this.emit("addTrack", e); + }); + } + else { + this.logger.warn("Using onaddstream which is deprecated"); + this.peerConnection.onaddstream = function (e) { + _this.logger.log("stream added"); + _this.emit("addStream", e); + }; + } + this.peerConnection.onicecandidate = function (e) { + _this.emit("iceCandidate", e); + if (e.candidate) { + _this.logger.log("ICE candidate received: " + + (e.candidate.candidate === null ? null : e.candidate.candidate.trim())); + } + else if (e.candidate === null) { + // indicates the end of candidate gathering + _this.logger.log("ICE candidate gathering complete"); + _this.triggerIceGatheringComplete(); + } + }; + this.peerConnection.onicegatheringstatechange = function () { + _this.logger.log("RTCIceGatheringState changed: " + _this.peerConnection.iceGatheringState); + switch (_this.peerConnection.iceGatheringState) { + case "gathering": + _this.emit("iceGathering", _this); + if (!_this.iceGatheringTimer && options.iceCheckingTimeout) { + _this.iceGatheringTimeout = false; + _this.iceGatheringTimer = setTimeout(function () { + _this.logger.log("RTCIceChecking Timeout Triggered after " + options.iceCheckingTimeout + " milliseconds"); + _this.iceGatheringTimeout = true; + _this.triggerIceGatheringComplete(); + }, options.iceCheckingTimeout); + } + break; + case "complete": + _this.triggerIceGatheringComplete(); + break; + } + }; + this.peerConnection.oniceconnectionstatechange = function () { + var stateEvent; + switch (_this.peerConnection.iceConnectionState) { + case "new": + stateEvent = "iceConnection"; + break; + case "checking": + stateEvent = "iceConnectionChecking"; + break; + case "connected": + stateEvent = "iceConnectionConnected"; + break; + case "completed": + stateEvent = "iceConnectionCompleted"; + break; + case "failed": + stateEvent = "iceConnectionFailed"; + break; + case "disconnected": + stateEvent = "iceConnectionDisconnected"; + break; + case "closed": + stateEvent = "iceConnectionClosed"; + break; + default: + _this.logger.warn("Unknown iceConnection state: " + _this.peerConnection.iceConnectionState); + return; + } + _this.logger.log("ICE Connection State changed to " + stateEvent); + _this.emit(stateEvent, _this); + }; + }; + SessionDescriptionHandler.prototype.acquire = function (constraints) { + var _this = this; + // Default audio & video to true + constraints = this.checkAndDefaultConstraints(constraints); + return new Promise(function (resolve, reject) { + /* + * Make the call asynchronous, so that ICCs have a chance + * to define callbacks to `userMediaRequest` + */ + _this.logger.log("acquiring local media"); + _this.emit("userMediaRequest", constraints); + if (constraints.audio || constraints.video) { + _this.WebRTC.getUserMedia(constraints).then(function (streams) { + _this.observer.trackAdded(); + _this.emit("userMedia", streams); + resolve(streams); + }).catch(function (e) { + _this.emit("userMediaFailed", e); + reject(e); + }); + } + else { + // Local streams were explicitly excluded. + resolve([]); + } + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "unable to acquire streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + _this.logger.log("acquired local media streams"); + try { + // Remove old tracks + if (_this.peerConnection.removeTrack) { + _this.peerConnection.getSenders().forEach(function (sender) { + _this.peerConnection.removeTrack(sender); + }); + } + return streams; + } + catch (e) { + return Promise.reject(e); + } + }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error removing streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + try { + streams = [].concat(streams); + streams.forEach(function (stream) { + if (_this.peerConnection.addTrack) { + stream.getTracks().forEach(function (track) { + _this.peerConnection.addTrack(track, stream); + }); + } + else { + // Chrome 59 does not support addTrack + _this.peerConnection.addStream(stream); + } + }); + } + catch (e) { + return Promise.reject(e); + } + return Promise.resolve(); + }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error adding stream"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }); + }; + SessionDescriptionHandler.prototype.hasOffer = function (where) { + var offerState = "have-" + where + "-offer"; + return this.peerConnection.signalingState === offerState; + }; + // ICE gathering state handling + SessionDescriptionHandler.prototype.isIceGatheringComplete = function () { + return this.peerConnection.iceGatheringState === "complete" || this.iceGatheringTimeout; + }; + SessionDescriptionHandler.prototype.resetIceGatheringComplete = function () { + this.iceGatheringTimeout = false; + this.logger.log("resetIceGatheringComplete"); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.reject(); + this.iceGatheringDeferred = undefined; + } + }; + SessionDescriptionHandler.prototype.setDirection = function (sdp) { + var match = sdp.match(/a=(sendrecv|sendonly|recvonly|inactive)/); + if (match === null) { + this.direction = this.C.DIRECTION.NULL; + this.observer.directionChanged(); + return; + } + var direction = match[1]; + switch (direction) { + case this.C.DIRECTION.SENDRECV: + case this.C.DIRECTION.SENDONLY: + case this.C.DIRECTION.RECVONLY: + case this.C.DIRECTION.INACTIVE: + this.direction = direction; + break; + default: + this.direction = this.C.DIRECTION.NULL; + break; + } + this.observer.directionChanged(); + }; + SessionDescriptionHandler.prototype.triggerIceGatheringComplete = function () { + if (this.isIceGatheringComplete()) { + this.emit("iceGatheringComplete", this); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.resolve(); + this.iceGatheringDeferred = undefined; + } + } + }; + SessionDescriptionHandler.prototype.waitForIceGatheringComplete = function () { + this.logger.log("waitForIceGatheringComplete"); + if (this.isIceGatheringComplete()) { + this.logger.log("ICE is already complete. Return resolved."); + return Promise.resolve(); + } + else if (!this.iceGatheringDeferred) { + this.iceGatheringDeferred = Utils_1.Utils.defer(); + } + this.logger.log("ICE is not complete. Returning promise"); + return this.iceGatheringDeferred ? this.iceGatheringDeferred.promise : Promise.resolve(); + }; + return SessionDescriptionHandler; +}(events_1.EventEmitter)); +exports.SessionDescriptionHandler = SessionDescriptionHandler; diff --git a/lib/React/SessionDescriptionHandlerObserver.d.ts b/lib/React/SessionDescriptionHandlerObserver.d.ts new file mode 100644 index 000000000..23e7576bb --- /dev/null +++ b/lib/React/SessionDescriptionHandlerObserver.d.ts @@ -0,0 +1,10 @@ +import { TypeStrings } from "../Enums"; +import { InviteClientContext, InviteServerContext } from "../Session"; +export declare class SessionDescriptionHandlerObserver { + type: TypeStrings; + private session; + private options; + constructor(session: InviteClientContext | InviteServerContext, options: any); + trackAdded(): void; + directionChanged(): void; +} diff --git a/lib/React/SessionDescriptionHandlerObserver.js b/lib/React/SessionDescriptionHandlerObserver.js new file mode 100644 index 000000000..d410c8968 --- /dev/null +++ b/lib/React/SessionDescriptionHandlerObserver.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Enums_1 = require("../Enums"); +/* SessionDescriptionHandlerObserver + * @class SessionDescriptionHandler Observer Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandlerObserver = /** @class */ (function () { + function SessionDescriptionHandlerObserver(session, options) { + this.type = Enums_1.TypeStrings.SessionDescriptionHandlerObserver; + this.session = session; + this.options = options; + } + SessionDescriptionHandlerObserver.prototype.trackAdded = function () { + this.session.emit("trackAdded"); + }; + SessionDescriptionHandlerObserver.prototype.directionChanged = function () { + this.session.emit("directionChanged"); + }; + return SessionDescriptionHandlerObserver; +}()); +exports.SessionDescriptionHandlerObserver = SessionDescriptionHandlerObserver; diff --git a/lib/React/index.d.ts b/lib/React/index.d.ts new file mode 100644 index 000000000..909fc1348 --- /dev/null +++ b/lib/React/index.d.ts @@ -0,0 +1 @@ +export { SessionDescriptionHandler } from "./SessionDescriptionHandler"; diff --git a/lib/React/index.js b/lib/React/index.js new file mode 100644 index 000000000..3196ab1f6 --- /dev/null +++ b/lib/React/index.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SessionDescriptionHandler_1 = require("./SessionDescriptionHandler"); +exports.SessionDescriptionHandler = SessionDescriptionHandler_1.SessionDescriptionHandler; diff --git a/lib/ReferContext.d.ts b/lib/ReferContext.d.ts new file mode 100644 index 000000000..44b151d40 --- /dev/null +++ b/lib/ReferContext.d.ts @@ -0,0 +1,56 @@ +import { ClientContext } from "./ClientContext"; +import { IncomingRequest, NameAddrHeader, Session, URI } from "./core"; +import { SessionStatus, TypeStrings } from "./Enums"; +import { ServerContext } from "./ServerContext"; +import { InviteClientContext, InviteServerContext } from "./Session"; +import { SessionDescriptionHandlerModifiers } from "./session-description-handler"; +import { UA } from "./UA"; +export declare namespace ReferServerContext { + interface AcceptOptions { + /** If true, accept REFER request and automatically attempt to follow it. */ + followRefer?: boolean; + /** If followRefer is true, options to following INVITE request. */ + inviteOptions?: InviteClientContext.Options; + } + interface RejectOptions { + } +} +export declare class ReferClientContext extends ClientContext { + type: TypeStrings; + protected extraHeaders: Array; + protected options: any; + protected applicant: InviteClientContext | InviteServerContext; + protected target: URI | string; + private errorListener; + constructor(ua: UA, applicant: InviteClientContext | InviteServerContext, target: InviteClientContext | InviteServerContext | string, options?: any); + refer(options?: any): ReferClientContext; + receiveNotify(request: IncomingRequest): void; + protected initReferTo(target: InviteClientContext | InviteServerContext | string): string | URI; +} +export declare class ReferServerContext extends ServerContext { + private session?; + type: TypeStrings; + referTo: NameAddrHeader; + targetSession: InviteClientContext | InviteServerContext | undefined; + protected status: SessionStatus; + protected fromTag: string; + protected fromUri: URI; + protected toUri: URI; + protected toTag: string; + protected routeSet: Array; + protected remoteTarget: URI; + protected id: string; + protected callId: string; + protected cseq: number; + protected contact: string; + protected referredBy: string | undefined; + protected referredSession: InviteClientContext | InviteServerContext | undefined; + protected replaces: string | undefined; + protected errorListener: (() => void); + constructor(ua: UA, incomingRequest: IncomingRequest, session?: Session | undefined); + progress(): void; + reject(options?: ReferServerContext.RejectOptions): void; + accept(options?: ReferServerContext.AcceptOptions, modifiers?: SessionDescriptionHandlerModifiers): void; + sendNotify(bodyStr: string): void; + on(name: "referAccepted" | "referInviteSent" | "referProgress" | "referRejected" | "referRequestAccepted" | "referRequestRejected", callback: (referServerContext: ReferServerContext) => void): this; +} diff --git a/lib/ReferContext.js b/lib/ReferContext.js new file mode 100644 index 000000000..2775dedb7 --- /dev/null +++ b/lib/ReferContext.js @@ -0,0 +1,344 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var ClientContext_1 = require("./ClientContext"); +var Constants_1 = require("./Constants"); +var core_1 = require("./core"); +var Enums_1 = require("./Enums"); +var Exceptions_1 = require("./Exceptions"); +var ServerContext_1 = require("./ServerContext"); +// tslint:disable-next-line:max-classes-per-file +var ReferClientContext = /** @class */ (function (_super) { + tslib_1.__extends(ReferClientContext, _super); + function ReferClientContext(ua, applicant, target, options) { + if (options === void 0) { options = {}; } + var _this = this; + if (ua === undefined || applicant === undefined || target === undefined) { + throw new TypeError("Not enough arguments"); + } + _this = _super.call(this, ua, Constants_1.C.REFER, applicant.remoteIdentity.uri.toString(), options) || this; + _this.type = Enums_1.TypeStrings.ReferClientContext; + _this.options = options; + _this.extraHeaders = (_this.options.extraHeaders || []).slice(); + _this.applicant = applicant; + _this.target = _this.initReferTo(target); + if (_this.ua) { + _this.extraHeaders.push("Referred-By: <" + _this.ua.configuration.uri + ">"); + } + // TODO: Check that this is correct isc/icc + _this.extraHeaders.push("Contact: " + applicant.contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + _this.extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + _this.extraHeaders.push("Refer-To: " + _this.target); + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + ReferClientContext.prototype.refer = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var extraHeaders = (this.extraHeaders || []).slice(); + if (options.extraHeaders) { + extraHeaders.concat(options.extraHeaders); + } + this.applicant.sendRequest(Constants_1.C.REFER, { + extraHeaders: this.extraHeaders, + receiveResponse: function (response) { + var statusCode = response && response.statusCode ? response.statusCode.toString() : ""; + if (/^1[0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestProgress", _this); + } + else if (/^2[0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestAccepted", _this); + } + else if (/^[4-6][0-9]{2}$/.test(statusCode)) { + _this.emit("referRequestRejected", _this); + } + if (options.receiveResponse) { + options.receiveResponse(response); + } + } + }); + return this; + }; + ReferClientContext.prototype.receiveNotify = function (request) { + // If we can correctly handle this, then we need to send a 200 OK! + var contentType = request.message.hasHeader("Content-Type") ? + request.message.getHeader("Content-Type") : undefined; + if (contentType && contentType.search(/^message\/sipfrag/) !== -1) { + var messageBody = core_1.Grammar.parse(request.message.body, "sipfrag"); + if (messageBody === -1) { + request.reject({ + statusCode: 489, + reasonPhrase: "Bad Event" + }); + return; + } + switch (true) { + case (/^1[0-9]{2}$/.test(messageBody.status_code)): + this.emit("referProgress", this); + break; + case (/^2[0-9]{2}$/.test(messageBody.status_code)): + this.emit("referAccepted", this); + if (!this.options.activeAfterTransfer && this.applicant.terminate) { + this.applicant.terminate(); + } + break; + default: + this.emit("referRejected", this); + break; + } + request.accept(); + this.emit("notify", request.message); + return; + } + request.reject({ + statusCode: 489, + reasonPhrase: "Bad Event" + }); + }; + ReferClientContext.prototype.initReferTo = function (target) { + var stringOrURI; + if (typeof target === "string") { + // REFER without Replaces (Blind Transfer) + var targetString = core_1.Grammar.parse(target, "Refer_To"); + stringOrURI = targetString && targetString.uri ? targetString.uri : target; + // Check target validity + var targetUri = this.ua.normalizeTarget(target); + if (!targetUri) { + throw new TypeError("Invalid target: " + target); + } + stringOrURI = targetUri; + } + else { + // REFER with Replaces (Attended Transfer) + if (!target.session) { + throw new Error("Session undefined."); + } + var displayName = target.remoteIdentity.friendlyName; + var remoteTarget = target.session.remoteTarget.toString(); + var callId = target.session.callId; + var remoteTag = target.session.remoteTag; + var localTag = target.session.localTag; + var replaces = encodeURIComponent(callId + ";to-tag=" + remoteTag + ";from-tag=" + localTag); + stringOrURI = "\"" + displayName + "\" <" + remoteTarget + "?Replaces=" + replaces + ">"; + } + return stringOrURI; + }; + return ReferClientContext; +}(ClientContext_1.ClientContext)); +exports.ReferClientContext = ReferClientContext; +// tslint:disable-next-line:max-classes-per-file +var ReferServerContext = /** @class */ (function (_super) { + tslib_1.__extends(ReferServerContext, _super); + function ReferServerContext(ua, incomingRequest, session) { + var _this = _super.call(this, ua, incomingRequest) || this; + _this.session = session; + _this.type = Enums_1.TypeStrings.ReferServerContext; + _this.ua = ua; + _this.status = Enums_1.SessionStatus.STATUS_INVITE_RECEIVED; + _this.fromTag = _this.request.fromTag; + _this.id = _this.request.callId + _this.fromTag; + _this.contact = _this.ua.contact.toString(); + _this.logger = ua.getLogger("sip.referservercontext", _this.id); + // Needed to send the NOTIFY's + _this.cseq = Math.floor(Math.random() * 10000); + _this.callId = _this.request.callId; + _this.fromUri = _this.request.to.uri; + _this.fromTag = _this.request.to.parameters.tag; + _this.remoteTarget = _this.request.headers.Contact[0].parsed.uri; + _this.toUri = _this.request.from.uri; + _this.toTag = _this.request.fromTag; + _this.routeSet = _this.request.getHeaders("record-route"); + // RFC 3515 2.4.1 + if (!_this.request.hasHeader("refer-to")) { + _this.logger.warn("Invalid REFER packet. A refer-to header is required. Rejecting refer."); + _this.reject(); + return _this; + } + _this.referTo = _this.request.parseHeader("refer-to"); + // TODO: Must set expiration timer and send 202 if there is no response by then + _this.referredSession = _this.ua.findSession(_this.request); + if (_this.request.hasHeader("referred-by")) { + _this.referredBy = _this.request.getHeader("referred-by"); + } + if (_this.referTo.uri.hasHeader("replaces")) { + _this.replaces = _this.referTo.uri.getHeader("replaces"); + } + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + return _this; + } + ReferServerContext.prototype.progress = function () { + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.incomingRequest.trying(); + }; + ReferServerContext.prototype.reject = function (options) { + if (options === void 0) { options = {}; } + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.logger.log("Rejecting refer"); + this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + _super.prototype.reject.call(this, options); + this.emit("referRequestRejected", this); + }; + ReferServerContext.prototype.accept = function (options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + } + else { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.incomingRequest.accept({ + statusCode: 202, + reasonPhrase: "Accepted" + }); + this.emit("referRequestAccepted", this); + if (options.followRefer) { + this.logger.log("Accepted refer, attempting to automatically follow it"); + var target = this.referTo.uri; + if (!target.scheme || !target.scheme.match("^sips?$")) { + this.logger.error("SIP.js can only automatically follow SIP refer target"); + this.reject(); + return; + } + var inviteOptions = options.inviteOptions || {}; + var extraHeaders = (inviteOptions.extraHeaders || []).slice(); + if (this.replaces) { + // decodeURIComponent is a holdover from 2c086eb4. Not sure that it is actually necessary + extraHeaders.push("Replaces: " + decodeURIComponent(this.replaces)); + } + if (this.referredBy) { + extraHeaders.push("Referred-By: " + this.referredBy); + } + inviteOptions.extraHeaders = extraHeaders; + target.clearHeaders(); + this.targetSession = this.ua.invite(target.toString(), inviteOptions, modifiers); + this.emit("referInviteSent", this); + if (this.targetSession) { + this.targetSession.once("progress", function (response) { + var statusCode = response.statusCode || 100; + var reasonPhrase = response.reasonPhrase; + _this.sendNotify(("SIP/2.0 " + statusCode + " " + reasonPhrase).trim()); + _this.emit("referProgress", _this); + if (_this.referredSession) { + _this.referredSession.emit("referProgress", _this); + } + }); + this.targetSession.once("accepted", function () { + _this.logger.log("Successfully followed the refer"); + _this.sendNotify("SIP/2.0 200 OK"); + _this.emit("referAccepted", _this); + if (_this.referredSession) { + _this.referredSession.emit("referAccepted", _this); + } + }); + var referFailed = function (response) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; // No throw here because it is possible this gets called multiple times + } + _this.logger.log("Refer was not successful. Resuming session"); + if (response && response.statusCode === 429) { + _this.logger.log("Alerting referrer that identity is required."); + _this.sendNotify("SIP/2.0 429 Provide Referrer Identity"); + return; + } + _this.sendNotify("SIP/2.0 603 Declined"); + // Must change the status after sending the final Notify or it will not send due to check + _this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + _this.emit("referRejected", _this); + if (_this.referredSession) { + _this.referredSession.emit("referRejected"); + } + }; + this.targetSession.once("rejected", referFailed); + this.targetSession.once("failed", referFailed); + } + } + else { + this.logger.log("Accepted refer, but did not automatically follow it"); + this.sendNotify("SIP/2.0 200 OK"); + this.emit("referAccepted", this); + if (this.referredSession) { + this.referredSession.emit("referAccepted", this); + } + } + }; + ReferServerContext.prototype.sendNotify = function (bodyStr) { + // FIXME: Ported this. Clean it up. Session knows its state. + if (this.status !== Enums_1.SessionStatus.STATUS_ANSWERED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (core_1.Grammar.parse(bodyStr, "sipfrag") === -1) { + throw new Error("sipfrag body is required to send notify for refer"); + } + var body = { + contentDisposition: "render", + contentType: "message/sipfrag", + content: bodyStr + }; + // NOTIFY requests sent in same dialog as in dialog REFER. + if (this.session) { + this.session.notify(undefined, { + extraHeaders: [ + "Event: refer", + "Subscription-State: terminated", + ], + body: body + }); + return; + } + // The implicit subscription created by a REFER is the same as a + // subscription created with a SUBSCRIBE request. The agent issuing the + // REFER can terminate this subscription prematurely by unsubscribing + // using the mechanisms described in [2]. Terminating a subscription, + // either by explicitly unsubscribing or rejecting NOTIFY, is not an + // indication that the referenced request should be withdrawn or + // abandoned. + // https://tools.ietf.org/html/rfc3515#section-2.4.4 + // NOTIFY requests sent in new dialog for out of dialog REFER. + // FIXME: TODO: This should be done in a subscribe dialog to satisfy the above. + var request = this.ua.userAgentCore.makeOutgoingRequestMessage(Constants_1.C.NOTIFY, this.remoteTarget, this.fromUri, this.toUri, { + cseq: this.cseq += 1, + callId: this.callId, + fromTag: this.fromTag, + toTag: this.toTag, + routeSet: this.routeSet + }, [ + "Event: refer", + "Subscription-State: terminated", + "Content-Type: message/sipfrag" + ], body); + var transport = this.ua.transport; + if (!transport) { + throw new Error("Transport undefined."); + } + var user = { + loggerFactory: this.ua.getLoggerFactory() + }; + var nic = new core_1.NonInviteClientTransaction(request, transport, user); + }; + ReferServerContext.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + return ReferServerContext; +}(ServerContext_1.ServerContext)); +exports.ReferServerContext = ReferServerContext; diff --git a/lib/RegisterContext.d.ts b/lib/RegisterContext.d.ts new file mode 100644 index 000000000..fd5f0cca7 --- /dev/null +++ b/lib/RegisterContext.d.ts @@ -0,0 +1,39 @@ +import { ClientContext } from "./ClientContext"; +import { IncomingResponseMessage } from "./core"; +import { TypeStrings } from "./Enums"; +import { UA } from "./UA"; +export declare namespace RegisterContext { + interface RegistrationConfiguration { + expires?: string; + extraContactHeaderParams?: Array; + instanceId?: string; + params?: any; + regId?: number; + registrar?: string; + } +} +export declare class RegisterContext extends ClientContext { + type: TypeStrings; + registered: boolean; + private options; + private expires; + private contact; + private registrationTimer; + private registrationExpiredTimer; + private registeredBefore; + private closeHeaders; + constructor(ua: UA, options?: any); + register(options?: any): void; + close(): void; + unregister(options?: any): void; + unregistered(response?: IncomingResponseMessage, cause?: string): void; + send(): this; + private registrationFailure; + private onTransportDisconnected; + /** + * Helper Function to generate Contact Header + * @private + * returns {String} + */ + private generateContactHeader; +} diff --git a/lib/RegisterContext.js b/lib/RegisterContext.js new file mode 100644 index 000000000..049bfd22f --- /dev/null +++ b/lib/RegisterContext.js @@ -0,0 +1,413 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var ClientContext_1 = require("./ClientContext"); +var Constants_1 = require("./Constants"); +var core_1 = require("./core"); +var Enums_1 = require("./Enums"); +var Exceptions_1 = require("./Exceptions"); +var Utils_1 = require("./Utils"); +/** + * Configuration load. + * @private + * returns {any} + */ +function loadConfig(configuration) { + var settings = { + expires: 600, + extraContactHeaderParams: [], + instanceId: undefined, + params: {}, + regId: undefined, + registrar: undefined, + }; + var configCheck = getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + if (value instanceof Array && value.length === 0) { + continue; + } + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if (value === null || value === "" || value === undefined || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + return settings; +} +function getConfigurationCheck() { + return { + mandatory: {}, + optional: { + expires: function (expires) { + if (Utils_1.Utils.isDecimal(expires)) { + var value = Number(expires); + if (value >= 0) { + return value; + } + } + }, + extraContactHeaderParams: function (extraContactHeaderParams) { + if (extraContactHeaderParams instanceof Array) { + return extraContactHeaderParams.filter(function (contactHeaderParam) { return (typeof contactHeaderParam === "string"); }); + } + }, + instanceId: function (instanceId) { + if (typeof instanceId !== "string") { + return; + } + if ((/^uuid:/i.test(instanceId))) { + instanceId = instanceId.substr(5); + } + if (core_1.Grammar.parse(instanceId, "uuid") === -1) { + return; + } + else { + return instanceId; + } + }, + params: function (params) { + if (typeof params === "object") { + return params; + } + }, + regId: function (regId) { + if (Utils_1.Utils.isDecimal(regId)) { + var value = Number(regId); + if (value >= 0) { + return value; + } + } + }, + registrar: function (registrar) { + if (typeof registrar !== "string") { + return; + } + if (!/^sip:/i.test(registrar)) { + registrar = Constants_1.C.SIP + ":" + registrar; + } + var parsed = core_1.Grammar.URIParse(registrar); + if (!parsed) { + return; + } + else if (parsed.user) { + return; + } + else { + return parsed; + } + } + } + }; +} +var RegisterContext = /** @class */ (function (_super) { + tslib_1.__extends(RegisterContext, _super); + function RegisterContext(ua, options) { + if (options === void 0) { options = {}; } + var _this = this; + var settings = loadConfig(options); + if (settings.regId && !settings.instanceId) { + settings.instanceId = Utils_1.Utils.newUUID(); + } + else if (!settings.regId && settings.instanceId) { + settings.regId = 1; + } + settings.params.toUri = settings.params.toUri || ua.configuration.uri; + settings.params.toDisplayName = settings.params.toDisplayName || ua.configuration.displayName; + settings.params.callId = settings.params.callId || Utils_1.Utils.createRandomToken(22); + settings.params.cseq = settings.params.cseq || Math.floor(Math.random() * 10000); + /* If no 'registrarServer' is set use the 'uri' value without user portion. */ + if (!settings.registrar) { + var registrarServer = {}; + if (typeof ua.configuration.uri === "object") { + registrarServer = ua.configuration.uri.clone(); + registrarServer.user = undefined; + } + else { + registrarServer = ua.configuration.uri; + } + settings.registrar = registrarServer; + } + _this = _super.call(this, ua, Constants_1.C.REGISTER, settings.registrar, settings) || this; + _this.type = Enums_1.TypeStrings.RegisterContext; + _this.options = settings; + _this.logger = ua.getLogger("sip.registercontext"); + _this.logger.log("configuration parameters for RegisterContext after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + _this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + // Registration expires + _this.expires = settings.expires; + // Contact header + _this.contact = ua.contact.toString(); + // Set status + _this.registered = false; + ua.on("transportCreated", function (transport) { + transport.on("disconnected", function () { return _this.onTransportDisconnected(); }); + }); + return _this; + } + RegisterContext.prototype.register = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Handle Options + this.options = tslib_1.__assign({}, this.options, options); + var extraHeaders = (this.options.extraHeaders || []).slice(); + extraHeaders.push("Contact: " + this.generateContactHeader(this.expires)); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + // Save original extraHeaders to be used in .close + this.closeHeaders = this.options.closeWithHeaders ? + (this.options.extraHeaders || []).slice() : []; + this.receiveResponse = function (response) { + // Discard responses to older REGISTER/un-REGISTER requests. + if (response.cseq !== _this.request.cseq) { + return; + } + // Clear registration timer + if (_this.registrationTimer !== undefined) { + clearTimeout(_this.registrationTimer); + _this.registrationTimer = undefined; + } + var statusCode = (response.statusCode || 0).toString(); + switch (true) { + case /^1[0-9]{2}$/.test(statusCode): + _this.emit("progress", response); + break; + case /^2[0-9]{2}$/.test(statusCode): + _this.emit("accepted", response); + var expires = void 0; + if (response.hasHeader("expires")) { + expires = Number(response.getHeader("expires")); + } + if (_this.registrationExpiredTimer !== undefined) { + clearTimeout(_this.registrationExpiredTimer); + _this.registrationExpiredTimer = undefined; + } + // Search the Contact pointing to us and update the expires value accordingly. + var contacts = response.getHeaders("contact").length; + if (!contacts) { + _this.logger.warn("no Contact header in response to REGISTER, response ignored"); + break; + } + var contact = void 0; + while (contacts--) { + contact = response.parseHeader("contact", contacts); + if (contact.uri.user === _this.ua.contact.uri.user) { + expires = contact.getParam("expires"); + break; + } + else { + contact = undefined; + } + } + if (!contact) { + _this.logger.warn("no Contact header pointing to us, response ignored"); + break; + } + if (expires === undefined) { + expires = _this.expires; + } + // Re-Register before the expiration interval has elapsed. + // For that, decrease the expires value. ie: 3 seconds + _this.registrationTimer = setTimeout(function () { + _this.registrationTimer = undefined; + _this.register(_this.options); + }, (expires * 1000) - 3000); + _this.registrationExpiredTimer = setTimeout(function () { + _this.logger.warn("registration expired"); + if (_this.registered) { + _this.unregistered(undefined, Constants_1.C.causes.EXPIRES); + } + }, expires * 1000); + // Save gruu values + if (contact.hasParam("temp-gruu")) { + _this.ua.contact.tempGruu = core_1.Grammar.URIParse(contact.getParam("temp-gruu").replace(/"/g, "")); + } + if (contact.hasParam("pub-gruu")) { + _this.ua.contact.pubGruu = core_1.Grammar.URIParse(contact.getParam("pub-gruu").replace(/"/g, "")); + } + _this.registered = true; + _this.emit("registered", response || undefined); + break; + // Interval too brief RFC3261 10.2.8 + case /^423$/.test(statusCode): + if (response.hasHeader("min-expires")) { + // Increase our registration interval to the suggested minimum + _this.expires = Number(response.getHeader("min-expires")); + // Attempt the registration again immediately + _this.register(_this.options); + } + else { // This response MUST contain a Min-Expires header field + _this.logger.warn("423 response received for REGISTER without Min-Expires"); + _this.registrationFailure(response, Constants_1.C.causes.SIP_FAILURE_CODE); + } + break; + default: + _this.registrationFailure(response, Utils_1.Utils.sipErrorCause(response.statusCode || 0)); + } + }; + this.onRequestTimeout = function () { + _this.registrationFailure(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + this.onTransportError = function () { + _this.registrationFailure(undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + this.request.cseq++; + this.request.setHeader("cseq", this.request.cseq + " REGISTER"); + this.request.extraHeaders = extraHeaders; + this.send(); + }; + RegisterContext.prototype.close = function () { + var options = { + all: false, + extraHeaders: this.closeHeaders + }; + this.registeredBefore = this.registered; + if (this.registered) { + this.unregister(options); + } + }; + RegisterContext.prototype.unregister = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (!this.registered && !options.all) { + this.logger.warn("Already unregistered, but sending an unregister anyways."); + } + var extraHeaders = (options.extraHeaders || []).slice(); + this.registered = false; + // Clear the registration timer. + if (this.registrationTimer !== undefined) { + clearTimeout(this.registrationTimer); + this.registrationTimer = undefined; + } + if (options.all) { + extraHeaders.push("Contact: *"); + extraHeaders.push("Expires: 0"); + } + else { + extraHeaders.push("Contact: " + this.generateContactHeader(0)); + } + this.receiveResponse = function (response) { + var statusCode = (response && response.statusCode) ? response.statusCode.toString() : ""; + switch (true) { + case /^1[0-9]{2}$/.test(statusCode): + _this.emit("progress", response); + break; + case /^2[0-9]{2}$/.test(statusCode): + _this.emit("accepted", response); + if (_this.registrationExpiredTimer !== undefined) { + clearTimeout(_this.registrationExpiredTimer); + _this.registrationExpiredTimer = undefined; + } + _this.unregistered(response); + break; + default: + _this.unregistered(response, Utils_1.Utils.sipErrorCause(response.statusCode || 0)); + } + }; + this.onRequestTimeout = function () { + // Not actually unregistered... + // this.unregistered(undefined, SIP.C.causes.REQUEST_TIMEOUT); + }; + this.request.cseq++; + this.request.setHeader("cseq", this.request.cseq + " REGISTER"); + this.request.extraHeaders = extraHeaders; + this.send(); + }; + RegisterContext.prototype.unregistered = function (response, cause) { + this.registered = false; + this.emit("unregistered", response || undefined, cause || undefined); + }; + RegisterContext.prototype.send = function () { + var _this = this; + this.ua.userAgentCore.register(this.request, { + onAccept: function (response) { return _this.receiveResponse(response.message); }, + onProgress: function (response) { return _this.receiveResponse(response.message); }, + onRedirect: function (response) { return _this.receiveResponse(response.message); }, + onReject: function (response) { return _this.receiveResponse(response.message); }, + onTrying: function (response) { return _this.receiveResponse(response.message); } + }); + return this; + }; + RegisterContext.prototype.registrationFailure = function (response, cause) { + this.emit("failed", response || undefined, cause || undefined); + }; + RegisterContext.prototype.onTransportDisconnected = function () { + this.registeredBefore = this.registered; + if (this.registrationTimer !== undefined) { + clearTimeout(this.registrationTimer); + this.registrationTimer = undefined; + } + if (this.registrationExpiredTimer !== undefined) { + clearTimeout(this.registrationExpiredTimer); + this.registrationExpiredTimer = undefined; + } + if (this.registered) { + this.unregistered(undefined, Constants_1.C.causes.CONNECTION_ERROR); + } + }; + /** + * Helper Function to generate Contact Header + * @private + * returns {String} + */ + RegisterContext.prototype.generateContactHeader = function (expires) { + if (expires === void 0) { expires = 0; } + var contact = this.contact; + if (this.options.regId && this.options.instanceId) { + contact += ";reg-id=" + this.options.regId; + contact += ';+sip.instance=""'; + } + if (this.options.extraContactHeaderParams) { + this.options.extraContactHeaderParams.forEach(function (header) { + contact += ";" + header; + }); + } + contact += ";expires=" + expires; + return contact; + }; + return RegisterContext; +}(ClientContext_1.ClientContext)); +exports.RegisterContext = RegisterContext; diff --git a/lib/ServerContext.d.ts b/lib/ServerContext.d.ts new file mode 100644 index 000000000..2bbcc5e74 --- /dev/null +++ b/lib/ServerContext.d.ts @@ -0,0 +1,28 @@ +/// +import { EventEmitter } from "events"; +import { IncomingRequest, IncomingRequestMessage, InviteServerTransaction, Logger, NameAddrHeader, NonInviteServerTransaction } from "./core"; +import { TypeStrings } from "./Enums"; +import { UA } from "./UA"; +export declare class ServerContext extends EventEmitter { + incomingRequest: IncomingRequest; + static initializer(objectToConstruct: ServerContext, ua: UA, incomingRequest: IncomingRequest): void; + type: TypeStrings; + ua: UA; + logger: Logger; + localIdentity: NameAddrHeader; + remoteIdentity: NameAddrHeader; + method: string; + request: IncomingRequestMessage; + data: any; + transaction: InviteServerTransaction | NonInviteServerTransaction; + body: any; + contentType: string | undefined; + assertedIdentity: NameAddrHeader | undefined; + constructor(ua: UA, incomingRequest: IncomingRequest); + progress(options?: any): any; + accept(options?: any): any; + reject(options?: any): any; + reply(options?: any): any; + onRequestTimeout(): void; + onTransportError(): void; +} diff --git a/lib/ServerContext.js b/lib/ServerContext.js new file mode 100644 index 000000000..37e40c588 --- /dev/null +++ b/lib/ServerContext.js @@ -0,0 +1,119 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var Constants_1 = require("./Constants"); +var core_1 = require("./core"); +var Enums_1 = require("./Enums"); +var Utils_1 = require("./Utils"); +var ServerContext = /** @class */ (function (_super) { + tslib_1.__extends(ServerContext, _super); + function ServerContext(ua, incomingRequest) { + var _this = _super.call(this) || this; + _this.incomingRequest = incomingRequest; + _this.data = {}; + ServerContext.initializer(_this, ua, incomingRequest); + return _this; + } + // hack to get around our multiple inheritance issues + ServerContext.initializer = function (objectToConstruct, ua, incomingRequest) { + var request = incomingRequest.message; + objectToConstruct.type = Enums_1.TypeStrings.ServerContext; + objectToConstruct.ua = ua; + objectToConstruct.logger = ua.getLogger("sip.servercontext"); + objectToConstruct.request = request; + if (request.body) { + objectToConstruct.body = request.body; + } + if (request.hasHeader("Content-Type")) { + objectToConstruct.contentType = request.getHeader("Content-Type"); + } + objectToConstruct.method = request.method; + objectToConstruct.localIdentity = request.to; + objectToConstruct.remoteIdentity = request.from; + var hasAssertedIdentity = request.hasHeader("P-Asserted-Identity"); + if (hasAssertedIdentity) { + var assertedIdentity = request.getHeader("P-Asserted-Identity"); + if (assertedIdentity) { + objectToConstruct.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(assertedIdentity); + } + } + }; + ServerContext.prototype.progress = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 180; + options.minCode = 100; + options.maxCode = 199; + options.events = ["progress"]; + return this.reply(options); + }; + ServerContext.prototype.accept = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 200; + options.minCode = 200; + options.maxCode = 299; + options.events = ["accepted"]; + return this.reply(options); + }; + ServerContext.prototype.reject = function (options) { + if (options === void 0) { options = {}; } + options.statusCode = options.statusCode || 480; + options.minCode = 300; + options.maxCode = 699; + options.events = ["rejected", "failed"]; + return this.reply(options); + }; + ServerContext.prototype.reply = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 100; + var minCode = options.minCode || 100; + var maxCode = options.maxCode || 699; + var reasonPhrase = Utils_1.Utils.getReasonPhrase(statusCode, options.reasonPhrase); + var extraHeaders = options.extraHeaders || []; + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + var events = options.events || []; + if (statusCode < minCode || statusCode > maxCode) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var responseOptions = { + statusCode: statusCode, + reasonPhrase: reasonPhrase, + extraHeaders: extraHeaders, + body: body + }; + var response; + var statusCodeString = statusCode.toString(); + switch (true) { + case /^100$/.test(statusCodeString): + response = this.incomingRequest.trying(responseOptions).message; + break; + case /^1[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.progress(responseOptions).message; + break; + case /^2[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.accept(responseOptions).message; + break; + case /^3[0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.redirect([], responseOptions).message; + break; + case /^[4-6][0-9]{2}$/.test(statusCodeString): + response = this.incomingRequest.reject(responseOptions).message; + break; + default: + throw new Error("Invalid status code " + statusCode); + } + events.forEach(function (event) { + _this.emit(event, response, reasonPhrase); + }); + return this; + }; + ServerContext.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + }; + ServerContext.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + }; + return ServerContext; +}(events_1.EventEmitter)); +exports.ServerContext = ServerContext; diff --git a/lib/Session.d.ts b/lib/Session.d.ts new file mode 100644 index 000000000..c74eb0eac --- /dev/null +++ b/lib/Session.d.ts @@ -0,0 +1,352 @@ +/// +import { EventEmitter } from "events"; +import { ClientContext } from "./ClientContext"; +import { C } from "./Constants"; +import { Body, IncomingAckRequest, IncomingInviteRequest, IncomingPrackRequest, IncomingRequest, IncomingRequestMessage, IncomingResponseMessage, InviteServerTransaction, Logger, NameAddrHeader, NonInviteServerTransaction, OutgoingRequestMessage, Session as SessionCore, URI } from "./core"; +import { SessionStatus, TypeStrings } from "./Enums"; +import { ReferClientContext, ReferServerContext } from "./ReferContext"; +import { ServerContext } from "./ServerContext"; +import { SessionDescriptionHandler, SessionDescriptionHandlerModifier, SessionDescriptionHandlerModifiers, SessionDescriptionHandlerOptions } from "./session-description-handler"; +import { SessionDescriptionHandlerFactory } from "./session-description-handler-factory"; +import { DTMF } from "./Session/DTMF"; +import { UA } from "./UA"; +export declare namespace Session { + interface DtmfOptions { + extraHeaders?: string[]; + duration?: number; + interToneGap?: number; + } +} +export declare abstract class Session extends EventEmitter { + static readonly C: typeof SessionStatus; + type: TypeStrings; + ua: UA; + logger: Logger; + method: string; + body: any; + status: SessionStatus; + contentType: string; + localIdentity: NameAddrHeader; + remoteIdentity: NameAddrHeader; + data: any; + assertedIdentity: NameAddrHeader | undefined; + id: string; + contact: string | undefined; + replacee: InviteClientContext | InviteServerContext | undefined; + localHold: boolean; + sessionDescriptionHandler: SessionDescriptionHandler | undefined; + startTime: Date | undefined; + endTime: Date | undefined; + session: SessionCore | undefined; + protected sessionDescriptionHandlerFactory: SessionDescriptionHandlerFactory; + protected sessionDescriptionHandlerOptions: any; + protected rel100: string; + protected earlySdp: string | undefined; + protected hasOffer: boolean; + protected hasAnswer: boolean; + protected timers: { + [name: string]: any; + }; + protected fromTag: string | undefined; + protected errorListener: ((...args: Array) => void); + protected renderbody: string | undefined; + protected rendertype: string | undefined; + protected modifiers: Array | undefined; + protected passedOptions: any; + protected onInfo: ((request: IncomingRequestMessage) => void) | undefined; + private tones; + private pendingReinvite; + private referContext; + protected constructor(sessionDescriptionHandlerFactory: SessionDescriptionHandlerFactory); + dtmf(tones: string | number, options?: Session.DtmfOptions): this; + bye(options?: any): this; + refer(target: string | InviteClientContext | InviteServerContext, options?: any): ReferClientContext; + /** + * Sends in dialog request. + * @param method Request method. + * @param options Options bucket. + */ + sendRequest(method: string, options?: any): this; + close(): this; + hold(options?: SessionDescriptionHandlerOptions, modifiers?: SessionDescriptionHandlerModifiers): void; + unhold(options?: SessionDescriptionHandlerOptions, modifiers?: SessionDescriptionHandlerModifiers): void; + reinvite(options?: any, modifiers?: SessionDescriptionHandlerModifiers): void; + terminate(options?: any): this; + onTransportError(): void; + onRequestTimeout(): void; + onDialogError(response: IncomingResponseMessage): void; + on(event: "dtmf", listener: (request: IncomingRequestMessage | OutgoingRequestMessage, dtmf: DTMF) => void): this; + on(event: "progress", listener: (response: string, reasonPhrase?: any) => void): this; + on(event: "referRequested", listener: (context: ReferServerContext) => void): this; + on(event: "referInviteSent" | "referProgress" | "referAccepted" | "referRejected" | "referRequestProgress" | "referRequestAccepted" | "referRequestRejected" | "reinvite" | "reinviteAccepted" | "reinviteFailed" | "replaced", listener: (session: Session) => void): this; + on(event: "SessionDescriptionHandler-created", listener: (sessionDescriptionHandler: SessionDescriptionHandler) => void): this; + on(event: "accepted", listener: (response: any, cause: C.causes) => void): this; + on(event: "ack" | "bye" | "confirmed" | "connecting" | "notify", listener: (request: any) => void): this; + on(event: "dialog", listener: (dialog: any) => void): this; + on(event: "renegotiationError", listener: (error: any) => void): this; + on(event: "failed" | "rejected", listener: (response?: any, cause?: C.causes) => void): this; + on(event: "terminated", listener: (message?: any, cause?: C.causes) => void): this; + on(event: "cancel" | "trackAdded" | "directionChanged", listener: () => void): this; + protected onAck(incomingRequest: IncomingAckRequest): void; + protected receiveRequest(incomingRequest: IncomingRequest): void; + protected receiveReinvite(incomingRequest: IncomingRequest): void; + protected sendReinvite(options?: any): void; + protected failed(response: IncomingResponseMessage | IncomingRequestMessage | undefined, cause: string): this; + protected rejected(response: IncomingResponseMessage | IncomingRequestMessage, cause: string): this; + protected canceled(): this; + protected accepted(response?: IncomingResponseMessage | string, cause?: string): this; + protected terminated(message?: IncomingResponseMessage | IncomingRequestMessage, cause?: string): this; + protected connecting(request: IncomingRequestMessage): this; +} +export declare namespace InviteServerContext { + interface Options { + /** Array of extra headers added to the INVITE. */ + extraHeaders?: Array; + /** Options to pass to SessionDescriptionHandler's getDescription() and setDescription(). */ + sessionDescriptionHandlerOptions?: SessionDescriptionHandlerOptions; + modifiers?: SessionDescriptionHandlerModifiers; + onInfo?: ((request: IncomingRequestMessage) => void); + statusCode?: number; + reasonPhrase?: string; + body?: any; + rel100?: boolean; + } +} +export declare class InviteServerContext extends Session implements ServerContext { + type: TypeStrings; + transaction: InviteServerTransaction | NonInviteServerTransaction; + request: IncomingRequestMessage; + incomingRequest: IncomingInviteRequest; + /** + * FIXME: TODO: + * Used to squelch throwing of errors due to async race condition. + * We have an internal race between calling `accept()` and handling + * an incoming CANCEL request. As there is no good way currently to + * delegate the handling of this async errors to the caller of + * `accept()`, we are squelching the throwing ALL errors when + * they occur after receiving a CANCEL to catch the ONE we know + * is a "normal" exceptional condition. While this is a completely + * reasonable appraoch, the decision should be left up to the library user. + */ + private _canceled; + private rseq; + private waitingForPrackPromise; + private waitingForPrackResolve; + private waitingForPrackReject; + constructor(ua: UA, incomingInviteRequest: IncomingInviteRequest); + /** + * If true, a first provisional response after the 100 Trying + * will be sent automatically. This is false it the UAC required + * reliable provisional responses (100rel in Require header), + * otherwise it is true. The provisional is sent by calling + * `progress()` without any options. + * + * FIXME: TODO: It seems reasonable that the ISC user should + * be able to optionally disable this behavior. As the provisional + * is sent prior to the "invite" event being emitted, it's a known + * issue that the ISC user cannot register listeners or do any other + * setup prior to the call to `progress()`. As an example why this is + * an issue, setting `ua.configuration.rel100` to REQUIRED will result + * in an attempt by `progress()` to send a 183 with SDP produced by + * calling `getDescription()` on a session description handler, but + * the ISC user cannot perform any potentially required session description + * handler initialization (thus preventing the utilization of setting + * `ua.configuration.rel100` to REQUIRED). That begs the question of + * why this behavior is disabled when the UAC requires 100rel but not + * when the UAS requires 100rel? But ignoring that, it's just one example + * of a class of cases where the ISC user needs to do something prior + * to the first call to `progress()` and is unable to do so. + */ + readonly autoSendAnInitialProvisionalResponse: boolean; + reply(options?: any): this; + reject(options?: InviteServerContext.Options): this; + /** + * Accept the incoming INVITE request to start a Session. + * Replies to the INVITE request with a 200 Ok response. + * @param options Options bucket. + */ + accept(options?: InviteServerContext.Options): this; + /** + * Report progress to the the caller. + * Replies to the INVITE request with a 1xx provisional response. + * @param options Options bucket. + */ + progress(options?: InviteServerContext.Options): this; + /** + * Reject an unaccepted incoming INVITE request or send BYE if established session. + * @param options Options bucket. FIXME: This options bucket needs to be typed. + */ + terminate(options?: any): this; + onCancel(message: IncomingRequestMessage): void; + protected receiveRequest(incomingRequest: IncomingRequest): void; + protected setupSessionDescriptionHandler(): SessionDescriptionHandler; + protected generateResponseOfferAnswer(options: { + sessionDescriptionHandlerOptions?: SessionDescriptionHandlerOptions; + modifiers?: SessionDescriptionHandlerModifiers; + }): Promise; + protected handlePrackOfferAnswer(request: IncomingPrackRequest, options: { + sessionDescriptionHandlerOptions?: SessionDescriptionHandlerOptions; + modifiers?: SessionDescriptionHandlerModifiers; + }): Promise; + /** + * Called when session canceled. + */ + protected canceled(): this; + /** + * Called when session terminated. + * Using it here just for the PRACK timeout. + */ + protected terminated(message?: IncomingResponseMessage | IncomingRequestMessage, cause?: string): this; + /** + * A version of `accept` which resolves a session when the 200 Ok response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `accept()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + private _accept; + /** + * A version of `progress` which resolves when the provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + private _progress; + /** + * A version of `progress` which resolves when the reliable provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + private _reliableProgress; + /** + * A version of `progress` which resolves when the reliable provisional response is acknowledged. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + private _reliableProgressWaitForPrack; + /** + * Callback for when ACK for a 2xx response is never received. + * @param session Session the ACK never arrived for + */ + private onAckTimeout; + /** + * FIXME: TODO: The current library interface presents async methods without a + * proper async error handling mechanism. Arguably a promise based interface + * would be an improvement over the pattern of returning `this`. The approach has + * been generally along the lines of log a error and terminate. + */ + private onContextError; + private prackArrived; + private prackNeverArrived; + /** + * @throws {Exceptions.TerminatedSessionError} The session terminated before being accepted (i.e. cancel arrived). + */ + private waitForArrivalOfPrack; + private getOffer; + private setAnswer; + private setOfferAndGetAnswer; + private getSessionDescriptionHandler; +} +export declare namespace InviteClientContext { + interface Options { + /** Array of extra headers added to the INVITE. */ + extraHeaders?: Array; + /** If true, send INVITE without SDP. */ + inviteWithoutSdp?: boolean; + /** Deprecated */ + params?: { + toUri?: string; + toDisplayName: string; + }; + /** Options to pass to SessionDescriptionHandler's getDescription() and setDescription(). */ + sessionDescriptionHandlerOptions?: SessionDescriptionHandlerOptions; + } +} +export declare class InviteClientContext extends Session implements ClientContext { + type: TypeStrings; + request: OutgoingRequestMessage; + protected anonymous: boolean; + protected inviteWithoutSdp: boolean; + protected isCanceled: boolean; + protected received100: boolean; + private earlyMediaSessionDescriptionHandlers; + private outgoingInviteRequest; + constructor(ua: UA, target: string | URI, options?: any, modifiers?: any); + receiveResponse(response: IncomingResponseMessage): void; + send(): this; + invite(): this; + cancel(options?: any): this; + terminate(options?: any): this; + /** + * 13.2.1 Creating the Initial INVITE + * + * Since the initial INVITE represents a request outside of a dialog, + * its construction follows the procedures of Section 8.1.1. Additional + * processing is required for the specific case of INVITE. + * + * An Allow header field (Section 20.5) SHOULD be present in the INVITE. + * It indicates what methods can be invoked within a dialog, on the UA + * sending the INVITE, for the duration of the dialog. For example, a + * UA capable of receiving INFO requests within a dialog [34] SHOULD + * include an Allow header field listing the INFO method. + * + * A Supported header field (Section 20.37) SHOULD be present in the + * INVITE. It enumerates all the extensions understood by the UAC. + * + * An Accept (Section 20.1) header field MAY be present in the INVITE. + * It indicates which Content-Types are acceptable to the UA, in both + * the response received by it, and in any subsequent requests sent to + * it within dialogs established by the INVITE. The Accept header field + * is especially useful for indicating support of various session + * description formats. + * + * The UAC MAY add an Expires header field (Section 20.19) to limit the + * validity of the invitation. If the time indicated in the Expires + * header field is reached and no final answer for the INVITE has been + * received, the UAC core SHOULD generate a CANCEL request for the + * INVITE, as per Section 9. + * + * A UAC MAY also find it useful to add, among others, Subject (Section + * 20.36), Organization (Section 20.25) and User-Agent (Section 20.41) + * header fields. They all contain information related to the INVITE. + * + * The UAC MAY choose to add a message body to the INVITE. Section + * 8.1.1.10 deals with how to construct the header fields -- Content- + * Type among others -- needed to describe the message body. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.1 + */ + private sendInvite; + private ackAndBye; + private disposeEarlyMedia; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 2xx response. + */ + private onAccept; + /** + * Handle provisional response to initial INVITE. + * @param inviteResponse 1xx response. + */ + private onProgress; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 3xx response. + */ + private onRedirect; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 4xx, 5xx, or 6xx response. + */ + private onReject; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 100 response. + */ + private onTrying; +} diff --git a/lib/Session.js b/lib/Session.js new file mode 100644 index 000000000..afc90dd44 --- /dev/null +++ b/lib/Session.js @@ -0,0 +1,2175 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var ClientContext_1 = require("./ClientContext"); +var Constants_1 = require("./Constants"); +var core_1 = require("./core"); +var Enums_1 = require("./Enums"); +var Exceptions_1 = require("./Exceptions"); +var ReferContext_1 = require("./ReferContext"); +var ServerContext_1 = require("./ServerContext"); +var DTMF_1 = require("./Session/DTMF"); +var Utils_1 = require("./Utils"); +/* + * @param {function returning SIP.sessionDescriptionHandler} [sessionDescriptionHandlerFactory] + * (See the documentation for the sessionDescriptionHandlerFactory argument of the UA constructor.) + */ +var Session = /** @class */ (function (_super) { + tslib_1.__extends(Session, _super); + function Session(sessionDescriptionHandlerFactory) { + var _this = _super.call(this) || this; + _this.data = {}; + _this.type = Enums_1.TypeStrings.Session; + if (!sessionDescriptionHandlerFactory) { + throw new Exceptions_1.Exceptions.SessionDescriptionHandlerError("A session description handler is required for the session to function"); + } + _this.status = Session.C.STATUS_NULL; + _this.pendingReinvite = false; + _this.sessionDescriptionHandlerFactory = sessionDescriptionHandlerFactory; + _this.hasOffer = false; + _this.hasAnswer = false; + // Session Timers + _this.timers = { + ackTimer: undefined, + expiresTimer: undefined, + invite2xxTimer: undefined, + userNoAnswerTimer: undefined, + rel1xxTimer: undefined, + prackTimer: undefined + }; + // Session info + _this.startTime = undefined; + _this.endTime = undefined; + _this.tones = undefined; + // Hold state + _this.localHold = false; + _this.earlySdp = undefined; + _this.rel100 = Constants_1.C.supported.UNSUPPORTED; + return _this; + } + Session.prototype.dtmf = function (tones, options) { + var _this = this; + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + // Check tones + if (!tones || !tones.toString().match(/^[0-9A-D#*,]+$/i)) { + throw new TypeError("Invalid tones: " + tones); + } + var sendDTMF = function () { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED || !_this.tones || _this.tones.length === 0) { + // Stop sending DTMF + _this.tones = undefined; + return; + } + var dtmf = _this.tones.shift(); + var timeout; + if (dtmf.tone === ",") { + timeout = 2000; + } + else { + dtmf.on("failed", function () { _this.tones = undefined; }); + dtmf.send(options); + timeout = dtmf.duration + dtmf.interToneGap; + } + // Set timeout for the next tone + setTimeout(sendDTMF, timeout); + }; + tones = tones.toString(); + var dtmfType = this.ua.configuration.dtmfType; + if (this.sessionDescriptionHandler && dtmfType === Constants_1.C.dtmfType.RTP) { + var sent = this.sessionDescriptionHandler.sendDtmf(tones, options); + if (!sent) { + this.logger.warn("Attempt to use dtmfType 'RTP' has failed, falling back to INFO packet method"); + dtmfType = Constants_1.C.dtmfType.INFO; + } + } + if (dtmfType === Constants_1.C.dtmfType.INFO) { + var dtmfs = []; + var tonesArray = tones.split(""); + while (tonesArray.length > 0) { + dtmfs.push(new DTMF_1.DTMF(this, tonesArray.shift(), options)); + } + if (this.tones) { + // Tones are already queued, just add to the queue + this.tones = this.tones.concat(dtmfs); + return this; + } + this.tones = dtmfs; + sendDTMF(); + } + return this; + }; + Session.prototype.bye = function (options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + this.logger.error("Error: Attempted to send BYE in a terminated session."); + return this; + } + this.logger.log("terminating Session"); + var statusCode = options.statusCode; + if (statusCode && (statusCode < 200 || statusCode >= 700)) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + options.receiveResponse = function () { }; + return this.sendRequest(Constants_1.C.BYE, options).terminated(); + }; + Session.prototype.refer = function (target, options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.referContext = new ReferContext_1.ReferClientContext(this.ua, this, target, options); + this.emit("referRequested", this.referContext); + this.referContext.refer(options); + return this.referContext; + }; + /** + * Sends in dialog request. + * @param method Request method. + * @param options Options bucket. + */ + Session.prototype.sendRequest = function (method, options) { + if (options === void 0) { options = {}; } + if (!this.session) { + throw new Error("Session undefined."); + } + // Convert any "body" option to a Body. + if (options.body) { + options.body = Utils_1.Utils.fromBodyObj(options.body); + } + // Convert any "receiveResponse" callback option passed to an OutgoingRequestDelegate. + var delegate; + var callback = options.receiveResponse; + if (callback) { + delegate = { + onAccept: function (response) { return callback(response.message); }, + onProgress: function (response) { return callback(response.message); }, + onRedirect: function (response) { return callback(response.message); }, + onReject: function (response) { return callback(response.message); }, + onTrying: function (response) { return callback(response.message); } + }; + } + var request; + var requestOptions = options; + switch (method) { + case Constants_1.C.BYE: + request = this.session.bye(delegate, requestOptions); + break; + case Constants_1.C.INVITE: + request = this.session.invite(delegate, requestOptions); + break; + case Constants_1.C.REFER: + request = this.session.refer(delegate, requestOptions); + break; + default: + throw new Error("Unexpected " + method + ". Method not implemented by user agent core."); + } + // Ported - Emit the request event + this.emit(method.toLowerCase(), request.message); + return this; + }; + Session.prototype.close = function () { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.logger.log("closing INVITE session " + this.id); + // 1st Step. Terminate media. + if (this.sessionDescriptionHandler) { + this.sessionDescriptionHandler.close(); + } + // 2nd Step. Terminate signaling. + // Clear session timers + for (var timer in this.timers) { + if (this.timers[timer]) { + clearTimeout(this.timers[timer]); + } + } + this.status = Enums_1.SessionStatus.STATUS_TERMINATED; + if (this.ua.transport) { + this.ua.transport.removeListener("transportError", this.errorListener); + } + delete this.ua.sessions[this.id]; + return this; + }; + Session.prototype.hold = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK && this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (this.localHold) { + this.logger.log("Session is already on hold, cannot put it on hold again"); + return; + } + options.modifiers = modifiers; + if (this.sessionDescriptionHandler) { + options.modifiers.push(this.sessionDescriptionHandler.holdModifier); + } + this.localHold = true; + this.sendReinvite(options); + }; + Session.prototype.unhold = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (this.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK && this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (!this.localHold) { + this.logger.log("Session is not on hold, cannot unhold it"); + return; + } + options.modifiers = modifiers; + this.localHold = false; + this.sendReinvite(options); + }; + Session.prototype.reinvite = function (options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + options.modifiers = modifiers; + return this.sendReinvite(options); + }; + Session.prototype.terminate = function (options) { + // here for types and to be overridden + return this; + }; + Session.prototype.onTransportError = function () { + if (this.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(undefined, Constants_1.C.causes.CONNECTION_ERROR); + } + }; + Session.prototype.onRequestTimeout = function () { + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.terminated(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + } + else if (this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + this.terminated(undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + } + }; + Session.prototype.onDialogError = function (response) { + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.terminated(response, Constants_1.C.causes.DIALOG_ERROR); + } + else if (this.status !== Enums_1.SessionStatus.STATUS_TERMINATED) { + this.failed(response, Constants_1.C.causes.DIALOG_ERROR); + this.terminated(response, Constants_1.C.causes.DIALOG_ERROR); + } + }; + Session.prototype.on = function (name, callback) { + return _super.prototype.on.call(this, name, callback); + }; + Session.prototype.onAck = function (incomingRequest) { + var _this = this; + var confirmSession = function () { + clearTimeout(_this.timers.ackTimer); + clearTimeout(_this.timers.invite2xxTimer); + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var contentDisp = incomingRequest.message.getHeader("Content-Disposition"); + if (contentDisp && contentDisp.type === "render") { + _this.renderbody = incomingRequest.message.body; + _this.rendertype = incomingRequest.message.getHeader("Content-Type"); + } + _this.emit("confirmed", incomingRequest.message); + }; + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + if (this.sessionDescriptionHandler && + this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + this.hasAnswer = true; + this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).catch(function (e) { + _this.logger.warn(e); + _this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + _this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + _this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + throw e; + }).then(function () { return confirmSession(); }); + } + else { + confirmSession(); + } + } + }; + Session.prototype.receiveRequest = function (incomingRequest) { + switch (incomingRequest.message.method) { // TODO: This needs a default case + case Constants_1.C.BYE: + incomingRequest.accept(); + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.emit("bye", incomingRequest.message); + this.terminated(incomingRequest.message, Constants_1.C.BYE); + } + break; + case Constants_1.C.INVITE: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.logger.log("re-INVITE received"); + this.receiveReinvite(incomingRequest); + } + break; + case Constants_1.C.INFO: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED || this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + if (this.onInfo) { + return this.onInfo(incomingRequest.message); + } + var contentType = incomingRequest.message.getHeader("content-type"); + if (contentType) { + if (contentType.match(/^application\/dtmf-relay/i)) { + if (incomingRequest.message.body) { + var body = incomingRequest.message.body.split("\r\n", 2); + if (body.length === 2) { + var tone = void 0; + var duration = void 0; + var regTone = /^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/; + if (regTone.test(body[0])) { + tone = body[0].replace(regTone, "$2"); + } + var regDuration = /^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/; + if (regDuration.test(body[1])) { + duration = parseInt(body[1].replace(regDuration, "$2"), 10); + } + if (tone && duration) { + new DTMF_1.DTMF(this, tone, { duration: duration }).init_incoming(incomingRequest); + } + } + } + } + else { + incomingRequest.reject({ + statusCode: 415, + extraHeaders: ["Accept: application/dtmf-relay"] + }); + } + } + } + break; + case Constants_1.C.REFER: + if (this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.logger.log("REFER received"); + this.referContext = new ReferContext_1.ReferServerContext(this.ua, incomingRequest, this.session); + if (this.listeners("referRequested").length) { + this.emit("referRequested", this.referContext); + } + else { + this.logger.log("No referRequested listeners, automatically accepting and following the refer"); + var options = { followRefer: true }; + if (this.passedOptions) { + options.inviteOptions = this.passedOptions; + } + this.referContext.accept(options, this.modifiers); + } + } + break; + case Constants_1.C.NOTIFY: + if (this.referContext && + this.referContext.type === Enums_1.TypeStrings.ReferClientContext && + incomingRequest.message.hasHeader("event") && + /^refer(;.*)?$/.test(incomingRequest.message.getHeader("event"))) { + this.referContext.receiveNotify(incomingRequest); + return; + } + incomingRequest.accept(); + this.emit("notify", incomingRequest.message); + break; + } + }; + // In dialog INVITE Reception + Session.prototype.receiveReinvite = function (incomingRequest) { + // TODO: Should probably check state of the session + var _this = this; + this.emit("reinvite", this, incomingRequest.message); + if (incomingRequest.message.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = + core_1.Grammar.nameAddrHeaderParse(incomingRequest.message.getHeader("P-Asserted-Identity")); + } + var promise; + if (!this.sessionDescriptionHandler) { + this.logger.warn("No SessionDescriptionHandler to reinvite"); + return; + } + if (incomingRequest.message.getHeader("Content-Length") === "0" && + !incomingRequest.message.getHeader("Content-Type")) { // Invite w/o SDP + promise = this.sessionDescriptionHandler.getDescription(this.sessionDescriptionHandlerOptions, this.modifiers); + } + else if (this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + // Invite w/ SDP + promise = this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).then(this.sessionDescriptionHandler.getDescription.bind(this.sessionDescriptionHandler, this.sessionDescriptionHandlerOptions, this.modifiers)); + } + else { // Bad Packet (should never get hit) + incomingRequest.reject({ statusCode: 415 }); + this.emit("reinviteFailed", this); + return; + } + promise.catch(function (e) { + var statusCode; + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + statusCode = 500; + } + else if (e.type === Enums_1.TypeStrings.RenegotiationError) { + _this.emit("renegotiationError", e); + _this.logger.warn(e.toString()); + statusCode = 488; + } + else { + _this.logger.error(e); + statusCode = 488; + } + incomingRequest.reject({ statusCode: statusCode }); + _this.emit("reinviteFailed", _this); + // TODO: This could be better + throw e; + }).then(function (description) { + var extraHeaders = ["Contact: " + _this.contact]; + incomingRequest.accept({ + statusCode: 200, + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(description) + }); + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK; + _this.emit("reinviteAccepted", _this); + }); + }; + Session.prototype.sendReinvite = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.pendingReinvite) { + this.logger.warn("Reinvite in progress. Please wait until complete, then try again."); + return; + } + if (!this.sessionDescriptionHandler) { + this.logger.warn("No SessionDescriptionHandler, can't reinvite.."); + return; + } + this.pendingReinvite = true; + options.modifiers = options.modifiers || []; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Contact: " + this.contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + this.sessionDescriptionHandler.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function (description) { + if (!_this.session) { + throw new Error("Session undefined."); + } + var delegate = { + onAccept: function (response) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + _this.logger.error("Received reinvite response, but in STATUS_TERMINATED"); + // TODO: Do we need to send a SIP response? + return; + } + if (!_this.pendingReinvite) { + _this.logger.error("Received reinvite response, but have no pending reinvite"); + // TODO: Do we need to send a SIP response? + return; + } + // FIXME: Why is this set here? + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + // 17.1.1.1 - For each final response that is received at the client transaction, + // the client transaction sends an ACK, + _this.emit("ack", response.ack()); + _this.pendingReinvite = false; + // TODO: All of these timers should move into the Transaction layer + clearTimeout(_this.timers.invite2xxTimer); + if (!_this.sessionDescriptionHandler || + !_this.sessionDescriptionHandler.hasDescription(response.message.getHeader("Content-Type") || "")) { + _this.logger.error("2XX response received to re-invite but did not have a description"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("2XX response received to re-invite but did not have a description")); + return; + } + _this.sessionDescriptionHandler + .setDescription(response.message.body, _this.sessionDescriptionHandlerOptions, _this.modifiers) + .catch(function (e) { + _this.logger.error("Could not set the description in 2XX response"); + _this.logger.error(e); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", e); + _this.sendRequest(Constants_1.C.BYE, { + extraHeaders: ["Reason: " + Utils_1.Utils.getReasonHeaderValue(488, "Not Acceptable Here")] + }); + _this.terminated(undefined, Constants_1.C.causes.INCOMPATIBLE_SDP); + throw e; + }) + .then(function () { + _this.emit("reinviteAccepted", _this); + }); + }, + onProgress: function (response) { + return; + }, + onRedirect: function (response) { + // FIXME: Does ACK need to be sent? + _this.pendingReinvite = false; + _this.logger.log("Received a non 1XX or 2XX response to a re-invite"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("Invalid response to a re-invite")); + }, + onReject: function (response) { + // FIXME: Does ACK need to be sent? + _this.pendingReinvite = false; + _this.logger.log("Received a non 1XX or 2XX response to a re-invite"); + _this.emit("reinviteFailed", _this); + _this.emit("renegotiationError", new Exceptions_1.Exceptions.RenegotiationError("Invalid response to a re-invite")); + }, + onTrying: function (response) { + return; + } + }; + var requestOptions = { + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(description) + }; + _this.session.invite(delegate, requestOptions); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.RenegotiationError) { + _this.pendingReinvite = false; + _this.emit("renegotiationError", e); + _this.logger.warn("Renegotiation Error"); + _this.logger.warn(e.toString()); + throw e; + } + _this.logger.error("sessionDescriptionHandler error"); + _this.logger.error(e); + throw e; + }); + }; + Session.prototype.failed = function (response, cause) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.emit("failed", response, cause); + return this; + }; + Session.prototype.rejected = function (response, cause) { + this.emit("rejected", response, cause); + return this; + }; + Session.prototype.canceled = function () { + if (this.sessionDescriptionHandler) { + this.sessionDescriptionHandler.close(); + } + this.emit("cancel"); + return this; + }; + Session.prototype.accepted = function (response, cause) { + if (!(response instanceof String)) { + cause = Utils_1.Utils.getReasonPhrase((response && response.statusCode) || 0, cause); + } + this.startTime = new Date(); + if (this.replacee) { + this.replacee.emit("replaced", this); + this.replacee.terminate(); + } + this.emit("accepted", response, cause); + return this; + }; + Session.prototype.terminated = function (message, cause) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + this.endTime = new Date(); + this.close(); + this.emit("terminated", message, cause); + return this; + }; + Session.prototype.connecting = function (request) { + this.emit("connecting", { request: request }); + return this; + }; + Session.C = Enums_1.SessionStatus; + return Session; +}(events_1.EventEmitter)); +exports.Session = Session; +// tslint:disable-next-line:max-classes-per-file +var InviteServerContext = /** @class */ (function (_super) { + tslib_1.__extends(InviteServerContext, _super); + function InviteServerContext(ua, incomingInviteRequest) { + var _this = this; + if (!ua.configuration.sessionDescriptionHandlerFactory) { + ua.logger.warn("Can't build ISC without SDH Factory"); + throw new Error("ISC Constructor Failed"); + } + _this = _super.call(this, ua.configuration.sessionDescriptionHandlerFactory) || this; + _this._canceled = false; + _this.rseq = Math.floor(Math.random() * 10000); + _this.incomingRequest = incomingInviteRequest; + var request = incomingInviteRequest.message; + ServerContext_1.ServerContext.initializer(_this, ua, incomingInviteRequest); + _this.type = Enums_1.TypeStrings.InviteServerContext; + var contentDisp = request.parseHeader("Content-Disposition"); + if (contentDisp && contentDisp.type === "render") { + _this.renderbody = request.body; + _this.rendertype = request.getHeader("Content-Type"); + } + _this.status = Enums_1.SessionStatus.STATUS_INVITE_RECEIVED; + _this.fromTag = request.fromTag; + _this.id = request.callId + _this.fromTag; + _this.request = request; + _this.contact = _this.ua.contact.toString(); + _this.logger = ua.getLogger("sip.inviteservercontext", _this.id); + // Save the session into the ua sessions collection. + _this.ua.sessions[_this.id] = _this; + // Set 100rel if necessary + var set100rel = function (header, relSetting) { + if (request.hasHeader(header) && request.getHeader(header).toLowerCase().indexOf("100rel") >= 0) { + _this.rel100 = relSetting; + } + }; + set100rel("require", Constants_1.C.supported.REQUIRED); + set100rel("supported", Constants_1.C.supported.SUPPORTED); + // Set the toTag on the incoming request to the toTag which + // will be used in the response to the incoming request!!! + // FIXME: HACK: This is a hack to port an existing behavior. + // The behavior being ported appears to be a hack itself, + // so this is a hack to port a hack. At least one test spec + // relies on it (which is yet another hack). + _this.request.toTag = incomingInviteRequest.toTag; + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + // Set userNoAnswerTimer + _this.timers.userNoAnswerTimer = setTimeout(function () { + incomingInviteRequest.reject({ statusCode: 408 }); + _this.failed(request, Constants_1.C.causes.NO_ANSWER); + _this.terminated(request, Constants_1.C.causes.NO_ANSWER); + }, _this.ua.configuration.noAnswerTimeout || 60); + /* Set expiresTimer + * RFC3261 13.3.1 + */ + // Get the Expires header value if exists + if (request.hasHeader("expires")) { + var expires = Number(request.getHeader("expires") || 0) * 1000; + _this.timers.expiresTimer = setTimeout(function () { + if (_this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + incomingInviteRequest.reject({ statusCode: 487 }); + _this.failed(request, Constants_1.C.causes.EXPIRES); + _this.terminated(request, Constants_1.C.causes.EXPIRES); + } + }, expires); + } + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + Object.defineProperty(InviteServerContext.prototype, "autoSendAnInitialProvisionalResponse", { + /** + * If true, a first provisional response after the 100 Trying + * will be sent automatically. This is false it the UAC required + * reliable provisional responses (100rel in Require header), + * otherwise it is true. The provisional is sent by calling + * `progress()` without any options. + * + * FIXME: TODO: It seems reasonable that the ISC user should + * be able to optionally disable this behavior. As the provisional + * is sent prior to the "invite" event being emitted, it's a known + * issue that the ISC user cannot register listeners or do any other + * setup prior to the call to `progress()`. As an example why this is + * an issue, setting `ua.configuration.rel100` to REQUIRED will result + * in an attempt by `progress()` to send a 183 with SDP produced by + * calling `getDescription()` on a session description handler, but + * the ISC user cannot perform any potentially required session description + * handler initialization (thus preventing the utilization of setting + * `ua.configuration.rel100` to REQUIRED). That begs the question of + * why this behavior is disabled when the UAC requires 100rel but not + * when the UAS requires 100rel? But ignoring that, it's just one example + * of a class of cases where the ISC user needs to do something prior + * to the first call to `progress()` and is unable to do so. + */ + get: function () { + return this.rel100 === Constants_1.C.supported.REQUIRED ? false : true; + }, + enumerable: true, + configurable: true + }); + // type hack for servercontext interface + InviteServerContext.prototype.reply = function (options) { + if (options === void 0) { options = {}; } + return this; + }; + // typing note: this was the only function using its super in ServerContext + // so the bottom half of this function is copied and paired down from that + InviteServerContext.prototype.reject = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + this.logger.log("rejecting RTCSession"); + var statusCode = options.statusCode || 480; + var reasonPhrase = Utils_1.Utils.getReasonPhrase(statusCode, options.reasonPhrase); + var extraHeaders = options.extraHeaders || []; + if (statusCode < 300 || statusCode > 699) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + // FIXME: Need to redirect to someplae + var response = statusCode < 400 ? + this.incomingRequest.redirect([], { statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }) : + this.incomingRequest.reject({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + (["rejected", "failed"]).forEach(function (event) { + _this.emit(event, response.message, reasonPhrase); + }); + return this.terminated(); + }; + /** + * Accept the incoming INVITE request to start a Session. + * Replies to the INVITE request with a 200 Ok response. + * @param options Options bucket. + */ + InviteServerContext.prototype.accept = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // FIXME: Need guard against calling more than once. + this._accept(options) + .then(function (_a) { + var message = _a.message, session = _a.session; + session.delegate = { + onAck: function (ackRequest) { return _this.onAck(ackRequest); }, + onAckTimeout: function () { return _this.onAckTimeout(); }, + onBye: function (byeRequest) { return _this.receiveRequest(byeRequest); }, + onInfo: function (infoRequest) { return _this.receiveRequest(infoRequest); }, + onInvite: function (inviteRequest) { return _this.receiveRequest(inviteRequest); }, + onNotify: function (notifyRequest) { return _this.receiveRequest(notifyRequest); }, + onPrack: function (prackRequest) { return _this.receiveRequest(prackRequest); }, + onRefer: function (referRequest) { return _this.receiveRequest(referRequest); } + }; + _this.session = session; + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK; + _this.accepted(message, Utils_1.Utils.getReasonPhrase(200)); + }) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + }; + /** + * Report progress to the the caller. + * Replies to the INVITE request with a 1xx provisional response. + * @param options Options bucket. + */ + InviteServerContext.prototype.progress = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // Ported + var statusCode = options.statusCode || 180; + if (statusCode < 100 || statusCode > 199) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + // Ported + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + this.logger.warn("Unexpected call for progress while terminated, ignoring"); + return this; + } + // Added + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED) { + this.logger.warn("Unexpected call for progress while answered, ignoring"); + return this; + } + // Added + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + this.logger.warn("Unexpected call for progress while answered (waiting for prack), ignoring"); + return this; + } + // After the first reliable provisional response for a request has been + // acknowledged, the UAS MAY send additional reliable provisional + // responses. The UAS MUST NOT send a second reliable provisional + // response until the first is acknowledged. After the first, it is + // RECOMMENDED that the UAS not send an additional reliable provisional + // response until the previous is acknowledged. The first reliable + // provisional response receives special treatment because it conveys + // the initial sequence number. If additional reliable provisional + // responses were sent before the first was acknowledged, the UAS could + // not be certain these were received in order. + // https://tools.ietf.org/html/rfc3262#section-3 + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + this.logger.warn("Unexpected call for progress while waiting for prack, ignoring"); + return this; + } + // Ported + if (options.statusCode === 100) { + try { + this.incomingRequest.trying(); + } + catch (error) { + this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!this._canceled) { + throw error; + } + } + return this; + } + // Standard provisional response. + if (!(this.rel100 === Constants_1.C.supported.REQUIRED) && + !(this.rel100 === Constants_1.C.supported.SUPPORTED && options.rel100) && + !(this.rel100 === Constants_1.C.supported.SUPPORTED && this.ua.configuration.rel100 === Constants_1.C.supported.REQUIRED)) { + this._progress(options) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + } + // Reliable provisional response. + this._reliableProgressWaitForPrack(options) + .catch(function (error) { + _this.onContextError(error); + // FIXME: Assuming error due to async race on CANCEL and eating error. + if (!_this._canceled) { + throw error; + } + }); + return this; + }; + /** + * Reject an unaccepted incoming INVITE request or send BYE if established session. + * @param options Options bucket. FIXME: This options bucket needs to be typed. + */ + InviteServerContext.prototype.terminate = function (options) { + // The caller's UA MAY send a BYE for either confirmed or early dialogs, + // and the callee's UA MAY send a BYE on confirmed dialogs, but MUST NOT + // send a BYE on early dialogs. However, the callee's UA MUST NOT send a + // BYE on a confirmed dialog until it has received an ACK for its 2xx + // response or until the server transaction times out. + // https://tools.ietf.org/html/rfc3261#section-15 + var _this = this; + if (options === void 0) { options = {}; } + // We don't yet have a dialog, so reject request. + if (!this.session) { + this.reject(options); + return this; + } + switch (this.session.sessionState) { + case core_1.SessionState.Initial: + this.reject(options); + return this; + case core_1.SessionState.Early: + this.reject(options); + return this; + case core_1.SessionState.AckWait: + this.session.delegate = { + // When ACK shows up, say BYE. + onAck: function () { + _this.sendRequest(Constants_1.C.BYE, options); + }, + // Or the server transaction times out before the ACK arrives. + onAckTimeout: function () { + _this.sendRequest(Constants_1.C.BYE, options); + } + }; + // Ported + this.emit("bye", this.request); + this.terminated(); + return this; + case core_1.SessionState.Confirmed: + this.bye(options); + return this; + case core_1.SessionState.Terminated: + return this; + default: + return this; + } + }; + InviteServerContext.prototype.onCancel = function (message) { + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER || + this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_EARLY_MEDIA || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED) { + this.status = Enums_1.SessionStatus.STATUS_CANCELED; + this.incomingRequest.reject({ statusCode: 487 }); + this.canceled(); + this.rejected(message, Constants_1.C.causes.CANCELED); + this.failed(message, Constants_1.C.causes.CANCELED); + this.terminated(message, Constants_1.C.causes.CANCELED); + } + }; + InviteServerContext.prototype.receiveRequest = function (incomingRequest) { + var _this = this; + switch (incomingRequest.message.method) { + case Constants_1.C.PRACK: + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK || + this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + if (!this.hasAnswer) { + this.sessionDescriptionHandler = this.setupSessionDescriptionHandler(); + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + if (this.sessionDescriptionHandler.hasDescription(incomingRequest.message.getHeader("Content-Type") || "")) { + this.hasAnswer = true; + this.sessionDescriptionHandler.setDescription(incomingRequest.message.body, this.sessionDescriptionHandlerOptions, this.modifiers).then(function () { + clearTimeout(_this.timers.rel1xxTimer); + clearTimeout(_this.timers.prackTimer); + incomingRequest.accept(); + if (_this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + _this.accept(); + } + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + }, function (e) { + _this.logger.warn(e); + _this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + _this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + _this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + }); + } + else { + this.terminate({ + statusCode: "488", + reasonPhrase: "Bad Media Description" + }); + this.failed(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + this.terminated(incomingRequest.message, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + } + } + else { + clearTimeout(this.timers.rel1xxTimer); + clearTimeout(this.timers.prackTimer); + incomingRequest.accept(); + if (this.status === Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK) { + this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + this.accept(); + } + this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + } + } + else if (this.status === Enums_1.SessionStatus.STATUS_EARLY_MEDIA) { + incomingRequest.accept(); + } + break; + default: + _super.prototype.receiveRequest.call(this, incomingRequest); + break; + } + }; + // Internal Function to setup the handler consistently + InviteServerContext.prototype.setupSessionDescriptionHandler = function () { + if (this.sessionDescriptionHandler) { + return this.sessionDescriptionHandler; + } + return this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions); + }; + InviteServerContext.prototype.generateResponseOfferAnswer = function (options) { + if (!this.session) { + var body = core_1.getBody(this.incomingRequest.message); + if (!body || body.contentDisposition !== "session") { + return this.getOffer(options); + } + else { + return this.setOfferAndGetAnswer(body, options); + } + } + else { + switch (this.session.signalingState) { + case core_1.SignalingState.Initial: + return this.getOffer(options); + case core_1.SignalingState.Stable: + return Promise.resolve(undefined); + case core_1.SignalingState.HaveLocalOffer: + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + return Promise.resolve(undefined); + case core_1.SignalingState.HaveRemoteOffer: + if (!this.session.offer) { + throw new Error("Session offer undefined"); + } + return this.setOfferAndGetAnswer(this.session.offer, options); + case core_1.SignalingState.Closed: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + default: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + } + } + }; + InviteServerContext.prototype.handlePrackOfferAnswer = function (request, options) { + if (!this.session) { + throw new Error("Session undefined."); + } + // If the PRACK doesn't have an offer/answer, nothing to be done. + var body = core_1.getBody(request.message); + if (!body || body.contentDisposition !== "session") { + return Promise.resolve(undefined); + } + // If the UAC receives a reliable provisional response with an offer + // (this would occur if the UAC sent an INVITE without an offer, in + // which case the first reliable provisional response will contain the + // offer), it MUST generate an answer in the PRACK. If the UAC receives + // a reliable provisional response with an answer, it MAY generate an + // additional offer in the PRACK. If the UAS receives a PRACK with an + // offer, it MUST place the answer in the 2xx to the PRACK. + // https://tools.ietf.org/html/rfc3262#section-5 + switch (this.session.signalingState) { + case core_1.SignalingState.Initial: + // State should never be reached as first reliable provisional response must have answer/offer. + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + case core_1.SignalingState.Stable: + // Receved answer. + return this.setAnswer(body, options).then(function () { return undefined; }); + case core_1.SignalingState.HaveLocalOffer: + // State should never be reached as local offer would be answered by this PRACK + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + case core_1.SignalingState.HaveRemoteOffer: + // Receved offer, generate answer. + return this.setOfferAndGetAnswer(body, options); + case core_1.SignalingState.Closed: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + default: + throw new Error("Invalid signaling state " + this.session.signalingState + "."); + } + }; + /** + * Called when session canceled. + */ + InviteServerContext.prototype.canceled = function () { + this._canceled = true; + return _super.prototype.canceled.call(this); + }; + /** + * Called when session terminated. + * Using it here just for the PRACK timeout. + */ + InviteServerContext.prototype.terminated = function (message, cause) { + this.prackNeverArrived(); + return _super.prototype.terminated.call(this, message, cause); + }; + /** + * A version of `accept` which resolves a session when the 200 Ok response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `accept()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._accept = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + // FIXME: Ported - callback for in dialog INFO requests. + // Turns out accept() can be called more than once if we are waiting + // for a PRACK in which case "options" get completely tossed away. + // So this is broken in that case (and potentially other uses of options). + // Tempted to just try to fix it now, but leaving it broken for the moment. + this.onInfo = options.onInfo; + // The UAS MAY send a final response to the initial request before + // having received PRACKs for all unacknowledged reliable provisional + // responses, unless the final response is 2xx and any of the + // unacknowledged reliable provisional responses contained a session + // description. In that case, it MUST NOT send a final response until + // those provisional responses are acknowledged. If the UAS does send a + // final response when reliable responses are still unacknowledged, it + // SHOULD NOT continue to retransmit the unacknowledged reliable + // provisional responses, but it MUST be prepared to process PRACK + // requests for those outstanding responses. A UAS MUST NOT send new + // reliable provisional responses (as opposed to retransmissions of + // unacknowledged ones) after sending a final response to a request. + // https://tools.ietf.org/html/rfc3262#section-3 + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED_WAITING_FOR_PRACK; + return this.waitForArrivalOfPrack() + .then(function () { + _this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + clearTimeout(_this.timers.userNoAnswerTimer); // Ported + }) + .then(function () { return _this.generateResponseOfferAnswer(options); }) + .then(function (body) { return _this.incomingRequest.accept({ statusCode: 200, body: body }); }); + } + // Ported + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER) { + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + } + else { + return Promise.reject(new Exceptions_1.Exceptions.InvalidStateError(this.status)); + } + this.status = Enums_1.SessionStatus.STATUS_ANSWERED; + clearTimeout(this.timers.userNoAnswerTimer); // Ported + return this.generateResponseOfferAnswer(options) + .then(function (body) { return _this.incomingRequest.accept({ statusCode: 200, body: body }); }); + }; + /** + * A version of `progress` which resolves when the provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._progress = function (options) { + if (options === void 0) { options = {}; } + // Ported + var statusCode = options.statusCode || 180; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + var body = options.body ? core_1.fromBodyLegacy(options.body) : undefined; + try { + var progressResponse = this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + this.emit("progress", progressResponse.message, reasonPhrase); // Ported + this.session = progressResponse.session; + return Promise.resolve(progressResponse); + } + catch (error) { + return Promise.reject(error); + } + }; + /** + * A version of `progress` which resolves when the reliable provisional response is sent. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._reliableProgress = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 183; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Require: 100rel"); + extraHeaders.push("RSeq: " + Math.floor(Math.random() * 10000)); + // Get an offer/answer and send a reply. + return this.generateResponseOfferAnswer(options) + .then(function (body) { return _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); }) + .then(function (progressResponse) { + _this.emit("progress", progressResponse.message, reasonPhrase); // Ported + _this.session = progressResponse.session; + return progressResponse; + }); + }; + /** + * A version of `progress` which resolves when the reliable provisional response is acknowledged. + * @param options Options bucket. + * @throws {ClosedSessionDescriptionHandlerError} The session description handler closed before method completed. + * @throws {TransactionStateError} The transaction state does not allow for `progress()` to be called. + * Note that the transaction state can change while this call is in progress. + */ + InviteServerContext.prototype._reliableProgressWaitForPrack = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + var statusCode = options.statusCode || 183; + var reasonPhrase = options.reasonPhrase; + var extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push("Require: 100rel"); + extraHeaders.push("RSeq: " + this.rseq++); + var body; + // Ported - set status. + this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK; + return new Promise(function (resolve, reject) { + var waitingForPrack = true; + return _this.generateResponseOfferAnswer(options) + .then(function (offerAnswer) { + body = offerAnswer; + return _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + }) + .then(function (progressResponse) { + _this.emit("progress", progressResponse.message, reasonPhrase); // Ported + _this.session = progressResponse.session; + var prackRequest; + var prackResponse; + progressResponse.session.delegate = { + onPrack: function (request) { + prackRequest = request; + clearTimeout(prackWaitTimeoutTimer); + clearTimeout(rel1xxRetransmissionTimer); + if (!waitingForPrack) { + return; + } + waitingForPrack = false; + _this.handlePrackOfferAnswer(prackRequest, options) + .then(function (prackResponseBody) { + try { + prackResponse = prackRequest.accept({ statusCode: 200, body: prackResponseBody }); + // Ported - set status. + if (_this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_PRACK) { + _this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ANSWER; + } + _this.prackArrived(); + resolve({ prackRequest: prackRequest, prackResponse: prackResponse, progressResponse: progressResponse }); + } + catch (error) { + reject(error); + } + }); + } + }; + // https://tools.ietf.org/html/rfc3262#section-3 + var prackWaitTimeout = function () { + if (!waitingForPrack) { + return; + } + waitingForPrack = false; + _this.logger.warn("No PRACK received, rejecting INVITE."); + clearTimeout(rel1xxRetransmissionTimer); + try { + _this.incomingRequest.reject({ statusCode: 504 }); + _this.terminated(undefined, Constants_1.C.causes.NO_PRACK); + reject(new Exceptions_1.Exceptions.TerminatedSessionError()); + } + catch (error) { + reject(error); + } + }; + var prackWaitTimeoutTimer = setTimeout(prackWaitTimeout, core_1.Timers.T1 * 64); + // https://tools.ietf.org/html/rfc3262#section-3 + var rel1xxRetransmission = function () { + try { + _this.incomingRequest.progress({ statusCode: statusCode, reasonPhrase: reasonPhrase, extraHeaders: extraHeaders, body: body }); + } + catch (error) { + waitingForPrack = false; + reject(error); + return; + } + rel1xxRetransmissionTimer = setTimeout(rel1xxRetransmission, timeout *= 2); + }; + var timeout = core_1.Timers.T1; + var rel1xxRetransmissionTimer = setTimeout(rel1xxRetransmission, timeout); + }); + }); + }; + /** + * Callback for when ACK for a 2xx response is never received. + * @param session Session the ACK never arrived for + */ + InviteServerContext.prototype.onAckTimeout = function () { + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + this.logger.log("no ACK received for an extended period of time, terminating the call"); + if (!this.session) { + throw new Error("Session undefined."); + } + this.session.bye(); + this.terminated(undefined, Constants_1.C.causes.NO_ACK); + } + }; + /** + * FIXME: TODO: The current library interface presents async methods without a + * proper async error handling mechanism. Arguably a promise based interface + * would be an improvement over the pattern of returning `this`. The approach has + * been generally along the lines of log a error and terminate. + */ + InviteServerContext.prototype.onContextError = function (error) { + var statusCode = 480; + if (error instanceof core_1.Exception) { // There might be interest in catching these Exceptions. + if (error instanceof Exceptions_1.Exceptions.SessionDescriptionHandlerError) { + this.logger.error(error.message); + if (error.error) { + this.logger.error(error.error); + } + } + else if (error instanceof Exceptions_1.Exceptions.TerminatedSessionError) { + // PRACK never arrived, so we timed out waiting for it. + this.logger.warn("Incoming session terminated while waiting for PRACK."); + } + else if (error instanceof Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError) { + statusCode = 415; + } + else if (error instanceof core_1.Exception) { + this.logger.error(error.message); + } + } + else if (error instanceof Error) { // Other Errors hould go uncaught. + this.logger.error(error.message); + } + else { + // We don't actually know what a session description handler implementation might throw + // our way, so as a last resort, just assume we are getting an "any" and log it. + this.logger.error("An error occurred in the session description handler."); + this.logger.error(error); + } + try { + this.incomingRequest.reject({ statusCode: statusCode }); // "Temporarily Unavailable" + this.failed(this.incomingRequest.message, error.message); + this.terminated(this.incomingRequest.message, error.message); + } + catch (error) { + return; + } + }; + InviteServerContext.prototype.prackArrived = function () { + if (this.waitingForPrackResolve) { + this.waitingForPrackResolve(); + } + this.waitingForPrackPromise = undefined; + this.waitingForPrackResolve = undefined; + this.waitingForPrackReject = undefined; + }; + InviteServerContext.prototype.prackNeverArrived = function () { + if (this.waitingForPrackReject) { + this.waitingForPrackReject(new Exceptions_1.Exceptions.TerminatedSessionError()); + } + this.waitingForPrackPromise = undefined; + this.waitingForPrackResolve = undefined; + this.waitingForPrackReject = undefined; + }; + /** + * @throws {Exceptions.TerminatedSessionError} The session terminated before being accepted (i.e. cancel arrived). + */ + InviteServerContext.prototype.waitForArrivalOfPrack = function () { + var _this = this; + if (this.waitingForPrackPromise) { + throw new Error("Already waiting for PRACK"); + } + this.waitingForPrackPromise = new Promise(function (resolve, reject) { + _this.waitingForPrackResolve = resolve; + _this.waitingForPrackReject = reject; + }); + return this.waitingForPrackPromise; + }; + InviteServerContext.prototype.getOffer = function (options) { + this.hasOffer = true; + var sdh = this.getSessionDescriptionHandler(); + return sdh + .getDescription(options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function (bodyObj) { return Utils_1.Utils.fromBodyObj(bodyObj); }); + }; + InviteServerContext.prototype.setAnswer = function (answer, options) { + this.hasAnswer = true; + var sdh = this.getSessionDescriptionHandler(); + if (!sdh.hasDescription(answer.contentType)) { + return Promise.reject(new Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError()); + } + return sdh + .setDescription(answer.content, options.sessionDescriptionHandlerOptions, options.modifiers); + }; + InviteServerContext.prototype.setOfferAndGetAnswer = function (offer, options) { + this.hasOffer = true; + this.hasAnswer = true; + var sdh = this.getSessionDescriptionHandler(); + if (!sdh.hasDescription(offer.contentType)) { + return Promise.reject(new Exceptions_1.Exceptions.UnsupportedSessionDescriptionContentTypeError()); + } + return sdh + .setDescription(offer.content, options.sessionDescriptionHandlerOptions, options.modifiers) + .then(function () { return sdh.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers); }) + .then(function (bodyObj) { return Utils_1.Utils.fromBodyObj(bodyObj); }); + }; + InviteServerContext.prototype.getSessionDescriptionHandler = function () { + // Create our session description handler if not already done so... + var sdh = this.sessionDescriptionHandler = this.setupSessionDescriptionHandler(); + // FIXME: Ported - this can get emitted multiple times even when only created once... don't we care? + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + // Return. + return sdh; + }; + return InviteServerContext; +}(Session)); +exports.InviteServerContext = InviteServerContext; +// tslint:disable-next-line:max-classes-per-file +var InviteClientContext = /** @class */ (function (_super) { + tslib_1.__extends(InviteClientContext, _super); + function InviteClientContext(ua, target, options, modifiers) { + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + var _this = this; + if (!ua.configuration.sessionDescriptionHandlerFactory) { + ua.logger.warn("Can't build ISC without SDH Factory"); + throw new Error("ICC Constructor Failed"); + } + options.params = options.params || {}; + var anonymous = options.anonymous || false; + var fromTag = Utils_1.Utils.newTag(); + options.params.fromTag = fromTag; + /* Do not add ;ob in initial forming dialog requests if the registration over + * the current connection got a GRUU URI. + */ + var contact = ua.contact.toString({ + anonymous: anonymous, + outbound: anonymous ? !ua.contact.tempGruu : !ua.contact.pubGruu + }); + var extraHeaders = (options.extraHeaders || []).slice(); + if (anonymous && ua.configuration.uri) { + options.params.from_displayName = "Anonymous"; + options.params.from_uri = "sip:anonymous@anonymous.invalid"; + extraHeaders.push("P-Preferred-Identity: " + ua.configuration.uri.toString()); + extraHeaders.push("Privacy: id"); + } + extraHeaders.push("Contact: " + contact); + // this is UA.C.ALLOWED_METHODS, removed to get around circular dependency + extraHeaders.push("Allow: " + [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ].toString()); + if (ua.configuration.rel100 === Constants_1.C.supported.REQUIRED) { + extraHeaders.push("Require: 100rel"); + } + if (ua.configuration.replaces === Constants_1.C.supported.REQUIRED) { + extraHeaders.push("Require: replaces"); + } + options.extraHeaders = extraHeaders; + _this = _super.call(this, ua.configuration.sessionDescriptionHandlerFactory) || this; + ClientContext_1.ClientContext.initializer(_this, ua, Constants_1.C.INVITE, target, options); + _this.earlyMediaSessionDescriptionHandlers = new Map(); + _this.type = Enums_1.TypeStrings.InviteClientContext; + _this.passedOptions = options; // Save for later to use with refer + _this.sessionDescriptionHandlerOptions = options.sessionDescriptionHandlerOptions || {}; + _this.modifiers = modifiers; + _this.inviteWithoutSdp = options.inviteWithoutSdp || false; + // Set anonymous property + _this.anonymous = options.anonymous || false; + // Custom data to be sent either in INVITE or in ACK + _this.renderbody = options.renderbody || undefined; + _this.rendertype = options.rendertype || "text/plain"; + // Session parameter initialization + _this.fromTag = fromTag; + _this.contact = contact; + // Check Session Status + if (_this.status !== Enums_1.SessionStatus.STATUS_NULL) { + throw new Exceptions_1.Exceptions.InvalidStateError(_this.status); + } + // OutgoingSession specific parameters + _this.isCanceled = false; + _this.received100 = false; + _this.method = Constants_1.C.INVITE; + _this.logger = ua.getLogger("sip.inviteclientcontext"); + ua.applicants[_this.toString()] = _this; + _this.id = _this.request.callId + _this.fromTag; + _this.onInfo = options.onInfo; + _this.errorListener = _this.onTransportError.bind(_this); + if (ua.transport) { + ua.transport.on("transportError", _this.errorListener); + } + return _this; + } + InviteClientContext.prototype.receiveResponse = function (response) { + throw new Error("Unimplemented."); + }; + // hack for getting around ClientContext interface + InviteClientContext.prototype.send = function () { + this.sendInvite(); + return this; + }; + InviteClientContext.prototype.invite = function () { + var _this = this; + // Save the session into the ua sessions collection. + // Note: placing in constructor breaks call to request.cancel on close... User does not need this anyway + this.ua.sessions[this.id] = this; + // This should allow the function to return so that listeners can be set up for these events + Promise.resolve().then(function () { + // FIXME: There is a race condition where cancel (or terminate) can be called synchronously after invite. + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + if (_this.inviteWithoutSdp) { + // just send an invite with no sdp... + if (_this.renderbody && _this.rendertype) { + _this.request.body = { + body: _this.renderbody, + contentType: _this.rendertype + }; + } + _this.status = Enums_1.SessionStatus.STATUS_INVITE_SENT; + _this.send(); + } + else { + // Initialize Media Session + _this.sessionDescriptionHandler = _this.sessionDescriptionHandlerFactory(_this, _this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + _this.emit("SessionDescriptionHandler-created", _this.sessionDescriptionHandler); + _this.sessionDescriptionHandler.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers) + .then(function (description) { + // FIXME: There is a race condition where cancel (or terminate) can be called (a)synchronously after invite. + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.hasOffer = true; + _this.request.body = description; + _this.status = Enums_1.SessionStatus.STATUS_INVITE_SENT; + _this.send(); + }, function (err) { + if (err.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + _this.logger.log(err.message); + if (err.error) { + _this.logger.log(err.error); + } + } + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.failed(undefined, Constants_1.C.causes.WEBRTC_ERROR); + _this.terminated(undefined, Constants_1.C.causes.WEBRTC_ERROR); + }); + } + }); + return this; + }; + InviteClientContext.prototype.cancel = function (options) { + if (options === void 0) { options = {}; } + // Check Session Status + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED || this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.status); + } + if (this.isCanceled) { + throw new Exceptions_1.Exceptions.InvalidStateError(Enums_1.SessionStatus.STATUS_CANCELED); + } + this.isCanceled = true; + this.logger.log("Canceling session"); + var cancelReason = Utils_1.Utils.getCancelReason(options.statusCode, options.reasonPhrase); + options.extraHeaders = (options.extraHeaders || []).slice(); + if (this.outgoingInviteRequest) { + this.logger.warn("Canceling session before it was created"); + this.outgoingInviteRequest.cancel(cancelReason, options); + } + return this.canceled(); + }; + InviteClientContext.prototype.terminate = function (options) { + if (this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return this; + } + if (this.status === Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK || this.status === Enums_1.SessionStatus.STATUS_CONFIRMED) { + this.bye(options); + } + else { + this.cancel(options); + } + return this; + }; + /** + * 13.2.1 Creating the Initial INVITE + * + * Since the initial INVITE represents a request outside of a dialog, + * its construction follows the procedures of Section 8.1.1. Additional + * processing is required for the specific case of INVITE. + * + * An Allow header field (Section 20.5) SHOULD be present in the INVITE. + * It indicates what methods can be invoked within a dialog, on the UA + * sending the INVITE, for the duration of the dialog. For example, a + * UA capable of receiving INFO requests within a dialog [34] SHOULD + * include an Allow header field listing the INFO method. + * + * A Supported header field (Section 20.37) SHOULD be present in the + * INVITE. It enumerates all the extensions understood by the UAC. + * + * An Accept (Section 20.1) header field MAY be present in the INVITE. + * It indicates which Content-Types are acceptable to the UA, in both + * the response received by it, and in any subsequent requests sent to + * it within dialogs established by the INVITE. The Accept header field + * is especially useful for indicating support of various session + * description formats. + * + * The UAC MAY add an Expires header field (Section 20.19) to limit the + * validity of the invitation. If the time indicated in the Expires + * header field is reached and no final answer for the INVITE has been + * received, the UAC core SHOULD generate a CANCEL request for the + * INVITE, as per Section 9. + * + * A UAC MAY also find it useful to add, among others, Subject (Section + * 20.36), Organization (Section 20.25) and User-Agent (Section 20.41) + * header fields. They all contain information related to the INVITE. + * + * The UAC MAY choose to add a message body to the INVITE. Section + * 8.1.1.10 deals with how to construct the header fields -- Content- + * Type among others -- needed to describe the message body. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.1 + */ + InviteClientContext.prototype.sendInvite = function () { + // There are special rules for message bodies that contain a session + // description - their corresponding Content-Disposition is "session". + // SIP uses an offer/answer model where one UA sends a session + // description, called the offer, which contains a proposed description + // of the session. The offer indicates the desired communications means + // (audio, video, games), parameters of those means (such as codec + // types) and addresses for receiving media from the answerer. The + // other UA responds with another session description, called the + // answer, which indicates which communications means are accepted, the + // parameters that apply to those means, and addresses for receiving + // media from the offerer. An offer/answer exchange is within the + // context of a dialog, so that if a SIP INVITE results in multiple + // dialogs, each is a separate offer/answer exchange. The offer/answer + // model defines restrictions on when offers and answers can be made + // (for example, you cannot make a new offer while one is in progress). + // This results in restrictions on where the offers and answers can + // appear in SIP messages. In this specification, offers and answers + // can only appear in INVITE requests and responses, and ACK. The usage + // of offers and answers is further restricted. For the initial INVITE + // transaction, the rules are: + // + // o The initial offer MUST be in either an INVITE or, if not there, + // in the first reliable non-failure message from the UAS back to + // the UAC. In this specification, that is the final 2xx + // response. + // + // o If the initial offer is in an INVITE, the answer MUST be in a + // reliable non-failure message from UAS back to UAC which is + // correlated to that INVITE. For this specification, that is + // only the final 2xx response to that INVITE. That same exact + // answer MAY also be placed in any provisional responses sent + // prior to the answer. The UAC MUST treat the first session + // description it receives as the answer, and MUST ignore any + // session descriptions in subsequent responses to the initial + // INVITE. + // + // o If the initial offer is in the first reliable non-failure + // message from the UAS back to UAC, the answer MUST be in the + // acknowledgement for that message (in this specification, ACK + // for a 2xx response). + // + // o After having sent or received an answer to the first offer, the + // UAC MAY generate subsequent offers in requests based on rules + // specified for that method, but only if it has received answers + // to any previous offers, and has not sent any offers to which it + // hasn't gotten an answer. + // + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + var _this = this; + // 5 The Offer/Answer Model and PRACK + // + // RFC 3261 describes guidelines for the sets of messages in which + // offers and answers [3] can appear. Based on those guidelines, this + // extension provides additional opportunities for offer/answer + // exchanges. + // If the INVITE contained an offer, the UAS MAY generate an answer in a + // reliable provisional response (assuming these are supported by the + // UAC). That results in the establishment of the session before + // completion of the call. Similarly, if a reliable provisional + // response is the first reliable message sent back to the UAC, and the + // INVITE did not contain an offer, one MUST appear in that reliable + // provisional response. + // If the UAC receives a reliable provisional response with an offer + // (this would occur if the UAC sent an INVITE without an offer, in + // which case the first reliable provisional response will contain the + // offer), it MUST generate an answer in the PRACK. If the UAC receives + // a reliable provisional response with an answer, it MAY generate an + // additional offer in the PRACK. If the UAS receives a PRACK with an + // offer, it MUST place the answer in the 2xx to the PRACK. + // Once an answer has been sent or received, the UA SHOULD establish the + // session based on the parameters of the offer and answer, even if the + // original INVITE itself has not been responded to. + // If the UAS had placed a session description in any reliable + // provisional response that is unacknowledged when the INVITE is + // accepted, the UAS MUST delay sending the 2xx until the provisional + // response is acknowledged. Otherwise, the reliability of the 1xx + // cannot be guaranteed, and reliability is needed for proper operation + // of the offer/answer exchange. + // All user agents that support this extension MUST support all + // offer/answer exchanges that are possible based on the rules in + // Section 13.2 of RFC 3261, based on the existence of INVITE and PRACK + // as requests, and 2xx and reliable 1xx as non-failure reliable + // responses. + // + // https://tools.ietf.org/html/rfc3262#section-5 + //// + // The Offer/Answer Model Implementation + // + // The offer/answer model is straight forward, but one MUST READ the specifications... + // + // 13.2.1 Creating the Initial INVITE (paragraph 8 in particular) + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // 5 The Offer/Answer Model and PRACK + // https://tools.ietf.org/html/rfc3262#section-5 + // + // Session Initiation Protocol (SIP) Usage of the Offer/Answer Model + // https://tools.ietf.org/html/rfc6337 + // + // *** IMPORTANT IMPLEMENTATION CHOICES *** + // + // TLDR... + // + // 1) Only one offer/answer exchange permitted during initial INVITE. + // 2) No "early media" if the initial offer is in an INVITE. + // + // + // 1) Initial Offer/Answer Restriction. + // + // Our implementation replaces the following bullet point... + // + // o After having sent or received an answer to the first offer, the + // UAC MAY generate subsequent offers in requests based on rules + // specified for that method, but only if it has received answers + // to any previous offers, and has not sent any offers to which it + // hasn't gotten an answer. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // ...with... + // + // o After having sent or received an answer to the first offer, the + // UAC MUST NOT generate subsequent offers in requests based on rules + // specified for that method. + // + // ...which in combination with this bullet point... + // + // o Once the UAS has sent or received an answer to the initial + // offer, it MUST NOT generate subsequent offers in any responses + // to the initial INVITE. This means that a UAS based on this + // specification alone can never generate subsequent offers until + // completion of the initial transaction. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // ...ensures that EXACTLY ONE offer/answer exchange will occur + // during an initial out of dialog INVITE request made by our UAC. + // + // + // 2) Early Media Restriction. + // + // While our implementation adheres to the following bullet point... + // + // o If the initial offer is in an INVITE, the answer MUST be in a + // reliable non-failure message from UAS back to UAC which is + // correlated to that INVITE. For this specification, that is + // only the final 2xx response to that INVITE. That same exact + // answer MAY also be placed in any provisional responses sent + // prior to the answer. The UAC MUST treat the first session + // description it receives as the answer, and MUST ignore any + // session descriptions in subsequent responses to the initial + // INVITE. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // + // We have made the following implementation decision with regard to early media... + // + // o If the initial offer is in the INVITE, the answer from the + // UAS back to the UAC will establish a media session only + // only after the final 2xx response to that INVITE is received. + // + // The reason for this decision is rooted in a restriction currently + // inherent in WebRTC. Specifically, while a SIP INVITE request with an + // initial offer may fork resulting in more than one provisional answer, + // there is currently no easy/good way to to "fork" an offer generated + // by a peer connection. In particular, a WebRTC offer currently may only + // be matched with one answer and we have no good way to know which + // "provisional answer" is going to be the "final answer". So we have + // decided to punt and not create any "early media" sessions in this case. + // + // The upshot is that if you want "early media", you must not put the + // initial offer in the INVITE. Instead, force the UAS to provide the + // initial offer by sending an INVITE without an offer. In the WebRTC + // case this allows us to create a unique peer connection with a unique + // answer for every provisional offer with "early media" on all of them. + //// + //// + // ROADMAP: The Offer/Answer Model Implementation + // + // The "no early media if offer in INVITE" implementation is not a + // welcome one. The masses want it. The want it and they want it + // to work for WebRTC (so they want to have their cake and eat too). + // + // So while we currently cannot make the offer in INVITE+forking+webrtc + // case work, we decided to do the following... + // + // 1) modify SDH Factory to provide an initial offer without giving us the SDH, and then... + // 2) stick that offer in the initial INVITE, and when 183 with initial answer is received... + // 3) ask SDH Factory if it supports "earlyRemoteAnswer" + // a) if true, ask SDH Factory to createSDH(localOffer).then((sdh) => sdh.setDescription(remoteAnswer) + // b) if false, defer getting a SDH until 2xx response is received + // + // Our supplied WebRTC SDH will default to behavior 3b which works in forking environment (without) + // early media if initial offer is in the INVITE). We will, however, provide an "inviteWillNotFork" + // option which if set to "true" will have our supplied WebRTC SDH behave in the 3a manner. + // That will result in + // - early media working with initial offer in the INVITE, and... + // - if the INVITE forks, the session terminating with an ERROR that reads like + // "You set 'inviteWillNotFork' to true but the INVITE forked. You can't eat your cake, and have it too." + // - furthermore, we accept that users will report that error to us as "bug" regardless + // + // So, SDH Factory is going to end up with a new interface along the lines of... + // + // interface SessionDescriptionHandlerFactory { + // makeLocalOffer(): Promise; + // makeSessionDescriptionHandler( + // initialOffer: ContentTypeAndBody, offerType: "local" | "remote" + // ): Promise; + // supportsEarlyRemoteAnswer: boolean; + // supportsContentType(contentType: string): boolean; + // getDescription(description: ContentTypeAndBody): Promise + // setDescription(description: ContentTypeAndBody): Promise + // } + // + // We should be able to get rid of all the hasOffer/hasAnswer tracking code and otherwise code + // it up to the same interaction with the SDH Factory and SDH regardless of signaling scenario. + //// + // Send the INVITE request. + this.outgoingInviteRequest = this.ua.userAgentCore.invite(this.request, { + onAccept: function (inviteResponse) { return _this.onAccept(inviteResponse); }, + onProgress: function (inviteResponse) { return _this.onProgress(inviteResponse); }, + onRedirect: function (inviteResponse) { return _this.onRedirect(inviteResponse); }, + onReject: function (inviteResponse) { return _this.onReject(inviteResponse); }, + onTrying: function (inviteResponse) { return _this.onTrying(inviteResponse); } + }); + }; + InviteClientContext.prototype.ackAndBye = function (inviteResponse, session, statusCode, reasonPhrase) { + if (!this.ua.userAgentCore) { + throw new Error("Method requires user agent core."); + } + var extraHeaders = []; + if (statusCode) { + extraHeaders.push("Reason: " + Utils_1.Utils.getReasonHeaderValue(statusCode, reasonPhrase)); + } + var outgoingAckRequest = inviteResponse.ack(); + this.emit("ack", outgoingAckRequest.message); + var outgoingByeRequest = session.bye(undefined, { extraHeaders: extraHeaders }); + this.emit("bye", outgoingByeRequest.message); + }; + InviteClientContext.prototype.disposeEarlyMedia = function () { + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + this.earlyMediaSessionDescriptionHandlers.forEach(function (sessionDescriptionHandler) { + sessionDescriptionHandler.close(); + }); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 2xx response. + */ + InviteClientContext.prototype.onAccept = function (inviteResponse) { + var _this = this; + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + var response = inviteResponse.message; + var session = inviteResponse.session; + // Our transaction layer is "non-standard" in that it will only + // pass us a 2xx response once per branch, so there is no need to + // worry about dealing with 2xx retransmissions. However, we can + // and do still get 2xx responses for multiple branches (when an + // INVITE is forked) which may create multiple confirmed dialogs. + // Herein we are acking and sending a bye to any confirmed dialogs + // which arrive beyond the first one. This is the desired behavior + // for most applications (but certainly not all). + // If we already received a confirmed dialog, ack & bye this session. + if (this.session) { + this.ackAndBye(inviteResponse, session); + return; + } + // If the user requested cancellation, ack & bye this session. + if (this.isCanceled) { + this.ackAndBye(inviteResponse, session); + this.emit("bye", this.request); // FIXME: Ported this odd second "bye" emit + return; + } + // Ported behavior. + if (response.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(response.getHeader("P-Asserted-Identity")); + } + // We have a confirmed dialog. + this.session = session; + this.session.delegate = { + onAck: function (ackRequest) { return _this.onAck(ackRequest); }, + onBye: function (byeRequest) { return _this.receiveRequest(byeRequest); }, + onInfo: function (infoRequest) { return _this.receiveRequest(infoRequest); }, + onInvite: function (inviteRequest) { return _this.receiveRequest(inviteRequest); }, + onNotify: function (notifyRequest) { return _this.receiveRequest(notifyRequest); }, + onPrack: function (prackRequest) { return _this.receiveRequest(prackRequest); }, + onRefer: function (referRequest) { return _this.receiveRequest(referRequest); } + }; + switch (session.signalingState) { + case core_1.SignalingState.Initial: + // INVITE without Offer, so MUST have Offer at this point, so invalid state. + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + case core_1.SignalingState.HaveLocalOffer: + // INVITE with Offer, so MUST have Answer at this point, so invalid state. + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + case core_1.SignalingState.HaveRemoteOffer: + // INVITE without Offer, received offer in 2xx, so MUST send Answer in ACK. + var sdh_1 = this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + this.sessionDescriptionHandler = sdh_1; + this.emit("SessionDescriptionHandler-created", this.sessionDescriptionHandler); + if (!sdh_1.hasDescription(response.getHeader("Content-Type") || "")) { + this.ackAndBye(inviteResponse, session, 400, "Missing session description"); + this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + break; + } + this.hasOffer = true; + sdh_1 + .setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { return sdh_1.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers); }) + .then(function (description) { + if (_this.isCanceled || _this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + _this.hasAnswer = true; + var body = { + contentDisposition: "session", contentType: description.contentType, content: description.body + }; + var ackRequest = inviteResponse.ack({ body: body }); + _this.emit("ack", ackRequest.message); + _this.accepted(response); + }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + _this.logger.warn("invalid description"); + _this.logger.warn(e.toString()); + // TODO: This message is inconsistent + _this.ackAndBye(inviteResponse, session, 488, "Invalid session description"); + _this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + } + else { + throw e; + } + }); + break; + case core_1.SignalingState.Stable: + // This session has completed an initial offer/answer exchange... + var options_1; + if (this.renderbody && this.rendertype) { + options_1 = { body: { contentDisposition: "render", contentType: this.rendertype, content: this.renderbody } }; + } + // If INVITE with Offer and we have been waiting till now to apply the answer. + if (this.hasOffer && !this.hasAnswer) { + if (!this.sessionDescriptionHandler) { + throw new Error("Session description handler undefined."); + } + var answer = session.answer; + if (!answer) { + throw new Error("Answer is undefined."); + } + this.sessionDescriptionHandler + .setDescription(answer.content, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { + _this.hasAnswer = true; + _this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var ackRequest = inviteResponse.ack(options_1); + _this.emit("ack", ackRequest.message); + _this.accepted(response); + }) + .catch(function (error) { + _this.logger.error(error); + _this.ackAndBye(inviteResponse, session, 488, "Not Acceptable Here"); + _this.failed(response, Constants_1.C.causes.BAD_MEDIA_DESCRIPTION); + // FIME: DON'T EAT UNHANDLED ERRORS! + }); + } + else { + // Otherwise INVITE with or without Offer and we have already completed the initial exchange. + this.sessionDescriptionHandler = this.earlyMediaSessionDescriptionHandlers.get(session.id); + if (!this.sessionDescriptionHandler) { + throw new Error("Session description handler undefined."); + } + this.earlyMediaSessionDescriptionHandlers.delete(session.id); + this.hasOffer = true; + this.hasAnswer = true; + this.status = Enums_1.SessionStatus.STATUS_CONFIRMED; + var ackRequest = inviteResponse.ack(); + this.emit("ack", ackRequest.message); + this.accepted(response); + } + break; + case core_1.SignalingState.Closed: + // Dialog has terminated. + break; + default: + throw new Error("Unknown session signaling state."); + } + this.disposeEarlyMedia(); + }; + /** + * Handle provisional response to initial INVITE. + * @param inviteResponse 1xx response. + */ + InviteClientContext.prototype.onProgress = function (inviteResponse) { + var _this = this; + // Ported - User requested cancellation. + if (this.isCanceled) { + return; + } + if (!this.outgoingInviteRequest) { + throw new Error("Outgoing INVITE request undefined."); + } + if (!this.earlyMediaSessionDescriptionHandlers) { + throw new Error("Early media session description handlers undefined."); + } + var response = inviteResponse.message; + var session = inviteResponse.session; + // Ported - Set status. + this.status = Enums_1.SessionStatus.STATUS_1XX_RECEIVED; + // Ported - Set assertedIdentity. + if (response.hasHeader("P-Asserted-Identity")) { + this.assertedIdentity = core_1.Grammar.nameAddrHeaderParse(response.getHeader("P-Asserted-Identity")); + } + // The provisional response MUST establish a dialog if one is not yet created. + // https://tools.ietf.org/html/rfc3262#section-4 + if (!session) { + // A response with a to tag MUST create a session (should never get here). + throw new Error("Session undefined."); + } + // If a provisional response is received for an initial request, and + // that response contains a Require header field containing the option + // tag 100rel, the response is to be sent reliably. If the response is + // a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be + // ignored, and the procedures below MUST NOT be used. + // https://tools.ietf.org/html/rfc3262#section-4 + var requireHeader = response.getHeader("require"); + var rseqHeader = response.getHeader("rseq"); + var rseq = requireHeader && requireHeader.includes("100rel") && rseqHeader ? Number(rseqHeader) : undefined; + var responseReliable = !!rseq; + var extraHeaders = []; + if (responseReliable) { + extraHeaders.push("RAck: " + response.getHeader("rseq") + " " + response.getHeader("cseq")); + } + // INVITE without Offer and session still has no offer (and no answer). + if (session.signalingState === core_1.SignalingState.Initial) { + // Similarly, if a reliable provisional + // response is the first reliable message sent back to the UAC, and the + // INVITE did not contain an offer, one MUST appear in that reliable + // provisional response. + // https://tools.ietf.org/html/rfc3262#section-5 + if (responseReliable) { + this.logger.warn("First reliable provisional response received MUST contain an offer when INVITE does not contain an offer."); + // FIXME: Known popular UA's currently end up here... + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + this.emit("progress", response); + return; + } + // INVITE with Offer and session only has that initial local offer. + if (session.signalingState === core_1.SignalingState.HaveLocalOffer) { + if (responseReliable) { + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + this.emit("progress", response); + return; + } + // INVITE without Offer and received initial offer in provisional response + if (session.signalingState === core_1.SignalingState.HaveRemoteOffer) { + // The initial offer MUST be in either an INVITE or, if not there, + // in the first reliable non-failure message from the UAS back to + // the UAC. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // According to Section 13.2.1 of [RFC3261], 'The first reliable + // non-failure message' must have an offer if there is no offer in the + // INVITE request. This means that the User Agent (UA) that receives + // the INVITE request without an offer must include an offer in the + // first reliable response with 100rel extension. If no reliable + // provisional response has been sent, the User Agent Server (UAS) must + // include an offer when sending 2xx response. + // https://tools.ietf.org/html/rfc6337#section-2.2 + if (!responseReliable) { + this.logger.warn("Non-reliable provisional response MUST NOT contain an initial offer, discarding response."); + return; + } + // If the initial offer is in the first reliable non-failure + // message from the UAS back to UAC, the answer MUST be in the + // acknowledgement for that message + var sdh_2 = this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions || {}); + this.emit("SessionDescriptionHandler-created", sdh_2); + this.earlyMediaSessionDescriptionHandlers.set(session.id, sdh_2); + sdh_2 + .setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { return sdh_2.getDescription(_this.sessionDescriptionHandlerOptions, _this.modifiers); }) + .then(function (description) { + var body = { + contentDisposition: "session", contentType: description.contentType, content: description.body + }; + inviteResponse.prack({ extraHeaders: extraHeaders, body: body }); + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + _this.emit("progress", response); + }) + .catch(function (error) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.failed(undefined, Constants_1.C.causes.WEBRTC_ERROR); + _this.terminated(undefined, Constants_1.C.causes.WEBRTC_ERROR); + }); + return; + } + // This session has completed an initial offer/answer exchange, so... + // - INVITE with SDP and this provisional response MAY be reliable + // - INVITE without SDP and this provisional response MAY be reliable + if (session.signalingState === core_1.SignalingState.Stable) { + if (responseReliable) { + inviteResponse.prack({ extraHeaders: extraHeaders }); + } + // Note: As documented, no early media if offer was in INVITE, so nothing to be done. + // FIXME: TODO: Add a flag/hack to allow early media in this case. There are people + // in non-forking environments (think straight to FreeSWITCH) who want + // early media on a 183. Not sure how to actually make it work, basically + // something like... + if (0 /* flag */ && this.hasOffer && !this.hasAnswer && this.sessionDescriptionHandler) { + this.hasAnswer = true; + this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + this.sessionDescriptionHandler + .setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers) + .then(function () { + _this.status = Enums_1.SessionStatus.STATUS_EARLY_MEDIA; + }) + .catch(function (error) { + if (_this.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + return; + } + _this.failed(undefined, Constants_1.C.causes.WEBRTC_ERROR); + _this.terminated(undefined, Constants_1.C.causes.WEBRTC_ERROR); + }); + } + this.emit("progress", response); + return; + } + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 3xx response. + */ + InviteClientContext.prototype.onRedirect = function (inviteResponse) { + this.disposeEarlyMedia(); + var response = inviteResponse.message; + var statusCode = response.statusCode; + var cause = Utils_1.Utils.sipErrorCause(statusCode || 0); + this.rejected(response, cause); + this.failed(response, cause); + this.terminated(response, cause); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 4xx, 5xx, or 6xx response. + */ + InviteClientContext.prototype.onReject = function (inviteResponse) { + this.disposeEarlyMedia(); + var response = inviteResponse.message; + var statusCode = response.statusCode; + var cause = Utils_1.Utils.sipErrorCause(statusCode || 0); + this.rejected(response, cause); + this.failed(response, cause); + this.terminated(response, cause); + }; + /** + * Handle final response to initial INVITE. + * @param inviteResponse 100 response. + */ + InviteClientContext.prototype.onTrying = function (inviteResponse) { + this.received100 = true; + this.emit("progress", inviteResponse.message); + }; + return InviteClientContext; +}(Session)); +exports.InviteClientContext = InviteClientContext; diff --git a/lib/Session/DTMF.d.ts b/lib/Session/DTMF.d.ts new file mode 100644 index 000000000..f8f02f075 --- /dev/null +++ b/lib/Session/DTMF.d.ts @@ -0,0 +1,25 @@ +/// +import { EventEmitter } from "events"; +import { IncomingRequest, IncomingResponseMessage } from "../core"; +import { TypeStrings } from "../Enums"; +import { Session } from "../Session"; +/** + * @class DTMF + * @param {SIP.Session} session + */ +export declare class DTMF extends EventEmitter { + type: TypeStrings; + tone: string; + duration: number; + interToneGap: number; + private C; + private logger; + private owner; + constructor(session: Session, tone: string | number, options?: any); + send(options?: any): void; + init_incoming(request: IncomingRequest): void; + receiveResponse(response: IncomingResponseMessage): void; + onRequestTimeout(): void; + onTransportError(): void; + onDialogError(response: IncomingResponseMessage): void; +} diff --git a/lib/Session/DTMF.js b/lib/Session/DTMF.js new file mode 100644 index 000000000..41ca9b646 --- /dev/null +++ b/lib/Session/DTMF.js @@ -0,0 +1,152 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var Constants_1 = require("../Constants"); +var Enums_1 = require("../Enums"); +var Exceptions_1 = require("../Exceptions"); +var Utils_1 = require("../Utils"); +/** + * @class DTMF + * @param {SIP.Session} session + */ +var DTMF = /** @class */ (function (_super) { + tslib_1.__extends(DTMF, _super); + function DTMF(session, tone, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.C = { + MIN_DURATION: 70, + MAX_DURATION: 6000, + DEFAULT_DURATION: 100, + MIN_INTER_TONE_GAP: 50, + DEFAULT_INTER_TONE_GAP: 500 + }; + _this.type = Enums_1.TypeStrings.DTMF; + if (tone === undefined) { + throw new TypeError("Not enough arguments"); + } + _this.logger = session.ua.getLogger("sip.invitecontext.dtmf", session.id); + _this.owner = session; + // Check tone type + if (typeof tone === "string") { + tone = tone.toUpperCase(); + } + else if (typeof tone === "number") { + tone = tone.toString(); + } + else { + throw new TypeError("Invalid tone: " + tone); + } + // Check tone value + if (!tone.match(/^[0-9A-D#*]$/)) { + throw new TypeError("Invalid tone: " + tone); + } + else { + _this.tone = tone; + } + var duration = options.duration; + var interToneGap = options.interToneGap; + // Check duration + if (duration && !Utils_1.Utils.isDecimal(duration)) { + throw new TypeError("Invalid tone duration: " + duration); + } + else if (!duration) { + duration = _this.C.DEFAULT_DURATION; + } + else if (duration < _this.C.MIN_DURATION) { + _this.logger.warn("'duration' value is lower than the minimum allowed, setting it to " + + _this.C.MIN_DURATION + " milliseconds"); + duration = _this.C.MIN_DURATION; + } + else if (duration > _this.C.MAX_DURATION) { + _this.logger.warn("'duration' value is greater than the maximum allowed, setting it to " + + _this.C.MAX_DURATION + " milliseconds"); + duration = _this.C.MAX_DURATION; + } + else { + duration = Math.abs(duration); + } + _this.duration = duration; + // Check interToneGap + if (interToneGap && !Utils_1.Utils.isDecimal(interToneGap)) { + throw new TypeError("Invalid interToneGap: " + interToneGap); + } + else if (!interToneGap) { + interToneGap = _this.C.DEFAULT_INTER_TONE_GAP; + } + else if (interToneGap < _this.C.MIN_INTER_TONE_GAP) { + _this.logger.warn("'interToneGap' value is lower than the minimum allowed, setting it to " + + _this.C.MIN_INTER_TONE_GAP + " milliseconds"); + interToneGap = _this.C.MIN_INTER_TONE_GAP; + } + else { + interToneGap = Math.abs(interToneGap); + } + _this.interToneGap = interToneGap; + return _this; + } + DTMF.prototype.send = function (options) { + if (options === void 0) { options = {}; } + // Check RTCSession Status + if (this.owner.status !== Enums_1.SessionStatus.STATUS_CONFIRMED && + this.owner.status !== Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK) { + throw new Exceptions_1.Exceptions.InvalidStateError(this.owner.status); + } + // Get DTMF options + var extraHeaders = options.extraHeaders ? options.extraHeaders.slice() : []; + var body = { + contentType: "application/dtmf-relay", + body: "Signal= " + this.tone + "\r\nDuration= " + this.duration + }; + if (this.owner.session) { + var request = this.owner.session.info(undefined, { + extraHeaders: extraHeaders, + body: Utils_1.Utils.fromBodyObj(body) + }); + this.owner.emit("dtmf", request.message, this); + return; + } + }; + DTMF.prototype.init_incoming = function (request) { + request.accept(); + if (!this.tone || !this.duration) { + this.logger.warn("invalid INFO DTMF received, discarded"); + } + else { + this.owner.emit("dtmf", request.message, this); + } + }; + DTMF.prototype.receiveResponse = function (response) { + var statusCode = response && response.statusCode ? response.statusCode : 0; + switch (true) { + case /^1[0-9]{2}$/.test(statusCode.toString()): + // Ignore provisional responses. + break; + case /^2[0-9]{2}$/.test(statusCode.toString()): + this.emit("succeeded", { + originator: "remote", + response: response + }); + break; + default: + var cause = Utils_1.Utils.sipErrorCause(statusCode); + this.emit("failed", response, cause); + break; + } + }; + DTMF.prototype.onRequestTimeout = function () { + this.emit("failed", undefined, Constants_1.C.causes.REQUEST_TIMEOUT); + this.owner.onRequestTimeout(); + }; + DTMF.prototype.onTransportError = function () { + this.emit("failed", undefined, Constants_1.C.causes.CONNECTION_ERROR); + this.owner.onTransportError(); + }; + DTMF.prototype.onDialogError = function (response) { + this.emit("failed", response, Constants_1.C.causes.DIALOG_ERROR); + this.owner.onDialogError(response); + }; + return DTMF; +}(events_1.EventEmitter)); +exports.DTMF = DTMF; diff --git a/lib/Subscription.d.ts b/lib/Subscription.d.ts new file mode 100644 index 000000000..08570c5f5 --- /dev/null +++ b/lib/Subscription.d.ts @@ -0,0 +1,127 @@ +/// +import { EventEmitter } from "events"; +import { ClientContext } from "./ClientContext"; +import { IncomingNotifyRequest, IncomingRequestMessage, IncomingResponse, IncomingResponseMessage, Logger, NameAddrHeader, OutgoingRequestMessage, OutgoingSubscribeRequest, URI } from "./core"; +import { TypeStrings } from "./Enums"; +import { BodyObj } from "./session-description-handler"; +import { UA } from "./UA"; +interface SubscriptionOptions { + expires?: number; + extraHeaders?: Array; + body?: string; + contentType?: string; +} +/** + * While this class is named `Subscription`, it is closer to + * an implementation of a "subscriber" as defined in RFC 6665 + * "SIP-Specific Event Notifications". + * https://tools.ietf.org/html/rfc6665 + * @class Class creating a SIP Subscriber. + */ +export declare class Subscription extends EventEmitter implements ClientContext { + type: TypeStrings; + ua: UA; + logger: Logger; + data: any; + method: string; + body: BodyObj | undefined; + localIdentity: NameAddrHeader; + remoteIdentity: NameAddrHeader; + request: OutgoingRequestMessage; + onRequestTimeout: () => void; + onTransportError: () => void; + receiveResponse: () => void; + send: () => this; + private id; + private context; + private disposed; + private event; + private expires; + private extraHeaders; + private retryAfterTimer; + private subscription; + private uri; + /** + * Constructor. + * @param ua User agent. + * @param target Subscription target. + * @param event Subscription event. + * @param options Options bucket. + */ + constructor(ua: UA, target: string | URI, event: string, options?: SubscriptionOptions); + /** + * Destructor. + */ + dispose(): void; + /** + * Registration of event listeners. + * + * The following events are emitted... + * - "accepted" A 200-class final response to a SUBSCRIBE request was received. + * - "failed" A non-200-class final response to a SUBSCRIBE request was received. + * - "rejected" Emitted immediately after a "failed" event (yes, it's redundant). + * - "notify" A NOTIFY request was received. + * - "terminated" The subscription is moving to or has moved to a terminated state. + * + * More than one SUBSCRIBE request may be sent, so "accepted", "failed" and "rejected" + * may be emitted multiple times. However these event will NOT be emitted for SUBSCRIBE + * requests with expires of zero (unsubscribe requests). + * + * Note that a "terminated" event does NOT indicate the subscription is in the "terminated" + * state as described in RFC 6665. Instead, a "terminated" event indicates that this class + * is no longer usable and/or is in the process of becoming no longer usable. + * + * The order the events are emitted in is not deterministic. Some examples... + * - "accepted" may occur multiple times + * - "accepted" may follow "notify" and "notify" may follow "accepted" + * - "terminated" may follow "accepted" and "accepted" may follow "terminated" + * - "terminated" may follow "notify" and "notify" may follow "terminated" + * + * Hint: Experience suggests one workable approach to utilizing these events + * is to make use of "notify" and "terminated" only. That is, call `subscribe()` + * and if a "notify" occurs then you have a subscription. If a "terminated" + * event occurs then either a new subscription failed to be established or an + * established subscription has terminated or is in the process of terminating. + * Note that "notify" events may follow a "terminated" event, but experience + * suggests it is reasonable to discontinue usage of this class after receipt + * of a "terminated" event. The other events are informational, but as they do not + * arrive in a deterministic manner it is difficult to make use of them otherwise. + * + * @param name Event name. + * @param callback Callback. + */ + on(name: "accepted" | "failed" | "rejected", callback: (message: IncomingResponseMessage, cause: string) => void): this; + on(name: "notify", callback: (notification: { + request: IncomingRequestMessage; + }) => void): this; + on(name: "terminated", callback: () => void): this; + emit(event: "accepted" | "failed" | "rejected", message: IncomingResponseMessage, cause: string): boolean; + emit(event: "notify", notification: { + request: IncomingRequestMessage; + }): boolean; + emit(event: "terminated"): boolean; + /** + * Gracefully terminate. + */ + close(): void; + /** + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + refresh(): void; + /** + * Send an initial SUBSCRIBE request if no subscription. + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + subscribe(): this; + /** + * Send a re-SUBSCRIBE request if there is a "pending" or "active" subscription. + */ + unsubscribe(): void; + protected onAccepted(response: IncomingResponse): void; + protected onFailed(response?: IncomingResponse): void; + protected onNotify(request: IncomingNotifyRequest): void; + protected onRefresh(request: OutgoingSubscribeRequest): void; + protected onTerminated(): void; + private initContext; +} +export {}; diff --git a/lib/Subscription.js b/lib/Subscription.js new file mode 100644 index 000000000..25b14ca17 --- /dev/null +++ b/lib/Subscription.js @@ -0,0 +1,433 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var Constants_1 = require("./Constants"); +var core_1 = require("./core"); +var allowed_methods_1 = require("./core/user-agent-core/allowed-methods"); +var Enums_1 = require("./Enums"); +var Utils_1 = require("./Utils"); +/** + * While this class is named `Subscription`, it is closer to + * an implementation of a "subscriber" as defined in RFC 6665 + * "SIP-Specific Event Notifications". + * https://tools.ietf.org/html/rfc6665 + * @class Class creating a SIP Subscriber. + */ +var Subscription = /** @class */ (function (_super) { + tslib_1.__extends(Subscription, _super); + /** + * Constructor. + * @param ua User agent. + * @param target Subscription target. + * @param event Subscription event. + * @param options Options bucket. + */ + function Subscription(ua, target, event, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.data = {}; + _this.method = Constants_1.C.SUBSCRIBE; + _this.body = undefined; + // ClientContext interface + _this.type = Enums_1.TypeStrings.Subscription; + _this.ua = ua; + _this.logger = ua.getLogger("sip.subscription"); + if (options.body) { + _this.body = { + body: options.body, + contentType: options.contentType ? options.contentType : "application/sdp" + }; + } + // Target URI + var uri = ua.normalizeTarget(target); + if (!uri) { + throw new TypeError("Invalid target: " + target); + } + _this.uri = uri; + // Subscription event + _this.event = event; + // Subscription expires + if (options.expires === undefined) { + _this.expires = 3600; + } + else if (typeof options.expires !== "number") { // pre-typescript type guard + ua.logger.warn("Option \"expires\" must be a number. Using default of 3600."); + _this.expires = 3600; + } + else { + _this.expires = options.expires; + } + // Subscription extra headers + _this.extraHeaders = (options.extraHeaders || []).slice(); + // Subscription context. + _this.context = _this.initContext(); + _this.disposed = false; + // ClientContext interface + _this.request = _this.context.message; + if (!_this.request.from) { + throw new Error("From undefined."); + } + if (!_this.request.to) { + throw new Error("From undefined."); + } + _this.localIdentity = _this.request.from; + _this.remoteIdentity = _this.request.to; + // Add to UA's collection + _this.id = _this.request.callId + _this.request.from.parameters.tag + _this.event; + _this.ua.subscriptions[_this.id] = _this; + return _this; + } + /** + * Destructor. + */ + Subscription.prototype.dispose = function () { + if (this.disposed) { + return; + } + if (this.retryAfterTimer) { + clearTimeout(this.retryAfterTimer); + this.retryAfterTimer = undefined; + } + this.context.dispose(); + this.disposed = true; + // Remove from UA's collection + delete this.ua.subscriptions[this.id]; + }; + Subscription.prototype.on = function (name, callback) { + return _super.prototype.on.call(this, name, callback); + }; + Subscription.prototype.emit = function (event) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + return _super.prototype.emit.apply(this, [event].concat(args)); + }; + /** + * Gracefully terminate. + */ + Subscription.prototype.close = function () { + if (this.disposed) { + return; + } + this.dispose(); + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + this.onTerminated(); + break; + case core_1.SubscriptionState.NotifyWait: + this.onTerminated(); + break; + case core_1.SubscriptionState.Pending: + this.unsubscribe(); + break; + case core_1.SubscriptionState.Active: + this.unsubscribe(); + break; + case core_1.SubscriptionState.Terminated: + this.onTerminated(); + break; + default: + break; + } + }; + /** + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + Subscription.prototype.refresh = function () { + var _this = this; + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + break; + case core_1.SubscriptionState.Active: + if (this.subscription) { + var request = this.subscription.refresh(); + request.delegate = { + onAccept: (function (response) { return _this.onAccepted(response); }), + onRedirect: (function (response) { return _this.onFailed(response); }), + onReject: (function (response) { return _this.onFailed(response); }), + }; + } + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + }; + /** + * Send an initial SUBSCRIBE request if no subscription. + * Send a re-SUBSCRIBE request if there is an "active" subscription. + */ + Subscription.prototype.subscribe = function () { + var _this = this; + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + this.context.subscribe().then(function (result) { + if (result.success) { + if (result.success.subscription) { + _this.subscription = result.success.subscription; + _this.subscription.delegate = { + onNotify: function (request) { return _this.onNotify(request); }, + onRefresh: function (request) { return _this.onRefresh(request); }, + onTerminated: function () { return _this.close(); } + }; + } + _this.onNotify(result.success.request); + } + else if (result.failure) { + _this.onFailed(result.failure.response); + } + }); + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + break; + case core_1.SubscriptionState.Active: + this.refresh(); + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + return this; + }; + /** + * Send a re-SUBSCRIBE request if there is a "pending" or "active" subscription. + */ + Subscription.prototype.unsubscribe = function () { + this.dispose(); + switch (this.context.state) { + case core_1.SubscriptionState.Initial: + break; + case core_1.SubscriptionState.NotifyWait: + break; + case core_1.SubscriptionState.Pending: + if (this.subscription) { + this.subscription.unsubscribe(); + // responses intentionally ignored + } + break; + case core_1.SubscriptionState.Active: + if (this.subscription) { + this.subscription.unsubscribe(); + // responses intentionally ignored + } + break; + case core_1.SubscriptionState.Terminated: + break; + default: + break; + } + this.onTerminated(); + }; + Subscription.prototype.onAccepted = function (response) { + var statusCode = response.message.statusCode ? response.message.statusCode : 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + this.emit("accepted", response.message, cause); + }; + Subscription.prototype.onFailed = function (response) { + this.close(); + if (response) { + var statusCode = response.message.statusCode ? response.message.statusCode : 0; + var cause = Utils_1.Utils.getReasonPhrase(statusCode); + this.emit("failed", response.message, cause); + this.emit("rejected", response.message, cause); + } + }; + Subscription.prototype.onNotify = function (request) { + var _this = this; + request.accept(); // Send 200 response. + this.emit("notify", { request: request.message }); + // If we've set state to done, no further processing should take place + // and we are only interested in cleaning up after the appropriate NOTIFY. + if (this.disposed) { + return; + } + // If the "Subscription-State" value is "terminated", the subscriber + // MUST consider the subscription terminated. The "expires" parameter + // has no semantics for "terminated" -- notifiers SHOULD NOT include an + // "expires" parameter on a "Subscription-State" header field with a + // value of "terminated", and subscribers MUST ignore any such + // parameter, if present. If a reason code is present, the client + // should behave as described below. If no reason code or an unknown + // reason code is present, the client MAY attempt to re-subscribe at any + // time (unless a "retry-after" parameter is present, in which case the + // client SHOULD NOT attempt re-subscription until after the number of + // seconds specified by the "retry-after" parameter). The reason codes + // defined by this document are: + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = request.message.parseHeader("Subscription-State"); + if (subscriptionState && subscriptionState.state) { + switch (subscriptionState.state) { + case "terminated": + if (subscriptionState.reason) { + this.logger.log("Terminated subscription with reason " + subscriptionState.reason); + switch (subscriptionState.reason) { + case "deactivated": + case "timeout": + this.initContext(); + this.subscribe(); + return; + case "probation": + case "giveup": + this.initContext(); + if (subscriptionState.params && subscriptionState.params["retry-after"]) { + this.retryAfterTimer = setTimeout(function () { return _this.subscribe(); }, subscriptionState.params["retry-after"]); + } + else { + this.subscribe(); + } + return; + case "rejected": + case "noresource": + case "invariant": + break; + } + } + this.close(); + break; + default: + break; + } + } + }; + Subscription.prototype.onRefresh = function (request) { + var _this = this; + request.delegate = { + onAccept: function (response) { return _this.onAccepted(response); } + }; + }; + Subscription.prototype.onTerminated = function () { + this.emit("terminated"); + }; + Subscription.prototype.initContext = function () { + var _this = this; + var options = { + extraHeaders: this.extraHeaders, + body: this.body ? Utils_1.Utils.fromBodyObj(this.body) : undefined + }; + this.context = new SubscribeClientContext(this.ua.userAgentCore, this.uri, this.event, this.expires, options); + this.context.delegate = { + onAccept: (function (response) { return _this.onAccepted(response); }) + }; + return this.context; + }; + return Subscription; +}(events_1.EventEmitter)); +exports.Subscription = Subscription; +// tslint:disable-next-line:max-classes-per-file +var SubscribeClientContext = /** @class */ (function () { + function SubscribeClientContext(core, target, event, expires, options, delegate) { + this.core = core; + this.target = target; + this.event = event; + this.expires = expires; + this.subscribed = false; + this.logger = core.loggerFactory.getLogger("sip.subscription"); + this.delegate = delegate; + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var extraHeaders = (options && options.extraHeaders || []).slice(); + extraHeaders.push(allowHeader); + extraHeaders.push("Event: " + this.event); + extraHeaders.push("Expires: " + this.expires); + extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + var body = options && options.body; + this.message = core.makeOutgoingRequestMessage(Constants_1.C.SUBSCRIBE, this.target, this.core.configuration.aor, this.target, {}, extraHeaders, body); + } + /** Destructor. */ + SubscribeClientContext.prototype.dispose = function () { + if (this.subscription) { + this.subscription.dispose(); + } + if (this.request) { + this.request.waitNotifyStop(); + this.request.dispose(); + } + }; + Object.defineProperty(SubscribeClientContext.prototype, "state", { + /** Subscription state. */ + get: function () { + if (this.subscription) { + return this.subscription.subscriptionState; + } + else if (this.subscribed) { + return core_1.SubscriptionState.NotifyWait; + } + else { + return core_1.SubscriptionState.Initial; + } + }, + enumerable: true, + configurable: true + }); + /** + * Establish subscription. + * @param options Options bucket. + */ + SubscribeClientContext.prototype.subscribe = function () { + var _this = this; + if (this.subscribed) { + return Promise.reject(new Error("Not in initial state. Did you call subscribe more than once?")); + } + this.subscribed = true; + return new Promise(function (resolve, reject) { + if (!_this.message) { + throw new Error("Message undefined."); + } + _this.request = _this.core.subscribe(_this.message, { + // This SUBSCRIBE request will be confirmed with a final response. + // 200-class responses indicate that the subscription has been accepted + // and that a NOTIFY request will be sent immediately. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onAccept: function (response) { + if (_this.delegate && _this.delegate.onAccept) { + _this.delegate.onAccept(response); + } + }, + // Due to the potential for out-of-order messages, packet loss, and + // forking, the subscriber MUST be prepared to receive NOTIFY requests + // before the SUBSCRIBE transaction has completed. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + onNotify: function (requestWithSubscription) { + _this.subscription = requestWithSubscription.subscription; + if (_this.subscription) { + _this.subscription.autoRefresh = true; + } + resolve({ success: requestWithSubscription }); + }, + // If this Timer N expires prior to the receipt of a NOTIFY request, + // the subscriber considers the subscription failed, and cleans up + // any state associated with the subscription attempt. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + onNotifyTimeout: function () { + resolve({ failure: {} }); + }, + // This SUBSCRIBE request will be confirmed with a final response. + // Non-200-class final responses indicate that no subscription or new + // dialog usage has been created, and no subsequent NOTIFY request will + // be sent. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onRedirect: function (response) { + resolve({ failure: { response: response } }); + }, + // This SUBSCRIBE request will be confirmed with a final response. + // Non-200-class final responses indicate that no subscription or new + // dialog usage has been created, and no subsequent NOTIFY request will + // be sent. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + onReject: function (response) { + resolve({ failure: { response: response } }); + } + }); + }); + }; + return SubscribeClientContext; +}()); diff --git a/lib/UA.d.ts b/lib/UA.d.ts new file mode 100644 index 000000000..f189c7776 --- /dev/null +++ b/lib/UA.d.ts @@ -0,0 +1,228 @@ +/// +import { EventEmitter } from "events"; +import { ClientContext } from "./ClientContext"; +import { C as SIPConstants } from "./Constants"; +import { DigestAuthentication, IncomingRequestMessage, IncomingSubscribeRequest, Logger, LoggerFactory, Transport, URI, UserAgentCore, UserAgentCoreConfiguration } from "./core"; +import { TypeStrings, UAStatus } from "./Enums"; +import { PublishContext } from "./PublishContext"; +import { ReferServerContext } from "./ReferContext"; +import { InviteClientContext, InviteServerContext } from "./Session"; +import { SessionDescriptionHandlerModifiers } from "./session-description-handler"; +import { SessionDescriptionHandlerFactory, SessionDescriptionHandlerFactoryOptions } from "./session-description-handler-factory"; +import { Subscription } from "./Subscription"; +export declare namespace UA { + interface Options { + uri?: string | URI; + allowLegacyNotifications?: boolean; + allowOutOfDialogRefers?: boolean; + authenticationFactory?: (ua: UA) => DigestAuthentication | any; + authorizationUser?: string; + autostart?: boolean; + autostop?: boolean; + displayName?: string; + dtmfType?: DtmfType; + experimentalFeatures?: boolean; + extraSupported?: Array; + forceRport?: boolean; + hackIpInContact?: boolean; + hackAllowUnregisteredOptionTags?: boolean; + hackViaTcp?: boolean; + hackWssInTransport?: boolean; + hostportParams?: any; + log?: { + builtinEnabled: boolean; + level: string | number; + connector: (level: string, category: string, label: string | undefined, content: any) => void; + }; + noAnswerTimeout?: number; + password?: string; + register?: boolean; + registerOptions?: RegisterOptions; + rel100?: SIPConstants.supported; + replaces?: SIPConstants.supported; + sessionDescriptionHandlerFactory?: SessionDescriptionHandlerFactory; + sessionDescriptionHandlerFactoryOptions?: SessionDescriptionHandlerFactoryOptions; + sipjsId?: string; + transportConstructor?: new (logger: any, options: any) => Transport; + transportOptions?: any; + userAgentString?: string; + usePreloadedRoute?: boolean; + viaHost?: string; + } + interface RegisterOptions { + expires?: number; + extraContactHeaderParams?: Array; + instanceId?: string; + params?: any; + regId?: number; + registrar?: string; + } +} +/** + * @class Class creating a SIP User Agent. + * @param {function returning SIP.sessionDescriptionHandler} [configuration.sessionDescriptionHandlerFactory] + * A function will be invoked by each of the UA's Sessions to build the sessionDescriptionHandler for that Session. + * If no (or a falsy) value is provided, each Session will use a default (WebRTC) sessionDescriptionHandler. + */ +export declare class UA extends EventEmitter { + static readonly C: { + STATUS_INIT: number; + STATUS_STARTING: number; + STATUS_READY: number; + STATUS_USER_CLOSED: number; + STATUS_NOT_READY: number; + CONFIGURATION_ERROR: number; + NETWORK_ERROR: number; + ALLOWED_METHODS: string[]; + ACCEPTED_BODY_TYPES: string[]; + MAX_FORWARDS: number; + TAG_LENGTH: number; + }; + type: TypeStrings; + configuration: UA.Options; + applicants: { + [id: string]: InviteClientContext; + }; + publishers: { + [id: string]: PublishContext; + }; + contact: { + pubGruu: URI | undefined; + tempGruu: URI | undefined; + uri: URI; + toString: (options?: any) => string; + }; + status: UAStatus; + transport: Transport | undefined; + sessions: { + [id: string]: InviteClientContext | InviteServerContext; + }; + subscriptions: { + [id: string]: Subscription; + }; + data: any; + logger: Logger; + userAgentCore: UserAgentCore; + private log; + private error; + private registerContext; + private environListener; + constructor(configuration?: UA.Options); + register(options?: any): this; + /** + * Unregister. + * + * @param {Boolean} [all] unregister all user bindings. + * + */ + unregister(options?: any): this; + isRegistered(): boolean; + /** + * Make an outgoing call. + * + * @param {String} target + * @param {Object} views + * @param {Object} [options.media] gets passed to SIP.sessionDescriptionHandler.getDescription as mediaHint + * + * @throws {TypeError} + * + */ + invite(target: string | URI, options?: InviteClientContext.Options, modifiers?: SessionDescriptionHandlerModifiers): InviteClientContext; + subscribe(target: string | URI, event: string, options: any): Subscription; + /** + * Send PUBLISH Event State Publication (RFC3903) + * + * @param {String} target + * @param {String} event + * @param {String} body + * @param {Object} [options] + * + * @throws {SIP.Exceptions.MethodParameterError} + */ + publish(target: string | URI, event: string, body: string, options: any): PublishContext; + /** + * Send a message. + * + * @param {String} target + * @param {String} body + * @param {Object} [options] + * + * @throws {TypeError} + */ + message(target: string | URI, body: string, options?: any): ClientContext; + request(method: string, target: string | URI, options?: any): ClientContext; + /** + * Gracefully close. + */ + stop(): this; + /** + * Connect to the WS server if status = STATUS_INIT. + * Resume UA after being closed. + * + */ + start(): this; + /** + * Normalize a string into a valid SIP request URI + * + * @param {String} target + * + * @returns {SIP.URI|undefined} + */ + normalizeTarget(target: string | URI): URI | undefined; + getLogger(category: string, label?: string): Logger; + getLoggerFactory(): LoggerFactory; + getSupportedResponseOptions(): Array; + /** + * Get the session to which the request belongs to, if any. + * @param {SIP.IncomingRequest} request. + * @returns {SIP.OutgoingSession|SIP.IncomingSession|undefined} + */ + findSession(request: IncomingRequestMessage): InviteClientContext | InviteServerContext | undefined; + on(name: "invite", callback: (session: InviteServerContext) => void): this; + on(name: "inviteSent", callback: (session: InviteClientContext) => void): this; + on(name: "outOfDialogReferRequested", callback: (context: ReferServerContext) => void): this; + on(name: "transportCreated", callback: (transport: Transport) => void): this; + on(name: "message", callback: (message: any) => void): this; + on(name: "notify", callback: (request: any) => void): this; + on(name: "subscribe", callback: (subscribe: IncomingSubscribeRequest) => void): this; + on(name: "registered", callback: (response?: any) => void): this; + on(name: "unregistered" | "registrationFailed", callback: (response?: any, cause?: any) => void): this; + private onTransportError; + /** + * Helper function. Sets transport listeners + */ + private setTransportListeners; + /** + * Transport connection event. + * @event + * @param {SIP.Transport} transport. + */ + private onTransportConnected; + /** + * Handle SIP message received from the transport. + * @param messageString The message. + */ + private onTransportReceiveMsg; + private checkAuthenticationFactory; + /** + * Configuration load. + * returns {void} + */ + private loadConfig; + /** + * Configuration checker. + * @return {Boolean} + */ + private getConfigurationCheck; +} +export declare namespace UA { + enum DtmfType { + RTP = "rtp", + INFO = "info" + } +} +/** + * Factory function to generate configuration give a UA. + * @param ua UA + */ +export declare function makeUserAgentCoreConfigurationFromUA(ua: UA): UserAgentCoreConfiguration; diff --git a/lib/UA.js b/lib/UA.js new file mode 100644 index 000000000..e1156bcea --- /dev/null +++ b/lib/UA.js @@ -0,0 +1,1111 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var ClientContext_1 = require("./ClientContext"); +var Constants_1 = require("./Constants"); +var core_1 = require("./core"); +var Enums_1 = require("./Enums"); +var Exceptions_1 = require("./Exceptions"); +var Parser_1 = require("./Parser"); +var PublishContext_1 = require("./PublishContext"); +var ReferContext_1 = require("./ReferContext"); +var RegisterContext_1 = require("./RegisterContext"); +var ServerContext_1 = require("./ServerContext"); +var Session_1 = require("./Session"); +var Subscription_1 = require("./Subscription"); +var Utils_1 = require("./Utils"); +var SessionDescriptionHandler_1 = require("./Web/SessionDescriptionHandler"); +var Transport_1 = require("./Web/Transport"); +var environment = global.window || global; +/** + * @class Class creating a SIP User Agent. + * @param {function returning SIP.sessionDescriptionHandler} [configuration.sessionDescriptionHandlerFactory] + * A function will be invoked by each of the UA's Sessions to build the sessionDescriptionHandler for that Session. + * If no (or a falsy) value is provided, each Session will use a default (WebRTC) sessionDescriptionHandler. + */ +var UA = /** @class */ (function (_super) { + tslib_1.__extends(UA, _super); + function UA(configuration) { + var _this = _super.call(this) || this; + _this.type = Enums_1.TypeStrings.UA; + _this.log = new core_1.LoggerFactory(); + _this.logger = _this.getLogger("sip.ua"); + _this.configuration = {}; + // User actions outside any session/dialog (MESSAGE) + _this.applicants = {}; + _this.data = {}; + _this.sessions = {}; + _this.subscriptions = {}; + _this.publishers = {}; + _this.status = Enums_1.UAStatus.STATUS_INIT; + /** + * Load configuration + * + * @throws {SIP.Exceptions.ConfigurationError} + * @throws {TypeError} + */ + if (configuration === undefined) { + configuration = {}; + } + else if (typeof configuration === "string" || configuration instanceof String) { + configuration = { + uri: configuration + }; + } + // Apply log configuration if present + if (configuration.log) { + if (configuration.log.hasOwnProperty("builtinEnabled")) { + _this.log.builtinEnabled = configuration.log.builtinEnabled; + } + if (configuration.log.hasOwnProperty("connector")) { + _this.log.connector = configuration.log.connector; + } + if (configuration.log.hasOwnProperty("level")) { + var level = configuration.log.level; + var normalized = void 0; + if (typeof level === "string") { + switch (level) { + case "error": + normalized = core_1.Levels.error; + break; + case "warn": + normalized = core_1.Levels.warn; + break; + case "log": + normalized = core_1.Levels.log; + break; + case "debug": + normalized = core_1.Levels.debug; + break; + default: + break; + } + } + else { + switch (level) { + case 0: + normalized = core_1.Levels.error; + break; + case 1: + normalized = core_1.Levels.warn; + break; + case 2: + normalized = core_1.Levels.log; + break; + case 3: + normalized = core_1.Levels.debug; + break; + default: + break; + } + } + // avoid setting level when invalid, use default level instead + if (normalized === undefined) { + _this.logger.error("Invalid \"level\" parameter value: " + JSON.stringify(level)); + } + else { + _this.log.level = normalized; + } + } + } + try { + _this.loadConfig(configuration); + } + catch (e) { + _this.status = Enums_1.UAStatus.STATUS_NOT_READY; + _this.error = UA.C.CONFIGURATION_ERROR; + throw e; + } + var userAgentCoreConfiguration = makeUserAgentCoreConfigurationFromUA(_this); + // The Replaces header contains information used to match an existing + // SIP dialog (call-id, to-tag, and from-tag). Upon receiving an INVITE + // with a Replaces header, the User Agent (UA) attempts to match this + // information with a confirmed or early dialog. + // https://tools.ietf.org/html/rfc3891#section-3 + var handleInviteWithReplacesHeader = function (context, request) { + if (_this.configuration.replaces !== Constants_1.C.supported.UNSUPPORTED) { + var replaces = request.parseHeader("replaces"); + if (replaces) { + var targetSession = _this.sessions[replaces.call_id + replaces.replaces_from_tag] || + _this.sessions[replaces.call_id + replaces.replaces_to_tag] || + undefined; + if (!targetSession) { + _this.userAgentCore.replyStateless(request, { statusCode: 481 }); + return; + } + if (targetSession.status === Enums_1.SessionStatus.STATUS_TERMINATED) { + _this.userAgentCore.replyStateless(request, { statusCode: 603 }); + return; + } + var targetDialogId = replaces.call_id + replaces.replaces_to_tag + replaces.replaces_from_tag; + var targetDialog = _this.userAgentCore.dialogs.get(targetDialogId); + if (!targetDialog) { + _this.userAgentCore.replyStateless(request, { statusCode: 481 }); + return; + } + if (!targetDialog.early && replaces.early_only) { + _this.userAgentCore.replyStateless(request, { statusCode: 486 }); + return; + } + context.replacee = targetSession; + } + } + }; + var userAgentCoreDelegate = { + onInvite: function (incomingInviteRequest) { + // FIXME: Ported - 100 Trying send should be configurable. + // Only required if TU will not respond in 200ms. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + incomingInviteRequest.trying(); + incomingInviteRequest.delegate = { + onCancel: function (cancel) { + context.onCancel(cancel); + }, + onTransportError: function (error) { + context.onTransportError(); + } + }; + var context = new Session_1.InviteServerContext(_this, incomingInviteRequest); + // Ported - handling of out of dialog INVITE with Replaces. + handleInviteWithReplacesHeader(context, incomingInviteRequest.message); + // Ported - make the first call to progress automatically. + if (context.autoSendAnInitialProvisionalResponse) { + context.progress(); + } + _this.emit("invite", context); + }, + onMessage: function (incomingMessageRequest) { + // Ported - handling of out of dialog MESSAGE. + var serverContext = new ServerContext_1.ServerContext(_this, incomingMessageRequest); + serverContext.body = incomingMessageRequest.message.body; + serverContext.contentType = incomingMessageRequest.message.getHeader("Content-Type") || "text/plain"; + incomingMessageRequest.accept(); + _this.emit("message", serverContext); // TODO: Review. Why is a "ServerContext" emitted? What use it is? + }, + onNotify: function (incomingNotifyRequest) { + // DEPRECATED: Out of dialog NOTIFY is an obsolete usage. + // Ported - handling of out of dialog NOTIFY. + if (_this.configuration.allowLegacyNotifications && _this.listeners("notify").length > 0) { + incomingNotifyRequest.accept(); + _this.emit("notify", { request: incomingNotifyRequest.message }); + } + else { + incomingNotifyRequest.reject({ statusCode: 481 }); + } + }, + onRefer: function (incomingReferRequest) { + // Ported - handling of out of dialog REFER. + _this.logger.log("Received an out of dialog refer"); + if (!_this.configuration.allowOutOfDialogRefers) { + incomingReferRequest.reject({ statusCode: 405 }); + } + _this.logger.log("Allow out of dialog refers is enabled on the UA"); + var referContext = new ReferContext_1.ReferServerContext(_this, incomingReferRequest); + if (_this.listeners("outOfDialogReferRequested").length) { + _this.emit("outOfDialogReferRequested", referContext); + } + else { + _this.logger.log("No outOfDialogReferRequest listeners, automatically accepting and following the out of dialog refer"); + referContext.accept({ followRefer: true }); + } + }, + onSubscribe: function (incomingSubscribeRequest) { + _this.emit("subscribe", incomingSubscribeRequest); + }, + }; + _this.userAgentCore = new core_1.UserAgentCore(userAgentCoreConfiguration, userAgentCoreDelegate); + // Initialize registerContext + _this.registerContext = new RegisterContext_1.RegisterContext(_this, configuration.registerOptions); + _this.registerContext.on("failed", _this.emit.bind(_this, "registrationFailed")); + _this.registerContext.on("registered", _this.emit.bind(_this, "registered")); + _this.registerContext.on("unregistered", _this.emit.bind(_this, "unregistered")); + if (_this.configuration.autostart) { + _this.start(); + } + return _this; + } + // ================= + // High Level API + // ================= + UA.prototype.register = function (options) { + if (options === void 0) { options = {}; } + if (options.register) { + this.configuration.register = true; + } + this.registerContext.register(options); + return this; + }; + /** + * Unregister. + * + * @param {Boolean} [all] unregister all user bindings. + * + */ + UA.prototype.unregister = function (options) { + var _this = this; + this.configuration.register = false; + if (this.transport) { + this.transport.afterConnected(function () { + _this.registerContext.unregister(options); + }); + } + return this; + }; + UA.prototype.isRegistered = function () { + return this.registerContext.registered; + }; + /** + * Make an outgoing call. + * + * @param {String} target + * @param {Object} views + * @param {Object} [options.media] gets passed to SIP.sessionDescriptionHandler.getDescription as mediaHint + * + * @throws {TypeError} + * + */ + UA.prototype.invite = function (target, options, modifiers) { + var _this = this; + var context = new Session_1.InviteClientContext(this, target, options, modifiers); + // Delay sending actual invite until the next 'tick' if we are already + // connected, so that API consumers can register to events fired by the + // the session. + if (this.transport) { + this.transport.afterConnected(function () { + context.invite(); + _this.emit("inviteSent", context); + }); + } + return context; + }; + UA.prototype.subscribe = function (target, event, options) { + var sub = new Subscription_1.Subscription(this, target, event, options); + if (this.transport) { + this.transport.afterConnected(function () { return sub.subscribe(); }); + } + return sub; + }; + /** + * Send PUBLISH Event State Publication (RFC3903) + * + * @param {String} target + * @param {String} event + * @param {String} body + * @param {Object} [options] + * + * @throws {SIP.Exceptions.MethodParameterError} + */ + UA.prototype.publish = function (target, event, body, options) { + var pub = new PublishContext_1.PublishContext(this, target, event, options); + if (this.transport) { + this.transport.afterConnected(function () { + pub.publish(body); + }); + } + return pub; + }; + /** + * Send a message. + * + * @param {String} target + * @param {String} body + * @param {Object} [options] + * + * @throws {TypeError} + */ + UA.prototype.message = function (target, body, options) { + if (options === void 0) { options = {}; } + if (body === undefined) { + throw new TypeError("Not enough arguments"); + } + // There is no Message module, so it is okay that the UA handles defaults here. + options.contentType = options.contentType || "text/plain"; + options.body = body; + return this.request(Constants_1.C.MESSAGE, target, options); + }; + UA.prototype.request = function (method, target, options) { + var req = new ClientContext_1.ClientContext(this, method, target, options); + if (this.transport) { + this.transport.afterConnected(function () { return req.send(); }); + } + return req; + }; + /** + * Gracefully close. + */ + UA.prototype.stop = function () { + this.logger.log("user requested closure..."); + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + this.logger.warn("UA already closed"); + return this; + } + // Close registerContext + this.logger.log("closing registerContext"); + this.registerContext.close(); + // Run terminate on every Session + for (var session in this.sessions) { + if (this.sessions[session]) { + this.logger.log("closing session " + session); + this.sessions[session].terminate(); + } + } + // Run unsubscribe on every Subscription + for (var subscription in this.subscriptions) { + if (this.subscriptions[subscription]) { + this.logger.log("unsubscribe " + subscription); + this.subscriptions[subscription].unsubscribe(); + } + } + // Run close on every Publisher + for (var publisher in this.publishers) { + if (this.publishers[publisher]) { + this.logger.log("unpublish " + publisher); + this.publishers[publisher].close(); + } + } + // Run close on every applicant + for (var applicant in this.applicants) { + if (this.applicants[applicant]) { + this.applicants[applicant].close(); + } + } + this.status = Enums_1.UAStatus.STATUS_USER_CLOSED; + // Disconnect the transport and reset user agent core + if (this.transport) { + this.transport.disconnect(); + } + this.userAgentCore.reset(); + if (typeof environment.removeEventListener === "function") { + // Google Chrome Packaged Apps don't allow 'unload' listeners: + // unload is not available in packaged apps + if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) { + environment.removeEventListener("unload", this.environListener); + } + } + return this; + }; + /** + * Connect to the WS server if status = STATUS_INIT. + * Resume UA after being closed. + * + */ + UA.prototype.start = function () { + var _this = this; + this.logger.log("user requested startup..."); + if (this.status === Enums_1.UAStatus.STATUS_INIT) { + this.status = Enums_1.UAStatus.STATUS_STARTING; + if (!this.configuration.transportConstructor) { + throw new core_1.TransportError("Transport constructor not set"); + } + this.transport = new this.configuration.transportConstructor(this.getLogger("sip.transport"), this.configuration.transportOptions); + this.setTransportListeners(); + this.emit("transportCreated", this.transport); + this.transport.connect(); + } + else if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + this.logger.log("resuming"); + this.status = Enums_1.UAStatus.STATUS_READY; + if (this.transport) { + this.transport.connect(); + } + } + else if (this.status === Enums_1.UAStatus.STATUS_STARTING) { + this.logger.log("UA is in STARTING status, not opening new connection"); + } + else if (this.status === Enums_1.UAStatus.STATUS_READY) { + this.logger.log("UA is in READY status, not resuming"); + } + else { + this.logger.error("Connection is down. Auto-Recovery system is trying to connect"); + } + if (this.configuration.autostop && typeof environment.addEventListener === "function") { + // Google Chrome Packaged Apps don't allow 'unload' listeners: + // unload is not available in packaged apps + if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) { + this.environListener = this.stop; + environment.addEventListener("unload", function () { return _this.environListener(); }); + } + } + return this; + }; + /** + * Normalize a string into a valid SIP request URI + * + * @param {String} target + * + * @returns {SIP.URI|undefined} + */ + UA.prototype.normalizeTarget = function (target) { + return Utils_1.Utils.normalizeTarget(target, this.configuration.hostportParams); + }; + UA.prototype.getLogger = function (category, label) { + return this.log.getLogger(category, label); + }; + UA.prototype.getLoggerFactory = function () { + return this.log; + }; + UA.prototype.getSupportedResponseOptions = function () { + var optionTags = []; + if (this.contact.pubGruu || this.contact.tempGruu) { + optionTags.push("gruu"); + } + if (this.configuration.rel100 === Constants_1.C.supported.SUPPORTED) { + optionTags.push("100rel"); + } + if (this.configuration.replaces === Constants_1.C.supported.SUPPORTED) { + optionTags.push("replaces"); + } + optionTags.push("outbound"); + optionTags = optionTags.concat(this.configuration.extraSupported || []); + var allowUnregistered = this.configuration.hackAllowUnregisteredOptionTags || false; + var optionTagSet = {}; + optionTags = optionTags.filter(function (optionTag) { + var registered = Constants_1.C.OPTION_TAGS[optionTag]; + var unique = !optionTagSet[optionTag]; + optionTagSet[optionTag] = true; + return (registered || allowUnregistered) && unique; + }); + return optionTags; + }; + /** + * Get the session to which the request belongs to, if any. + * @param {SIP.IncomingRequest} request. + * @returns {SIP.OutgoingSession|SIP.IncomingSession|undefined} + */ + UA.prototype.findSession = function (request) { + return this.sessions[request.callId + request.fromTag] || + this.sessions[request.callId + request.toTag] || + undefined; + }; + UA.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + // ============================== + // Event Handlers + // ============================== + UA.prototype.onTransportError = function () { + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED) { + return; + } + if (!this.error || this.error !== UA.C.NETWORK_ERROR) { + this.status = Enums_1.UAStatus.STATUS_NOT_READY; + this.error = UA.C.NETWORK_ERROR; + } + }; + /** + * Helper function. Sets transport listeners + */ + UA.prototype.setTransportListeners = function () { + var _this = this; + if (this.transport) { + this.transport.on("connected", function () { return _this.onTransportConnected(); }); + this.transport.on("message", function (message) { return _this.onTransportReceiveMsg(message); }); + this.transport.on("transportError", function () { return _this.onTransportError(); }); + } + }; + /** + * Transport connection event. + * @event + * @param {SIP.Transport} transport. + */ + UA.prototype.onTransportConnected = function () { + var _this = this; + if (this.configuration.register) { + // In an effor to maintain behavior from when we "initialized" an + // authentication factory, this is in a Promise.then + Promise.resolve().then(function () { return _this.registerContext.register(); }); + } + }; + /** + * Handle SIP message received from the transport. + * @param messageString The message. + */ + UA.prototype.onTransportReceiveMsg = function (messageString) { + var _this = this; + var message = Parser_1.Parser.parseMessage(messageString, this.getLogger("sip.parser")); + if (!message) { + this.logger.warn("UA failed to parse incoming SIP message - discarding."); + return; + } + if (this.status === Enums_1.UAStatus.STATUS_USER_CLOSED && message instanceof core_1.IncomingRequestMessage) { + this.logger.warn("UA received message when status = USER_CLOSED - aborting"); + return; + } + // A valid SIP request formulated by a UAC MUST, at a minimum, contain + // the following header fields: To, From, CSeq, Call-ID, Max-Forwards, + // and Via; all of these header fields are mandatory in all SIP + // requests. + // https://tools.ietf.org/html/rfc3261#section-8.1.1 + var hasMinimumHeaders = function () { + var mandatoryHeaders = ["from", "to", "call_id", "cseq", "via"]; + for (var _i = 0, mandatoryHeaders_1 = mandatoryHeaders; _i < mandatoryHeaders_1.length; _i++) { + var header = mandatoryHeaders_1[_i]; + if (!message.hasHeader(header)) { + _this.logger.warn("Missing mandatory header field : " + header + "."); + return false; + } + } + return true; + }; + // Request Checks + if (message instanceof core_1.IncomingRequestMessage) { + // This is port of SanityCheck.minimumHeaders(). + if (!hasMinimumHeaders()) { + this.logger.warn("Request missing mandatory header field. Dropping."); + return; + } + // FIXME: This is non-standard and should be a configruable behavior (desirable regardless). + // Custom SIP.js check to reject request from ourself (this instance of SIP.js). + // This is port of SanityCheck.rfc3261_16_3_4(). + if (!message.toTag && message.callId.substr(0, 5) === this.configuration.sipjsId) { + this.userAgentCore.replyStateless(message, { statusCode: 482 }); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to reject requests if body length wrong. + // This is port of SanityCheck.rfc3261_18_3_request(). + var len = Utils_1.Utils.str_utf8_length(message.body); + var contentLength = message.getHeader("content-length"); + if (contentLength && len < Number(contentLength)) { + this.userAgentCore.replyStateless(message, { statusCode: 400 }); + return; + } + } + // Reponse Checks + if (message instanceof core_1.IncomingResponseMessage) { + // This is port of SanityCheck.minimumHeaders(). + if (!hasMinimumHeaders()) { + this.logger.warn("Response missing mandatory header field. Dropping."); + return; + } + // Custom SIP.js check to drop responses if multiple Via headers. + // This is port of SanityCheck.rfc3261_8_1_3_3(). + if (message.getHeaders("via").length > 1) { + this.logger.warn("More than one Via header field present in the response. Dropping."); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to drop responses if bad Via header. + // This is port of SanityCheck.rfc3261_18_1_2(). + if (message.via.host !== this.configuration.viaHost || message.via.port !== undefined) { + this.logger.warn("Via sent-by in the response does not match UA Via host value. Dropping."); + return; + } + // FIXME: This should be Transport check before we get here (Section 18). + // Custom SIP.js check to reject requests if body length wrong. + // This is port of SanityCheck.rfc3261_18_3_response(). + var len = Utils_1.Utils.str_utf8_length(message.body); + var contentLength = message.getHeader("content-length"); + if (contentLength && len < Number(contentLength)) { + this.logger.warn("Message body length is lower than the value in Content-Length header field. Dropping."); + return; + } + } + // Handle Request + if (message instanceof core_1.IncomingRequestMessage) { + this.userAgentCore.receiveIncomingRequestFromTransport(message); + return; + } + // Handle Response + if (message instanceof core_1.IncomingResponseMessage) { + this.userAgentCore.receiveIncomingResponseFromTransport(message); + return; + } + throw new Error("Invalid message type."); + }; + // ================= + // Utils + // ================= + UA.prototype.checkAuthenticationFactory = function (authenticationFactory) { + if (!(authenticationFactory instanceof Function)) { + return; + } + if (!authenticationFactory.initialize) { + authenticationFactory.initialize = function () { + return Promise.resolve(); + }; + } + return authenticationFactory; + }; + /** + * Configuration load. + * returns {void} + */ + UA.prototype.loadConfig = function (configuration) { + var _this = this; + // Settings and default values + var settings = { + /* Host address + * Value to be set in Via sent_by and host part of Contact FQDN + */ + viaHost: Utils_1.Utils.createRandomToken(12) + ".invalid", + uri: new core_1.URI("sip", "anonymous." + Utils_1.Utils.createRandomToken(6), "anonymous.invalid", undefined, undefined), + // Custom Configuration Settings + custom: {}, + // Display name + displayName: "", + // Password + password: undefined, + register: true, + // Registration parameters + registerOptions: {}, + // Transport related parameters + transportConstructor: Transport_1.Transport, + transportOptions: {}, + // string to be inserted into User-Agent request header + userAgentString: Constants_1.C.USER_AGENT, + // Session parameters + noAnswerTimeout: 60, + // Hacks + hackViaTcp: false, + hackIpInContact: false, + hackWssInTransport: false, + hackAllowUnregisteredOptionTags: false, + // Session Description Handler Options + sessionDescriptionHandlerFactoryOptions: { + constraints: {}, + peerConnectionOptions: {} + }, + extraSupported: [], + contactName: Utils_1.Utils.createRandomToken(8), + contactTransport: "ws", + forceRport: false, + // autostarting + autostart: true, + autostop: true, + // Reliable Provisional Responses + rel100: Constants_1.C.supported.UNSUPPORTED, + // DTMF type: 'info' or 'rtp' (RFC 4733) + // RTP Payload Spec: https://tools.ietf.org/html/rfc4733 + // WebRTC Audio Spec: https://tools.ietf.org/html/rfc7874 + dtmfType: Constants_1.C.dtmfType.INFO, + // Replaces header (RFC 3891) + // http://tools.ietf.org/html/rfc3891 + replaces: Constants_1.C.supported.UNSUPPORTED, + sessionDescriptionHandlerFactory: SessionDescriptionHandler_1.SessionDescriptionHandler.defaultFactory, + authenticationFactory: this.checkAuthenticationFactory(function (ua) { + return new core_1.DigestAuthentication(ua.getLoggerFactory(), _this.configuration.authorizationUser, _this.configuration.password); + }), + allowLegacyNotifications: false, + allowOutOfDialogRefers: false, + experimentalFeatures: false + }; + var configCheck = this.getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if ((value instanceof Array && value.length === 0) || + (value === null || value === "" || value === undefined) || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Post Configuration Process + // Allow passing 0 number as displayName. + if (settings.displayName === 0) { + settings.displayName = "0"; + } + // sipjsId instance parameter. Static random tag of length 5 + settings.sipjsId = Utils_1.Utils.createRandomToken(5); + // String containing settings.uri without scheme and user. + var hostportParams = settings.uri.clone(); + hostportParams.user = undefined; + settings.hostportParams = hostportParams.toRaw().replace(/^sip:/i, ""); + /* Check whether authorizationUser is explicitly defined. + * Take 'settings.uri.user' value if not. + */ + if (!settings.authorizationUser) { + settings.authorizationUser = settings.uri.user; + } + // User noAnswerTimeout + settings.noAnswerTimeout = settings.noAnswerTimeout * 1000; + // Via Host + if (settings.hackIpInContact) { + if (typeof settings.hackIpInContact === "boolean") { + var from = 1; + var to = 254; + var octet = Math.floor(Math.random() * (to - from + 1) + from); + // random Test-Net IP (http://tools.ietf.org/html/rfc5735) + settings.viaHost = "192.0.2." + octet; + } + else if (typeof settings.hackIpInContact === "string") { + settings.viaHost = settings.hackIpInContact; + } + } + // Contact transport parameter + if (settings.hackWssInTransport) { + settings.contactTransport = "wss"; + } + this.contact = { + pubGruu: undefined, + tempGruu: undefined, + uri: new core_1.URI("sip", settings.contactName, settings.viaHost, undefined, { transport: settings.contactTransport }), + toString: function (options) { + if (options === void 0) { options = {}; } + var anonymous = options.anonymous || false; + var outbound = options.outbound || false; + var contact = "<"; + if (anonymous) { + contact += (_this.contact.tempGruu || + ("sip:anonymous@anonymous.invalid;transport=" + settings.contactTransport)).toString(); + } + else { + contact += (_this.contact.pubGruu || _this.contact.uri).toString(); + } + if (outbound) { + contact += ";ob"; + } + contact += ">"; + return contact; + } + }; + var skeleton = {}; + // Fill the value of the configuration_skeleton + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + skeleton[parameter] = settings[parameter]; + } + } + Object.assign(this.configuration, skeleton); + this.logger.log("configuration parameters after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + switch (parameter) { + case "uri": + case "sessionDescriptionHandlerFactory": + this.logger.log("· " + parameter + ": " + settings[parameter]); + break; + case "password": + this.logger.log("· " + parameter + ": " + "NOT SHOWN"); + break; + case "transportConstructor": + this.logger.log("· " + parameter + ": " + settings[parameter].name); + break; + default: + this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + } + return; + }; + /** + * Configuration checker. + * @return {Boolean} + */ + UA.prototype.getConfigurationCheck = function () { + return { + mandatory: {}, + optional: { + uri: function (uri) { + if (!(/^sip:/i).test(uri)) { + uri = Constants_1.C.SIP + ":" + uri; + } + var parsed = core_1.Grammar.URIParse(uri); + if (!parsed || !parsed.user) { + return; + } + else { + return parsed; + } + }, + transportConstructor: function (transportConstructor) { + if (transportConstructor instanceof Function) { + return transportConstructor; + } + }, + transportOptions: function (transportOptions) { + if (typeof transportOptions === "object") { + return transportOptions; + } + }, + authorizationUser: function (authorizationUser) { + if (core_1.Grammar.parse('"' + authorizationUser + '"', "quoted_string") === -1) { + return; + } + else { + return authorizationUser; + } + }, + displayName: function (displayName) { + if (core_1.Grammar.parse('"' + displayName + '"', "displayName") === -1) { + return; + } + else { + return displayName; + } + }, + dtmfType: function (dtmfType) { + switch (dtmfType) { + case Constants_1.C.dtmfType.RTP: + return Constants_1.C.dtmfType.RTP; + case Constants_1.C.dtmfType.INFO: + // Fall through + default: + return Constants_1.C.dtmfType.INFO; + } + }, + hackViaTcp: function (hackViaTcp) { + if (typeof hackViaTcp === "boolean") { + return hackViaTcp; + } + }, + hackIpInContact: function (hackIpInContact) { + if (typeof hackIpInContact === "boolean") { + return hackIpInContact; + } + else if (typeof hackIpInContact === "string" && core_1.Grammar.parse(hackIpInContact, "host") !== -1) { + return hackIpInContact; + } + }, + hackWssInTransport: function (hackWssInTransport) { + if (typeof hackWssInTransport === "boolean") { + return hackWssInTransport; + } + }, + hackAllowUnregisteredOptionTags: function (hackAllowUnregisteredOptionTags) { + if (typeof hackAllowUnregisteredOptionTags === "boolean") { + return hackAllowUnregisteredOptionTags; + } + }, + contactTransport: function (contactTransport) { + if (typeof contactTransport === "string") { + return contactTransport; + } + }, + extraSupported: function (optionTags) { + if (!(optionTags instanceof Array)) { + return; + } + for (var _i = 0, optionTags_1 = optionTags; _i < optionTags_1.length; _i++) { + var tag = optionTags_1[_i]; + if (typeof tag !== "string") { + return; + } + } + return optionTags; + }, + forceRport: function (forceRport) { + if (typeof forceRport === "boolean") { + return forceRport; + } + }, + noAnswerTimeout: function (noAnswerTimeout) { + if (Utils_1.Utils.isDecimal(noAnswerTimeout)) { + var value = Number(noAnswerTimeout); + if (value > 0) { + return value; + } + } + }, + password: function (password) { + return String(password); + }, + rel100: function (rel100) { + if (rel100 === Constants_1.C.supported.REQUIRED) { + return Constants_1.C.supported.REQUIRED; + } + else if (rel100 === Constants_1.C.supported.SUPPORTED) { + return Constants_1.C.supported.SUPPORTED; + } + else { + return Constants_1.C.supported.UNSUPPORTED; + } + }, + replaces: function (replaces) { + if (replaces === Constants_1.C.supported.REQUIRED) { + return Constants_1.C.supported.REQUIRED; + } + else if (replaces === Constants_1.C.supported.SUPPORTED) { + return Constants_1.C.supported.SUPPORTED; + } + else { + return Constants_1.C.supported.UNSUPPORTED; + } + }, + register: function (register) { + if (typeof register === "boolean") { + return register; + } + }, + registerOptions: function (registerOptions) { + if (typeof registerOptions === "object") { + return registerOptions; + } + }, + userAgentString: function (userAgentString) { + if (typeof userAgentString === "string") { + return userAgentString; + } + }, + autostart: function (autostart) { + if (typeof autostart === "boolean") { + return autostart; + } + }, + autostop: function (autostop) { + if (typeof autostop === "boolean") { + return autostop; + } + }, + sessionDescriptionHandlerFactory: function (sessionDescriptionHandlerFactory) { + if (sessionDescriptionHandlerFactory instanceof Function) { + return sessionDescriptionHandlerFactory; + } + }, + sessionDescriptionHandlerFactoryOptions: function (options) { + if (typeof options === "object") { + return options; + } + }, + authenticationFactory: this.checkAuthenticationFactory, + allowLegacyNotifications: function (allowLegacyNotifications) { + if (typeof allowLegacyNotifications === "boolean") { + return allowLegacyNotifications; + } + }, + custom: function (custom) { + if (typeof custom === "object") { + return custom; + } + }, + contactName: function (contactName) { + if (typeof contactName === "string") { + return contactName; + } + }, + experimentalFeatures: function (experimentalFeatures) { + if (typeof experimentalFeatures === "boolean") { + return experimentalFeatures; + } + }, + } + }; + }; + UA.C = { + // UA status codes + STATUS_INIT: 0, + STATUS_STARTING: 1, + STATUS_READY: 2, + STATUS_USER_CLOSED: 3, + STATUS_NOT_READY: 4, + // UA error codes + CONFIGURATION_ERROR: 1, + NETWORK_ERROR: 2, + ALLOWED_METHODS: [ + "ACK", + "CANCEL", + "INVITE", + "MESSAGE", + "BYE", + "OPTIONS", + "INFO", + "NOTIFY", + "REFER" + ], + ACCEPTED_BODY_TYPES: [ + "application/sdp", + "application/dtmf-relay" + ], + MAX_FORWARDS: 70, + TAG_LENGTH: 10 + }; + return UA; +}(events_1.EventEmitter)); +exports.UA = UA; +(function (UA) { + var DtmfType; + (function (DtmfType) { + DtmfType["RTP"] = "rtp"; + DtmfType["INFO"] = "info"; + })(DtmfType = UA.DtmfType || (UA.DtmfType = {})); +})(UA = exports.UA || (exports.UA = {})); +exports.UA = UA; +/** + * Factory function to generate configuration give a UA. + * @param ua UA + */ +function makeUserAgentCoreConfigurationFromUA(ua) { + // FIXME: Configuration URI is a bad mix of types currently. It also needs to exist. + if (!(ua.configuration.uri instanceof core_1.URI)) { + throw new Error("Configuration URI not instance of URI."); + } + var aor = ua.configuration.uri; + var contact = ua.contact; + var displayName = ua.configuration.displayName ? ua.configuration.displayName : ""; + var hackViaTcp = ua.configuration.hackViaTcp ? true : false; + var routeSet = ua.configuration.usePreloadedRoute && ua.transport ? [ua.transport.server.sipUri] : []; + var sipjsId = ua.configuration.sipjsId || Utils_1.Utils.createRandomToken(5); + var supportedOptionTags = []; + supportedOptionTags.push("outbound"); // TODO: is this really supported? + if (ua.configuration.rel100 === Constants_1.C.supported.SUPPORTED) { + supportedOptionTags.push("100rel"); + } + if (ua.configuration.replaces === Constants_1.C.supported.SUPPORTED) { + supportedOptionTags.push("replaces"); + } + if (ua.configuration.extraSupported) { + supportedOptionTags.push.apply(supportedOptionTags, ua.configuration.extraSupported); + } + if (!ua.configuration.hackAllowUnregisteredOptionTags) { + supportedOptionTags = supportedOptionTags.filter(function (optionTag) { return Constants_1.C.OPTION_TAGS[optionTag]; }); + } + supportedOptionTags = Array.from(new Set(supportedOptionTags)); // array of unique values + var supportedOptionTagsResponse = ua.getSupportedResponseOptions(); + var userAgentHeaderFieldValue = ua.configuration.userAgentString || "sipjs"; + if (!(ua.configuration.viaHost)) { + throw new Error("Configuration via host undefined"); + } + var viaForceRport = ua.configuration.forceRport ? true : false; + var viaHost = ua.configuration.viaHost; + var configuration = { + aor: aor, + contact: contact, + displayName: displayName, + hackViaTcp: hackViaTcp, + loggerFactory: ua.getLoggerFactory(), + routeSet: routeSet, + sipjsId: sipjsId, + supportedOptionTags: supportedOptionTags, + supportedOptionTagsResponse: supportedOptionTagsResponse, + userAgentHeaderFieldValue: userAgentHeaderFieldValue, + viaForceRport: viaForceRport, + viaHost: viaHost, + authenticationFactory: function () { + if (ua.configuration.authenticationFactory) { + return ua.configuration.authenticationFactory(ua); + } + return undefined; + }, + transportAccessor: function () { return ua.transport; } + }; + return configuration; +} +exports.makeUserAgentCoreConfigurationFromUA = makeUserAgentCoreConfigurationFromUA; diff --git a/lib/Utils.d.ts b/lib/Utils.d.ts new file mode 100644 index 000000000..1cfdfc9ec --- /dev/null +++ b/lib/Utils.d.ts @@ -0,0 +1,36 @@ +import { Body } from "./core/messages/body"; +import { URI } from "./core/messages/uri"; +import { BodyObj, SessionDescriptionHandlerModifier } from "./session-description-handler"; +export declare namespace Utils { + interface Deferred { + promise: Promise; + resolve: () => T; + reject: () => T; + } + function defer(): Deferred; + function reducePromises(arr: Array, val: any): Promise; + function str_utf8_length(str: string): number; + function generateFakeSDP(body: string): string | undefined; + function isDecimal(num: string): boolean; + function createRandomToken(size: number, base?: number): string; + function newTag(): string; + function newUUID(): string; + function normalizeTarget(target: string | URI, domain?: string): URI | undefined; + function escapeUser(user: string): string; + function headerize(str: string): string; + function sipErrorCause(statusCode: number): string; + function getReasonPhrase(code: number, specific?: string): string; + function getReasonHeaderValue(code: number, reason?: string): string; + function getCancelReason(code: number, reason: string): string | undefined; + function buildStatusLine(code: number, reason?: string): string; + /** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ + function fromBodyObj(bodyObj: BodyObj): Body; + /** + * Create a BodyObj given a Body. + * @param bodyObj Body Object + */ + function toBodyObj(body: Body): BodyObj; +} diff --git a/lib/Utils.js b/lib/Utils.js new file mode 100644 index 000000000..b937ab9fe --- /dev/null +++ b/lib/Utils.js @@ -0,0 +1,238 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Constants_1 = require("./Constants"); +var grammar_1 = require("./core/messages/grammar"); +var uri_1 = require("./core/messages/uri"); +var Utils; +(function (Utils) { + function defer() { + var deferred = {}; + deferred.promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; + } + Utils.defer = defer; + function reducePromises(arr, val) { + return arr.reduce(function (acc, fn) { + acc = acc.then(fn); + return acc; + }, Promise.resolve(val)); + } + Utils.reducePromises = reducePromises; + function str_utf8_length(str) { + return encodeURIComponent(str).replace(/%[A-F\d]{2}/g, "U").length; + } + Utils.str_utf8_length = str_utf8_length; + function generateFakeSDP(body) { + if (!body) { + return; + } + var start = body.indexOf("o="); + var end = body.indexOf("\r\n", start); + return "v=0\r\n" + body.slice(start, end) + "\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0"; + } + Utils.generateFakeSDP = generateFakeSDP; + function isDecimal(num) { + var numAsNum = parseInt(num, 10); + return !isNaN(numAsNum) && (parseFloat(num) === numAsNum); + } + Utils.isDecimal = isDecimal; + function createRandomToken(size, base) { + if (base === void 0) { base = 32; } + var token = ""; + for (var i = 0; i < size; i++) { + var r = Math.floor(Math.random() * base); + token += r.toString(base); + } + return token; + } + Utils.createRandomToken = createRandomToken; + function newTag() { + // used to use the constant in UA + return Utils.createRandomToken(10); + } + Utils.newTag = newTag; + // http://stackoverflow.com/users/109538/broofa + function newUUID() { + var UUID = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + var r = Math.floor(Math.random() * 16); + var v = c === "x" ? r : (r % 4 + 8); + return v.toString(16); + }); + return UUID; + } + Utils.newUUID = newUUID; + /* + * Normalize SIP URI. + * NOTE: It does not allow a SIP URI without username. + * Accepts 'sip', 'sips' and 'tel' URIs and convert them into 'sip'. + * Detects the domain part (if given) and properly hex-escapes the user portion. + * If the user portion has only 'tel' number symbols the user portion is clean of 'tel' visual separators. + * @private + * @param {String} target + * @param {String} [domain] + */ + function normalizeTarget(target, domain) { + // If no target is given then raise an error. + if (!target) { + return; + // If a SIP.URI instance is given then return it. + } + else if (target instanceof uri_1.URI) { + return target; + // If a string is given split it by '@': + // - Last fragment is the desired domain. + // - Otherwise append the given domain argument. + } + else if (typeof target === "string") { + var targetArray = target.split("@"); + var targetUser = void 0; + var targetDomain = void 0; + switch (targetArray.length) { + case 1: + if (!domain) { + return; + } + targetUser = target; + targetDomain = domain; + break; + case 2: + targetUser = targetArray[0]; + targetDomain = targetArray[1]; + break; + default: + targetUser = targetArray.slice(0, targetArray.length - 1).join("@"); + targetDomain = targetArray[targetArray.length - 1]; + } + // Remove the URI scheme (if present). + targetUser = targetUser.replace(/^(sips?|tel):/i, ""); + // Remove 'tel' visual separators if the user portion just contains 'tel' number symbols. + if (/^[\-\.\(\)]*\+?[0-9\-\.\(\)]+$/.test(targetUser)) { + targetUser = targetUser.replace(/[\-\.\(\)]/g, ""); + } + // Build the complete SIP URI. + target = Constants_1.C.SIP + ":" + Utils.escapeUser(targetUser) + "@" + targetDomain; + // Finally parse the resulting URI. + return grammar_1.Grammar.URIParse(target); + } + else { + return; + } + } + Utils.normalizeTarget = normalizeTarget; + /* + * Hex-escape a SIP URI user. + * @private + * @param {String} user + */ + function escapeUser(user) { + // Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F). + return encodeURIComponent(decodeURIComponent(user)) + .replace(/%3A/ig, ":") + .replace(/%2B/ig, "+") + .replace(/%3F/ig, "?") + .replace(/%2F/ig, "/"); + } + Utils.escapeUser = escapeUser; + function headerize(str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; + } + Utils.headerize = headerize; + function sipErrorCause(statusCode) { + for (var cause in Constants_1.C.SIP_ERROR_CAUSES) { + if (Constants_1.C.SIP_ERROR_CAUSES[cause].indexOf(statusCode) !== -1) { + return Constants_1.C.causes[cause]; + } + } + return Constants_1.C.causes.SIP_FAILURE_CODE; + } + Utils.sipErrorCause = sipErrorCause; + function getReasonPhrase(code, specific) { + return specific || Constants_1.C.REASON_PHRASE[code] || ""; + } + Utils.getReasonPhrase = getReasonPhrase; + function getReasonHeaderValue(code, reason) { + reason = Utils.getReasonPhrase(code, reason); + return "SIP;cause=" + code + ';text="' + reason + '"'; + } + Utils.getReasonHeaderValue = getReasonHeaderValue; + function getCancelReason(code, reason) { + if (code && code < 200 || code > 699) { + throw new TypeError("Invalid statusCode: " + code); + } + else if (code) { + return Utils.getReasonHeaderValue(code, reason); + } + } + Utils.getCancelReason = getCancelReason; + function buildStatusLine(code, reason) { + // Validate code and reason values + if (!code || (code < 100 || code > 699)) { + throw new TypeError("Invalid statusCode: " + code); + } + else if (reason && typeof reason !== "string" && !(reason instanceof String)) { + throw new TypeError("Invalid reason: " + reason); + } + reason = Utils.getReasonPhrase(code, reason); + return "SIP/2.0 " + code + " " + reason + "\r\n"; + } + Utils.buildStatusLine = buildStatusLine; + /** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ + function fromBodyObj(bodyObj) { + var content = bodyObj.body; + var contentType = bodyObj.contentType; + var contentDisposition = contentTypeToContentDisposition(contentType); + var body = { contentDisposition: contentDisposition, contentType: contentType, content: content }; + return body; + } + Utils.fromBodyObj = fromBodyObj; + /** + * Create a BodyObj given a Body. + * @param bodyObj Body Object + */ + function toBodyObj(body) { + var bodyObj = { + body: body.content, + contentType: body.contentType + }; + return bodyObj; + } + Utils.toBodyObj = toBodyObj; + // If the Content-Disposition header field is missing, bodies of + // Content-Type application/sdp imply the disposition "session", while + // other content types imply "render". + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + function contentTypeToContentDisposition(contentType) { + if (contentType === "application/sdp") { + return "session"; + } + else { + return "render"; + } + } +})(Utils = exports.Utils || (exports.Utils = {})); diff --git a/lib/Web/Modifiers.d.ts b/lib/Web/Modifiers.d.ts new file mode 100644 index 000000000..124b2795a --- /dev/null +++ b/lib/Web/Modifiers.d.ts @@ -0,0 +1,8 @@ +import { SessionDescriptionHandlerModifier } from "../session-description-handler"; +export declare function stripTcpCandidates(description: RTCSessionDescriptionInit): Promise; +export declare function stripTelephoneEvent(description: RTCSessionDescriptionInit): Promise; +export declare function cleanJitsiSdpImageattr(description: RTCSessionDescriptionInit): Promise; +export declare function stripG722(description: RTCSessionDescriptionInit): Promise; +export declare function stripRtpPayload(payload: string): SessionDescriptionHandlerModifier; +export declare function stripVideo(description: RTCSessionDescriptionInit): Promise; +export declare function addMidLines(description: RTCSessionDescriptionInit): Promise; diff --git a/lib/Web/Modifiers.js b/lib/Web/Modifiers.js new file mode 100644 index 000000000..97d2ec179 --- /dev/null +++ b/lib/Web/Modifiers.js @@ -0,0 +1,121 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var stripPayload = function (sdp, payload) { + var mediaDescs = []; + var lines = sdp.split(/\r\n/); + var currentMediaDesc; + for (var i = 0; i < lines.length;) { + var line = lines[i]; + if (/^m=(?:audio|video)/.test(line)) { + currentMediaDesc = { + index: i, + stripped: [] + }; + mediaDescs.push(currentMediaDesc); + } + else if (currentMediaDesc) { + var rtpmap = /^a=rtpmap:(\d+) ([^/]+)\//.exec(line); + if (rtpmap && payload === rtpmap[2]) { + lines.splice(i, 1); + currentMediaDesc.stripped.push(rtpmap[1]); + continue; // Don't increment 'i' + } + } + i++; + } + for (var _i = 0, mediaDescs_1 = mediaDescs; _i < mediaDescs_1.length; _i++) { + var mediaDesc = mediaDescs_1[_i]; + var mline = lines[mediaDesc.index].split(" "); + // Ignore the first 3 parameters of the mline. The codec information is after that + for (var j = 3; j < mline.length;) { + if (mediaDesc.stripped.indexOf(mline[j]) !== -1) { + mline.splice(j, 1); + continue; + } + j++; + } + lines[mediaDesc.index] = mline.join(" "); + } + return lines.join("\r\n"); +}; +var stripMediaDescription = function (sdp, description) { + var descriptionRegExp = new RegExp("m=" + description + ".*$", "gm"); + var groupRegExp = new RegExp("^a=group:.*$", "gm"); + if (descriptionRegExp.test(sdp)) { + var midLineToRemove_1; + sdp = sdp.split(/^m=/gm).filter(function (section) { + if (section.substr(0, description.length) === description) { + midLineToRemove_1 = section.match(/^a=mid:.*$/gm); + if (midLineToRemove_1) { + var step = midLineToRemove_1[0].match(/:.+$/g); + if (step) { + midLineToRemove_1 = step[0].substr(1); + } + } + return false; + } + return true; + }).join("m="); + var groupLine = sdp.match(groupRegExp); + if (groupLine && groupLine.length === 1) { + var groupLinePortion = groupLine[0]; + var groupRegExpReplace = new RegExp("\ *" + midLineToRemove_1 + "[^\ ]*", "g"); + groupLinePortion = groupLinePortion.replace(groupRegExpReplace, ""); + sdp = sdp.split(groupRegExp).join(groupLinePortion); + } + } + return sdp; +}; +function stripTcpCandidates(description) { + description.sdp = (description.sdp || "").replace(/^a=candidate:\d+ \d+ tcp .*?\r\n/img, ""); + return Promise.resolve(description); +} +exports.stripTcpCandidates = stripTcpCandidates; +function stripTelephoneEvent(description) { + description.sdp = stripPayload(description.sdp || "", "telephone-event"); + return Promise.resolve(description); +} +exports.stripTelephoneEvent = stripTelephoneEvent; +function cleanJitsiSdpImageattr(description) { + description.sdp = (description.sdp || "").replace(/^(a=imageattr:.*?)(x|y)=\[0-/gm, "$1$2=[1:"); + return Promise.resolve(description); +} +exports.cleanJitsiSdpImageattr = cleanJitsiSdpImageattr; +function stripG722(description) { + description.sdp = stripPayload(description.sdp || "", "G722"); + return Promise.resolve(description); +} +exports.stripG722 = stripG722; +function stripRtpPayload(payload) { + return function (description) { + description.sdp = stripPayload(description.sdp || "", payload); + return Promise.resolve(description); + }; +} +exports.stripRtpPayload = stripRtpPayload; +function stripVideo(description) { + description.sdp = stripMediaDescription(description.sdp || "", "video"); + return Promise.resolve(description); +} +exports.stripVideo = stripVideo; +function addMidLines(description) { + var sdp = description.sdp || ""; + if (sdp.search(/^a=mid.*$/gm) === -1) { + var mlines_1 = sdp.match(/^m=.*$/gm); + var sdpArray_1 = sdp.split(/^m=.*$/gm); + if (mlines_1) { + mlines_1.forEach(function (elem, idx) { + mlines_1[idx] = elem + "\na=mid:" + idx; + }); + } + sdpArray_1.forEach(function (elem, idx) { + if (mlines_1 && mlines_1[idx]) { + sdpArray_1[idx] = elem + mlines_1[idx]; + } + }); + sdp = sdpArray_1.join(""); + description.sdp = sdp; + } + return Promise.resolve(description); +} +exports.addMidLines = addMidLines; diff --git a/lib/Web/SessionDescriptionHandler.d.ts b/lib/Web/SessionDescriptionHandler.d.ts new file mode 100644 index 000000000..ada48ef93 --- /dev/null +++ b/lib/Web/SessionDescriptionHandler.d.ts @@ -0,0 +1,122 @@ +/// +import { EventEmitter } from "events"; +import { Logger } from "../core"; +import { TypeStrings } from "../Enums"; +import { InviteClientContext, InviteServerContext } from "../Session"; +import { BodyObj, SessionDescriptionHandler as SessionDescriptionHandlerDefinition, SessionDescriptionHandlerModifiers, SessionDescriptionHandlerOptions } from "../session-description-handler"; +import { SessionDescriptionHandlerObserver } from "./SessionDescriptionHandlerObserver"; +export interface WebSessionDescriptionHandlerOptions extends SessionDescriptionHandlerOptions { + peerConnectionOptions?: PeerConnectionOptions; + alwaysAcquireMediaFirst?: boolean; + disableAudioFallback?: boolean; + RTCOfferOptions?: any; + constraints?: any; +} +export interface PeerConnectionOptions { + iceCheckingTimeout?: number; + rtcConfiguration?: any; +} +export declare class SessionDescriptionHandler extends EventEmitter implements SessionDescriptionHandlerDefinition { + /** + * @param {SIP.Session} session + * @param {Object} [options] + */ + static defaultFactory(session: InviteClientContext | InviteServerContext, options: any): SessionDescriptionHandler; + type: TypeStrings; + peerConnection: RTCPeerConnection; + private options; + private logger; + private observer; + private dtmfSender; + private shouldAcquireMedia; + private CONTENT_TYPE; + private direction; + private C; + private modifiers; + private WebRTC; + private iceGatheringDeferred; + private iceGatheringTimeout; + private iceGatheringTimer; + private constraints; + constructor(logger: Logger, observer: SessionDescriptionHandlerObserver, options: any); + /** + * Destructor + */ + close(): void; + /** + * Gets the local description from the underlying media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves with the local description to be used for the session + */ + getDescription(options?: WebSessionDescriptionHandlerOptions, modifiers?: SessionDescriptionHandlerModifiers): Promise; + /** + * Check if the Session Description Handler can handle the Content-Type described by a SIP Message + * @param {String} contentType The content type that is in the SIP Message + * @returns {boolean} + */ + hasDescription(contentType: string): boolean; + /** + * The modifier that should be used when the session would like to place the call on hold + * @param {String} [sdp] The description that will be modified + * @returns {Promise} Promise that resolves with modified SDP + */ + holdModifier(description: RTCSessionDescriptionInit): Promise; + /** + * Set the remote description to the underlying media implementation + * @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves once the description is set + */ + setDescription(sessionDescription: string, options?: WebSessionDescriptionHandlerOptions, modifiers?: SessionDescriptionHandlerModifiers): Promise; + /** + * Send DTMF via RTP (RFC 4733) + * @param {String} tones A string containing DTMF digits + * @param {Object} [options] Options object to be used by sendDtmf + * @returns {boolean} true if DTMF send is successful, false otherwise + */ + sendDtmf(tones: string, options?: any): boolean; + /** + * Get the direction of the session description + * @returns {String} direction of the description + */ + getDirection(): string; + on(event: "getDescription" | "setDescription", listener: (description: RTCSessionDescriptionInit) => void): this; + on(event: "peerConnection-setRemoteDescriptionFailed", listener: (error: any) => void): this; + on(event: "setRemoteDescription", listener: (receivers: Array) => void): this; + on(event: "confirmed", listener: (sessionDescriptionHandler: SessionDescriptionHandler) => void): this; + on(event: "peerConnection-createAnswerFailed" | "peerConnection-createOfferFailed", listener: (error: any) => void): this; + on(event: "peerConnection-SetLocalDescriptionFailed", listener: (error: any) => void): this; + on(event: "addTrack", listener: (track: MediaStreamTrack) => void): this; + on(event: "addStream", listener: (track: MediaStream) => void): this; + on(event: "iceCandidate", listener: (candidate: RTCIceCandidate) => void): this; + on(event: "iceConnection" | "iceConnectionChecking" | "iceConnectionConnected" | "iceConnectionCompleted" | "iceConnectionFailed" | "iceConnectionDisconnected" | "iceConectionClosed", listener: (sessionDescriptionHandler: SessionDescriptionHandler) => void): this; + on(event: "iceGathering" | "iceGatheringComplete", listener: (sessionDescriptionHandler: SessionDescriptionHandler) => void): this; + on(event: "userMediaRequest", listener: (constraints: MediaStreamConstraints) => void): this; + on(event: "userMedia", listener: (streams: MediaStream) => void): this; + on(event: "userMediaFailed", listener: (error: any) => void): this; + private createOfferOrAnswer; + private createRTCSessionDescriptionInit; + private addDefaultIceCheckingTimeout; + private addDefaultIceServers; + private checkAndDefaultConstraints; + private hasBrowserTrackSupport; + private hasBrowserGetSenderSupport; + private initPeerConnection; + private acquire; + private hasOffer; + private isIceGatheringComplete; + private resetIceGatheringComplete; + private setDirection; + private triggerIceGatheringComplete; + private waitForIceGatheringComplete; +} diff --git a/lib/Web/SessionDescriptionHandler.js b/lib/Web/SessionDescriptionHandler.js new file mode 100644 index 000000000..ed2b25455 --- /dev/null +++ b/lib/Web/SessionDescriptionHandler.js @@ -0,0 +1,626 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var Enums_1 = require("../Enums"); +var Exceptions_1 = require("../Exceptions"); +var Utils_1 = require("../Utils"); +var Modifiers = tslib_1.__importStar(require("./Modifiers")); +var SessionDescriptionHandlerObserver_1 = require("./SessionDescriptionHandlerObserver"); +/* SessionDescriptionHandler + * @class PeerConnection helper Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandler = /** @class */ (function (_super) { + tslib_1.__extends(SessionDescriptionHandler, _super); + function SessionDescriptionHandler(logger, observer, options) { + var _this = _super.call(this) || this; + _this.type = Enums_1.TypeStrings.SessionDescriptionHandler; + // TODO: Validate the options + _this.options = options || {}; + _this.logger = logger; + _this.observer = observer; + _this.dtmfSender = undefined; + _this.shouldAcquireMedia = true; + _this.CONTENT_TYPE = "application/sdp"; + _this.C = { + DIRECTION: { + NULL: null, + SENDRECV: "sendrecv", + SENDONLY: "sendonly", + RECVONLY: "recvonly", + INACTIVE: "inactive" + } + }; + _this.logger.log("SessionDescriptionHandlerOptions: " + JSON.stringify(_this.options)); + _this.direction = _this.C.DIRECTION.NULL; + _this.modifiers = _this.options.modifiers || []; + if (!Array.isArray(_this.modifiers)) { + _this.modifiers = [_this.modifiers]; + } + var environment = global.window || global; + _this.WebRTC = { + MediaStream: environment.MediaStream, + getUserMedia: environment.navigator.mediaDevices.getUserMedia.bind(environment.navigator.mediaDevices), + RTCPeerConnection: environment.RTCPeerConnection + }; + _this.iceGatheringTimeout = false; + _this.initPeerConnection(_this.options.peerConnectionOptions); + _this.constraints = _this.checkAndDefaultConstraints(_this.options.constraints); + return _this; + } + /** + * @param {SIP.Session} session + * @param {Object} [options] + */ + SessionDescriptionHandler.defaultFactory = function (session, options) { + var logger = session.ua.getLogger("sip.invitecontext.sessionDescriptionHandler", session.id); + var observer = new SessionDescriptionHandlerObserver_1.SessionDescriptionHandlerObserver(session, options); + return new SessionDescriptionHandler(logger, observer, options); + }; + // Functions the sesssion can use + /** + * Destructor + */ + SessionDescriptionHandler.prototype.close = function () { + this.logger.log("closing PeerConnection"); + // have to check signalingState since this.close() gets called multiple times + if (this.peerConnection && this.peerConnection.signalingState !== "closed") { + if (this.peerConnection.getSenders) { + this.peerConnection.getSenders().forEach(function (sender) { + if (sender.track) { + sender.track.stop(); + } + }); + } + else { + this.logger.warn("Using getLocalStreams which is deprecated"); + this.peerConnection.getLocalStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + if (this.peerConnection.getReceivers) { + this.peerConnection.getReceivers().forEach(function (receiver) { + if (receiver.track) { + receiver.track.stop(); + } + }); + } + else { + this.logger.warn("Using getRemoteStreams which is deprecated"); + this.peerConnection.getRemoteStreams().forEach(function (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + }); + } + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + }; + /** + * Gets the local description from the underlying media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves with the local description to be used for the session + */ + SessionDescriptionHandler.prototype.getDescription = function (options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + // Merge passed constraints with saved constraints and save + var newConstraints = Object.assign({}, this.constraints, options.constraints); + newConstraints = this.checkAndDefaultConstraints(newConstraints); + if (JSON.stringify(newConstraints) !== JSON.stringify(this.constraints)) { + this.constraints = newConstraints; + this.shouldAcquireMedia = true; + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + return Promise.resolve().then(function () { + if (_this.shouldAcquireMedia) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return _this.createOfferOrAnswer(options.RTCOfferOptions, modifiers); }) + .then(function (description) { + if (description.sdp === undefined) { + throw new Exceptions_1.Exceptions.SessionDescriptionHandlerError("getDescription", undefined, "SDP undefined"); + } + _this.emit("getDescription", description); + return { + body: description.sdp, + contentType: _this.CONTENT_TYPE + }; + }); + }; + /** + * Check if the Session Description Handler can handle the Content-Type described by a SIP Message + * @param {String} contentType The content type that is in the SIP Message + * @returns {boolean} + */ + SessionDescriptionHandler.prototype.hasDescription = function (contentType) { + return contentType === this.CONTENT_TYPE; + }; + /** + * The modifier that should be used when the session would like to place the call on hold + * @param {String} [sdp] The description that will be modified + * @returns {Promise} Promise that resolves with modified SDP + */ + SessionDescriptionHandler.prototype.holdModifier = function (description) { + if (!description.sdp) { + return Promise.resolve(description); + } + if (!(/a=(sendrecv|sendonly|recvonly|inactive)/).test(description.sdp)) { + description.sdp = description.sdp.replace(/(m=[^\r]*\r\n)/g, "$1a=sendonly\r\n"); + } + else { + description.sdp = description.sdp.replace(/a=sendrecv\r\n/g, "a=sendonly\r\n"); + description.sdp = description.sdp.replace(/a=recvonly\r\n/g, "a=inactive\r\n"); + } + return Promise.resolve(description); + }; + /** + * Set the remote description to the underlying media implementation + * @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation + * @param {Object} [options] Options object to be used by getDescription + * @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints + * https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints + * @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer + * connection with the new options + * @param {Array} [modifiers] Array with one time use description modifiers + * @returns {Promise} Promise that resolves once the description is set + */ + SessionDescriptionHandler.prototype.setDescription = function (sessionDescription, options, modifiers) { + var _this = this; + if (options === void 0) { options = {}; } + if (modifiers === void 0) { modifiers = []; } + if (options.peerConnectionOptions) { + this.initPeerConnection(options.peerConnectionOptions); + } + if (!Array.isArray(modifiers)) { + modifiers = [modifiers]; + } + modifiers = modifiers.concat(this.modifiers); + var description = { + type: this.hasOffer("local") ? "answer" : "offer", + sdp: sessionDescription + }; + return Promise.resolve().then(function () { + // Media should be acquired in getDescription unless we need to do it sooner for some reason (FF61+) + if (_this.shouldAcquireMedia && _this.options.alwaysAcquireMediaFirst) { + return _this.acquire(_this.constraints).then(function () { + _this.shouldAcquireMedia = false; + }); + } + }).then(function () { return Utils_1.Utils.reducePromises(modifiers, description); }) + .catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e, "The modifiers did not resolve successfully"); + _this.logger.error(error.message); + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function (modifiedDescription) { + _this.emit("setDescription", modifiedDescription); + return _this.peerConnection.setRemoteDescription(modifiedDescription); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + // Check the original SDP for video, and ensure that we have want to do audio fallback + if ((/^m=video.+$/gm).test(sessionDescription) && !options.disableAudioFallback) { + // Do not try to audio fallback again + options.disableAudioFallback = true; + // Remove video first, then do the other modifiers + return _this.setDescription(sessionDescription, options, [Modifiers.stripVideo].concat(modifiers)); + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("setDescription", e); + if (error.error) { + _this.logger.error(error.error); + } + _this.emit("peerConnection-setRemoteDescriptionFailed", error); + throw error; + }).then(function () { + if (_this.peerConnection.getReceivers) { + _this.emit("setRemoteDescription", _this.peerConnection.getReceivers()); + } + else { + _this.emit("setRemoteDescription", _this.peerConnection.getRemoteStreams()); + } + _this.emit("confirmed", _this); + }); + }; + /** + * Send DTMF via RTP (RFC 4733) + * @param {String} tones A string containing DTMF digits + * @param {Object} [options] Options object to be used by sendDtmf + * @returns {boolean} true if DTMF send is successful, false otherwise + */ + SessionDescriptionHandler.prototype.sendDtmf = function (tones, options) { + if (options === void 0) { options = {}; } + if (!this.dtmfSender && this.hasBrowserGetSenderSupport()) { + var senders = this.peerConnection.getSenders(); + if (senders.length > 0) { + this.dtmfSender = senders[0].dtmf; + } + } + if (!this.dtmfSender && this.hasBrowserTrackSupport()) { + var streams = this.peerConnection.getLocalStreams(); + if (streams.length > 0) { + var audioTracks = streams[0].getAudioTracks(); + if (audioTracks.length > 0) { + this.dtmfSender = this.peerConnection.createDTMFSender(audioTracks[0]); + } + } + } + if (!this.dtmfSender) { + return false; + } + try { + this.dtmfSender.insertDTMF(tones, options.duration, options.interToneGap); + } + catch (e) { + if (e.type === "InvalidStateError" || e.type === "InvalidCharacterError") { + this.logger.error(e); + return false; + } + else { + throw e; + } + } + this.logger.log("DTMF sent via RTP: " + tones.toString()); + return true; + }; + /** + * Get the direction of the session description + * @returns {String} direction of the description + */ + SessionDescriptionHandler.prototype.getDirection = function () { + return this.direction; + }; + SessionDescriptionHandler.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + // Internal functions + SessionDescriptionHandler.prototype.createOfferOrAnswer = function (RTCOfferOptions, modifiers) { + var _this = this; + if (RTCOfferOptions === void 0) { RTCOfferOptions = {}; } + if (modifiers === void 0) { modifiers = []; } + var methodName = this.hasOffer("remote") ? "createAnswer" : "createOffer"; + var pc = this.peerConnection; + this.logger.log(methodName); + var method = this.hasOffer("remote") ? pc.createAnswer : pc.createOffer; + return method.apply(pc, RTCOfferOptions).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-" + methodName + "Failed"); + _this.emit("peerConnection-" + methodName + "Failed", error); + throw error; + }).then(function (sdp) { + return Utils_1.Utils.reducePromises(modifiers, _this.createRTCSessionDescriptionInit(sdp)); + }).then(function (sdp) { + _this.resetIceGatheringComplete(); + _this.logger.log("Setting local sdp."); + _this.logger.log("sdp is " + sdp.sdp || "undefined"); + return pc.setLocalDescription(sdp); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, "peerConnection-SetLocalDescriptionFailed"); + _this.emit("peerConnection-SetLocalDescriptionFailed", error); + throw error; + }).then(function () { return _this.waitForIceGatheringComplete(); }) + .then(function () { + var localDescription = _this.createRTCSessionDescriptionInit(_this.peerConnection.localDescription); + return Utils_1.Utils.reducePromises(modifiers, localDescription); + }).then(function (localDescription) { + _this.setDirection(localDescription.sdp || ""); + return localDescription; + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e); + _this.logger.error(error.toString()); + throw error; + }); + }; + // Creates an RTCSessionDescriptionInit from an RTCSessionDescription + SessionDescriptionHandler.prototype.createRTCSessionDescriptionInit = function (RTCSessionDescription) { + return { + type: RTCSessionDescription.type, + sdp: RTCSessionDescription.sdp + }; + }; + SessionDescriptionHandler.prototype.addDefaultIceCheckingTimeout = function (peerConnectionOptions) { + if (peerConnectionOptions.iceCheckingTimeout === undefined) { + peerConnectionOptions.iceCheckingTimeout = 5000; + } + return peerConnectionOptions; + }; + SessionDescriptionHandler.prototype.addDefaultIceServers = function (rtcConfiguration) { + if (!rtcConfiguration.iceServers) { + rtcConfiguration.iceServers = [{ urls: "stun:stun.l.google.com:19302" }]; + } + return rtcConfiguration; + }; + SessionDescriptionHandler.prototype.checkAndDefaultConstraints = function (constraints) { + var defaultConstraints = { audio: true, video: !this.options.alwaysAcquireMediaFirst }; + constraints = constraints || defaultConstraints; + // Empty object check + if (Object.keys(constraints).length === 0 && constraints.constructor === Object) { + return defaultConstraints; + } + return constraints; + }; + SessionDescriptionHandler.prototype.hasBrowserTrackSupport = function () { + return Boolean(this.peerConnection.addTrack); + }; + SessionDescriptionHandler.prototype.hasBrowserGetSenderSupport = function () { + return Boolean(this.peerConnection.getSenders); + }; + SessionDescriptionHandler.prototype.initPeerConnection = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + options = this.addDefaultIceCheckingTimeout(options); + options.rtcConfiguration = options.rtcConfiguration || {}; + options.rtcConfiguration = this.addDefaultIceServers(options.rtcConfiguration); + this.logger.log("initPeerConnection"); + if (this.peerConnection) { + this.logger.log("Already have a peer connection for this session. Tearing down."); + this.resetIceGatheringComplete(); + this.peerConnection.close(); + } + this.peerConnection = new this.WebRTC.RTCPeerConnection(options.rtcConfiguration); + this.logger.log("New peer connection created"); + if ("ontrack" in this.peerConnection) { + this.peerConnection.addEventListener("track", function (e) { + _this.logger.log("track added"); + _this.observer.trackAdded(); + _this.emit("addTrack", e); + }); + } + else { + this.logger.warn("Using onaddstream which is deprecated"); + this.peerConnection.onaddstream = function (e) { + _this.logger.log("stream added"); + _this.emit("addStream", e); + }; + } + this.peerConnection.onicecandidate = function (e) { + _this.emit("iceCandidate", e); + if (e.candidate) { + _this.logger.log("ICE candidate received: " + + (e.candidate.candidate === null ? null : e.candidate.candidate.trim())); + } + else if (e.candidate === null) { + // indicates the end of candidate gathering + _this.logger.log("ICE candidate gathering complete"); + _this.triggerIceGatheringComplete(); + } + }; + this.peerConnection.onicegatheringstatechange = function () { + _this.logger.log("RTCIceGatheringState changed: " + _this.peerConnection.iceGatheringState); + switch (_this.peerConnection.iceGatheringState) { + case "gathering": + _this.emit("iceGathering", _this); + if (!_this.iceGatheringTimer && options.iceCheckingTimeout) { + _this.iceGatheringTimeout = false; + _this.iceGatheringTimer = setTimeout(function () { + _this.logger.log("RTCIceChecking Timeout Triggered after " + options.iceCheckingTimeout + " milliseconds"); + _this.iceGatheringTimeout = true; + _this.triggerIceGatheringComplete(); + }, options.iceCheckingTimeout); + } + break; + case "complete": + _this.triggerIceGatheringComplete(); + break; + } + }; + this.peerConnection.oniceconnectionstatechange = function () { + var stateEvent; + switch (_this.peerConnection.iceConnectionState) { + case "new": + stateEvent = "iceConnection"; + break; + case "checking": + stateEvent = "iceConnectionChecking"; + break; + case "connected": + stateEvent = "iceConnectionConnected"; + break; + case "completed": + stateEvent = "iceConnectionCompleted"; + break; + case "failed": + stateEvent = "iceConnectionFailed"; + break; + case "disconnected": + stateEvent = "iceConnectionDisconnected"; + break; + case "closed": + stateEvent = "iceConnectionClosed"; + break; + default: + _this.logger.warn("Unknown iceConnection state: " + _this.peerConnection.iceConnectionState); + return; + } + _this.logger.log("ICE Connection State changed to " + stateEvent); + _this.emit(stateEvent, _this); + }; + }; + SessionDescriptionHandler.prototype.acquire = function (constraints) { + var _this = this; + // Default audio & video to true + constraints = this.checkAndDefaultConstraints(constraints); + return new Promise(function (resolve, reject) { + /* + * Make the call asynchronous, so that ICCs have a chance + * to define callbacks to `userMediaRequest` + */ + _this.logger.log("acquiring local media"); + _this.emit("userMediaRequest", constraints); + if (constraints.audio || constraints.video) { + _this.WebRTC.getUserMedia(constraints).then(function (streams) { + _this.observer.trackAdded(); + _this.emit("userMedia", streams); + resolve(streams); + }).catch(function (e) { + _this.emit("userMediaFailed", e); + reject(e); + }); + } + else { + // Local streams were explicitly excluded. + resolve([]); + } + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "unable to acquire streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + _this.logger.log("acquired local media streams"); + try { + // Remove old tracks + if (_this.peerConnection.removeTrack) { + _this.peerConnection.getSenders().forEach(function (sender) { + _this.peerConnection.removeTrack(sender); + }); + } + return streams; + } + catch (e) { + return Promise.reject(e); + } + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error removing streams"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }).then(function (streams) { + try { + streams = [].concat(streams); + streams.forEach(function (stream) { + if (_this.peerConnection.addTrack) { + stream.getTracks().forEach(function (track) { + _this.peerConnection.addTrack(track, stream); + }); + } + else { + // Chrome 59 does not support addTrack + _this.peerConnection.addStream(stream); + } + }); + } + catch (e) { + return Promise.reject(e); + } + return Promise.resolve(); + }).catch(function (e) { + if (e.type === Enums_1.TypeStrings.SessionDescriptionHandlerError) { + throw e; + } + var error = new Exceptions_1.Exceptions.SessionDescriptionHandlerError("acquire", e, "error adding stream"); + _this.logger.error(error.message); + if (error.error) { + _this.logger.error(error.error); + } + throw error; + }); + }; + SessionDescriptionHandler.prototype.hasOffer = function (where) { + var offerState = "have-" + where + "-offer"; + return this.peerConnection.signalingState === offerState; + }; + // ICE gathering state handling + SessionDescriptionHandler.prototype.isIceGatheringComplete = function () { + return this.peerConnection.iceGatheringState === "complete" || this.iceGatheringTimeout; + }; + SessionDescriptionHandler.prototype.resetIceGatheringComplete = function () { + this.iceGatheringTimeout = false; + this.logger.log("resetIceGatheringComplete"); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.reject(); + this.iceGatheringDeferred = undefined; + } + }; + SessionDescriptionHandler.prototype.setDirection = function (sdp) { + var match = sdp.match(/a=(sendrecv|sendonly|recvonly|inactive)/); + if (match === null) { + this.direction = this.C.DIRECTION.NULL; + this.observer.directionChanged(); + return; + } + var direction = match[1]; + switch (direction) { + case this.C.DIRECTION.SENDRECV: + case this.C.DIRECTION.SENDONLY: + case this.C.DIRECTION.RECVONLY: + case this.C.DIRECTION.INACTIVE: + this.direction = direction; + break; + default: + this.direction = this.C.DIRECTION.NULL; + break; + } + this.observer.directionChanged(); + }; + SessionDescriptionHandler.prototype.triggerIceGatheringComplete = function () { + if (this.isIceGatheringComplete()) { + this.emit("iceGatheringComplete", this); + if (this.iceGatheringTimer) { + clearTimeout(this.iceGatheringTimer); + this.iceGatheringTimer = undefined; + } + if (this.iceGatheringDeferred) { + this.iceGatheringDeferred.resolve(); + this.iceGatheringDeferred = undefined; + } + } + }; + SessionDescriptionHandler.prototype.waitForIceGatheringComplete = function () { + this.logger.log("waitForIceGatheringComplete"); + if (this.isIceGatheringComplete()) { + this.logger.log("ICE is already complete. Return resolved."); + return Promise.resolve(); + } + else if (!this.iceGatheringDeferred) { + this.iceGatheringDeferred = Utils_1.Utils.defer(); + } + this.logger.log("ICE is not complete. Returning promise"); + return this.iceGatheringDeferred ? this.iceGatheringDeferred.promise : Promise.resolve(); + }; + return SessionDescriptionHandler; +}(events_1.EventEmitter)); +exports.SessionDescriptionHandler = SessionDescriptionHandler; diff --git a/lib/Web/SessionDescriptionHandlerObserver.d.ts b/lib/Web/SessionDescriptionHandlerObserver.d.ts new file mode 100644 index 000000000..23e7576bb --- /dev/null +++ b/lib/Web/SessionDescriptionHandlerObserver.d.ts @@ -0,0 +1,10 @@ +import { TypeStrings } from "../Enums"; +import { InviteClientContext, InviteServerContext } from "../Session"; +export declare class SessionDescriptionHandlerObserver { + type: TypeStrings; + private session; + private options; + constructor(session: InviteClientContext | InviteServerContext, options: any); + trackAdded(): void; + directionChanged(): void; +} diff --git a/lib/Web/SessionDescriptionHandlerObserver.js b/lib/Web/SessionDescriptionHandlerObserver.js new file mode 100644 index 000000000..d410c8968 --- /dev/null +++ b/lib/Web/SessionDescriptionHandlerObserver.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Enums_1 = require("../Enums"); +/* SessionDescriptionHandlerObserver + * @class SessionDescriptionHandler Observer Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +var SessionDescriptionHandlerObserver = /** @class */ (function () { + function SessionDescriptionHandlerObserver(session, options) { + this.type = Enums_1.TypeStrings.SessionDescriptionHandlerObserver; + this.session = session; + this.options = options; + } + SessionDescriptionHandlerObserver.prototype.trackAdded = function () { + this.session.emit("trackAdded"); + }; + SessionDescriptionHandlerObserver.prototype.directionChanged = function () { + this.session.emit("directionChanged"); + }; + return SessionDescriptionHandlerObserver; +}()); +exports.SessionDescriptionHandlerObserver = SessionDescriptionHandlerObserver; diff --git a/lib/Web/Simple.d.ts b/lib/Web/Simple.d.ts new file mode 100644 index 000000000..7ab64fd99 --- /dev/null +++ b/lib/Web/Simple.d.ts @@ -0,0 +1,45 @@ +/// +import { EventEmitter } from "events"; +import { Logger } from "../core"; +import { InviteClientContext, InviteServerContext } from "../Session"; +import { UA } from "../UA"; +export declare enum SimpleStatus { + STATUS_NULL = 0, + STATUS_NEW = 1, + STATUS_CONNECTING = 2, + STATUS_CONNECTED = 3, + STATUS_COMPLETED = 4 +} +export declare class Simple extends EventEmitter { + static readonly C: typeof SimpleStatus; + video: boolean; + audio: boolean; + anonymous: boolean; + options: any; + ua: UA; + state: SimpleStatus; + logger: Logger; + session: InviteClientContext | InviteServerContext | undefined; + constructor(options: any); + call(destination: string): InviteClientContext | InviteServerContext | void; + answer(): InviteServerContext | void; + reject(): InviteServerContext | undefined; + hangup(): InviteClientContext | InviteServerContext | undefined; + hold(): InviteClientContext | InviteServerContext | void; + unhold(): InviteClientContext | InviteServerContext | void; + mute(): void; + unmute(): void; + sendDTMF(tone: string): void; + message(destination: string, message: string): void; + private checkRegistration; + private setupRemoteMedia; + private setupLocalMedia; + private cleanupMedia; + private setupSession; + private destroyMedia; + private toggleMute; + private onAccepted; + private onProgress; + private onFailed; + private onEnded; +} diff --git a/lib/Web/Simple.js b/lib/Web/Simple.js new file mode 100644 index 000000000..d7fb8d5dd --- /dev/null +++ b/lib/Web/Simple.js @@ -0,0 +1,411 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var UA_1 = require("../UA"); +var Modifiers = tslib_1.__importStar(require("./Modifiers")); +/* Simple + * @class Simple + */ +var SimpleStatus; +(function (SimpleStatus) { + SimpleStatus[SimpleStatus["STATUS_NULL"] = 0] = "STATUS_NULL"; + SimpleStatus[SimpleStatus["STATUS_NEW"] = 1] = "STATUS_NEW"; + SimpleStatus[SimpleStatus["STATUS_CONNECTING"] = 2] = "STATUS_CONNECTING"; + SimpleStatus[SimpleStatus["STATUS_CONNECTED"] = 3] = "STATUS_CONNECTED"; + SimpleStatus[SimpleStatus["STATUS_COMPLETED"] = 4] = "STATUS_COMPLETED"; +})(SimpleStatus = exports.SimpleStatus || (exports.SimpleStatus = {})); +var Simple = /** @class */ (function (_super) { + tslib_1.__extends(Simple, _super); + function Simple(options) { + var _this = _super.call(this) || this; + /* + * { + * media: { + * remote: { + * audio: , + * video: + * }, + * local: { + * video: + * } + * }, + * ua: { + * + * } + * } + */ + if (options.media.remote.video) { + _this.video = true; + } + else { + _this.video = false; + } + if (options.media.remote.audio) { + _this.audio = true; + } + else { + _this.audio = false; + } + if (!_this.audio && !_this.video) { + // Need to do at least audio or video + // Error + throw new Error("At least one remote audio or video element is required for Simple."); + } + _this.options = options; + // https://stackoverflow.com/questions/7944460/detect-safari-browser + var browserUa = global.navigator.userAgent.toLowerCase(); + var isSafari = false; + var isFirefox = false; + if (browserUa.indexOf("safari") > -1 && browserUa.indexOf("chrome") < 0) { + isSafari = true; + } + else if (browserUa.indexOf("firefox") > -1 && browserUa.indexOf("chrome") < 0) { + isFirefox = true; + } + var sessionDescriptionHandlerFactoryOptions = {}; + if (isSafari) { + sessionDescriptionHandlerFactoryOptions.modifiers = [Modifiers.stripG722]; + } + if (isFirefox) { + sessionDescriptionHandlerFactoryOptions.alwaysAcquireMediaFirst = true; + } + if (!_this.options.ua.uri) { + _this.anonymous = true; + } + else { + _this.anonymous = false; + } + _this.ua = new UA_1.UA({ + // User Configurable Options + uri: _this.options.ua.uri, + authorizationUser: _this.options.ua.authorizationUser, + password: _this.options.ua.password, + displayName: _this.options.ua.displayName, + // Undocumented "Advanced" Options + userAgentString: _this.options.ua.userAgentString, + // Fixed Options + register: true, + sessionDescriptionHandlerFactoryOptions: sessionDescriptionHandlerFactoryOptions, + transportOptions: { + traceSip: _this.options.ua.traceSip, + wsServers: _this.options.ua.wsServers + } + }); + _this.state = SimpleStatus.STATUS_NULL; + _this.logger = _this.ua.getLogger("sip.simple"); + _this.ua.on("registered", function () { + _this.emit("registered", _this.ua); + }); + _this.ua.on("unregistered", function () { + _this.emit("unregistered", _this.ua); + }); + _this.ua.on("registrationFailed", function () { + _this.emit("unregistered", _this.ua); + }); + _this.ua.on("invite", function (session) { + // If there is already an active session reject the incoming session + if (_this.state !== SimpleStatus.STATUS_NULL && _this.state !== SimpleStatus.STATUS_COMPLETED) { + _this.logger.warn("Rejecting incoming call. Simple only supports 1 call at a time"); + session.reject(); + return; + } + _this.session = session; + _this.setupSession(); + _this.emit("ringing", _this.session); + }); + _this.ua.on("message", function (message) { + _this.emit("message", message); + }); + return _this; + } + Simple.prototype.call = function (destination) { + if (!this.ua || !this.checkRegistration()) { + this.logger.warn("A registered UA is required for calling"); + return; + } + if (this.state !== SimpleStatus.STATUS_NULL && this.state !== SimpleStatus.STATUS_COMPLETED) { + this.logger.warn("Cannot make more than a single call with Simple"); + return; + } + // Safari hack, because you cannot call .play() from a non user action + if (this.options.media.remote.audio) { + this.options.media.remote.audio.autoplay = true; + } + if (this.options.media.remote.video) { + this.options.media.remote.video.autoplay = true; + } + if (this.options.media.local && this.options.media.local.video) { + this.options.media.local.video.autoplay = true; + this.options.media.local.video.volume = 0; + } + this.session = this.ua.invite(destination, { + sessionDescriptionHandlerOptions: { + constraints: { + audio: this.audio, + video: this.video + } + } + }); + this.setupSession(); + return this.session; + }; + Simple.prototype.answer = function () { + if (this.state !== SimpleStatus.STATUS_NEW && this.state !== SimpleStatus.STATUS_CONNECTING) { + this.logger.warn("No call to answer"); + return; + } + // Safari hack, because you cannot call .play() from a non user action + if (this.options.media.remote.audio) { + this.options.media.remote.audio.autoplay = true; + } + if (this.options.media.remote.video) { + this.options.media.remote.video.autoplay = true; + } + return this.session.accept({ + sessionDescriptionHandlerOptions: { + constraints: { + audio: this.audio, + video: this.video + } + } + }); + // emit call is active + }; + Simple.prototype.reject = function () { + if (this.state !== SimpleStatus.STATUS_NEW && this.state !== SimpleStatus.STATUS_CONNECTING) { + this.logger.warn("Call is already answered"); + return; + } + return this.session.reject(); + }; + Simple.prototype.hangup = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED && + this.state !== SimpleStatus.STATUS_CONNECTING && + this.state !== SimpleStatus.STATUS_NEW) { + this.logger.warn("No active call to hang up on"); + return; + } + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + return this.session.cancel(); + } + else if (this.session) { + return this.session.bye(); + } + }; + Simple.prototype.hold = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session || this.session.localHold) { + this.logger.warn("Cannot put call on hold"); + return; + } + this.mute(); + this.logger.log("Placing session on hold"); + return this.session.hold(); + }; + Simple.prototype.unhold = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session || !this.session.localHold) { + this.logger.warn("Cannot unhold a call that is not on hold"); + return; + } + this.unmute(); + this.logger.log("Placing call off hold"); + return this.session.unhold(); + }; + Simple.prototype.mute = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + this.logger.warn("An acitve call is required to mute audio"); + return; + } + this.logger.log("Muting Audio"); + this.toggleMute(true); + this.emit("mute", this); + }; + Simple.prototype.unmute = function () { + if (this.state !== SimpleStatus.STATUS_CONNECTED) { + this.logger.warn("An active call is required to unmute audio"); + return; + } + this.logger.log("Unmuting Audio"); + this.toggleMute(false); + this.emit("unmute", this); + }; + Simple.prototype.sendDTMF = function (tone) { + if (this.state !== SimpleStatus.STATUS_CONNECTED || !this.session) { + this.logger.warn("An active call is required to send a DTMF tone"); + return; + } + this.logger.log("Sending DTMF tone: " + tone); + this.session.dtmf(tone); + }; + Simple.prototype.message = function (destination, message) { + if (!this.ua || !this.checkRegistration()) { + this.logger.warn("A registered UA is required to send a message"); + return; + } + if (!destination || !message) { + this.logger.warn("A destination and message are required to send a message"); + return; + } + this.ua.message(destination, message); + }; + // Private Helpers + Simple.prototype.checkRegistration = function () { + return (this.anonymous || (this.ua && this.ua.isRegistered())); + }; + Simple.prototype.setupRemoteMedia = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session to set remote media on"); + return; + } + // If there is a video track, it will attach the video and audio to the same element + var pc = this.session.sessionDescriptionHandler.peerConnection; + var remoteStream; + if (pc.getReceivers) { + remoteStream = new global.window.MediaStream(); + pc.getReceivers().forEach(function (receiver) { + var track = receiver.track; + if (track) { + remoteStream.addTrack(track); + } + }); + } + else { + remoteStream = pc.getRemoteStreams()[0]; + } + if (this.video) { + this.options.media.remote.video.srcObject = remoteStream; + this.options.media.remote.video.play().catch(function () { + _this.logger.log("play was rejected"); + }); + } + else if (this.audio) { + this.options.media.remote.audio.srcObject = remoteStream; + this.options.media.remote.audio.play().catch(function () { + _this.logger.log("play was rejected"); + }); + } + }; + Simple.prototype.setupLocalMedia = function () { + if (!this.session) { + this.logger.warn("No session to set local media on"); + return; + } + if (this.video && this.options.media.local && this.options.media.local.video) { + var pc = this.session.sessionDescriptionHandler.peerConnection; + var localStream_1; + if (pc.getSenders) { + localStream_1 = new global.window.MediaStream(); + pc.getSenders().forEach(function (sender) { + var track = sender.track; + if (track && track.kind === "video") { + localStream_1.addTrack(track); + } + }); + } + else { + localStream_1 = pc.getLocalStreams()[0]; + } + this.options.media.local.video.srcObject = localStream_1; + this.options.media.local.video.volume = 0; + this.options.media.local.video.play(); + } + }; + Simple.prototype.cleanupMedia = function () { + if (this.video) { + this.options.media.remote.video.srcObject = null; + this.options.media.remote.video.pause(); + if (this.options.media.local && this.options.media.local.video) { + this.options.media.local.video.srcObject = null; + this.options.media.local.video.pause(); + } + } + if (this.audio) { + this.options.media.remote.audio.srcObject = null; + this.options.media.remote.audio.pause(); + } + }; + Simple.prototype.setupSession = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session to set up"); + return; + } + this.state = SimpleStatus.STATUS_NEW; + this.emit("new", this.session); + this.session.on("progress", function () { return _this.onProgress(); }); + this.session.on("accepted", function () { return _this.onAccepted(); }); + this.session.on("rejected", function () { return _this.onEnded(); }); + this.session.on("failed", function () { return _this.onFailed(); }); + this.session.on("terminated", function () { return _this.onEnded(); }); + }; + Simple.prototype.destroyMedia = function () { + if (this.session && this.session.sessionDescriptionHandler) { + this.session.sessionDescriptionHandler.close(); + } + }; + Simple.prototype.toggleMute = function (mute) { + if (!this.session) { + this.logger.warn("No session to toggle mute"); + return; + } + var pc = this.session.sessionDescriptionHandler.peerConnection; + if (pc.getSenders) { + pc.getSenders().forEach(function (sender) { + if (sender.track) { + sender.track.enabled = !mute; + } + }); + } + else { + pc.getLocalStreams().forEach(function (stream) { + stream.getAudioTracks().forEach(function (track) { + track.enabled = !mute; + }); + stream.getVideoTracks().forEach(function (track) { + track.enabled = !mute; + }); + }); + } + }; + Simple.prototype.onAccepted = function () { + var _this = this; + if (!this.session) { + this.logger.warn("No session for accepting"); + return; + } + this.state = SimpleStatus.STATUS_CONNECTED; + this.emit("connected", this.session); + this.setupLocalMedia(); + this.setupRemoteMedia(); + if (this.session.sessionDescriptionHandler) { + this.session.sessionDescriptionHandler.on("addTrack", function () { + _this.logger.log("A track has been added, triggering new remoteMedia setup"); + _this.setupRemoteMedia(); + }); + this.session.sessionDescriptionHandler.on("addStream", function () { + _this.logger.log("A stream has been added, trigger new remoteMedia setup"); + _this.setupRemoteMedia(); + }); + } + this.session.on("dtmf", function (request, dtmf) { + _this.emit("dtmf", dtmf.tone); + }); + this.session.on("bye", function () { return _this.onEnded(); }); + }; + Simple.prototype.onProgress = function () { + this.state = SimpleStatus.STATUS_CONNECTING; + this.emit("connecting", this.session); + }; + Simple.prototype.onFailed = function () { + this.onEnded(); + }; + Simple.prototype.onEnded = function () { + this.state = SimpleStatus.STATUS_COMPLETED; + this.emit("ended", this.session); + this.cleanupMedia(); + }; + Simple.C = SimpleStatus; + return Simple; +}(events_1.EventEmitter)); +exports.Simple = Simple; diff --git a/lib/Web/Transport.d.ts b/lib/Web/Transport.d.ts new file mode 100644 index 000000000..3049fcb70 --- /dev/null +++ b/lib/Web/Transport.d.ts @@ -0,0 +1,158 @@ +import { Logger, OutgoingRequestMessage, Transport as TransportBase } from "../core"; +import { TypeStrings } from "../Enums"; +export declare enum TransportStatus { + STATUS_CONNECTING = 0, + STATUS_OPEN = 1, + STATUS_CLOSING = 2, + STATUS_CLOSED = 3 +} +export interface WsServer { + scheme: string; + sipUri: string; + wsUri: string; + weight: number; + isError: boolean; +} +export interface Configuration { + wsServers: Array; + connectionTimeout: number; + maxReconnectionAttempts: number; + reconnectionTimeout: number; + keepAliveInterval: number; + keepAliveDebounce: number; + traceSip: boolean; +} +/** + * @class Transport + * @param {Object} options + */ +export declare class Transport extends TransportBase { + static readonly C: typeof TransportStatus; + type: TypeStrings; + server: WsServer; + ws: any; + private WebSocket; + private connectionPromise; + private connectDeferredResolve; + private connectionTimeout; + private disconnectionPromise; + private disconnectDeferredResolve; + private reconnectionAttempts; + private reconnectTimer; + private keepAliveInterval; + private keepAliveDebounceTimeout; + private status; + private configuration; + private boundOnOpen; + private boundOnMessage; + private boundOnClose; + private boundOnError; + constructor(logger: Logger, options?: any); + /** + * @returns {Boolean} + */ + isConnected(): boolean; + /** + * Send a message. + * @param {SIP.OutgoingRequest|String} msg + * @param {Object} [options] + * @returns {Promise} + */ + protected sendPromise(msg: OutgoingRequestMessage | string, options?: any): Promise<{ + msg: string; + }>; + /** + * Disconnect socket. + */ + protected disconnectPromise(options?: any): Promise; + /** + * Connect socket. + */ + protected connectPromise(options?: any): Promise; + /** + * @event + * @param {event} e + */ + protected onMessage(e: any): void; + /** + * @event + * @param {event} e + */ + private onOpen; + /** + * @event + * @param {event} e + */ + private onClose; + /** + * Removes event listeners and clears the instance ws + */ + private disposeWs; + /** + * @event + * @param {string} e + */ + private onError; + /** + * @event + * @private + */ + private onWebsocketError; + /** + * Reconnection attempt logic. + */ + private reconnect; + /** + * Resets the error state of all servers in the configuration + */ + private resetServerErrorStatus; + /** + * Retrieve the next server to which connect. + * @param {Boolean} force allows bypass of server error status checking + * @returns {Object} WsServer + */ + private getNextWsServer; + /** + * Checks all configuration servers, returns true if all of them have isError: true and false otherwise + * @returns {Boolean} + */ + private noAvailableServers; + /** + * Send a keep-alive (a double-CRLF sequence). + * @returns {Boolean} + */ + private sendKeepAlive; + private clearKeepAliveTimeout; + /** + * Start sending keep-alives. + */ + private startSendingKeepAlives; + /** + * Stop sending keep-alives. + */ + private stopSendingKeepAlives; + /** + * Checks given status against instance current status. Returns true if they match + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + private statusAssert; + /** + * Transitions the status. Checks for legal transition via assertion beforehand + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + private statusTransition; + /** + * Configuration load. + * returns {Configuration} + */ + private loadConfig; + /** + * Configuration checker. + * @return {Boolean} + */ + private getConfigurationCheck; +} diff --git a/lib/Web/Transport.js b/lib/Web/Transport.js new file mode 100644 index 000000000..f268ca65a --- /dev/null +++ b/lib/Web/Transport.js @@ -0,0 +1,693 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var core_1 = require("../core"); +var Enums_1 = require("../Enums"); +var Exceptions_1 = require("../Exceptions"); +var Utils_1 = require("../Utils"); +var TransportStatus; +(function (TransportStatus) { + TransportStatus[TransportStatus["STATUS_CONNECTING"] = 0] = "STATUS_CONNECTING"; + TransportStatus[TransportStatus["STATUS_OPEN"] = 1] = "STATUS_OPEN"; + TransportStatus[TransportStatus["STATUS_CLOSING"] = 2] = "STATUS_CLOSING"; + TransportStatus[TransportStatus["STATUS_CLOSED"] = 3] = "STATUS_CLOSED"; +})(TransportStatus = exports.TransportStatus || (exports.TransportStatus = {})); +/** + * Compute an amount of time in seconds to wait before sending another + * keep-alive. + * @returns {Number} + */ +var computeKeepAliveTimeout = function (upperBound) { + var lowerBound = upperBound * 0.8; + return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound); +}; +/** + * @class Transport + * @param {Object} options + */ +var Transport = /** @class */ (function (_super) { + tslib_1.__extends(Transport, _super); + function Transport(logger, options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this, logger, options) || this; + _this.WebSocket = (global.window || global).WebSocket; + _this.type = Enums_1.TypeStrings.Transport; + _this.reconnectionAttempts = 0; + _this.status = TransportStatus.STATUS_CONNECTING; + _this.configuration = _this.loadConfig(options); + _this.server = _this.configuration.wsServers[0]; + return _this; + } + /** + * @returns {Boolean} + */ + Transport.prototype.isConnected = function () { + return this.status === TransportStatus.STATUS_OPEN; + }; + /** + * Send a message. + * @param {SIP.OutgoingRequest|String} msg + * @param {Object} [options] + * @returns {Promise} + */ + Transport.prototype.sendPromise = function (msg, options) { + if (options === void 0) { options = {}; } + if (!this.statusAssert(TransportStatus.STATUS_OPEN, options.force)) { + this.onError("unable to send message - WebSocket not open"); + return Promise.reject(); + } + var message = msg.toString(); + if (this.ws) { + if (this.configuration.traceSip === true) { + this.logger.log("sending WebSocket message:\n\n" + message + "\n"); + } + this.ws.send(message); + return Promise.resolve({ msg: message }); + } + else { + this.onError("unable to send message - WebSocket does not exist"); + return Promise.reject(); + } + }; + /** + * Disconnect socket. + */ + Transport.prototype.disconnectPromise = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.disconnectionPromise) { // Already disconnecting. Just return this. + return this.disconnectionPromise; + } + options.code = options.code || 1000; + if (!this.statusTransition(TransportStatus.STATUS_CLOSING, options.force)) { + if (this.status === TransportStatus.STATUS_CLOSED) { // Websocket is already closed + return Promise.resolve({ overrideEvent: true }); + } + else if (this.connectionPromise) { // Websocket is connecting, cannot move to disconneting yet + return this.connectionPromise.then(function () { return Promise.reject("The websocket did not disconnect"); }) + .catch(function () { return Promise.resolve({ overrideEvent: true }); }); + } + else { + // Cannot move to disconnecting, but not in connecting state. + return Promise.reject("The websocket did not disconnect"); + } + } + this.emit("disconnecting"); + this.disconnectionPromise = new Promise(function (resolve, reject) { + _this.disconnectDeferredResolve = resolve; + if (_this.reconnectTimer) { + clearTimeout(_this.reconnectTimer); + _this.reconnectTimer = undefined; + } + if (_this.ws) { + _this.stopSendingKeepAlives(); + _this.logger.log("closing WebSocket " + _this.server.wsUri); + _this.ws.close(options.code, options.reason); + } + else { + reject("Attempted to disconnect but the websocket doesn't exist"); + } + }); + return this.disconnectionPromise; + }; + /** + * Connect socket. + */ + Transport.prototype.connectPromise = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.status === TransportStatus.STATUS_CLOSING && !options.force) { + return Promise.reject("WebSocket " + this.server.wsUri + " is closing"); + } + if (this.connectionPromise) { + return this.connectionPromise; + } + this.server = this.server || this.getNextWsServer(options.force); + this.connectionPromise = new Promise(function (resolve, reject) { + if ((_this.status === TransportStatus.STATUS_OPEN || _this.status === TransportStatus.STATUS_CLOSING) + && !options.force) { + _this.logger.warn("WebSocket " + _this.server.wsUri + " is already connected"); + reject("Failed status check - attempted to open a connection but already open/closing"); + return; + } + _this.connectDeferredResolve = resolve; + _this.status = TransportStatus.STATUS_CONNECTING; + _this.emit("connecting"); + _this.logger.log("connecting to WebSocket " + _this.server.wsUri); + _this.disposeWs(); + try { + _this.ws = new WebSocket(_this.server.wsUri, "sip"); + } + catch (e) { + _this.ws = null; + _this.statusTransition(TransportStatus.STATUS_CLOSED, true); + _this.onError("error connecting to WebSocket " + _this.server.wsUri + ":" + e); + reject("Failed to create a websocket"); + return; + } + if (!_this.ws) { + reject("Unexpected instance websocket not set"); + return; + } + _this.connectionTimeout = setTimeout(function () { + _this.statusTransition(TransportStatus.STATUS_CLOSED); + _this.logger.warn("took too long to connect - exceeded time set in configuration.connectionTimeout: " + + _this.configuration.connectionTimeout + "s"); + _this.emit("disconnected", { code: 1000 }); + _this.connectionPromise = undefined; + reject("Connection timeout"); + }, _this.configuration.connectionTimeout * 1000); + _this.boundOnOpen = _this.onOpen.bind(_this); + _this.boundOnMessage = _this.onMessage.bind(_this); + _this.boundOnClose = _this.onClose.bind(_this); + _this.boundOnError = _this.onWebsocketError.bind(_this); + _this.ws.addEventListener("open", _this.boundOnOpen); + _this.ws.addEventListener("message", _this.boundOnMessage); + _this.ws.addEventListener("close", _this.boundOnClose); + _this.ws.addEventListener("error", _this.boundOnError); + }); + return this.connectionPromise; + }; + /** + * @event + * @param {event} e + */ + Transport.prototype.onMessage = function (e) { + var data = e.data; + var finishedData; + // CRLF Keep Alive response from server. Clear our keep alive timeout. + if (/^(\r\n)+$/.test(data)) { + this.clearKeepAliveTimeout(); + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket message with CRLF Keep Alive response"); + } + return; + } + else if (!data) { + this.logger.warn("received empty message, message discarded"); + return; + } + else if (typeof data !== "string") { // WebSocket binary message. + try { + // the UInt8Data was here prior to types, and doesn't check + finishedData = String.fromCharCode.apply(null, new Uint8Array(data)); + } + catch (err) { + this.logger.warn("received WebSocket binary message failed to be converted into string, message discarded"); + return; + } + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket binary message:\n\n" + data + "\n"); + } + } + else { // WebSocket text message. + if (this.configuration.traceSip === true) { + this.logger.log("received WebSocket text message:\n\n" + data + "\n"); + } + finishedData = data; + } + this.emit("message", finishedData); + }; + // Transport Event Handlers + /** + * @event + * @param {event} e + */ + Transport.prototype.onOpen = function () { + if (this.status === TransportStatus.STATUS_CLOSED) { // Indicated that the transport thinks the ws is dead already + var ws = this.ws; + this.disposeWs(); + ws.close(1000); + return; + } + this.statusTransition(TransportStatus.STATUS_OPEN, true); + this.emit("connected"); + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + this.connectionTimeout = undefined; + } + this.logger.log("WebSocket " + this.server.wsUri + " connected"); + // Clear reconnectTimer since we are not disconnected + if (this.reconnectTimer !== undefined) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + // Reset reconnectionAttempts + this.reconnectionAttempts = 0; + // Reset disconnection promise so we can disconnect from a fresh state + this.disconnectionPromise = undefined; + this.disconnectDeferredResolve = undefined; + // Start sending keep-alives + this.startSendingKeepAlives(); + if (this.connectDeferredResolve) { + this.connectDeferredResolve({ overrideEvent: true }); + } + else { + this.logger.warn("Unexpected websocket.onOpen with no connectDeferredResolve"); + } + }; + /** + * @event + * @param {event} e + */ + Transport.prototype.onClose = function (e) { + this.logger.log("WebSocket disconnected (code: " + e.code + (e.reason ? "| reason: " + e.reason : "") + ")"); + if (this.status !== TransportStatus.STATUS_CLOSING) { + this.logger.warn("WebSocket closed without SIP.js requesting it"); + this.emit("transportError"); + } + this.stopSendingKeepAlives(); + // Clean up connection variables so we can connect again from a fresh state + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + } + this.connectionTimeout = undefined; + this.connectionPromise = undefined; + this.connectDeferredResolve = undefined; + // Check whether the user requested to close. + if (this.disconnectDeferredResolve) { + this.disconnectDeferredResolve({ overrideEvent: true }); + this.statusTransition(TransportStatus.STATUS_CLOSED); + this.disconnectDeferredResolve = undefined; + return; + } + this.statusTransition(TransportStatus.STATUS_CLOSED, true); + this.emit("disconnected", { code: e.code, reason: e.reason }); + this.reconnect(); + }; + /** + * Removes event listeners and clears the instance ws + */ + Transport.prototype.disposeWs = function () { + if (this.ws) { + this.ws.removeEventListener("open", this.boundOnOpen); + this.ws.removeEventListener("message", this.boundOnMessage); + this.ws.removeEventListener("close", this.boundOnClose); + this.ws.removeEventListener("error", this.boundOnError); + this.ws = undefined; + } + }; + /** + * @event + * @param {string} e + */ + Transport.prototype.onError = function (e) { + this.logger.warn("Transport error: " + e); + this.emit("transportError"); + }; + /** + * @event + * @private + */ + Transport.prototype.onWebsocketError = function () { + this.onError("The Websocket had an error"); + }; + /** + * Reconnection attempt logic. + */ + Transport.prototype.reconnect = function () { + var _this = this; + if (this.reconnectionAttempts > 0) { + this.logger.log("Reconnection attempt " + this.reconnectionAttempts + " failed"); + } + if (this.noAvailableServers()) { + this.logger.warn("attempted to get next ws server but there are no available ws servers left"); + this.logger.warn("no available ws servers left - going to closed state"); + this.statusTransition(TransportStatus.STATUS_CLOSED, true); + this.emit("closed"); + this.resetServerErrorStatus(); + return; + } + if (this.isConnected()) { + this.logger.warn("attempted to reconnect while connected - forcing disconnect"); + this.disconnect({ force: true }); + } + this.reconnectionAttempts += 1; + if (this.reconnectionAttempts > this.configuration.maxReconnectionAttempts) { + this.logger.warn("maximum reconnection attempts for WebSocket " + this.server.wsUri); + this.logger.log("transport " + this.server.wsUri + " failed | connection state set to 'error'"); + this.server.isError = true; + this.emit("transportError"); + if (!this.noAvailableServers()) { + this.server = this.getNextWsServer(); + } + // When there are no available servers, the reconnect function ends on the next recursive call + // after checking for no available servers again. + this.reconnectionAttempts = 0; + this.reconnect(); + } + else { + this.logger.log("trying to reconnect to WebSocket " + + this.server.wsUri + " (reconnection attempt " + this.reconnectionAttempts + ")"); + this.reconnectTimer = setTimeout(function () { + _this.connect(); + _this.reconnectTimer = undefined; + }, (this.reconnectionAttempts === 1) ? 0 : this.configuration.reconnectionTimeout * 1000); + } + }; + /** + * Resets the error state of all servers in the configuration + */ + Transport.prototype.resetServerErrorStatus = function () { + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var websocket = _a[_i]; + websocket.isError = false; + } + }; + /** + * Retrieve the next server to which connect. + * @param {Boolean} force allows bypass of server error status checking + * @returns {Object} WsServer + */ + Transport.prototype.getNextWsServer = function (force) { + if (force === void 0) { force = false; } + if (this.noAvailableServers()) { + this.logger.warn("attempted to get next ws server but there are no available ws servers left"); + throw new Error("Attempted to get next ws server, but there are no available ws servers left."); + } + // Order servers by weight + var candidates = []; + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var wsServer = _a[_i]; + if (wsServer.isError && !force) { + continue; + } + else if (candidates.length === 0) { + candidates.push(wsServer); + } + else if (wsServer.weight > candidates[0].weight) { + candidates = [wsServer]; + } + else if (wsServer.weight === candidates[0].weight) { + candidates.push(wsServer); + } + } + var idx = Math.floor(Math.random() * candidates.length); + return candidates[idx]; + }; + /** + * Checks all configuration servers, returns true if all of them have isError: true and false otherwise + * @returns {Boolean} + */ + Transport.prototype.noAvailableServers = function () { + for (var _i = 0, _a = this.configuration.wsServers; _i < _a.length; _i++) { + var server = _a[_i]; + if (!server.isError) { + return false; + } + } + return true; + }; + // ============================== + // KeepAlive Stuff + // ============================== + /** + * Send a keep-alive (a double-CRLF sequence). + * @returns {Boolean} + */ + Transport.prototype.sendKeepAlive = function () { + var _this = this; + if (this.keepAliveDebounceTimeout) { + // We already have an outstanding keep alive, do not send another. + return; + } + this.keepAliveDebounceTimeout = setTimeout(function () { + _this.emit("keepAliveDebounceTimeout"); + _this.clearKeepAliveTimeout(); + }, this.configuration.keepAliveDebounce * 1000); + return this.send("\r\n\r\n"); + }; + Transport.prototype.clearKeepAliveTimeout = function () { + if (this.keepAliveDebounceTimeout) { + clearTimeout(this.keepAliveDebounceTimeout); + } + this.keepAliveDebounceTimeout = undefined; + }; + /** + * Start sending keep-alives. + */ + Transport.prototype.startSendingKeepAlives = function () { + var _this = this; + if (this.configuration.keepAliveInterval && !this.keepAliveInterval) { + this.keepAliveInterval = setInterval(function () { + _this.sendKeepAlive(); + _this.startSendingKeepAlives(); + }, computeKeepAliveTimeout(this.configuration.keepAliveInterval)); + } + }; + /** + * Stop sending keep-alives. + */ + Transport.prototype.stopSendingKeepAlives = function () { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + } + if (this.keepAliveDebounceTimeout) { + clearTimeout(this.keepAliveDebounceTimeout); + } + this.keepAliveInterval = undefined; + this.keepAliveDebounceTimeout = undefined; + }; + // ============================== + // Status Stuff + // ============================== + /** + * Checks given status against instance current status. Returns true if they match + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + Transport.prototype.statusAssert = function (status, force) { + if (status === this.status) { + return true; + } + else { + if (force) { + this.logger.warn("Attempted to assert " + + Object.keys(TransportStatus)[this.status] + " as " + + Object.keys(TransportStatus)[status] + "- continuing with option: 'force'"); + return true; + } + else { + this.logger.warn("Tried to assert " + + Object.keys(TransportStatus)[status] + " but is currently " + + Object.keys(TransportStatus)[this.status]); + return false; + } + } + }; + /** + * Transitions the status. Checks for legal transition via assertion beforehand + * @param {Number} status + * @param {Boolean} [force] + * @returns {Boolean} + */ + Transport.prototype.statusTransition = function (status, force) { + if (force === void 0) { force = false; } + this.logger.log("Attempting to transition status from " + + Object.keys(TransportStatus)[this.status] + " to " + + Object.keys(TransportStatus)[status]); + if ((status === TransportStatus.STATUS_CONNECTING && this.statusAssert(TransportStatus.STATUS_CLOSED, force)) || + (status === TransportStatus.STATUS_OPEN && this.statusAssert(TransportStatus.STATUS_CONNECTING, force)) || + (status === TransportStatus.STATUS_CLOSING && this.statusAssert(TransportStatus.STATUS_OPEN, force)) || + (status === TransportStatus.STATUS_CLOSED)) { + this.status = status; + return true; + } + else { + this.logger.warn("Status transition failed - result: no-op - reason:" + + " either gave an nonexistent status or attempted illegal transition"); + return false; + } + }; + // ============================== + // Configuration Handling + // ============================== + /** + * Configuration load. + * returns {Configuration} + */ + Transport.prototype.loadConfig = function (configuration) { + var settings = { + wsServers: [{ + scheme: "WSS", + sipUri: "", + weight: 0, + wsUri: "wss://edge.sip.onsip.com", + isError: false + }], + connectionTimeout: 5, + maxReconnectionAttempts: 3, + reconnectionTimeout: 4, + keepAliveInterval: 0, + keepAliveDebounce: 10, + // Logging + traceSip: false + }; + var configCheck = this.getConfigurationCheck(); + // Check Mandatory parameters + for (var parameter in configCheck.mandatory) { + if (!configuration.hasOwnProperty(parameter)) { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter); + } + else { + var value = configuration[parameter]; + var checkedValue = configCheck.mandatory[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + // Check Optional parameters + for (var parameter in configCheck.optional) { + if (configuration.hasOwnProperty(parameter)) { + var value = configuration[parameter]; + // If the parameter value is an empty array, but shouldn't be, apply its default value. + // If the parameter value is null, empty string, or undefined then apply its default value. + // If it's a number with NaN value then also apply its default value. + // NOTE: JS does not allow "value === NaN", the following does the work: + if ((value instanceof Array && value.length === 0) || + (value === null || value === "" || value === undefined) || + (typeof (value) === "number" && isNaN(value))) { + continue; + } + var checkedValue = configCheck.optional[parameter](value); + if (checkedValue !== undefined) { + settings[parameter] = checkedValue; + } + else { + throw new Exceptions_1.Exceptions.ConfigurationError(parameter, value); + } + } + } + var skeleton = {}; // Fill the value of the configuration_skeleton + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + skeleton[parameter] = { + value: settings[parameter], + }; + } + } + var returnConfiguration = Object.defineProperties({}, skeleton); + this.logger.log("configuration parameters after validation:"); + for (var parameter in settings) { + if (settings.hasOwnProperty(parameter)) { + this.logger.log("· " + parameter + ": " + JSON.stringify(settings[parameter])); + } + } + return returnConfiguration; + }; + /** + * Configuration checker. + * @return {Boolean} + */ + Transport.prototype.getConfigurationCheck = function () { + return { + mandatory: {}, + optional: { + // Note: this function used to call 'this.logger.error' but calling 'this' with anything here is invalid + wsServers: function (wsServers) { + /* Allow defining wsServers parameter as: + * String: "host" + * Array of Strings: ["host1", "host2"] + * Array of Objects: [{wsUri:"host1", weight:1}, {wsUri:"host2", weight:0}] + * Array of Objects and Strings: [{wsUri:"host1"}, "host2"] + */ + if (typeof wsServers === "string") { + wsServers = [{ wsUri: wsServers }]; + } + else if (wsServers instanceof Array) { + for (var idx = 0; idx < wsServers.length; idx++) { + if (typeof wsServers[idx] === "string") { + wsServers[idx] = { wsUri: wsServers[idx] }; + } + } + } + else { + return; + } + if (wsServers.length === 0) { + return false; + } + for (var _i = 0, wsServers_1 = wsServers; _i < wsServers_1.length; _i++) { + var wsServer = wsServers_1[_i]; + if (!wsServer.wsUri) { + return; + } + if (wsServer.weight && !Number(wsServer.weight)) { + return; + } + var url = core_1.Grammar.parse(wsServer.wsUri, "absoluteURI"); + if (url === -1) { + return; + } + else if (["wss", "ws", "udp"].indexOf(url.scheme) < 0) { + return; + } + else { + wsServer.sipUri = ""; + if (!wsServer.weight) { + wsServer.weight = 0; + } + wsServer.isError = false; + wsServer.scheme = url.scheme.toUpperCase(); + } + } + return wsServers; + }, + keepAliveInterval: function (keepAliveInterval) { + if (Utils_1.Utils.isDecimal(keepAliveInterval)) { + var value = Number(keepAliveInterval); + if (value > 0) { + return value; + } + } + }, + keepAliveDebounce: function (keepAliveDebounce) { + if (Utils_1.Utils.isDecimal(keepAliveDebounce)) { + var value = Number(keepAliveDebounce); + if (value > 0) { + return value; + } + } + }, + traceSip: function (traceSip) { + if (typeof traceSip === "boolean") { + return traceSip; + } + }, + connectionTimeout: function (connectionTimeout) { + if (Utils_1.Utils.isDecimal(connectionTimeout)) { + var value = Number(connectionTimeout); + if (value > 0) { + return value; + } + } + }, + maxReconnectionAttempts: function (maxReconnectionAttempts) { + if (Utils_1.Utils.isDecimal(maxReconnectionAttempts)) { + var value = Number(maxReconnectionAttempts); + if (value >= 0) { + return value; + } + } + }, + reconnectionTimeout: function (reconnectionTimeout) { + if (Utils_1.Utils.isDecimal(reconnectionTimeout)) { + var value = Number(reconnectionTimeout); + if (value > 0) { + return value; + } + } + } + } + }; + }; + Transport.C = TransportStatus; + return Transport; +}(core_1.Transport)); +exports.Transport = Transport; diff --git a/lib/Web/index.d.ts b/lib/Web/index.d.ts new file mode 100644 index 000000000..ffd3a7a3b --- /dev/null +++ b/lib/Web/index.d.ts @@ -0,0 +1,5 @@ +import * as Modifiers from "./Modifiers"; +export { Modifiers }; +export { Simple } from "./Simple"; +export { SessionDescriptionHandler } from "./SessionDescriptionHandler"; +export { Transport } from "./Transport"; diff --git a/lib/Web/index.js b/lib/Web/index.js new file mode 100644 index 000000000..0892aa284 --- /dev/null +++ b/lib/Web/index.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var Modifiers = tslib_1.__importStar(require("./Modifiers")); +exports.Modifiers = Modifiers; +var Simple_1 = require("./Simple"); +exports.Simple = Simple_1.Simple; +var SessionDescriptionHandler_1 = require("./SessionDescriptionHandler"); +exports.SessionDescriptionHandler = SessionDescriptionHandler_1.SessionDescriptionHandler; +var Transport_1 = require("./Transport"); +exports.Transport = Transport_1.Transport; diff --git a/lib/core/dialogs/dialog-state.d.ts b/lib/core/dialogs/dialog-state.d.ts new file mode 100644 index 000000000..a54800e72 --- /dev/null +++ b/lib/core/dialogs/dialog-state.d.ts @@ -0,0 +1,31 @@ +import { URI } from "../messages/uri"; +/** + * A dialog contains certain pieces of state needed for further message + * transmissions within the dialog. This state consists of the dialog + * ID, a local sequence number (used to order requests from the UA to + * its peer), a remote sequence number (used to order requests from its + * peer to the UA), a local URI, a remote URI, remote target, a boolean + * flag called "secure", and a route set, which is an ordered list of + * URIs. The route set is the list of servers that need to be traversed + * to send a request to the peer. A dialog can also be in the "early" + * state, which occurs when it is created with a provisional response, + * and then transition to the "confirmed" state when a 2xx final + * response arrives. For other responses, or if no response arrives at + * all on that dialog, the early dialog terminates. + * + * https://tools.ietf.org/html/rfc3261#section-12 + */ +export interface DialogState { + id: string; + early: boolean; + callId: string; + localTag: string; + remoteTag: string; + localSequenceNumber: number | undefined; + remoteSequenceNumber: number | undefined; + localURI: URI; + remoteURI: URI; + remoteTarget: URI; + routeSet: Array; + secure: boolean; +} diff --git a/lib/core/dialogs/dialog-state.js b/lib/core/dialogs/dialog-state.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/dialogs/dialog-state.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/dialogs/dialog.d.ts b/lib/core/dialogs/dialog.d.ts new file mode 100644 index 000000000..055913354 --- /dev/null +++ b/lib/core/dialogs/dialog.d.ts @@ -0,0 +1,151 @@ +import { Body, IncomingRequestMessage, IncomingResponseMessage, OutgoingRequestMessage, URI } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { DialogState } from "./dialog-state"; +/** + * A key concept for a user agent is that of a dialog. A dialog + * represents a peer-to-peer SIP relationship between two user agents + * that persists for some time. The dialog facilitates sequencing of + * messages between the user agents and proper routing of requests + * between both of them. The dialog represents a context in which to + * interpret SIP messages. + * https://tools.ietf.org/html/rfc3261#section-12 + */ +export declare class Dialog { + protected core: UserAgentCore; + protected dialogState: DialogState; + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + static initialDialogStateForUserAgentClient(outgoingRequestMessage: OutgoingRequestMessage, incomingResponseMessage: IncomingResponseMessage): DialogState; + /** + * The UAS then constructs the state of the dialog. This state MUST be + * maintained for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.1 + * @param incomingRequestMessage Incoming request message creating dialog. + * @param toTag Tag in the To field in the response to the incoming request. + */ + static initialDialogStateForUserAgentServer(incomingRequestMessage: IncomingRequestMessage, toTag: string, early?: boolean): DialogState; + /** + * Dialog constructor. + * @param core User agent core. + * @param dialogState Initial dialog state. + */ + protected constructor(core: UserAgentCore, dialogState: DialogState); + /** Destructor. */ + dispose(): void; + /** + * A dialog is identified at each UA with a dialog ID, which consists of + * a Call-ID value, a local tag and a remote tag. The dialog ID at each + * UA involved in the dialog is not the same. Specifically, the local + * tag at one UA is identical to the remote tag at the peer UA. The + * tags are opaque tokens that facilitate the generation of unique + * dialog IDs. + * https://tools.ietf.org/html/rfc3261#section-12 + */ + readonly id: string; + /** + * A dialog can also be in the "early" state, which occurs when it is + * created with a provisional response, and then it transition to the + * "confirmed" state when a 2xx final response received or is sent. + * + * Note: RFC 3261 is concise on when a dialog is "confirmed", but it + * can be a point of confusion if an INVITE dialog is "confirmed" after + * a 2xx is sent or after receiving the ACK for the 2xx response. + * With careful reading it can be inferred a dialog is always is + * "confirmed" when the 2xx is sent (regardless of type of dialog). + * However a INVITE dialog does have additional considerations + * when it is confirmed but an ACK has not yet been received (in + * particular with regard to a callee sending BYE requests). + */ + readonly early: boolean; + /** Call identifier component of the dialog id. */ + readonly callId: string; + /** Local tag component of the dialog id. */ + readonly localTag: string; + /** Remote tag component of the dialog id. */ + readonly remoteTag: string; + /** Local sequence number (used to order requests from the UA to its peer). */ + readonly localSequenceNumber: number | undefined; + /** Remote sequence number (used to order requests from its peer to the UA). */ + readonly remoteSequenceNumber: number | undefined; + /** Local URI. */ + readonly localURI: URI; + /** Remote URI. */ + readonly remoteURI: URI; + /** Remote target. */ + readonly remoteTarget: URI; + /** + * Route set, which is an ordered list of URIs. The route set is the + * list of servers that need to be traversed to send a request to the peer. + */ + readonly routeSet: Array; + /** + * If the request was sent over TLS, and the Request-URI contained + * a SIPS URI, the "secure" flag is set to true. *NOT IMPLEMENTED* + */ + readonly secure: boolean; + /** The user agent core servicing this dialog. */ + readonly userAgentCore: UserAgentCore; + /** Confirm the dialog. Only matters if dialog is currently early. */ + confirm(): void; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * + * Note that some requests, such as INVITEs, affect several pieces of + * state. + * + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + receiveRequest(message: IncomingRequestMessage): void; + /** + * If the dialog identifier in the 2xx response matches the dialog + * identifier of an existing dialog, the dialog MUST be transitioned to + * the "confirmed" state, and the route set for the dialog MUST be + * recomputed based on the 2xx response using the procedures of Section + * 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + * constructed using the procedures of Section 12.1.2. + * + * Note that the only piece of state that is recomputed is the route + * set. Other pieces of state such as the highest sequence numbers + * (remote and local) sent within the dialog are not recomputed. The + * route set only is recomputed for backwards compatibility. RFC + * 2543 did not mandate mirroring of the Record-Route header field in + * a 1xx, only 2xx. However, we cannot update the entire state of + * the dialog, since mid-dialog requests may have been sent within + * the early dialog, modifying the sequence numbers, for example. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + */ + recomputeRouteSet(message: IncomingResponseMessage): void; + /** + * A request within a dialog is constructed by using many of the + * components of the state stored as part of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + * @param method Outgoing request method. + */ + createOutgoingRequestMessage(method: string, options?: { + cseq?: number; + extraHeaders?: Array; + body?: Body; + }): OutgoingRequestMessage; + /** + * If the remote sequence number was not empty, but the sequence number + * of the request is lower than the remote sequence number, the request + * is out of order and MUST be rejected with a 500 (Server Internal + * Error) response. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param request Incoming request to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise a 500 Server Internal Error was stateless sent and request processing must stop. + */ + protected sequenceGuard(message: IncomingRequestMessage): boolean; +} diff --git a/lib/core/dialogs/dialog.js b/lib/core/dialogs/dialog.js new file mode 100644 index 000000000..dee94a45b --- /dev/null +++ b/lib/core/dialogs/dialog.js @@ -0,0 +1,559 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = require("../messages"); +/** + * A key concept for a user agent is that of a dialog. A dialog + * represents a peer-to-peer SIP relationship between two user agents + * that persists for some time. The dialog facilitates sequencing of + * messages between the user agents and proper routing of requests + * between both of them. The dialog represents a context in which to + * interpret SIP messages. + * https://tools.ietf.org/html/rfc3261#section-12 + */ +var Dialog = /** @class */ (function () { + /** + * Dialog constructor. + * @param core User agent core. + * @param dialogState Initial dialog state. + */ + function Dialog(core, dialogState) { + this.core = core; + this.dialogState = dialogState; + this.core.dialogs.set(this.id, this); + } + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + Dialog.initialDialogStateForUserAgentClient = function (outgoingRequestMessage, incomingResponseMessage) { + // If the request was sent over TLS, and the Request-URI contained a + // SIPS URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the response, taken in reverse order and preserving + // all URI parameters. If no Record-Route header field is present in + // the response, the route set MUST be set to the empty set. This route + // set, even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the response. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var routeSet = incomingResponseMessage.getHeaders("record-route").reverse(); + var contact = incomingResponseMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The local sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The remote sequence + // number MUST be empty (it is established when the remote UA sends a + // request within the dialog). The call identifier component of the + // dialog ID MUST be set to the value of the Call-ID in the request. + // The local tag component of the dialog ID MUST be set to the tag in + // the From field in the request, and the remote tag component of the + // dialog ID MUST be set to the tag in the To field of the response. A + // UAC MUST be prepared to receive a response without a tag in the To + // field, in which case the tag is considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate To tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var localSequenceNumber = outgoingRequestMessage.cseq; + var remoteSequenceNumber = undefined; + var callId = outgoingRequestMessage.callId; + var localTag = outgoingRequestMessage.fromTag; + var remoteTag = incomingResponseMessage.toTag; + if (!callId) { // TODO: Review to make sure this will never happen + throw new Error("Call id undefined."); + } + if (!localTag) { // TODO: Review to make sure this will never happen + throw new Error("From tag undefined."); + } + if (!remoteTag) { // TODO: Review to make sure this will never happen + throw new Error("To tag undefined."); // FIXME: No backwards compatibility with RFC 2543 + } + // The remote URI MUST be set to the URI in the To field, and the local + // URI MUST be set to the URI in the From field. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + if (!outgoingRequestMessage.from) { // TODO: Review to make sure this will never happen + throw new Error("From undefined."); + } + if (!outgoingRequestMessage.to) { // TODO: Review to make sure this will never happen + throw new Error("To undefined."); + } + var localURI = outgoingRequestMessage.from.uri; + var remoteURI = outgoingRequestMessage.to.uri; + // A dialog can also be in the "early" state, which occurs when it is + // created with a provisional response, and then transition to the + // "confirmed" state when a 2xx final response arrives. + // https://tools.ietf.org/html/rfc3261#section-12 + if (!incomingResponseMessage.statusCode) { + throw new Error("Incoming response status code undefined."); + } + var early = incomingResponseMessage.statusCode < 200 ? true : false; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + /** + * The UAS then constructs the state of the dialog. This state MUST be + * maintained for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.1 + * @param incomingRequestMessage Incoming request message creating dialog. + * @param toTag Tag in the To field in the response to the incoming request. + */ + Dialog.initialDialogStateForUserAgentServer = function (incomingRequestMessage, toTag, early) { + if (early === void 0) { early = false; } + // If the request arrived over TLS, and the Request-URI contained a SIPS + // URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the request, taken in order and preserving all URI + // parameters. If no Record-Route header field is present in the + // request, the route set MUST be set to the empty set. This route set, + // even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the request. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var routeSet = incomingRequestMessage.getHeaders("record-route"); + var contact = incomingRequestMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The remote sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The local sequence + // number MUST be empty. The call identifier component of the dialog ID + // MUST be set to the value of the Call-ID in the request. The local + // tag component of the dialog ID MUST be set to the tag in the To field + // in the response to the request (which always includes a tag), and the + // remote tag component of the dialog ID MUST be set to the tag from the + // From field in the request. A UAS MUST be prepared to receive a + // request without a tag in the From field, in which case the tag is + // considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate From tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var remoteSequenceNumber = incomingRequestMessage.cseq; + var localSequenceNumber = undefined; + var callId = incomingRequestMessage.callId; + var localTag = toTag; + var remoteTag = incomingRequestMessage.fromTag; + // The remote URI MUST be set to the URI in the From field, and the + // local URI MUST be set to the URI in the To field. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var remoteURI = incomingRequestMessage.from.uri; + var localURI = incomingRequestMessage.to.uri; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + /** Destructor. */ + Dialog.prototype.dispose = function () { + this.core.dialogs.delete(this.id); + }; + Object.defineProperty(Dialog.prototype, "id", { + /** + * A dialog is identified at each UA with a dialog ID, which consists of + * a Call-ID value, a local tag and a remote tag. The dialog ID at each + * UA involved in the dialog is not the same. Specifically, the local + * tag at one UA is identical to the remote tag at the peer UA. The + * tags are opaque tokens that facilitate the generation of unique + * dialog IDs. + * https://tools.ietf.org/html/rfc3261#section-12 + */ + get: function () { + return this.dialogState.id; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "early", { + /** + * A dialog can also be in the "early" state, which occurs when it is + * created with a provisional response, and then it transition to the + * "confirmed" state when a 2xx final response received or is sent. + * + * Note: RFC 3261 is concise on when a dialog is "confirmed", but it + * can be a point of confusion if an INVITE dialog is "confirmed" after + * a 2xx is sent or after receiving the ACK for the 2xx response. + * With careful reading it can be inferred a dialog is always is + * "confirmed" when the 2xx is sent (regardless of type of dialog). + * However a INVITE dialog does have additional considerations + * when it is confirmed but an ACK has not yet been received (in + * particular with regard to a callee sending BYE requests). + */ + get: function () { + return this.dialogState.early; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "callId", { + /** Call identifier component of the dialog id. */ + get: function () { + return this.dialogState.callId; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localTag", { + /** Local tag component of the dialog id. */ + get: function () { + return this.dialogState.localTag; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteTag", { + /** Remote tag component of the dialog id. */ + get: function () { + return this.dialogState.remoteTag; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localSequenceNumber", { + /** Local sequence number (used to order requests from the UA to its peer). */ + get: function () { + return this.dialogState.localSequenceNumber; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteSequenceNumber", { + /** Remote sequence number (used to order requests from its peer to the UA). */ + get: function () { + return this.dialogState.remoteSequenceNumber; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "localURI", { + /** Local URI. */ + get: function () { + return this.dialogState.localURI; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteURI", { + /** Remote URI. */ + get: function () { + return this.dialogState.remoteURI; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "remoteTarget", { + /** Remote target. */ + get: function () { + return this.dialogState.remoteTarget; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "routeSet", { + /** + * Route set, which is an ordered list of URIs. The route set is the + * list of servers that need to be traversed to send a request to the peer. + */ + get: function () { + return this.dialogState.routeSet; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "secure", { + /** + * If the request was sent over TLS, and the Request-URI contained + * a SIPS URI, the "secure" flag is set to true. *NOT IMPLEMENTED* + */ + get: function () { + return this.dialogState.secure; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Dialog.prototype, "userAgentCore", { + /** The user agent core servicing this dialog. */ + get: function () { + return this.core; + }, + enumerable: true, + configurable: true + }); + /** Confirm the dialog. Only matters if dialog is currently early. */ + Dialog.prototype.confirm = function () { + this.dialogState.early = false; + }; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * + * Note that some requests, such as INVITEs, affect several pieces of + * state. + * + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + Dialog.prototype.receiveRequest = function (message) { + // ACK guard. + // By convention, the handling of ACKs is the responsibility + // the particular dialog implementation. For example, see SessionDialog. + // Furthermore, ACKs have same sequence number as the associated INVITE. + if (message.method === messages_1.C.ACK) { + return; + } + // If the remote sequence number was not empty, but the sequence number + // of the request is lower than the remote sequence number, the request + // is out of order and MUST be rejected with a 500 (Server Internal + // Error) response. If the remote sequence number was not empty, and + // the sequence number of the request is greater than the remote + // sequence number, the request is in order. It is possible for the + // CSeq sequence number to be higher than the remote sequence number by + // more than one. This is not an error condition, and a UAS SHOULD be + // prepared to receive and process requests with CSeq values more than + // one higher than the previous received request. The UAS MUST then set + // the remote sequence number to the value of the sequence number in the + // CSeq header field value in the request. + // + // If a proxy challenges a request generated by the UAC, the UAC has + // to resubmit the request with credentials. The resubmitted request + // will have a new CSeq number. The UAS will never see the first + // request, and thus, it will notice a gap in the CSeq number space. + // Such a gap does not represent any error condition. + // + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (this.remoteSequenceNumber) { + if (message.cseq <= this.remoteSequenceNumber) { + throw new Error("Out of sequence in dialog request. Did you forget to call sequenceGuard()?"); + } + this.dialogState.remoteSequenceNumber = message.cseq; + } + // If the remote sequence number is empty, it MUST be set to the value + // of the sequence number in the CSeq header field value in the request. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.remoteSequenceNumber) { + this.dialogState.remoteSequenceNumber = message.cseq; + } + // When a UAS receives a target refresh request, it MUST replace the + // dialog's remote target URI with the URI from the Contact header field + // in that request, if present. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + // Note: "target refresh request" processing delegated to sub-class. + }; + /** + * If the dialog identifier in the 2xx response matches the dialog + * identifier of an existing dialog, the dialog MUST be transitioned to + * the "confirmed" state, and the route set for the dialog MUST be + * recomputed based on the 2xx response using the procedures of Section + * 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + * constructed using the procedures of Section 12.1.2. + * + * Note that the only piece of state that is recomputed is the route + * set. Other pieces of state such as the highest sequence numbers + * (remote and local) sent within the dialog are not recomputed. The + * route set only is recomputed for backwards compatibility. RFC + * 2543 did not mandate mirroring of the Record-Route header field in + * a 1xx, only 2xx. However, we cannot update the entire state of + * the dialog, since mid-dialog requests may have been sent within + * the early dialog, modifying the sequence numbers, for example. + * + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + */ + Dialog.prototype.recomputeRouteSet = function (message) { + this.dialogState.routeSet = message.getHeaders("record-route").reverse(); + }; + /** + * A request within a dialog is constructed by using many of the + * components of the state stored as part of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + * @param method Outgoing request method. + */ + Dialog.prototype.createOutgoingRequestMessage = function (method, options) { + // The URI in the To field of the request MUST be set to the remote URI + // from the dialog state. The tag in the To header field of the request + // MUST be set to the remote tag of the dialog ID. The From URI of the + // request MUST be set to the local URI from the dialog state. The tag + // in the From header field of the request MUST be set to the local tag + // of the dialog ID. If the value of the remote or local tags is null, + // the tag parameter MUST be omitted from the To or From header fields, + // respectively. + // + // Usage of the URI from the To and From fields in the original + // request within subsequent requests is done for backwards + // compatibility with RFC 2543, which used the URI for dialog + // identification. In this specification, only the tags are used for + // dialog identification. It is expected that mandatory reflection + // of the original To and From URI in mid-dialog requests will be + // deprecated in a subsequent revision of this specification. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + var toUri = this.remoteURI; + var toTag = this.remoteTag; + var fromUri = this.localURI; + var fromTag = this.localTag; + // The Call-ID of the request MUST be set to the Call-ID of the dialog. + // Requests within a dialog MUST contain strictly monotonically + // increasing and contiguous CSeq sequence numbers (increasing-by-one) + // in each direction (excepting ACK and CANCEL of course, whose numbers + // equal the requests being acknowledged or cancelled). Therefore, if + // the local sequence number is not empty, the value of the local + // sequence number MUST be incremented by one, and this value MUST be + // placed into the CSeq header field. If the local sequence number is + // empty, an initial value MUST be chosen using the guidelines of + // Section 8.1.1.5. The method field in the CSeq header field value + // MUST match the method of the request. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + var callId = this.callId; + var cseq; + if (options && options.cseq) { + cseq = options.cseq; + } + else if (!this.dialogState.localSequenceNumber) { + cseq = this.dialogState.localSequenceNumber = 1; // https://tools.ietf.org/html/rfc3261#section-8.1.1.5 + } + else { + cseq = this.dialogState.localSequenceNumber += 1; + } + // The UAC uses the remote target and route set to build the Request-URI + // and Route header field of the request. + // + // If the route set is empty, the UAC MUST place the remote target URI + // into the Request-URI. The UAC MUST NOT add a Route header field to + // the request. + // + // If the route set is not empty, and the first URI in the route set + // contains the lr parameter (see Section 19.1.1), the UAC MUST place + // the remote target URI into the Request-URI and MUST include a Route + // header field containing the route set values in order, including all + // parameters. + // + // If the route set is not empty, and its first URI does not contain the + // lr parameter, the UAC MUST place the first URI from the route set + // into the Request-URI, stripping any parameters that are not allowed + // in a Request-URI. The UAC MUST add a Route header field containing + // the remainder of the route set values in order, including all + // parameters. The UAC MUST then place the remote target URI into the + // Route header field as the last value. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + // The lr parameter, when present, indicates that the element + // responsible for this resource implements the routing mechanisms + // specified in this document. This parameter will be used in the + // URIs proxies place into Record-Route header field values, and + // may appear in the URIs in a pre-existing route set. + // + // This parameter is used to achieve backwards compatibility with + // systems implementing the strict-routing mechanisms of RFC 2543 + // and the rfc2543bis drafts up to bis-05. An element preparing + // to send a request based on a URI not containing this parameter + // can assume the receiving element implements strict-routing and + // reformat the message to preserve the information in the + // Request-URI. + // https://tools.ietf.org/html/rfc3261#section-19.1.1 + // NOTE: Not backwards compatible with RFC 2543 (no support for strict-routing). + var ruri = this.remoteTarget; + var routeSet = this.routeSet; + var extraHeaders = options && options.extraHeaders; + var body = options && options.body; + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + var message = this.userAgentCore.makeOutgoingRequestMessage(method, ruri, fromUri, toUri, { + callId: callId, + cseq: cseq, + fromTag: fromTag, + toTag: toTag, + routeSet: routeSet + }, extraHeaders, body); + return message; + }; + /** + * If the remote sequence number was not empty, but the sequence number + * of the request is lower than the remote sequence number, the request + * is out of order and MUST be rejected with a 500 (Server Internal + * Error) response. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param request Incoming request to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise a 500 Server Internal Error was stateless sent and request processing must stop. + */ + Dialog.prototype.sequenceGuard = function (message) { + // ACK guard. + // By convention, handling of unexpected ACKs is responsibility + // the particular dialog implementation. For example, see SessionDialog. + // Furthermore, we cannot reply to an "out of sequence" ACK. + if (message.method === messages_1.C.ACK) { + return true; + } + // Note: We are rejecting on "less than or equal to" the remote + // sequence number (excepting ACK whose numbers equal the requests + // being acknowledged or cancelled), which is the correct thing to + // do in our case. The only time a request with the same sequence number + // will show up here if is a) it is a very late retransmission of a + // request we already handled or b) it is a different request with the + // same sequence number which would be violation of the standard. + // Request retransmissions are absorbed by the transaction layer, + // so any request with a duplicate sequence number getting here + // would have to be a retransmission after the transaction terminated + // or a broken request (with unique via branch value). + // Requests within a dialog MUST contain strictly monotonically + // increasing and contiguous CSeq sequence numbers (increasing-by-one) + // in each direction (excepting ACK and CANCEL of course, whose numbers + // equal the requests being acknowledged or cancelled). Therefore, if + // the local sequence number is not empty, the value of the local + // sequence number MUST be incremented by one, and this value MUST be + // placed into the CSeq header field. + // https://tools.ietf.org/html/rfc3261#section-12.2.1.1 + if (this.remoteSequenceNumber && message.cseq <= this.remoteSequenceNumber) { + this.core.replyStateless(message, { statusCode: 500 }); + return false; + } + return true; + }; + return Dialog; +}()); +exports.Dialog = Dialog; diff --git a/lib/core/dialogs/index.d.ts b/lib/core/dialogs/index.d.ts new file mode 100644 index 000000000..7cacf07f2 --- /dev/null +++ b/lib/core/dialogs/index.d.ts @@ -0,0 +1,4 @@ +export * from "./dialog"; +export * from "./dialog-state"; +export * from "./session-dialog"; +export * from "./subscription-dialog"; diff --git a/lib/core/dialogs/index.js b/lib/core/dialogs/index.js new file mode 100644 index 000000000..2e8adb3a6 --- /dev/null +++ b/lib/core/dialogs/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./dialog"), exports); +tslib_1.__exportStar(require("./session-dialog"), exports); +tslib_1.__exportStar(require("./subscription-dialog"), exports); diff --git a/lib/core/dialogs/session-dialog.d.ts b/lib/core/dialogs/session-dialog.d.ts new file mode 100644 index 000000000..ed5071c43 --- /dev/null +++ b/lib/core/dialogs/session-dialog.d.ts @@ -0,0 +1,168 @@ +import { Body, IncomingRequestMessage, IncomingResponseMessage, OutgoingAckRequest, OutgoingByeRequest, OutgoingInfoRequest, OutgoingInviteRequest, OutgoingInviteRequestDelegate, OutgoingNotifyRequest, OutgoingPrackRequest, OutgoingReferRequest, OutgoingRequestDelegate, OutgoingRequestMessage, RequestOptions } from "../messages"; +import { Session, SessionDelegate, SessionState, SignalingState } from "../session"; +import { InviteClientTransaction, InviteServerTransaction } from "../transactions"; +import { UserAgentCore } from "../user-agent-core"; +import { ReInviteUserAgentClient } from "../user-agents/re-invite-user-agent-client"; +import { ReInviteUserAgentServer } from "../user-agents/re-invite-user-agent-server"; +import { Dialog } from "./dialog"; +import { DialogState } from "./dialog-state"; +export declare class SessionDialog extends Dialog implements Session { + private initialTransaction; + delegate: SessionDelegate | undefined; + reinviteUserAgentClient: ReInviteUserAgentClient | undefined; + reinviteUserAgentServer: ReInviteUserAgentServer | undefined; + /** The state of the offer/answer exchange. */ + private _signalingState; + /** The current offer. Undefined unless signaling state HaveLocalOffer, HaveRemoteOffer, of Stable. */ + private _offer; + /** The current answer. Undefined unless signaling state Stable. */ + private _answer; + /** True if waiting for an ACK to the initial transaction 2xx (UAS only). */ + private ackWait; + /** Retransmission timer for 2xx response which confirmed the dialog. */ + private invite2xxTimer; + /** The rseq of the last reliable response. */ + private rseq; + private logger; + constructor(initialTransaction: InviteClientTransaction | InviteServerTransaction, core: UserAgentCore, state: DialogState, delegate?: SessionDelegate); + dispose(): void; + readonly sessionState: SessionState; + /** The state of the offer/answer exchange. */ + readonly signalingState: SignalingState; + /** The current offer. Undefined unless signaling state HaveLocalOffer, HaveRemoteOffer, of Stable. */ + readonly offer: Body | undefined; + /** The current answer. Undefined unless signaling state Stable. */ + readonly answer: Body | undefined; + /** Confirm the dialog. Only matters if dialog is currently early. */ + confirm(): void; + /** Re-confirm the dialog. Only matters if handling re-INVITE request. */ + reConfirm(): void; + /** + * The UAC core MUST generate an ACK request for each 2xx received from + * the transaction layer. The header fields of the ACK are constructed + * in the same way as for any request sent within a dialog (see Section + * 12) with the exception of the CSeq and the header fields related to + * authentication. The sequence number of the CSeq header field MUST be + * the same as the INVITE being acknowledged, but the CSeq method MUST + * be ACK. The ACK MUST contain the same credentials as the INVITE. If + * the 2xx contains an offer (based on the rules above), the ACK MUST + * carry an answer in its body. If the offer in the 2xx response is not + * acceptable, the UAC core MUST generate a valid answer in the ACK and + * then send a BYE immediately. + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + * @param options ACK options bucket. + */ + ack(options?: RequestOptions): OutgoingAckRequest; + /** + * Terminating a Session + * + * This section describes the procedures for terminating a session + * established by SIP. The state of the session and the state of the + * dialog are very closely related. When a session is initiated with an + * INVITE, each 1xx or 2xx response from a distinct UAS creates a + * dialog, and if that response completes the offer/answer exchange, it + * also creates a session. As a result, each session is "associated" + * with a single dialog - the one which resulted in its creation. If an + * initial INVITE generates a non-2xx final response, that terminates + * all sessions (if any) and all dialogs (if any) that were created + * through responses to the request. By virtue of completing the + * transaction, a non-2xx final response also prevents further sessions + * from being created as a result of the INVITE. The BYE request is + * used to terminate a specific session or attempted session. In this + * case, the specific session is the one with the peer UA on the other + * side of the dialog. When a BYE is received on a dialog, any session + * associated with that dialog SHOULD terminate. A UA MUST NOT send a + * BYE outside of a dialog. The caller's UA MAY send a BYE for either + * confirmed or early dialogs, and the callee's UA MAY send a BYE on + * confirmed dialogs, but MUST NOT send a BYE on early dialogs. + * + * However, the callee's UA MUST NOT send a BYE on a confirmed dialog + * until it has received an ACK for its 2xx response or until the server + * transaction times out. If no SIP extensions have defined other + * application layer states associated with the dialog, the BYE also + * terminates the dialog. + * + * https://tools.ietf.org/html/rfc3261#section-15 + * FIXME: Make these proper Exceptions... + * @param options BYE options bucket. + * @throws {Error} If callee's UA attempts a BYE on an early dialog. + * @throws {Error} If callee's UA attempts a BYE on a confirmed dialog + * while it's waiting on the ACK for its 2xx response. + */ + bye(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingByeRequest; + /** + * An INFO request can be associated with an Info Package (see + * Section 5), or associated with a legacy INFO usage (see Section 2). + * + * The construction of the INFO request is the same as any other + * non-target refresh request within an existing invite dialog usage as + * described in Section 12.2 of RFC 3261. + * https://tools.ietf.org/html/rfc6086#section-4.2.1 + * @param options Options bucket. + */ + info(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingInfoRequest; + /** + * Modifying an Existing Session + * + * A successful INVITE request (see Section 13) establishes both a + * dialog between two user agents and a session using the offer-answer + * model. Section 12 explains how to modify an existing dialog using a + * target refresh request (for example, changing the remote target URI + * of the dialog). This section describes how to modify the actual + * session. This modification can involve changing addresses or ports, + * adding a media stream, deleting a media stream, and so on. This is + * accomplished by sending a new INVITE request within the same dialog + * that established the session. An INVITE request sent within an + * existing dialog is known as a re-INVITE. + * + * Note that a single re-INVITE can modify the dialog and the + * parameters of the session at the same time. + * + * Either the caller or callee can modify an existing session. + * https://tools.ietf.org/html/rfc3261#section-14 + * @param options Options bucket + */ + invite(delegate?: OutgoingInviteRequestDelegate, options?: RequestOptions): OutgoingInviteRequest; + /** + * The NOTIFY mechanism defined in [2] MUST be used to inform the agent + * sending the REFER of the status of the reference. + * https://tools.ietf.org/html/rfc3515#section-2.4.4 + * @param options Options bucket. + */ + notify(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingNotifyRequest; + /** + * Assuming the response is to be transmitted reliably, the UAC MUST + * create a new request with method PRACK. This request is sent within + * the dialog associated with the provisional response (indeed, the + * provisional response may have created the dialog). PRACK requests + * MAY contain bodies, which are interpreted according to their type and + * disposition. + * https://tools.ietf.org/html/rfc3262#section-4 + * @param options Options bucket. + */ + prack(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingPrackRequest; + /** + * REFER is a SIP request and is constructed as defined in [1]. A REFER + * request MUST contain exactly one Refer-To header field value. + * https://tools.ietf.org/html/rfc3515#section-2.4.1 + * @param options Options bucket. + */ + refer(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingReferRequest; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + receiveRequest(message: IncomingRequestMessage): void; + reliableSequenceGuard(message: IncomingResponseMessage): boolean; + /** + * Update the signaling state of the dialog. + * @param message The message to base the update off of. + */ + signalingStateTransition(message: IncomingRequestMessage | IncomingResponseMessage | OutgoingRequestMessage | Body): void; + private start2xxRetransmissionTimer; + private startReInvite2xxRetransmissionTimer; +} diff --git a/lib/core/dialogs/session-dialog.js b/lib/core/dialogs/session-dialog.js new file mode 100644 index 000000000..d37ca926c --- /dev/null +++ b/lib/core/dialogs/session-dialog.js @@ -0,0 +1,797 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var session_1 = require("../session"); +var timers_1 = require("../timers"); +var transactions_1 = require("../transactions"); +var bye_user_agent_client_1 = require("../user-agents/bye-user-agent-client"); +var bye_user_agent_server_1 = require("../user-agents/bye-user-agent-server"); +var info_user_agent_client_1 = require("../user-agents/info-user-agent-client"); +var info_user_agent_server_1 = require("../user-agents/info-user-agent-server"); +var notify_user_agent_client_1 = require("../user-agents/notify-user-agent-client"); +var notify_user_agent_server_1 = require("../user-agents/notify-user-agent-server"); +var prack_user_agent_client_1 = require("../user-agents/prack-user-agent-client"); +var prack_user_agent_server_1 = require("../user-agents/prack-user-agent-server"); +var re_invite_user_agent_client_1 = require("../user-agents/re-invite-user-agent-client"); +var re_invite_user_agent_server_1 = require("../user-agents/re-invite-user-agent-server"); +var refer_user_agent_client_1 = require("../user-agents/refer-user-agent-client"); +var refer_user_agent_server_1 = require("../user-agents/refer-user-agent-server"); +var dialog_1 = require("./dialog"); +var SessionDialog = /** @class */ (function (_super) { + tslib_1.__extends(SessionDialog, _super); + function SessionDialog(initialTransaction, core, state, delegate) { + var _this = _super.call(this, core, state) || this; + _this.initialTransaction = initialTransaction; + /** The state of the offer/answer exchange. */ + _this._signalingState = session_1.SignalingState.Initial; + /** True if waiting for an ACK to the initial transaction 2xx (UAS only). */ + _this.ackWait = false; + _this.delegate = delegate; + if (initialTransaction instanceof transactions_1.InviteServerTransaction) { + // If we're created by an invite server transaction, we're + // going to be waiting for an ACK if are to be confirmed. + _this.ackWait = true; + } + // If we're confirmed upon creation start the retransmitting whatever + // the 2xx final response was that confirmed us into existence. + if (!_this.early) { + _this.start2xxRetransmissionTimer(); + } + _this.signalingStateTransition(initialTransaction.request); + _this.logger = core.loggerFactory.getLogger("sip.invite-dialog"); + _this.logger.log("INVITE dialog " + _this.id + " constructed"); + return _this; + } + SessionDialog.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._signalingState = session_1.SignalingState.Closed; + this._offer = undefined; + this._answer = undefined; + if (this.invite2xxTimer) { + clearTimeout(this.invite2xxTimer); + this.invite2xxTimer = undefined; + } + // The UAS MUST still respond to any pending requests received for that + // dialog. It is RECOMMENDED that a 487 (Request Terminated) response + // be generated to those pending requests. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + // TODO: + // this.userAgentServers.forEach((uas) => uas.reply(487)); + this.logger.log("INVITE dialog " + this.id + " destroyed"); + }; + Object.defineProperty(SessionDialog.prototype, "sessionState", { + // FIXME: Need real state machine + get: function () { + if (this.early) { + return session_1.SessionState.Early; + } + else if (this.ackWait) { + return session_1.SessionState.AckWait; + } + else if (this._signalingState === session_1.SignalingState.Closed) { + return session_1.SessionState.Terminated; + } + else { + return session_1.SessionState.Confirmed; + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "signalingState", { + /** The state of the offer/answer exchange. */ + get: function () { + return this._signalingState; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "offer", { + /** The current offer. Undefined unless signaling state HaveLocalOffer, HaveRemoteOffer, of Stable. */ + get: function () { + return this._offer; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionDialog.prototype, "answer", { + /** The current answer. Undefined unless signaling state Stable. */ + get: function () { + return this._answer; + }, + enumerable: true, + configurable: true + }); + /** Confirm the dialog. Only matters if dialog is currently early. */ + SessionDialog.prototype.confirm = function () { + // When we're confirmed start the retransmitting whatever + // the 2xx final response that may have confirmed us. + if (this.early) { + this.start2xxRetransmissionTimer(); + } + _super.prototype.confirm.call(this); + }; + /** Re-confirm the dialog. Only matters if handling re-INVITE request. */ + SessionDialog.prototype.reConfirm = function () { + // When we're confirmed start the retransmitting whatever + // the 2xx final response that may have confirmed us. + if (this.reinviteUserAgentServer) { + this.startReInvite2xxRetransmissionTimer(); + } + }; + /** + * The UAC core MUST generate an ACK request for each 2xx received from + * the transaction layer. The header fields of the ACK are constructed + * in the same way as for any request sent within a dialog (see Section + * 12) with the exception of the CSeq and the header fields related to + * authentication. The sequence number of the CSeq header field MUST be + * the same as the INVITE being acknowledged, but the CSeq method MUST + * be ACK. The ACK MUST contain the same credentials as the INVITE. If + * the 2xx contains an offer (based on the rules above), the ACK MUST + * carry an answer in its body. If the offer in the 2xx response is not + * acceptable, the UAC core MUST generate a valid answer in the ACK and + * then send a BYE immediately. + * https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + * @param options ACK options bucket. + */ + SessionDialog.prototype.ack = function (options) { + if (options === void 0) { options = {}; } + this.logger.log("INVITE dialog " + this.id + " sending ACK request"); + var transaction; + if (this.reinviteUserAgentClient) { + // We're sending ACK for a re-INVITE + if (!(this.reinviteUserAgentClient.transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + transaction = this.reinviteUserAgentClient.transaction; + this.reinviteUserAgentClient = undefined; + } + else { + // We're sending ACK for the initial INVITE + if (!(this.initialTransaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Initial transaction not instance of InviteClientTransaction."); + } + transaction = this.initialTransaction; + } + options.cseq = transaction.request.cseq; // ACK cseq is INVITE cseq + var message = this.createOutgoingRequestMessage(messages_1.C.ACK, options); + transaction.ackResponse(message); // See InviteClientTransaction for details. + this.signalingStateTransition(message); + return { message: message }; + }; + /** + * Terminating a Session + * + * This section describes the procedures for terminating a session + * established by SIP. The state of the session and the state of the + * dialog are very closely related. When a session is initiated with an + * INVITE, each 1xx or 2xx response from a distinct UAS creates a + * dialog, and if that response completes the offer/answer exchange, it + * also creates a session. As a result, each session is "associated" + * with a single dialog - the one which resulted in its creation. If an + * initial INVITE generates a non-2xx final response, that terminates + * all sessions (if any) and all dialogs (if any) that were created + * through responses to the request. By virtue of completing the + * transaction, a non-2xx final response also prevents further sessions + * from being created as a result of the INVITE. The BYE request is + * used to terminate a specific session or attempted session. In this + * case, the specific session is the one with the peer UA on the other + * side of the dialog. When a BYE is received on a dialog, any session + * associated with that dialog SHOULD terminate. A UA MUST NOT send a + * BYE outside of a dialog. The caller's UA MAY send a BYE for either + * confirmed or early dialogs, and the callee's UA MAY send a BYE on + * confirmed dialogs, but MUST NOT send a BYE on early dialogs. + * + * However, the callee's UA MUST NOT send a BYE on a confirmed dialog + * until it has received an ACK for its 2xx response or until the server + * transaction times out. If no SIP extensions have defined other + * application layer states associated with the dialog, the BYE also + * terminates the dialog. + * + * https://tools.ietf.org/html/rfc3261#section-15 + * FIXME: Make these proper Exceptions... + * @param options BYE options bucket. + * @throws {Error} If callee's UA attempts a BYE on an early dialog. + * @throws {Error} If callee's UA attempts a BYE on a confirmed dialog + * while it's waiting on the ACK for its 2xx response. + */ + SessionDialog.prototype.bye = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending BYE request"); + // The caller's UA MAY send a BYE for either + // confirmed or early dialogs, and the callee's UA MAY send a BYE on + // confirmed dialogs, but MUST NOT send a BYE on early dialogs. + // + // However, the callee's UA MUST NOT send a BYE on a confirmed dialog + // until it has received an ACK for its 2xx response or until the server + // transaction times out. + // https://tools.ietf.org/html/rfc3261#section-15 + if (this.initialTransaction instanceof transactions_1.InviteServerTransaction) { + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("UAS MUST NOT send a BYE on early dialogs."); + } + if (this.ackWait && this.initialTransaction.state !== transactions_1.TransactionState.Terminated) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("UAS MUST NOT send a BYE on a confirmed dialog " + + "until it has received an ACK for its 2xx response " + + "or until the server transaction times out."); + } + } + // A BYE request is constructed as would any other request within a + // dialog, as described in Section 12. + // + // Once the BYE is constructed, the UAC core creates a new non-INVITE + // client transaction, and passes it the BYE request. The UAC MUST + // consider the session terminated (and therefore stop sending or + // listening for media) as soon as the BYE request is passed to the + // client transaction. If the response for the BYE is a 481 + // (Call/Transaction Does Not Exist) or a 408 (Request Timeout) or no + // response at all is received for the BYE (that is, a timeout is + // returned by the client transaction), the UAC MUST consider the + // session and the dialog terminated. + // https://tools.ietf.org/html/rfc3261#section-15.1.1 + return new bye_user_agent_client_1.ByeUserAgentClient(this, delegate, options); + }; + /** + * An INFO request can be associated with an Info Package (see + * Section 5), or associated with a legacy INFO usage (see Section 2). + * + * The construction of the INFO request is the same as any other + * non-target refresh request within an existing invite dialog usage as + * described in Section 12.2 of RFC 3261. + * https://tools.ietf.org/html/rfc6086#section-4.2.1 + * @param options Options bucket. + */ + SessionDialog.prototype.info = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending INFO request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + return new info_user_agent_client_1.InfoUserAgentClient(this, delegate, options); + }; + /** + * Modifying an Existing Session + * + * A successful INVITE request (see Section 13) establishes both a + * dialog between two user agents and a session using the offer-answer + * model. Section 12 explains how to modify an existing dialog using a + * target refresh request (for example, changing the remote target URI + * of the dialog). This section describes how to modify the actual + * session. This modification can involve changing addresses or ports, + * adding a media stream, deleting a media stream, and so on. This is + * accomplished by sending a new INVITE request within the same dialog + * that established the session. An INVITE request sent within an + * existing dialog is known as a re-INVITE. + * + * Note that a single re-INVITE can modify the dialog and the + * parameters of the session at the same time. + * + * Either the caller or callee can modify an existing session. + * https://tools.ietf.org/html/rfc3261#section-14 + * @param options Options bucket + */ + SessionDialog.prototype.invite = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending INVITE request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + // Note that a UAC MUST NOT initiate a new INVITE transaction within a + // dialog while another INVITE transaction is in progress in either + // direction. + // + // 1. If there is an ongoing INVITE client transaction, the TU MUST + // wait until the transaction reaches the completed or terminated + // state before initiating the new INVITE. + // + // 2. If there is an ongoing INVITE server transaction, the TU MUST + // wait until the transaction reaches the confirmed or terminated + // state before initiating the new INVITE. + // + // However, a UA MAY initiate a regular transaction while an INVITE + // transaction is in progress. A UA MAY also initiate an INVITE + // transaction while a regular transaction is in progress. + // https://tools.ietf.org/html/rfc3261#section-14.1 + if (this.reinviteUserAgentClient) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("There is an ongoing re-INVITE client transaction."); + } + if (this.reinviteUserAgentServer) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("There is an ongoing re-INVITE server transaction."); + } + return new re_invite_user_agent_client_1.ReInviteUserAgentClient(this, delegate, options); + }; + /** + * The NOTIFY mechanism defined in [2] MUST be used to inform the agent + * sending the REFER of the status of the reference. + * https://tools.ietf.org/html/rfc3515#section-2.4.4 + * @param options Options bucket. + */ + SessionDialog.prototype.notify = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending NOTIFY request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + return new notify_user_agent_client_1.NotifyUserAgentClient(this, delegate, options); + }; + /** + * Assuming the response is to be transmitted reliably, the UAC MUST + * create a new request with method PRACK. This request is sent within + * the dialog associated with the provisional response (indeed, the + * provisional response may have created the dialog). PRACK requests + * MAY contain bodies, which are interpreted according to their type and + * disposition. + * https://tools.ietf.org/html/rfc3262#section-4 + * @param options Options bucket. + */ + SessionDialog.prototype.prack = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending PRACK request"); + return new prack_user_agent_client_1.PrackUserAgentClient(this, delegate, options); + }; + /** + * REFER is a SIP request and is constructed as defined in [1]. A REFER + * request MUST contain exactly one Refer-To header field value. + * https://tools.ietf.org/html/rfc3515#section-2.4.1 + * @param options Options bucket. + */ + SessionDialog.prototype.refer = function (delegate, options) { + this.logger.log("INVITE dialog " + this.id + " sending REFER request"); + if (this.early) { + // FIXME: TODO: This should throw a proper exception. + throw new Error("Dialog not confirmed."); + } + // FIXME: TODO: Validate Refer-To header field value. + return new refer_user_agent_client_1.ReferUserAgentClient(this, delegate, options); + }; + /** + * Requests sent within a dialog, as any other requests, are atomic. If + * a particular request is accepted by the UAS, all the state changes + * associated with it are performed. If the request is rejected, none + * of the state changes are performed. + * https://tools.ietf.org/html/rfc3261#section-12.2.2 + * @param message Incoming request message within this dialog. + */ + SessionDialog.prototype.receiveRequest = function (message) { + this.logger.log("INVITE dialog " + this.id + " received " + message.method + " request"); + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + if (message.method === messages_1.C.ACK) { + // If ackWait is true, then this is the ACK to the initial INVITE, + // otherwise this is an ACK to an in dialog INVITE. In either case, + // guard to make sure the sequence number of the ACK matches the INVITE. + if (this.ackWait) { + if (this.initialTransaction instanceof transactions_1.InviteClientTransaction) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + if (this.initialTransaction.request.cseq !== message.cseq) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + this.ackWait = false; + } + else { + if (!this.reinviteUserAgentServer) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + if (this.reinviteUserAgentServer.transaction.request.cseq !== message.cseq) { + this.logger.warn("INVITE dialog " + this.id + " received unexpected " + message.method + " request, dropping."); + return; + } + this.reinviteUserAgentServer = undefined; + } + this.signalingStateTransition(message); + if (this.delegate && this.delegate.onAck) { + this.delegate.onAck({ message: message }); + } + return; + } + // Request within a dialog out of sequence guard. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.sequenceGuard(message)) { + this.logger.log("INVITE dialog " + this.id + " rejected out of order " + message.method + " request."); + return; + } + if (message.method === messages_1.C.INVITE) { + // A UAS that receives a second INVITE before it sends the final + // response to a first INVITE with a lower CSeq sequence number on the + // same dialog MUST return a 500 (Server Internal Error) response to the + // second INVITE and MUST include a Retry-After header field with a + // randomly chosen value of between 0 and 10 seconds. + // https://tools.ietf.org/html/rfc3261#section-14.2 + if (this.reinviteUserAgentServer) { + // https://tools.ietf.org/html/rfc3261#section-20.33 + var retryAfter = Math.floor((Math.random() * 10)) + 1; + var extraHeaders = ["Retry-After: " + retryAfter]; + this.core.replyStateless(message, { statusCode: 500, extraHeaders: extraHeaders }); + return; + } + // A UAS that receives an INVITE on a dialog while an INVITE it had sent + // on that dialog is in progress MUST return a 491 (Request Pending) + // response to the received INVITE. + // https://tools.ietf.org/html/rfc3261#section-14.2 + if (this.reinviteUserAgentClient) { + this.core.replyStateless(message, { statusCode: 491 }); + return; + } + } + // Request within a dialog common processing. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + _super.prototype.receiveRequest.call(this, message); + // Requests within a dialog MAY contain Record-Route and Contact header + // fields. However, these requests do not cause the dialog's route set + // to be modified, although they may modify the remote target URI. + // Specifically, requests that are not target refresh requests do not + // modify the dialog's remote target URI, and requests that are target + // refresh requests do. For dialogs that have been established with an + // INVITE, the only target refresh request defined is re-INVITE (see + // Section 14). Other extensions may define different target refresh + // requests for dialogs established in other ways. + // + // Note that an ACK is NOT a target refresh request. + // + // Target refresh requests only update the dialog's remote target URI, + // and not the route set formed from the Record-Route. Updating the + // latter would introduce severe backwards compatibility problems with + // RFC 2543-compliant systems. + // https://tools.ietf.org/html/rfc3261#section-15 + if (message.method === messages_1.C.INVITE) { + // FIXME: parser needs to be typed... + var contact = message.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + this.dialogState.remoteTarget = contact.uri; + } + // Switch on method and then delegate. + switch (message.method) { + case messages_1.C.BYE: + // A UAS core receiving a BYE request for an existing dialog MUST follow + // the procedures of Section 12.2.2 to process the request. Once done, + // the UAS SHOULD terminate the session (and therefore stop sending and + // listening for media). The only case where it can elect not to are + // multicast sessions, where participation is possible even if the other + // participant in the dialog has terminated its involvement in the + // session. Whether or not it ends its participation on the session, + // the UAS core MUST generate a 2xx response to the BYE, and MUST pass + // that to the server transaction for transmission. + // + // The UAS MUST still respond to any pending requests received for that + // dialog. It is RECOMMENDED that a 487 (Request Terminated) response + // be generated to those pending requests. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + { + var uas = new bye_user_agent_server_1.ByeUserAgentServer(this, message); + this.delegate && this.delegate.onBye ? + this.delegate.onBye(uas) : + uas.accept(); + this.dispose(); + } + break; + case messages_1.C.INFO: + // If a UA receives an INFO request associated with an Info Package that + // the UA has not indicated willingness to receive, the UA MUST send a + // 469 (Bad Info Package) response (see Section 11.6), which contains a + // Recv-Info header field with Info Packages for which the UA is willing + // to receive INFO requests. + { + var uas = new info_user_agent_server_1.InfoUserAgentServer(this, message); + this.delegate && this.delegate.onInfo ? + this.delegate.onInfo(uas) : + uas.reject({ + statusCode: 469, + extraHeaders: ["Recv-Info :"] + }); + } + break; + case messages_1.C.INVITE: + // If the new session description is not acceptable, the UAS can reject + // it by returning a 488 (Not Acceptable Here) response for the re- + // INVITE. This response SHOULD include a Warning header field. + // https://tools.ietf.org/html/rfc3261#section-14.2 + { + var uas = new re_invite_user_agent_server_1.ReInviteUserAgentServer(this, message); + this.delegate && this.delegate.onInvite ? + this.delegate.onInvite(uas) : + uas.reject({ statusCode: 488 }); // TODO: Warning header field. + } + break; + case messages_1.C.NOTIFY: + // https://tools.ietf.org/html/rfc3515#section-2.4.4 + { + var uas = new notify_user_agent_server_1.NotifyUserAgentServer(this, message); + this.delegate && this.delegate.onNotify ? + this.delegate.onNotify(uas) : + uas.accept(); + } + break; + case messages_1.C.PRACK: + // https://tools.ietf.org/html/rfc3262#section-4 + { + var uas = new prack_user_agent_server_1.PrackUserAgentServer(this, message); + this.delegate && this.delegate.onPrack ? + this.delegate.onPrack(uas) : + uas.accept(); + } + break; + case messages_1.C.REFER: + // https://tools.ietf.org/html/rfc3515#section-2.4.2 + { + var uas = new refer_user_agent_server_1.ReferUserAgentServer(this, message); + this.delegate && this.delegate.onRefer ? + this.delegate.onRefer(uas) : + uas.reject(); + } + break; + default: + { + this.logger.log("INVITE dialog " + this.id + " received unimplemented " + message.method + " request"); + this.core.replyStateless(message, { statusCode: 501 }); + } + break; + } + }; + SessionDialog.prototype.reliableSequenceGuard = function (message) { + var statusCode = message.statusCode; + if (!statusCode) { + throw new Error("Status code undefined"); + } + if (statusCode > 100 && statusCode < 200) { + // If a provisional response is received for an initial request, and + // that response contains a Require header field containing the option + // tag 100rel, the response is to be sent reliably. If the response is + // a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be + // ignored, and the procedures below MUST NOT be used. + // https://tools.ietf.org/html/rfc3262#section-4 + var requireHeader = message.getHeader("require"); + var rseqHeader = message.getHeader("rseq"); + var rseq = requireHeader && requireHeader.includes("100rel") && rseqHeader ? Number(rseqHeader) : undefined; + if (rseq) { + // Handling of subsequent reliable provisional responses for the same + // initial request follows the same rules as above, with the following + // difference: reliable provisional responses are guaranteed to be in + // order. As a result, if the UAC receives another reliable provisional + // response to the same request, and its RSeq value is not one higher + // than the value of the sequence number, that response MUST NOT be + // acknowledged with a PRACK, and MUST NOT be processed further by the + // UAC. An implementation MAY discard the response, or MAY cache the + // response in the hopes of receiving the missing responses. + // https://tools.ietf.org/html/rfc3262#section-4 + if (this.rseq && this.rseq + 1 !== rseq) { + return false; + } + // Once a reliable provisional response is received, retransmissions of + // that response MUST be discarded. A response is a retransmission when + // its dialog ID, CSeq, and RSeq match the original response. The UAC + // MUST maintain a sequence number that indicates the most recently + // received in-order reliable provisional response for the initial + // request. This sequence number MUST be maintained until a final + // response is received for the initial request. Its value MUST be + // initialized to the RSeq header field in the first reliable + // provisional response received for the initial request. + // https://tools.ietf.org/html/rfc3262#section-4 + if (!this.rseq) { + this.rseq = rseq; + } + } + } + return true; + }; + /** + * Update the signaling state of the dialog. + * @param message The message to base the update off of. + */ + SessionDialog.prototype.signalingStateTransition = function (message) { + var body = messages_1.getBody(message); + // No body, no session. No, woman, no cry. + if (!body || body.contentDisposition !== "session") { + return; + } + // We're in UAS role, receiving incoming request with session description + if (message instanceof messages_1.IncomingRequestMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveRemoteOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.HaveRemoteOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAC role, receiving incoming response with session description + if (message instanceof messages_1.IncomingResponseMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveRemoteOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.HaveRemoteOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAC role, sending outgoing request with session description + if (message instanceof messages_1.OutgoingRequestMessage) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveLocalOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.HaveRemoteOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + // We're in UAS role, sending outgoing response with session description + if (messages_1.isBody(message)) { + switch (this._signalingState) { + case session_1.SignalingState.Initial: + case session_1.SignalingState.Stable: + this._signalingState = session_1.SignalingState.HaveLocalOffer; + this._offer = body; + this._answer = undefined; + break; + case session_1.SignalingState.HaveLocalOffer: + // You cannot make a new offer while one is in progress. + // https://tools.ietf.org/html/rfc3261#section-13.2.1 + // FIXME: What to do here? + break; + case session_1.SignalingState.HaveRemoteOffer: + this._signalingState = session_1.SignalingState.Stable; + this._answer = body; + break; + case session_1.SignalingState.Closed: + break; + default: + throw new Error("Unexpected signaling state."); + } + } + }; + SessionDialog.prototype.start2xxRetransmissionTimer = function () { + var _this = this; + if (this.initialTransaction instanceof transactions_1.InviteServerTransaction) { + var transaction_1 = this.initialTransaction; + // Once the response has been constructed, it is passed to the INVITE + // server transaction. In order to ensure reliable end-to-end + // transport of the response, it is necessary to periodically pass + // the response directly to the transport until the ACK arrives. The + // 2xx response is passed to the transport with an interval that + // starts at T1 seconds and doubles for each retransmission until it + // reaches T2 seconds (T1 and T2 are defined in Section 17). + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + var timeout_1 = timers_1.Timers.T1; + var retransmission_1 = function () { + if (!_this.ackWait) { + _this.invite2xxTimer = undefined; + return; + } + _this.logger.log("No ACK for 2xx response received, attempting retransmission"); + transaction_1.retransmitAcceptedResponse(); + timeout_1 = Math.min(timeout_1 * 2, timers_1.Timers.T2); + _this.invite2xxTimer = setTimeout(retransmission_1, timeout_1); + }; + this.invite2xxTimer = setTimeout(retransmission_1, timeout_1); + // If the server retransmits the 2xx response for 64*T1 seconds without + // receiving an ACK, the dialog is confirmed, but the session SHOULD be + // terminated. This is accomplished with a BYE, as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + var stateChanged_1 = function () { + if (transaction_1.state === transactions_1.TransactionState.Terminated) { + transaction_1.removeListener("stateChanged", stateChanged_1); + if (_this.invite2xxTimer) { + clearTimeout(_this.invite2xxTimer); + _this.invite2xxTimer = undefined; + } + if (_this.ackWait) { + if (_this.delegate && _this.delegate.onAckTimeout) { + _this.delegate.onAckTimeout(); + } + else { + _this.bye(); + } + } + } + }; + transaction_1.addListener("stateChanged", stateChanged_1); + } + }; + // FIXME: Refactor + SessionDialog.prototype.startReInvite2xxRetransmissionTimer = function () { + var _this = this; + if (this.reinviteUserAgentServer && this.reinviteUserAgentServer.transaction instanceof transactions_1.InviteServerTransaction) { + var transaction_2 = this.reinviteUserAgentServer.transaction; + // Once the response has been constructed, it is passed to the INVITE + // server transaction. In order to ensure reliable end-to-end + // transport of the response, it is necessary to periodically pass + // the response directly to the transport until the ACK arrives. The + // 2xx response is passed to the transport with an interval that + // starts at T1 seconds and doubles for each retransmission until it + // reaches T2 seconds (T1 and T2 are defined in Section 17). + // Response retransmissions cease when an ACK request for the + // response is received. This is independent of whatever transport + // protocols are used to send the response. + // https://tools.ietf.org/html/rfc6026#section-8.1 + var timeout_2 = timers_1.Timers.T1; + var retransmission_2 = function () { + if (!_this.reinviteUserAgentServer) { + _this.invite2xxTimer = undefined; + return; + } + _this.logger.log("No ACK for 2xx response received, attempting retransmission"); + transaction_2.retransmitAcceptedResponse(); + timeout_2 = Math.min(timeout_2 * 2, timers_1.Timers.T2); + _this.invite2xxTimer = setTimeout(retransmission_2, timeout_2); + }; + this.invite2xxTimer = setTimeout(retransmission_2, timeout_2); + // If the server retransmits the 2xx response for 64*T1 seconds without + // receiving an ACK, the dialog is confirmed, but the session SHOULD be + // terminated. This is accomplished with a BYE, as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + var stateChanged_2 = function () { + if (transaction_2.state === transactions_1.TransactionState.Terminated) { + transaction_2.removeListener("stateChanged", stateChanged_2); + if (_this.invite2xxTimer) { + clearTimeout(_this.invite2xxTimer); + _this.invite2xxTimer = undefined; + } + if (_this.reinviteUserAgentServer) { + // FIXME: TODO: What to do here + } + } + }; + transaction_2.addListener("stateChanged", stateChanged_2); + } + }; + return SessionDialog; +}(dialog_1.Dialog)); +exports.SessionDialog = SessionDialog; diff --git a/lib/core/dialogs/subscription-dialog.d.ts b/lib/core/dialogs/subscription-dialog.d.ts new file mode 100644 index 000000000..f185d6254 --- /dev/null +++ b/lib/core/dialogs/subscription-dialog.d.ts @@ -0,0 +1,115 @@ +import { IncomingRequestMessage, OutgoingRequestMessage, OutgoingSubscribeRequest, OutgoingSubscribeRequestDelegate, RequestOptions } from "../messages"; +import { Subscription, SubscriptionDelegate, SubscriptionState } from "../subscription"; +import { UserAgentCore } from "../user-agent-core/user-agent-core"; +import { Dialog } from "./dialog"; +import { DialogState } from "./dialog-state"; +/** + * SIP-Specific Event Notification + * + * Abstract + * + * This document describes an extension to the Session Initiation + * Protocol (SIP) defined by RFC 3261. The purpose of this extension is + * to provide an extensible framework by which SIP nodes can request + * notification from remote nodes indicating that certain events have + * occurred. + * + * Note that the event notification mechanisms defined herein are NOT + * intended to be a general-purpose infrastructure for all classes of + * event subscription and notification. + * + * This document represents a backwards-compatible improvement on the + * original mechanism described by RFC 3265, taking into account several + * years of implementation experience. Accordingly, this document + * obsoletes RFC 3265. This document also updates RFC 4660 slightly to + * accommodate some small changes to the mechanism that were discussed + * in that document. + * + * https://tools.ietf.org/html/rfc6665 + */ +export declare class SubscriptionDialog extends Dialog implements Subscription { + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + static initialDialogStateForSubscription(outgoingSubscribeRequestMessage: OutgoingRequestMessage, incomingNotifyRequestMessage: IncomingRequestMessage): DialogState; + delegate: SubscriptionDelegate | undefined; + private _autoRefresh; + private _subscriptionEvent; + private _subscriptionExpires; + private _subscriptionExpiresInitial; + private _subscriptionExpiresLastSet; + private _subscriptionRefresh; + private _subscriptionRefreshLastSet; + private _subscriptionState; + private logger; + private N; + private refreshTimer; + constructor(subscriptionEvent: string, subscriptionExpires: number, subscriptionState: SubscriptionState, core: UserAgentCore, state: DialogState, delegate?: SubscriptionDelegate); + dispose(): void; + autoRefresh: boolean; + readonly subscriptionEvent: string; + /** Number of seconds until subscription expires. */ + subscriptionExpires: number; + readonly subscriptionExpiresInitial: number; + /** Number of seconds until subscription auto refresh. */ + readonly subscriptionRefresh: number | undefined; + readonly subscriptionState: SubscriptionState; + /** + * Receive in dialog request message from transport. + * @param message The incoming request message. + */ + receiveRequest(message: IncomingRequestMessage): void; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + refresh(): OutgoingSubscribeRequest; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + * @param delegate Delegate to handle responses. + * @param options Options bucket. + */ + subscribe(delegate?: OutgoingSubscribeRequestDelegate, options?: RequestOptions): OutgoingSubscribeRequest; + /** + * 4.4.1. Dialog Creation and Termination + * A subscription is destroyed after a notifier sends a NOTIFY request + * with a "Subscription-State" of "terminated", or in certain error + * situations described elsewhere in this document. + * https://tools.ietf.org/html/rfc6665#section-4.4.1 + */ + terminate(): void; + /** + * 4.1.2.3. Unsubscribing + * https://tools.ietf.org/html/rfc6665#section-4.1.2.3 + */ + unsubscribe(): OutgoingSubscribeRequest; + /** + * Handle in dialog NOTIFY requests. + * This does not include the first NOTIFY which created the dialog. + * @param message The incoming NOTIFY request message. + */ + private onNotify; + private onRefresh; + private onTerminated; + private refreshTimerClear; + private refreshTimerSet; + private stateTransition; + /** + * When refreshing a subscription, a subscriber starts Timer N, set to + * 64*T1, when it sends the SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription terminated. If the subscriber receives a success + * response to the SUBSCRIBE request that indicates that no NOTIFY + * request will be generated -- such as the 204 response defined for use + * with the optional extension described in [RFC5839] -- then it MUST + * cancel Timer N. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + private timer_N; +} diff --git a/lib/core/dialogs/subscription-dialog.js b/lib/core/dialogs/subscription-dialog.js new file mode 100644 index 000000000..b80df4a12 --- /dev/null +++ b/lib/core/dialogs/subscription-dialog.js @@ -0,0 +1,491 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var subscription_1 = require("../subscription"); +var timers_1 = require("../timers"); +var allowed_methods_1 = require("../user-agent-core/allowed-methods"); +var notify_user_agent_server_1 = require("../user-agents/notify-user-agent-server"); +var re_subscribe_user_agent_client_1 = require("../user-agents/re-subscribe-user-agent-client"); +var dialog_1 = require("./dialog"); +/** + * SIP-Specific Event Notification + * + * Abstract + * + * This document describes an extension to the Session Initiation + * Protocol (SIP) defined by RFC 3261. The purpose of this extension is + * to provide an extensible framework by which SIP nodes can request + * notification from remote nodes indicating that certain events have + * occurred. + * + * Note that the event notification mechanisms defined herein are NOT + * intended to be a general-purpose infrastructure for all classes of + * event subscription and notification. + * + * This document represents a backwards-compatible improvement on the + * original mechanism described by RFC 3265, taking into account several + * years of implementation experience. Accordingly, this document + * obsoletes RFC 3265. This document also updates RFC 4660 slightly to + * accommodate some small changes to the mechanism that were discussed + * in that document. + * + * https://tools.ietf.org/html/rfc6665 + */ +var SubscriptionDialog = /** @class */ (function (_super) { + tslib_1.__extends(SubscriptionDialog, _super); + function SubscriptionDialog(subscriptionEvent, subscriptionExpires, subscriptionState, core, state, delegate) { + var _this = _super.call(this, core, state) || this; + _this.delegate = delegate; + _this._autoRefresh = false; + _this._subscriptionEvent = subscriptionEvent; + _this._subscriptionExpires = subscriptionExpires; + _this._subscriptionExpiresInitial = subscriptionExpires; + _this._subscriptionExpiresLastSet = Math.floor(Date.now() / 1000); + _this._subscriptionRefresh = undefined; + _this._subscriptionRefreshLastSet = undefined; + _this._subscriptionState = subscriptionState; + _this.logger = core.loggerFactory.getLogger("sip.subscribe-dialog"); + _this.logger.log("SUBSCRIBE dialog " + _this.id + " constructed"); + return _this; + } + /** + * When a UAC receives a response that establishes a dialog, it + * constructs the state of the dialog. This state MUST be maintained + * for the duration of the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.1.2 + * @param outgoingRequestMessage Outgoing request message for dialog. + * @param incomingResponseMessage Incoming response message creating dialog. + */ + SubscriptionDialog.initialDialogStateForSubscription = function (outgoingSubscribeRequestMessage, incomingNotifyRequestMessage) { + // If the request was sent over TLS, and the Request-URI contained a + // SIPS URI, the "secure" flag is set to TRUE. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var secure = false; // FIXME: Currently no support for TLS. + // The route set MUST be set to the list of URIs in the Record-Route + // header field from the response, taken in reverse order and preserving + // all URI parameters. If no Record-Route header field is present in + // the response, the route set MUST be set to the empty set. This route + // set, even if empty, overrides any pre-existing route set for future + // requests in this dialog. The remote target MUST be set to the URI + // from the Contact header field of the response. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var routeSet = incomingNotifyRequestMessage.getHeaders("record-route"); + var contact = incomingNotifyRequestMessage.parseHeader("contact"); + if (!contact) { // TODO: Review to make sure this will never happen + throw new Error("Contact undefined."); + } + if (!(contact instanceof messages_1.NameAddrHeader)) { + throw new Error("Contact not instance of NameAddrHeader."); + } + var remoteTarget = contact.uri; + // The local sequence number MUST be set to the value of the sequence + // number in the CSeq header field of the request. The remote sequence + // number MUST be empty (it is established when the remote UA sends a + // request within the dialog). The call identifier component of the + // dialog ID MUST be set to the value of the Call-ID in the request. + // The local tag component of the dialog ID MUST be set to the tag in + // the From field in the request, and the remote tag component of the + // dialog ID MUST be set to the tag in the To field of the response. A + // UAC MUST be prepared to receive a response without a tag in the To + // field, in which case the tag is considered to have a value of null. + // + // This is to maintain backwards compatibility with RFC 2543, which + // did not mandate To tags. + // + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + var localSequenceNumber = outgoingSubscribeRequestMessage.cseq; + var remoteSequenceNumber = undefined; + var callId = outgoingSubscribeRequestMessage.callId; + var localTag = outgoingSubscribeRequestMessage.fromTag; + var remoteTag = incomingNotifyRequestMessage.fromTag; + if (!callId) { // TODO: Review to make sure this will never happen + throw new Error("Call id undefined."); + } + if (!localTag) { // TODO: Review to make sure this will never happen + throw new Error("From tag undefined."); + } + if (!remoteTag) { // TODO: Review to make sure this will never happen + throw new Error("To tag undefined."); // FIXME: No backwards compatibility with RFC 2543 + } + // The remote URI MUST be set to the URI in the To field, and the local + // URI MUST be set to the URI in the From field. + // https://tools.ietf.org/html/rfc3261#section-12.1.2 + if (!outgoingSubscribeRequestMessage.from) { // TODO: Review to make sure this will never happen + throw new Error("From undefined."); + } + if (!outgoingSubscribeRequestMessage.to) { // TODO: Review to make sure this will never happen + throw new Error("To undefined."); + } + var localURI = outgoingSubscribeRequestMessage.from.uri; + var remoteURI = outgoingSubscribeRequestMessage.to.uri; + // A dialog can also be in the "early" state, which occurs when it is + // created with a provisional response, and then transition to the + // "confirmed" state when a 2xx final response arrives. + // https://tools.ietf.org/html/rfc3261#section-12 + var early = false; + var dialogState = { + id: callId + localTag + remoteTag, + early: early, + callId: callId, + localTag: localTag, + remoteTag: remoteTag, + localSequenceNumber: localSequenceNumber, + remoteSequenceNumber: remoteSequenceNumber, + localURI: localURI, + remoteURI: remoteURI, + remoteTarget: remoteTarget, + routeSet: routeSet, + secure: secure + }; + return dialogState; + }; + SubscriptionDialog.prototype.dispose = function () { + _super.prototype.dispose.call(this); + if (this.N) { + clearTimeout(this.N); + this.N = undefined; + } + this.refreshTimerClear(); + this.logger.log("SUBSCRIBE dialog " + this.id + " destroyed"); + }; + Object.defineProperty(SubscriptionDialog.prototype, "autoRefresh", { + get: function () { + return this._autoRefresh; + }, + set: function (autoRefresh) { + this._autoRefresh = true; + this.refreshTimerSet(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionEvent", { + get: function () { + return this._subscriptionEvent; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionExpires", { + /** Number of seconds until subscription expires. */ + get: function () { + var secondsSinceLastSet = Math.floor(Date.now() / 1000) - this._subscriptionExpiresLastSet; + var secondsUntilExpires = this._subscriptionExpires - secondsSinceLastSet; + return Math.max(secondsUntilExpires, 0); + }, + set: function (expires) { + if (expires < 0) { + throw new Error("Expires must be greater than or equal to zero."); + } + this._subscriptionExpires = expires; + this._subscriptionExpiresLastSet = Math.floor(Date.now() / 1000); + if (this.autoRefresh) { + var refresh = this.subscriptionRefresh; + if (refresh === undefined || refresh >= expires) { + this.refreshTimerSet(); + } + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionExpiresInitial", { + get: function () { + return this._subscriptionExpiresInitial; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionRefresh", { + /** Number of seconds until subscription auto refresh. */ + get: function () { + if (this._subscriptionRefresh === undefined || this._subscriptionRefreshLastSet === undefined) { + return undefined; + } + var secondsSinceLastSet = Math.floor(Date.now() / 1000) - this._subscriptionRefreshLastSet; + var secondsUntilExpires = this._subscriptionRefresh - secondsSinceLastSet; + return Math.max(secondsUntilExpires, 0); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SubscriptionDialog.prototype, "subscriptionState", { + get: function () { + return this._subscriptionState; + }, + enumerable: true, + configurable: true + }); + /** + * Receive in dialog request message from transport. + * @param message The incoming request message. + */ + SubscriptionDialog.prototype.receiveRequest = function (message) { + this.logger.log("SUBSCRIBE dialog " + this.id + " received " + message.method + " request"); + // Request within a dialog out of sequence guard. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (!this.sequenceGuard(message)) { + this.logger.log("SUBSCRIBE dialog " + this.id + " rejected out of order " + message.method + " request."); + return; + } + // Request within a dialog common processing. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + _super.prototype.receiveRequest.call(this, message); + // Switch on method and then delegate. + switch (message.method) { + case messages_1.C.NOTIFY: + this.onNotify(message); + break; + default: + this.logger.log("SUBSCRIBE dialog " + this.id + " received unimplemented " + message.method + " request"); + this.core.replyStateless(message, { statusCode: 501 }); + break; + } + }; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + SubscriptionDialog.prototype.refresh = function () { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var options = {}; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push("Event: " + this.subscriptionEvent); + options.extraHeaders.push("Expires: " + this.subscriptionExpiresInitial); + options.extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + return this.subscribe(undefined, options); + }; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + * @param delegate Delegate to handle responses. + * @param options Options bucket. + */ + SubscriptionDialog.prototype.subscribe = function (delegate, options) { + var _this = this; + if (options === void 0) { options = {}; } + if (this.subscriptionState !== subscription_1.SubscriptionState.Pending && this.subscriptionState !== subscription_1.SubscriptionState.Active) { + // FIXME: This needs to be a proper exception + throw new Error("Invalid state " + this.subscriptionState + ". May only re-subscribe while in state \"pending\" or \"active\"."); + } + this.logger.log("SUBSCRIBE dialog " + this.id + " sending SUBSCRIBE request"); + var uac = new re_subscribe_user_agent_client_1.ReSubscribeUserAgentClient(this, delegate, options); + // When refreshing a subscription, a subscriber starts Timer N, set to + // 64*T1, when it sends the SUBSCRIBE request. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + this.N = setTimeout(function () { return _this.timer_N(); }, timers_1.Timers.TIMER_N); + return uac; + }; + /** + * 4.4.1. Dialog Creation and Termination + * A subscription is destroyed after a notifier sends a NOTIFY request + * with a "Subscription-State" of "terminated", or in certain error + * situations described elsewhere in this document. + * https://tools.ietf.org/html/rfc6665#section-4.4.1 + */ + SubscriptionDialog.prototype.terminate = function () { + this.stateTransition(subscription_1.SubscriptionState.Terminated); + this.onTerminated(); + }; + /** + * 4.1.2.3. Unsubscribing + * https://tools.ietf.org/html/rfc6665#section-4.1.2.3 + */ + SubscriptionDialog.prototype.unsubscribe = function () { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var options = {}; + options.extraHeaders = (options.extraHeaders || []).slice(); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push("Event: " + this.subscriptionEvent); + options.extraHeaders.push("Expires: 0"); + options.extraHeaders.push("Contact: " + this.core.configuration.contact.toString()); + return this.subscribe(undefined, options); + }; + /** + * Handle in dialog NOTIFY requests. + * This does not include the first NOTIFY which created the dialog. + * @param message The incoming NOTIFY request message. + */ + SubscriptionDialog.prototype.onNotify = function (message) { + // If, for some reason, the event package designated in the "Event" + // header field of the NOTIFY request is not supported, the subscriber + // will respond with a 489 (Bad Event) response. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var event = message.parseHeader("Event").event; + if (!event || event !== this.subscriptionEvent) { + this.core.replyStateless(message, { statusCode: 489 }); + return; + } + // In the state diagram, "Re-subscription times out" means that an + // attempt to refresh or update the subscription using a new SUBSCRIBE + // request does not result in a NOTIFY request before the corresponding + // Timer N expires. + // https://tools.ietf.org/html/rfc6665#section-4.1.2 + if (this.N) { + clearTimeout(this.N); + this.N = undefined; + } + // NOTIFY requests MUST contain "Subscription-State" header fields that + // indicate the status of the subscription. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = message.parseHeader("Subscription-State"); + if (!subscriptionState || !subscriptionState.state) { + this.core.replyStateless(message, { statusCode: 489 }); + return; + } + var state = subscriptionState.state; + var expires = subscriptionState.expires ? Math.max(subscriptionState.expires, 0) : undefined; + // Update our state and expiration. + switch (state) { + case "pending": + this.stateTransition(subscription_1.SubscriptionState.Pending, expires); + break; + case "active": + this.stateTransition(subscription_1.SubscriptionState.Active, expires); + break; + case "terminated": + this.stateTransition(subscription_1.SubscriptionState.Terminated, expires); + break; + default: + this.logger.warn("Unrecognized subscription state."); + break; + } + // Delegate remainder of NOTIFY handling. + var uas = new notify_user_agent_server_1.NotifyUserAgentServer(this, message); + if (this.delegate && this.delegate.onNotify) { + this.delegate.onNotify(uas); + } + else { + uas.accept(); + } + }; + SubscriptionDialog.prototype.onRefresh = function (request) { + if (this.delegate && this.delegate.onRefresh) { + this.delegate.onRefresh(request); + } + }; + SubscriptionDialog.prototype.onTerminated = function () { + if (this.delegate && this.delegate.onTerminated) { + this.delegate.onTerminated(); + } + }; + SubscriptionDialog.prototype.refreshTimerClear = function () { + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); + this.refreshTimer = undefined; + } + }; + SubscriptionDialog.prototype.refreshTimerSet = function () { + var _this = this; + this.refreshTimerClear(); + if (this.autoRefresh && this.subscriptionExpires > 0) { + var refresh = this.subscriptionExpires * 900; + this._subscriptionRefresh = Math.floor(refresh / 1000); + this._subscriptionRefreshLastSet = Math.floor(Date.now() / 1000); + this.refreshTimer = setTimeout(function () { + _this.refreshTimer = undefined; + _this._subscriptionRefresh = undefined; + _this._subscriptionRefreshLastSet = undefined; + _this.onRefresh(_this.refresh()); + }, refresh); + } + }; + SubscriptionDialog.prototype.stateTransition = function (newState, newExpires) { + var _this = this; + // Assert valid state transitions. + var invalidStateTransition = function () { + _this.logger.warn("Invalid subscription state transition from " + _this.subscriptionState + " to " + newState); + }; + switch (newState) { + case subscription_1.SubscriptionState.Initial: + invalidStateTransition(); + return; + case subscription_1.SubscriptionState.NotifyWait: + invalidStateTransition(); + return; + case subscription_1.SubscriptionState.Pending: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending) { + invalidStateTransition(); + return; + } + break; + case subscription_1.SubscriptionState.Active: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending && + this.subscriptionState !== subscription_1.SubscriptionState.Active) { + invalidStateTransition(); + return; + } + break; + case subscription_1.SubscriptionState.Terminated: + if (this.subscriptionState !== subscription_1.SubscriptionState.NotifyWait && + this.subscriptionState !== subscription_1.SubscriptionState.Pending && + this.subscriptionState !== subscription_1.SubscriptionState.Active) { + invalidStateTransition(); + return; + } + break; + default: + invalidStateTransition(); + return; + } + // If the "Subscription-State" value is "pending", the subscription has + // been received by the notifier, but there is insufficient policy + // information to grant or deny the subscription yet. If the header + // field also contains an "expires" parameter, the subscriber SHOULD + // take it as the authoritative subscription duration and adjust + // accordingly. No further action is necessary on the part of the + // subscriber. The "retry-after" and "reason" parameters have no + // semantics for "pending". + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + if (newState === subscription_1.SubscriptionState.Pending) { + if (newExpires) { + this.subscriptionExpires = newExpires; + } + } + // If the "Subscription-State" header field value is "active", it means + // that the subscription has been accepted and (in general) has been + // authorized. If the header field also contains an "expires" + // parameter, the subscriber SHOULD take it as the authoritative + // subscription duration and adjust accordingly. The "retry-after" and + // "reason" parameters have no semantics for "active". + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + if (newState === subscription_1.SubscriptionState.Active) { + if (newExpires) { + this.subscriptionExpires = newExpires; + } + } + // If the "Subscription-State" value is "terminated", the subscriber + // MUST consider the subscription terminated. The "expires" parameter + // has no semantics for "terminated" -- notifiers SHOULD NOT include an + // "expires" parameter on a "Subscription-State" header field with a + // value of "terminated", and subscribers MUST ignore any such + // parameter, if present. + if (newState === subscription_1.SubscriptionState.Terminated) { + this.dispose(); + } + this._subscriptionState = newState; + }; + /** + * When refreshing a subscription, a subscriber starts Timer N, set to + * 64*T1, when it sends the SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription terminated. If the subscriber receives a success + * response to the SUBSCRIBE request that indicates that no NOTIFY + * request will be generated -- such as the 204 response defined for use + * with the optional extension described in [RFC5839] -- then it MUST + * cancel Timer N. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + SubscriptionDialog.prototype.timer_N = function () { + if (this.subscriptionState !== subscription_1.SubscriptionState.Terminated) { + this.stateTransition(subscription_1.SubscriptionState.Terminated); + this.onTerminated(); + } + }; + return SubscriptionDialog; +}(dialog_1.Dialog)); +exports.SubscriptionDialog = SubscriptionDialog; diff --git a/lib/core/exceptions/exception.d.ts b/lib/core/exceptions/exception.d.ts new file mode 100644 index 000000000..b63fdc7dd --- /dev/null +++ b/lib/core/exceptions/exception.d.ts @@ -0,0 +1,8 @@ +/** + * An Exception is considered a condition that a reasonable application may wish to catch. + * An Error indicates serious problems that a reasonable application should not try to catch. + * @public + */ +export declare abstract class Exception extends Error { + protected constructor(message?: string); +} diff --git a/lib/core/exceptions/exception.js b/lib/core/exceptions/exception.js new file mode 100644 index 000000000..4e25fb887 --- /dev/null +++ b/lib/core/exceptions/exception.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +/** + * An Exception is considered a condition that a reasonable application may wish to catch. + * An Error indicates serious problems that a reasonable application should not try to catch. + * @public + */ +var Exception = /** @class */ (function (_super) { + tslib_1.__extends(Exception, _super); + function Exception(message) { + var _newTarget = this.constructor; + var _this = _super.call(this, message) || this; + Object.setPrototypeOf(_this, _newTarget.prototype); // restore prototype chain + return _this; + } + return Exception; +}(Error)); +exports.Exception = Exception; diff --git a/lib/core/exceptions/index.d.ts b/lib/core/exceptions/index.d.ts new file mode 100644 index 000000000..a7b5eb621 --- /dev/null +++ b/lib/core/exceptions/index.d.ts @@ -0,0 +1,3 @@ +export * from "./exception"; +export * from "./transaction-state-error"; +export * from "./transport-error"; diff --git a/lib/core/exceptions/index.js b/lib/core/exceptions/index.js new file mode 100644 index 000000000..4b11b7ca6 --- /dev/null +++ b/lib/core/exceptions/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./exception"), exports); +tslib_1.__exportStar(require("./transaction-state-error"), exports); +tslib_1.__exportStar(require("./transport-error"), exports); diff --git a/lib/core/exceptions/transaction-state-error.d.ts b/lib/core/exceptions/transaction-state-error.d.ts new file mode 100644 index 000000000..9aa390ffd --- /dev/null +++ b/lib/core/exceptions/transaction-state-error.d.ts @@ -0,0 +1,8 @@ +import { Exception } from "./exception"; +/** + * Indicates that the operation could not be completed given the current transaction state. + * @public + */ +export declare class TransactionStateError extends Exception { + constructor(message?: string); +} diff --git a/lib/core/exceptions/transaction-state-error.js b/lib/core/exceptions/transaction-state-error.js new file mode 100644 index 000000000..19b33884b --- /dev/null +++ b/lib/core/exceptions/transaction-state-error.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var exception_1 = require("./exception"); +/** + * Indicates that the operation could not be completed given the current transaction state. + * @public + */ +var TransactionStateError = /** @class */ (function (_super) { + tslib_1.__extends(TransactionStateError, _super); + function TransactionStateError(message) { + return _super.call(this, message ? message : "Transaction state error.") || this; + } + return TransactionStateError; +}(exception_1.Exception)); +exports.TransactionStateError = TransactionStateError; diff --git a/lib/core/exceptions/transport-error.d.ts b/lib/core/exceptions/transport-error.d.ts new file mode 100644 index 000000000..51ce14f5a --- /dev/null +++ b/lib/core/exceptions/transport-error.d.ts @@ -0,0 +1,8 @@ +import { Exception } from "./exception"; +/** + * Transport error. + * @public + */ +export declare class TransportError extends Exception { + constructor(message?: string); +} diff --git a/lib/core/exceptions/transport-error.js b/lib/core/exceptions/transport-error.js new file mode 100644 index 000000000..c53d994ab --- /dev/null +++ b/lib/core/exceptions/transport-error.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var exception_1 = require("./exception"); +/** + * Transport error. + * @public + */ +var TransportError = /** @class */ (function (_super) { + tslib_1.__extends(TransportError, _super); + function TransportError(message) { + return _super.call(this, message ? message : "Unspecified transport error.") || this; + } + return TransportError; +}(exception_1.Exception)); +exports.TransportError = TransportError; diff --git a/lib/core/index.d.ts b/lib/core/index.d.ts new file mode 100644 index 000000000..30575135a --- /dev/null +++ b/lib/core/index.d.ts @@ -0,0 +1,11 @@ +export * from "./dialogs"; +export * from "./exceptions"; +export * from "./log"; +export * from "./messages"; +export * from "./session"; +export * from "./subscription"; +export * from "./transactions"; +export * from "./user-agent-core"; +export * from "./user-agents"; +export * from "./timers"; +export * from "./transport"; diff --git a/lib/core/index.js b/lib/core/index.js new file mode 100644 index 000000000..c1a64a6c0 --- /dev/null +++ b/lib/core/index.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +// Directories +tslib_1.__exportStar(require("./dialogs"), exports); +tslib_1.__exportStar(require("./exceptions"), exports); +tslib_1.__exportStar(require("./log"), exports); +tslib_1.__exportStar(require("./messages"), exports); +tslib_1.__exportStar(require("./session"), exports); +tslib_1.__exportStar(require("./subscription"), exports); +tslib_1.__exportStar(require("./transactions"), exports); +tslib_1.__exportStar(require("./user-agent-core"), exports); +tslib_1.__exportStar(require("./user-agents"), exports); +// Files +tslib_1.__exportStar(require("./timers"), exports); +tslib_1.__exportStar(require("./transport"), exports); diff --git a/lib/core/log/index.d.ts b/lib/core/log/index.d.ts new file mode 100644 index 000000000..2d59354ff --- /dev/null +++ b/lib/core/log/index.d.ts @@ -0,0 +1,3 @@ +export * from "./levels"; +export * from "./logger-factory"; +export * from "./logger"; diff --git a/lib/core/log/index.js b/lib/core/log/index.js new file mode 100644 index 000000000..394ac7e28 --- /dev/null +++ b/lib/core/log/index.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./levels"), exports); +tslib_1.__exportStar(require("./logger-factory"), exports); +tslib_1.__exportStar(require("./logger"), exports); diff --git a/lib/core/log/levels.d.ts b/lib/core/log/levels.d.ts new file mode 100644 index 000000000..8faf93db2 --- /dev/null +++ b/lib/core/log/levels.d.ts @@ -0,0 +1,10 @@ +/** + * Log levels. + * @public + */ +export declare enum Levels { + error = 0, + warn = 1, + log = 2, + debug = 3 +} diff --git a/lib/core/log/levels.js b/lib/core/log/levels.js new file mode 100644 index 000000000..dfa4ef191 --- /dev/null +++ b/lib/core/log/levels.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Log levels. + * @public + */ +var Levels; +(function (Levels) { + Levels[Levels["error"] = 0] = "error"; + Levels[Levels["warn"] = 1] = "warn"; + Levels[Levels["log"] = 2] = "log"; + Levels[Levels["debug"] = 3] = "debug"; +})(Levels = exports.Levels || (exports.Levels = {})); diff --git a/lib/core/log/logger-factory.d.ts b/lib/core/log/logger-factory.d.ts new file mode 100644 index 000000000..64a57247c --- /dev/null +++ b/lib/core/log/logger-factory.d.ts @@ -0,0 +1,19 @@ +import { Levels } from "./levels"; +import { Logger } from "./logger"; +/** + * Logger. + * @public + */ +export declare class LoggerFactory { + builtinEnabled: boolean; + private _level; + private _connector; + private loggers; + private logger; + constructor(); + level: Levels; + connector: ((level: string, category: string, label: string | undefined, content: any) => void) | undefined; + getLogger(category: string, label?: string): Logger; + genericLog(levelToLog: Levels, category: string, label: string | undefined, content: any): void; + private print; +} diff --git a/lib/core/log/logger-factory.js b/lib/core/log/logger-factory.js new file mode 100644 index 000000000..47cc61303 --- /dev/null +++ b/lib/core/log/logger-factory.js @@ -0,0 +1,107 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var levels_1 = require("./levels"); +var logger_1 = require("./logger"); +/** + * Logger. + * @public + */ +var LoggerFactory = /** @class */ (function () { + function LoggerFactory() { + this.builtinEnabled = true; + this._level = levels_1.Levels.log; + this.loggers = {}; + this.logger = this.getLogger("sip:loggerfactory"); + } + Object.defineProperty(LoggerFactory.prototype, "level", { + get: function () { return this._level; }, + set: function (newLevel) { + if (newLevel >= 0 && newLevel <= 3) { + this._level = newLevel; + } + else if (newLevel > 3) { + this._level = 3; + } + else if (levels_1.Levels.hasOwnProperty(newLevel)) { + this._level = newLevel; + } + else { + this.logger.error("invalid 'level' parameter value: " + JSON.stringify(newLevel)); + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(LoggerFactory.prototype, "connector", { + get: function () { + return this._connector; + }, + set: function (value) { + if (!value) { + this._connector = undefined; + } + else if (typeof value === "function") { + this._connector = value; + } + else { + this.logger.error("invalid 'connector' parameter value: " + JSON.stringify(value)); + } + }, + enumerable: true, + configurable: true + }); + LoggerFactory.prototype.getLogger = function (category, label) { + if (label && this.level === 3) { + return new logger_1.Logger(this, category, label); + } + else if (this.loggers[category]) { + return this.loggers[category]; + } + else { + var logger = new logger_1.Logger(this, category); + this.loggers[category] = logger; + return logger; + } + }; + LoggerFactory.prototype.genericLog = function (levelToLog, category, label, content) { + if (this.level >= levelToLog) { + if (this.builtinEnabled) { + this.print(levelToLog, category, label, content); + } + } + if (this.connector) { + this.connector(levels_1.Levels[levelToLog], category, label, content); + } + }; + LoggerFactory.prototype.print = function (levelToLog, category, label, content) { + if (typeof content === "string") { + var prefix = [new Date(), category]; + if (label) { + prefix.push(label); + } + content = prefix.concat(content).join(" | "); + } + switch (levelToLog) { + case levels_1.Levels.error: + // tslint:disable-next-line:no-console + console.error(content); + break; + case levels_1.Levels.warn: + // tslint:disable-next-line:no-console + console.warn(content); + break; + case levels_1.Levels.log: + // tslint:disable-next-line:no-console + console.log(content); + break; + case levels_1.Levels.debug: + // tslint:disable-next-line:no-console + console.debug(content); + break; + default: + break; + } + }; + return LoggerFactory; +}()); +exports.LoggerFactory = LoggerFactory; diff --git a/lib/core/log/logger.d.ts b/lib/core/log/logger.d.ts new file mode 100644 index 000000000..91b7c0604 --- /dev/null +++ b/lib/core/log/logger.d.ts @@ -0,0 +1,16 @@ +import { LoggerFactory } from "./logger-factory"; +/** + * Logger. + * @public + */ +export declare class Logger { + private logger; + private category; + private label; + constructor(logger: LoggerFactory, category: string, label?: string); + error(content: string): void; + warn(content: string): void; + log(content: string): void; + debug(content: string): void; + private genericLog; +} diff --git a/lib/core/log/logger.js b/lib/core/log/logger.js new file mode 100644 index 000000000..959d93f9f --- /dev/null +++ b/lib/core/log/logger.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var levels_1 = require("./levels"); +/** + * Logger. + * @public + */ +var Logger = /** @class */ (function () { + function Logger(logger, category, label) { + this.logger = logger; + this.category = category; + this.label = label; + } + Logger.prototype.error = function (content) { this.genericLog(levels_1.Levels.error, content); }; + Logger.prototype.warn = function (content) { this.genericLog(levels_1.Levels.warn, content); }; + Logger.prototype.log = function (content) { this.genericLog(levels_1.Levels.log, content); }; + Logger.prototype.debug = function (content) { this.genericLog(levels_1.Levels.debug, content); }; + Logger.prototype.genericLog = function (level, content) { + this.logger.genericLog(level, this.category, this.label, content); + }; + return Logger; +}()); +exports.Logger = Logger; diff --git a/lib/core/messages/body.d.ts b/lib/core/messages/body.d.ts new file mode 100644 index 000000000..88ab8b433 --- /dev/null +++ b/lib/core/messages/body.d.ts @@ -0,0 +1,61 @@ +import { IncomingRequestMessage } from "./incoming-request-message"; +import { IncomingResponseMessage } from "./incoming-response-message"; +import { OutgoingRequestMessage } from "./outgoing-request-message"; +/** + * SIP Message Body. + * https://tools.ietf.org/html/rfc3261#section-7.4 + */ +export interface Body { + /** + * If the Content-Disposition header field is missing, bodies of + * Content-Type application/sdp imply the disposition "session", while + * other content types imply "render". + * https://tools.ietf.org/html/rfc3261#section-13.2.1 + * For backward-compatibility, if the Content-Disposition header field + * is missing, the server SHOULD assume bodies of Content-Type + * application/sdp are the disposition "session", while other content + * types are "render". + * https://tools.ietf.org/html/rfc3261#section-20.11 + */ + contentDisposition: string; + /** + * The Content-Type header field indicates the media type of the + * message-body sent to the recipient. The Content-Type header field + * MUST be present if the body is not empty. If the body is empty, + * and a Content-Type header field is present, it indicates that the body + * of the specific type has zero length (for example, an empty audio file). + * https://tools.ietf.org/html/rfc3261#section-20.15 + */ + contentType: string; + /** + * Requests, including new requests defined in extensions to this + * specification, MAY contain message bodies unless otherwise noted. + * The interpretation of the body depends on the request method. + * For response messages, the request method and the response status + * code determine the type and interpretation of any message body. All + * responses MAY include a body. + * https://tools.ietf.org/html/rfc3261#section-7.4 + */ + content: string; +} +/** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ +export declare function fromBodyLegacy(bodyLegacy: string | { + body: string; + contentType: string; +}): Body; +/** Outgoing response body */ +export declare type OutgoingResponseBody = Body; +/** + * Given a message, get a normalized body. + * The content disposition is inferred if not set. + * @param message The message. + */ +export declare function getBody(message: IncomingRequestMessage | IncomingResponseMessage | OutgoingRequestMessage | OutgoingResponseBody): Body | undefined; +/** + * User-Defined Type Guard for Body. + * @param body Body to check. + */ +export declare function isBody(body: any): body is Body; diff --git a/lib/core/messages/body.js b/lib/core/messages/body.js new file mode 100644 index 000000000..e5e9d590e --- /dev/null +++ b/lib/core/messages/body.js @@ -0,0 +1,117 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var incoming_request_message_1 = require("./incoming-request-message"); +var incoming_response_message_1 = require("./incoming-response-message"); +var outgoing_request_message_1 = require("./outgoing-request-message"); +/** + * Create a Body given a BodyObj. + * @param bodyObj Body Object + */ +function fromBodyLegacy(bodyLegacy) { + var content = (typeof bodyLegacy === "string") ? bodyLegacy : bodyLegacy.body; + var contentType = (typeof bodyLegacy === "string") ? "application/sdp" : bodyLegacy.contentType; + var contentDisposition = contentTypeToContentDisposition(contentType); + var body = { contentDisposition: contentDisposition, contentType: contentType, content: content }; + return body; +} +exports.fromBodyLegacy = fromBodyLegacy; +/** + * Given a message, get a normalized body. + * The content disposition is inferred if not set. + * @param message The message. + */ +function getBody(message) { + var contentDisposition; + var contentType; + var content; + // We're in UAS role, receiving incoming request + if (message instanceof incoming_request_message_1.IncomingRequestMessage) { + if (message.body) { + // FIXME: Parsing needs typing + var parse = message.parseHeader("Content-Disposition"); + contentDisposition = parse ? parse.type : undefined; + contentType = message.parseHeader("Content-Type"); + content = message.body; + } + } + // We're in UAC role, receiving incoming response + if (message instanceof incoming_response_message_1.IncomingResponseMessage) { + if (message.body) { + // FIXME: Parsing needs typing + var parse = message.parseHeader("Content-Disposition"); + contentDisposition = parse ? parse.type : undefined; + contentType = message.parseHeader("Content-Type"); + content = message.body; + } + } + // We're in UAC role, sending outgoing request + if (message instanceof outgoing_request_message_1.OutgoingRequestMessage) { + if (message.body) { + contentDisposition = message.getHeader("Content-Disposition"); + contentType = message.getHeader("Content-Type"); + if (typeof message.body === "string") { + // FIXME: OutgoingRequest should not allow a "string" body without a "Content-Type" header. + if (!contentType) { + throw new Error("Header content type header does not equal body content type."); + } + content = message.body; + } + else { + // FIXME: OutgoingRequest should not allow the "Content-Type" header not to match th body content type + if (contentType && contentType !== message.body.contentType) { + throw new Error("Header content type header does not equal body content type."); + } + contentType = message.body.contentType; + content = message.body.body; + } + } + } + // We're in UAS role, sending outgoing response + if (isBody(message)) { + contentDisposition = message.contentDisposition; + contentType = message.contentType; + content = message.content; + } + // No content, no body. + if (!content) { + return undefined; + } + if (contentType && !contentDisposition) { + contentDisposition = contentTypeToContentDisposition(contentType); + } + if (!contentDisposition) { + throw new Error("Content disposition undefined."); + } + if (!contentType) { + throw new Error("Content type undefined."); + } + return { + contentDisposition: contentDisposition, + contentType: contentType, + content: content + }; +} +exports.getBody = getBody; +/** + * User-Defined Type Guard for Body. + * @param body Body to check. + */ +function isBody(body) { + return body && + typeof body.content === "string" && + typeof body.contentType === "string" && + body.contentDisposition === undefined ? true : typeof body.contentDisposition === "string"; +} +exports.isBody = isBody; +// If the Content-Disposition header field is missing, bodies of +// Content-Type application/sdp imply the disposition "session", while +// other content types imply "render". +// https://tools.ietf.org/html/rfc3261#section-13.2.1 +function contentTypeToContentDisposition(contentType) { + if (contentType === "application/sdp") { + return "session"; + } + else { + return "render"; + } +} diff --git a/lib/core/messages/digest-authentication.d.ts b/lib/core/messages/digest-authentication.d.ts new file mode 100644 index 000000000..dafe30a14 --- /dev/null +++ b/lib/core/messages/digest-authentication.d.ts @@ -0,0 +1,50 @@ +import { LoggerFactory } from "../log"; +import { OutgoingRequestMessage } from "./outgoing-request-message"; +/** + * Digest Authentication. + * @internal + */ +export declare class DigestAuthentication { + stale: boolean | undefined; + private logger; + private username; + private password; + private cnonce; + private nc; + private ncHex; + private response; + private algorithm; + private realm; + private nonce; + private opaque; + private qop; + private method; + private uri; + /** + * Constructor. + * @param loggerFactory - LoggerFactory. + * @param username - Username. + * @param password - Password. + */ + constructor(loggerFactory: LoggerFactory, username: string | undefined, password: string | undefined); + /** + * Performs Digest authentication given a SIP request and the challenge + * received in a response to that request. + * @param request - + * @param challenge - + * @returns true if credentials were successfully generated, false otherwise. + */ + authenticate(request: OutgoingRequestMessage, challenge: any, body?: string): boolean; + /** + * Return the Proxy-Authorization or WWW-Authorization header value. + */ + toString(): string; + /** + * Generate the 'nc' value as required by Digest in this.ncHex by reading this.nc. + */ + private updateNcHex; + /** + * Generate Digest 'response' value. + */ + private calculateResponse; +} diff --git a/lib/core/messages/digest-authentication.js b/lib/core/messages/digest-authentication.js new file mode 100644 index 000000000..360f1d6d1 --- /dev/null +++ b/lib/core/messages/digest-authentication.js @@ -0,0 +1,146 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var md5_1 = tslib_1.__importDefault(require("crypto-js/md5")); +var utils_1 = require("./utils"); +/** + * Digest Authentication. + * @internal + */ +var DigestAuthentication = /** @class */ (function () { + /** + * Constructor. + * @param loggerFactory - LoggerFactory. + * @param username - Username. + * @param password - Password. + */ + function DigestAuthentication(loggerFactory, username, password) { + this.logger = loggerFactory.getLogger("sipjs.digestauthentication"); + this.username = username; + this.password = password; + this.nc = 0; + this.ncHex = "00000000"; + } + /** + * Performs Digest authentication given a SIP request and the challenge + * received in a response to that request. + * @param request - + * @param challenge - + * @returns true if credentials were successfully generated, false otherwise. + */ + DigestAuthentication.prototype.authenticate = function (request, challenge, body) { + // Inspect and validate the challenge. + this.algorithm = challenge.algorithm; + this.realm = challenge.realm; + this.nonce = challenge.nonce; + this.opaque = challenge.opaque; + this.stale = challenge.stale; + if (this.algorithm) { + if (this.algorithm !== "MD5") { + this.logger.warn("challenge with Digest algorithm different than 'MD5', authentication aborted"); + return false; + } + } + else { + this.algorithm = "MD5"; + } + if (!this.realm) { + this.logger.warn("challenge without Digest realm, authentication aborted"); + return false; + } + if (!this.nonce) { + this.logger.warn("challenge without Digest nonce, authentication aborted"); + return false; + } + // 'qop' can contain a list of values (Array). Let's choose just one. + if (challenge.qop) { + if (challenge.qop.indexOf("auth") > -1) { + this.qop = "auth"; + } + else if (challenge.qop.indexOf("auth-int") > -1) { + this.qop = "auth-int"; + } + else { + // Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here. + this.logger.warn("challenge without Digest qop different than 'auth' or 'auth-int', authentication aborted"); + return false; + } + } + else { + this.qop = undefined; + } + // Fill other attributes. + this.method = request.method; + this.uri = request.ruri; + this.cnonce = utils_1.createRandomToken(12); + this.nc += 1; + this.updateNcHex(); + // nc-value = 8LHEX. Max value = 'FFFFFFFF'. + if (this.nc === 4294967296) { + this.nc = 1; + this.ncHex = "00000001"; + } + // Calculate the Digest "response" value. + this.calculateResponse(body); + return true; + }; + /** + * Return the Proxy-Authorization or WWW-Authorization header value. + */ + DigestAuthentication.prototype.toString = function () { + var authParams = []; + if (!this.response) { + throw new Error("response field does not exist, cannot generate Authorization header"); + } + authParams.push("algorithm=" + this.algorithm); + authParams.push('username="' + this.username + '"'); + authParams.push('realm="' + this.realm + '"'); + authParams.push('nonce="' + this.nonce + '"'); + authParams.push('uri="' + this.uri + '"'); + authParams.push('response="' + this.response + '"'); + if (this.opaque) { + authParams.push('opaque="' + this.opaque + '"'); + } + if (this.qop) { + authParams.push("qop=" + this.qop); + authParams.push('cnonce="' + this.cnonce + '"'); + authParams.push("nc=" + this.ncHex); + } + return "Digest " + authParams.join(", "); + }; + /** + * Generate the 'nc' value as required by Digest in this.ncHex by reading this.nc. + */ + DigestAuthentication.prototype.updateNcHex = function () { + var hex = Number(this.nc).toString(16); + this.ncHex = "00000000".substr(0, 8 - hex.length) + hex; + }; + /** + * Generate Digest 'response' value. + */ + DigestAuthentication.prototype.calculateResponse = function (body) { + var ha2; + // HA1 = MD5(A1) = MD5(username:realm:password) + var ha1 = md5_1.default(this.username + ":" + this.realm + ":" + this.password); + if (this.qop === "auth") { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = md5_1.default(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth:" + ha2); + } + else if (this.qop === "auth-int") { + // HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody)) + ha2 = md5_1.default(this.method + ":" + this.uri + ":" + md5_1.default(body ? body : "")); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth-int:" + ha2); + } + else if (this.qop === undefined) { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = md5_1.default(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:HA2) + this.response = md5_1.default(ha1 + ":" + this.nonce + ":" + ha2); + } + }; + return DigestAuthentication; +}()); +exports.DigestAuthentication = DigestAuthentication; diff --git a/lib/core/messages/grammar.d.ts b/lib/core/messages/grammar.d.ts new file mode 100644 index 000000000..d9ca32d34 --- /dev/null +++ b/lib/core/messages/grammar.d.ts @@ -0,0 +1,26 @@ +import { NameAddrHeader } from "./name-addr-header"; +import { URI } from "./uri"; +/** + * Grammar. + * @internal + */ +export declare namespace Grammar { + /** + * Parse. + * @param input - + * @param startRule - + */ + function parse(input: string, startRule: string): any; + /** + * Parse the given string and returns a SIP.NameAddrHeader instance or undefined if + * it is an invalid NameAddrHeader. + * @param name_addr_header - + */ + function nameAddrHeaderParse(nameAddrHeader: string): NameAddrHeader | undefined; + /** + * Parse the given string and returns a SIP.URI instance or undefined if + * it is an invalid URI. + * @param uri - + */ + function URIParse(uri: string): URI | undefined; +} diff --git a/lib/core/messages/grammar.js b/lib/core/messages/grammar.js new file mode 100644 index 000000000..fd944feb1 --- /dev/null +++ b/lib/core/messages/grammar.js @@ -0,0 +1,47 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var pegGrammar = tslib_1.__importStar(require("../../grammar/dist/grammar")); +/** + * Grammar. + * @internal + */ +var Grammar; +(function (Grammar) { + /** + * Parse. + * @param input - + * @param startRule - + */ + function parse(input, startRule) { + var options = { startRule: startRule }; + try { + pegGrammar.parse(input, options); + } + catch (e) { + options.data = -1; + } + return options.data; + } + Grammar.parse = parse; + /** + * Parse the given string and returns a SIP.NameAddrHeader instance or undefined if + * it is an invalid NameAddrHeader. + * @param name_addr_header - + */ + function nameAddrHeaderParse(nameAddrHeader) { + var parsedNameAddrHeader = Grammar.parse(nameAddrHeader, "Name_Addr_Header"); + return parsedNameAddrHeader !== -1 ? parsedNameAddrHeader : undefined; + } + Grammar.nameAddrHeaderParse = nameAddrHeaderParse; + /** + * Parse the given string and returns a SIP.URI instance or undefined if + * it is an invalid URI. + * @param uri - + */ + function URIParse(uri) { + var parsedUri = Grammar.parse(uri, "SIP_URI"); + return parsedUri !== -1 ? parsedUri : undefined; + } + Grammar.URIParse = URIParse; +})(Grammar = exports.Grammar || (exports.Grammar = {})); diff --git a/lib/core/messages/incoming-message.d.ts b/lib/core/messages/incoming-message.d.ts new file mode 100644 index 000000000..089fcb562 --- /dev/null +++ b/lib/core/messages/incoming-message.d.ts @@ -0,0 +1,79 @@ +import { NameAddrHeader } from "./name-addr-header"; +/** + * Incoming SIP message. + * @public + */ +export declare class IncomingMessage { + viaBranch: string; + method: string; + body: string; + toTag: string; + to: NameAddrHeader; + fromTag: string; + from: NameAddrHeader; + callId: string; + cseq: number; + via: { + host: string; + port: number; + }; + headers: { + [name: string]: Array<{ + parsed?: any; + raw: string; + }>; + }; + referTo: string | undefined; + data: string; + /** + * Insert a header of the given name and value into the last position of the + * header array. + * @param name - header name + * @param value - header value + */ + addHeader(name: string, value: string): void; + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + getHeader(name: string): string | undefined; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array - with all the headers of the specified name. + */ + getHeaders(name: string): Array; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + hasHeader(name: string): boolean; + /** + * Parse the given header on the given index. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + */ + parseHeader(name: string, idx?: number): any | undefined; + /** + * Message Header attribute selector. Alias of parseHeader. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + * + * @example + * message.s('via',3).port + */ + s(name: string, idx?: number): any | undefined; + /** + * Replace the value of the given header by the value. + * @param name - header name + * @param value - header value + */ + setHeader(name: string, value: string): void; + toString(): string; +} diff --git a/lib/core/messages/incoming-message.js b/lib/core/messages/incoming-message.js new file mode 100644 index 000000000..a9a0b6981 --- /dev/null +++ b/lib/core/messages/incoming-message.js @@ -0,0 +1,132 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var grammar_1 = require("./grammar"); +var utils_1 = require("./utils"); +/** + * Incoming SIP message. + * @public + */ +var IncomingMessage = /** @class */ (function () { + function IncomingMessage() { + this.headers = {}; + } + /** + * Insert a header of the given name and value into the last position of the + * header array. + * @param name - header name + * @param value - header value + */ + IncomingMessage.prototype.addHeader = function (name, value) { + var header = { raw: value }; + name = utils_1.headerize(name); + if (this.headers[name]) { + this.headers[name].push(header); + } + else { + this.headers[name] = [header]; + } + }; + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + IncomingMessage.prototype.getHeader = function (name) { + var header = this.headers[utils_1.headerize(name)]; + if (header) { + if (header[0]) { + return header[0].raw; + } + } + else { + return; + } + }; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array - with all the headers of the specified name. + */ + IncomingMessage.prototype.getHeaders = function (name) { + var header = this.headers[utils_1.headerize(name)]; + var result = []; + if (!header) { + return []; + } + for (var _i = 0, header_1 = header; _i < header_1.length; _i++) { + var headerPart = header_1[_i]; + result.push(headerPart.raw); + } + return result; + }; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + IncomingMessage.prototype.hasHeader = function (name) { + return !!this.headers[utils_1.headerize(name)]; + }; + /** + * Parse the given header on the given index. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + */ + IncomingMessage.prototype.parseHeader = function (name, idx) { + if (idx === void 0) { idx = 0; } + name = utils_1.headerize(name); + if (!this.headers[name]) { + // this.logger.log("header '" + name + "' not present"); + return; + } + else if (idx >= this.headers[name].length) { + // this.logger.log("not so many '" + name + "' headers present"); + return; + } + var header = this.headers[name][idx]; + var value = header.raw; + if (header.parsed) { + return header.parsed; + } + // substitute '-' by '_' for grammar rule matching. + var parsed = grammar_1.Grammar.parse(value, name.replace(/-/g, "_")); + if (parsed === -1) { + this.headers[name].splice(idx, 1); // delete from headers + // this.logger.warn('error parsing "' + name + '" header field with value "' + value + '"'); + return; + } + else { + header.parsed = parsed; + return parsed; + } + }; + /** + * Message Header attribute selector. Alias of parseHeader. + * @param name - header name + * @param idx - header index + * @returns Parsed header object, undefined if the + * header is not present or in case of a parsing error. + * + * @example + * message.s('via',3).port + */ + IncomingMessage.prototype.s = function (name, idx) { + if (idx === void 0) { idx = 0; } + return this.parseHeader(name, idx); + }; + /** + * Replace the value of the given header by the value. + * @param name - header name + * @param value - header value + */ + IncomingMessage.prototype.setHeader = function (name, value) { + this.headers[utils_1.headerize(name)] = [{ raw: value }]; + }; + IncomingMessage.prototype.toString = function () { + return this.data; + }; + return IncomingMessage; +}()); +exports.IncomingMessage = IncomingMessage; diff --git a/lib/core/messages/incoming-request-message.d.ts b/lib/core/messages/incoming-request-message.d.ts new file mode 100644 index 000000000..fe2cdc1f0 --- /dev/null +++ b/lib/core/messages/incoming-request-message.d.ts @@ -0,0 +1,9 @@ +import { URI } from "../messages"; +import { IncomingMessage } from "./incoming-message"; +/** + * Incoming SIP request message. + */ +export declare class IncomingRequestMessage extends IncomingMessage { + ruri: URI | undefined; + constructor(); +} diff --git a/lib/core/messages/incoming-request-message.js b/lib/core/messages/incoming-request-message.js new file mode 100644 index 000000000..877d35d16 --- /dev/null +++ b/lib/core/messages/incoming-request-message.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var incoming_message_1 = require("./incoming-message"); +/** + * Incoming SIP request message. + */ +var IncomingRequestMessage = /** @class */ (function (_super) { + tslib_1.__extends(IncomingRequestMessage, _super); + function IncomingRequestMessage() { + return _super.call(this) || this; + } + return IncomingRequestMessage; +}(incoming_message_1.IncomingMessage)); +exports.IncomingRequestMessage = IncomingRequestMessage; diff --git a/lib/core/messages/incoming-request.d.ts b/lib/core/messages/incoming-request.d.ts new file mode 100644 index 000000000..9023e4351 --- /dev/null +++ b/lib/core/messages/incoming-request.d.ts @@ -0,0 +1,62 @@ +import { TransportError } from "../exceptions"; +import { URI } from "../messages"; +import { IncomingRequestMessage } from "./incoming-request-message"; +import { OutgoingResponse, ResponseOptions } from "./outgoing-response"; +/** + * A SIP message sent from a remote client to a local server, + * for the purpose of invoking a particular operation. + * https://tools.ietf.org/html/rfc3261#section-7.1 + */ +export interface IncomingRequest { + /** Delegate providing custom handling of this incoming request. */ + delegate?: IncomingRequestDelegate; + /** The incoming message. */ + readonly message: IncomingRequestMessage; + /** + * Send a 2xx positive final response to this request. Defaults to 200. + * @param options Response options bucket. + */ + accept(options?: ResponseOptions): OutgoingResponse; + /** + * Send a 1xx provisional response to this request. Defaults to 180. Excludes 100. + * Note that per RFC 4320, this method may only be used to respond to INVITE requests. + * @param options Response options bucket. + */ + progress(options?: ResponseOptions): OutgoingResponse; + /** + * Send a 3xx negative final response to this request. Defaults to 302. + * @param contacts Contacts to redirect the UAC to. + * @param options Response options bucket. + */ + redirect(contacts: Array, options?: ResponseOptions): OutgoingResponse; + /** + * Send a 4xx, 5xx, or 6xx negative final response to this request. Defaults to 480. + * @param options Response options bucket. + */ + reject(options?: ResponseOptions): OutgoingResponse; + /** + * Send a 100 outgoing response to this request. + * @param options Response options bucket. + */ + trying(options?: ResponseOptions): OutgoingResponse; +} +/** Delegate providing custom handling of incoming requests. */ +export interface IncomingRequestDelegate { + /** + * Receive CANCEL request. + * https://tools.ietf.org/html/rfc3261#section-9.2 + * Note: Currently CANCEL is being handled as a special case. + * No UAS is created to handle the CANCEL and the response to + * it CANCEL is being handled statelessly by the user agent core. + * As such, there is currently no way to externally impact the + * response to the a CANCEL request and thus the method here is + * receiving a "message" (as apposed to a "uas"). + * @param message Incoming CANCEL request message. + */ + onCancel?(message: IncomingRequestMessage): void; + /** + * A transport error occurred attempted to send a response. + * @param error Transport error. + */ + onTransportError?(error: TransportError): void; +} diff --git a/lib/core/messages/incoming-request.js b/lib/core/messages/incoming-request.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/incoming-request.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/incoming-response-message.d.ts b/lib/core/messages/incoming-response-message.d.ts new file mode 100644 index 000000000..1d12d2229 --- /dev/null +++ b/lib/core/messages/incoming-response-message.d.ts @@ -0,0 +1,9 @@ +import { IncomingMessage } from "./incoming-message"; +/** + * Incoming SIP response message. + */ +export declare class IncomingResponseMessage extends IncomingMessage { + statusCode: number | undefined; + reasonPhrase: string | undefined; + constructor(); +} diff --git a/lib/core/messages/incoming-response-message.js b/lib/core/messages/incoming-response-message.js new file mode 100644 index 000000000..192cf13b5 --- /dev/null +++ b/lib/core/messages/incoming-response-message.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var incoming_message_1 = require("./incoming-message"); +/** + * Incoming SIP response message. + */ +var IncomingResponseMessage = /** @class */ (function (_super) { + tslib_1.__extends(IncomingResponseMessage, _super); + function IncomingResponseMessage() { + var _this = _super.call(this) || this; + _this.headers = {}; + return _this; + } + return IncomingResponseMessage; +}(incoming_message_1.IncomingMessage)); +exports.IncomingResponseMessage = IncomingResponseMessage; diff --git a/lib/core/messages/incoming-response.d.ts b/lib/core/messages/incoming-response.d.ts new file mode 100644 index 000000000..588efb961 --- /dev/null +++ b/lib/core/messages/incoming-response.d.ts @@ -0,0 +1,11 @@ +import { IncomingResponseMessage } from "./incoming-response-message"; +/** + * A SIP message sent from a remote server to a local client, + * for indicating the status of a request sent from the + * client to the server. + * https://tools.ietf.org/html/rfc3261#section-7.2 + */ +export interface IncomingResponse { + /** The incoming message. */ + readonly message: IncomingResponseMessage; +} diff --git a/lib/core/messages/incoming-response.js b/lib/core/messages/incoming-response.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/incoming-response.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/index.d.ts b/lib/core/messages/index.d.ts new file mode 100644 index 000000000..6eadaa6e0 --- /dev/null +++ b/lib/core/messages/index.d.ts @@ -0,0 +1,15 @@ +export * from "./methods"; +export * from "./body"; +export * from "./digest-authentication"; +export * from "./grammar"; +export * from "./incoming-message"; +export * from "./incoming-request-message"; +export * from "./incoming-request"; +export * from "./incoming-response-message"; +export * from "./incoming-response"; +export * from "./name-addr-header"; +export * from "./outgoing-request-message"; +export * from "./outgoing-request"; +export * from "./outgoing-response"; +export * from "./parameters"; +export * from "./uri"; diff --git a/lib/core/messages/index.js b/lib/core/messages/index.js new file mode 100644 index 000000000..9fcd15f62 --- /dev/null +++ b/lib/core/messages/index.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +// Directories +tslib_1.__exportStar(require("./methods"), exports); +// Files +tslib_1.__exportStar(require("./body"), exports); +tslib_1.__exportStar(require("./digest-authentication"), exports); +tslib_1.__exportStar(require("./grammar"), exports); +tslib_1.__exportStar(require("./incoming-message"), exports); +tslib_1.__exportStar(require("./incoming-request-message"), exports); +tslib_1.__exportStar(require("./incoming-response-message"), exports); +tslib_1.__exportStar(require("./name-addr-header"), exports); +tslib_1.__exportStar(require("./outgoing-request-message"), exports); +tslib_1.__exportStar(require("./outgoing-response"), exports); +tslib_1.__exportStar(require("./parameters"), exports); +tslib_1.__exportStar(require("./uri"), exports); diff --git a/lib/core/messages/methods/ack.d.ts b/lib/core/messages/methods/ack.d.ts new file mode 100644 index 000000000..9aca2ea60 --- /dev/null +++ b/lib/core/messages/methods/ack.d.ts @@ -0,0 +1,12 @@ +import { IncomingRequestMessage } from "../incoming-request-message"; +import { OutgoingRequestMessage } from "../outgoing-request-message"; +/** ACK message sent from remote client to local server. */ +export interface IncomingAckRequest { + /** The incoming message. */ + readonly message: IncomingRequestMessage; +} +/** ACK message sent from local client to remote server. */ +export interface OutgoingAckRequest { + /** The outgoing message. */ + readonly message: OutgoingRequestMessage; +} diff --git a/lib/core/messages/methods/ack.js b/lib/core/messages/methods/ack.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/ack.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/bye.d.ts b/lib/core/messages/methods/bye.d.ts new file mode 100644 index 000000000..bac866b66 --- /dev/null +++ b/lib/core/messages/methods/bye.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingByeRequest extends IncomingRequest { +} +export interface IncomingByeResponse extends IncomingResponse { +} +export interface OutgoingByeRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/bye.js b/lib/core/messages/methods/bye.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/bye.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/cancel.d.ts b/lib/core/messages/methods/cancel.d.ts new file mode 100644 index 000000000..d7965a204 --- /dev/null +++ b/lib/core/messages/methods/cancel.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingCancelRequest extends IncomingRequest { +} +export interface IncomingCancelResponse extends IncomingResponse { +} +export interface OutgoingCancelRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/cancel.js b/lib/core/messages/methods/cancel.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/cancel.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/constants.d.ts b/lib/core/messages/methods/constants.d.ts new file mode 100644 index 000000000..5cfb174d0 --- /dev/null +++ b/lib/core/messages/methods/constants.d.ts @@ -0,0 +1,20 @@ +/** + * SIP Methods + * @internal + */ +export declare namespace C { + const ACK = "ACK"; + const BYE = "BYE"; + const CANCEL = "CANCEL"; + const INFO = "INFO"; + const INVITE = "INVITE"; + const MESSAGE = "MESSAGE"; + const NOTIFY = "NOTIFY"; + const OPTIONS = "OPTIONS"; + const REGISTER = "REGISTER"; + const UPDATE = "UPDATE"; + const SUBSCRIBE = "SUBSCRIBE"; + const PUBLISH = "PUBLISH"; + const REFER = "REFER"; + const PRACK = "PRACK"; +} diff --git a/lib/core/messages/methods/constants.js b/lib/core/messages/methods/constants.js new file mode 100644 index 000000000..e71d0b379 --- /dev/null +++ b/lib/core/messages/methods/constants.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * SIP Methods + * @internal + */ +var C; +(function (C) { + C.ACK = "ACK"; + C.BYE = "BYE"; + C.CANCEL = "CANCEL"; + C.INFO = "INFO"; + C.INVITE = "INVITE"; + C.MESSAGE = "MESSAGE"; + C.NOTIFY = "NOTIFY"; + C.OPTIONS = "OPTIONS"; + C.REGISTER = "REGISTER"; + C.UPDATE = "UPDATE"; + C.SUBSCRIBE = "SUBSCRIBE"; + C.PUBLISH = "PUBLISH"; + C.REFER = "REFER"; + C.PRACK = "PRACK"; +})(C = exports.C || (exports.C = {})); diff --git a/lib/core/messages/methods/index.d.ts b/lib/core/messages/methods/index.d.ts new file mode 100644 index 000000000..f60ff2931 --- /dev/null +++ b/lib/core/messages/methods/index.d.ts @@ -0,0 +1,13 @@ +export * from "./constants"; +export * from "./ack"; +export * from "./bye"; +export * from "./cancel"; +export * from "./info"; +export * from "./invite"; +export * from "./message"; +export * from "./notify"; +export * from "./prack"; +export * from "./publish"; +export * from "./register"; +export * from "./refer"; +export * from "./subscribe"; diff --git a/lib/core/messages/methods/index.js b/lib/core/messages/methods/index.js new file mode 100644 index 000000000..d5d39c544 --- /dev/null +++ b/lib/core/messages/methods/index.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./constants"), exports); diff --git a/lib/core/messages/methods/info.d.ts b/lib/core/messages/methods/info.d.ts new file mode 100644 index 000000000..0aed392b7 --- /dev/null +++ b/lib/core/messages/methods/info.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingInfoRequest extends IncomingRequest { +} +export interface IncomingInfoResponse extends IncomingResponse { +} +export interface OutgoingInfoRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/info.js b/lib/core/messages/methods/info.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/info.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/invite.d.ts b/lib/core/messages/methods/invite.d.ts new file mode 100644 index 000000000..e57ec0f6d --- /dev/null +++ b/lib/core/messages/methods/invite.d.ts @@ -0,0 +1,70 @@ +import { Session } from "../../session"; +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest, OutgoingRequestDelegate, RequestOptions } from "../outgoing-request"; +import { OutgoingResponse, ResponseOptions } from "../outgoing-response"; +import { OutgoingAckRequest } from "./ack"; +import { OutgoingPrackRequest } from "./prack"; +/** INVITE message sent from remote client to local server. */ +export interface IncomingInviteRequest extends IncomingRequest { + /** + * Send a 2xx positive final response to this request. Defaults to 200. + * @param options Response options bucket. + * @returns Outgoing response and a confirmed Session. + */ + accept(options?: ResponseOptions): OutgoingResponseWithSession; + /** + * Send a 1xx provisional response to this request. Defaults to 180. Excludes 100. + * @param options Response options bucket. + * @returns Outgoing response and an early Session. + */ + progress(options?: ResponseOptions): OutgoingResponseWithSession; +} +/** Response received when accepting an incoming INVITE request. */ +export interface OutgoingResponseWithSession extends OutgoingResponse { + /** Session associated with incoming request acceptance. */ + readonly session: Session; +} +/** Response received when progressing an incoming INVITE request. */ +export interface OutgoingResponseWithSession extends OutgoingResponse { + /** Session associated with incoming request progress. If out of dialog request, an early dialog. */ + readonly session: Session; +} +/** INVITE message sent from local client to remote server. */ +export interface OutgoingInviteRequest extends OutgoingRequest { + /** Delegate providing custom handling of this outgoing INVITE request. */ + delegate?: OutgoingInviteRequestDelegate; +} +/** Delegate providing custom handling of outgoing INVITE requests. */ +export interface OutgoingInviteRequestDelegate extends OutgoingRequestDelegate { + /** + * Received a 2xx positive final response to this request. + * @param response Incoming response (including a confirmed Session). + */ + onAccept?(response: AckableIncomingResponseWithSession): void; + /** + * Received a 1xx provisional response to this request. Excluding 100 responses. + * @param response Incoming response (including an early Session). + */ + onProgress?(response: PrackableIncomingResponseWithSession): void; +} +/** Response received when an outgoing INVITE request is accepted. */ +export interface AckableIncomingResponseWithSession extends IncomingResponse { + /** Session associated with outgoing request acceptance. */ + readonly session: Session; + /** + * Send an ACK to acknowledge this response. + * @param options Request options bucket. + */ + ack(options?: RequestOptions): OutgoingAckRequest; +} +/** Response received when an outgoing INVITE request is progressed. */ +export interface PrackableIncomingResponseWithSession extends IncomingResponse { + /** Session associated with outgoing request progress. If out of dialog request, an early dialog. */ + readonly session: Session; + /** + * Send an PRACK to acknowledge this response. + * @param options Request options bucket. + */ + prack(options?: RequestOptions): OutgoingPrackRequest; +} diff --git a/lib/core/messages/methods/invite.js b/lib/core/messages/methods/invite.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/invite.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/message.d.ts b/lib/core/messages/methods/message.d.ts new file mode 100644 index 000000000..5965fd57f --- /dev/null +++ b/lib/core/messages/methods/message.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingMessageRequest extends IncomingRequest { +} +export interface IncomingMessageResponse extends IncomingResponse { +} +export interface OutgoingMessageRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/message.js b/lib/core/messages/methods/message.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/message.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/notify.d.ts b/lib/core/messages/methods/notify.d.ts new file mode 100644 index 000000000..cd25b4980 --- /dev/null +++ b/lib/core/messages/methods/notify.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingNotifyRequest extends IncomingRequest { +} +export interface IncomingNotifyResponse extends IncomingResponse { +} +export interface OutgoingNotifyRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/notify.js b/lib/core/messages/methods/notify.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/notify.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/prack.d.ts b/lib/core/messages/methods/prack.d.ts new file mode 100644 index 000000000..e87dbb190 --- /dev/null +++ b/lib/core/messages/methods/prack.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingPrackRequest extends IncomingRequest { +} +export interface IncomingPrackResponse extends IncomingResponse { +} +export interface OutgoingPrackRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/prack.js b/lib/core/messages/methods/prack.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/prack.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/publish.d.ts b/lib/core/messages/methods/publish.d.ts new file mode 100644 index 000000000..9a115746b --- /dev/null +++ b/lib/core/messages/methods/publish.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingPublishRequest extends IncomingRequest { +} +export interface IncomingPublishResponse extends IncomingResponse { +} +export interface OutgoingPublishRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/publish.js b/lib/core/messages/methods/publish.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/publish.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/refer.d.ts b/lib/core/messages/methods/refer.d.ts new file mode 100644 index 000000000..51ad80664 --- /dev/null +++ b/lib/core/messages/methods/refer.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingReferRequest extends IncomingRequest { +} +export interface IncomingReferResponse extends IncomingResponse { +} +export interface OutgoingReferRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/refer.js b/lib/core/messages/methods/refer.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/refer.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/register.d.ts b/lib/core/messages/methods/register.d.ts new file mode 100644 index 000000000..5f2cf6924 --- /dev/null +++ b/lib/core/messages/methods/register.d.ts @@ -0,0 +1,9 @@ +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest } from "../outgoing-request"; +export interface IncomingRegisterRequest extends IncomingRequest { +} +export interface IncomingRegisterResponse extends IncomingResponse { +} +export interface OutgoingRegisterRequest extends OutgoingRequest { +} diff --git a/lib/core/messages/methods/register.js b/lib/core/messages/methods/register.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/register.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/methods/subscribe.d.ts b/lib/core/messages/methods/subscribe.d.ts new file mode 100644 index 000000000..410ecfe3c --- /dev/null +++ b/lib/core/messages/methods/subscribe.d.ts @@ -0,0 +1,36 @@ +import { Subscription } from "../../subscription"; +import { IncomingRequest } from "../incoming-request"; +import { IncomingResponse } from "../incoming-response"; +import { OutgoingRequest, OutgoingRequestDelegate } from "../outgoing-request"; +import { IncomingNotifyRequest } from "./notify"; +export interface IncomingSubscribeRequest extends IncomingRequest { +} +export interface IncomingSubscribeResponse extends IncomingResponse { +} +/** SUBSCRIBE message sent from local client to remote server. */ +export interface OutgoingSubscribeRequest extends OutgoingRequest { + /** Delegate providing custom handling of this outgoing SUBSCRIBE request. */ + delegate?: OutgoingSubscribeRequestDelegate; + /** Stop waiting for an inital subscription creating NOTIFY. */ + waitNotifyStop(): void; +} +/** Delegate providing custom handling of outgoing SUBSCRIBE requests. */ +export interface OutgoingSubscribeRequestDelegate extends OutgoingRequestDelegate { + /** + * Received the initial subscription creating NOTIFY in response to this request. + * Called for out of dialog SUBSCRIBE requests only (not called for re-SUBSCRIBE requests). + * @param request Incoming NOTIFY request (including a Subscription). + */ + onNotify?(request: IncomingRequestWithSubscription): void; + /** + * Timed out waiting to receive the initial subscription creating NOTIFY in response to this request. + * Called for out of dialog SUBSCRIBE requests only (not called for re-SUBSCRIBE requests). + */ + onNotifyTimeout?(): void; +} +export interface IncomingRequestWithSubscription { + /** The NOTIFY request which established the subscription. */ + readonly request: IncomingNotifyRequest; + /** If subscription state is not "terminated", then the subscription. Otherwise undefined. */ + readonly subscription?: Subscription; +} diff --git a/lib/core/messages/methods/subscribe.js b/lib/core/messages/methods/subscribe.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/methods/subscribe.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/name-addr-header.d.ts b/lib/core/messages/name-addr-header.d.ts new file mode 100644 index 000000000..c6423e710 --- /dev/null +++ b/lib/core/messages/name-addr-header.d.ts @@ -0,0 +1,23 @@ +import { Parameters } from "./parameters"; +import { URI } from "./uri"; +/** + * Name Address SIP header. + * @public + */ +export declare class NameAddrHeader extends Parameters { + uri: URI; + private _displayName; + /** + * Constructor + * @param uri + * @param displayName + * @param parameters + */ + constructor(uri: URI, displayName: string, parameters: { + [name: string]: string; + }); + readonly friendlyName: string; + displayName: string; + clone(): NameAddrHeader; + toString(): string; +} diff --git a/lib/core/messages/name-addr-header.js b/lib/core/messages/name-addr-header.js new file mode 100644 index 000000000..a2e136b70 --- /dev/null +++ b/lib/core/messages/name-addr-header.js @@ -0,0 +1,56 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var parameters_1 = require("./parameters"); +/** + * Name Address SIP header. + * @public + */ +var NameAddrHeader = /** @class */ (function (_super) { + tslib_1.__extends(NameAddrHeader, _super); + /** + * Constructor + * @param uri + * @param displayName + * @param parameters + */ + function NameAddrHeader(uri, displayName, parameters) { + var _this = _super.call(this, parameters) || this; + _this.uri = uri; + _this._displayName = displayName; + return _this; + } + Object.defineProperty(NameAddrHeader.prototype, "friendlyName", { + get: function () { + return this.displayName || this.uri.aor; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(NameAddrHeader.prototype, "displayName", { + get: function () { return this._displayName; }, + set: function (value) { + this._displayName = value; + }, + enumerable: true, + configurable: true + }); + NameAddrHeader.prototype.clone = function () { + return new NameAddrHeader(this.uri.clone(), this._displayName, JSON.parse(JSON.stringify(this.parameters))); + }; + NameAddrHeader.prototype.toString = function () { + var body = (this.displayName || this.displayName === "0") ? '"' + this.displayName + '" ' : ""; + body += "<" + this.uri.toString() + ">"; + for (var parameter in this.parameters) { + if (this.parameters.hasOwnProperty(parameter)) { + body += ";" + parameter; + if (this.parameters[parameter] !== null) { + body += "=" + this.parameters[parameter]; + } + } + } + return body; + }; + return NameAddrHeader; +}(parameters_1.Parameters)); +exports.NameAddrHeader = NameAddrHeader; diff --git a/lib/core/messages/outgoing-request-message.d.ts b/lib/core/messages/outgoing-request-message.d.ts new file mode 100644 index 000000000..47c6602db --- /dev/null +++ b/lib/core/messages/outgoing-request-message.d.ts @@ -0,0 +1,95 @@ +import { Body } from "./body"; +import { NameAddrHeader } from "./name-addr-header"; +import { URI } from "./uri"; +/** + * Outgoing SIP request message options. + * @public + */ +export interface OutgoingRequestMessageOptions { + callId?: string; + callIdPrefix?: string; + cseq?: number; + toDisplayName?: string; + toTag?: string; + fromDisplayName?: string; + fromTag?: string; + forceRport?: boolean; + hackViaTcp?: boolean; + optionTags?: Array; + routeSet?: Array; + userAgentString?: string; + viaHost?: string; +} +/** + * Outgoing SIP request message. + * @public + */ +export declare class OutgoingRequestMessage { + /** Get a copy of the default options. */ + private static getDefaultOptions; + private static makeNameAddrHeader; + readonly headers: { + [name: string]: Array; + }; + readonly method: string; + readonly ruri: URI; + readonly from: NameAddrHeader; + readonly fromTag: string; + readonly fromURI: URI; + readonly to: NameAddrHeader; + readonly toTag: string | undefined; + readonly toURI: URI; + branch: string | undefined; + readonly callId: string; + cseq: number; + extraHeaders: Array; + body: { + body: string; + contentType: string; + } | undefined; + private options; + constructor(method: string, ruri: URI, fromURI: URI, toURI: URI, options?: OutgoingRequestMessageOptions, extraHeaders?: Array, body?: Body); + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + getHeader(name: string): string | undefined; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array with all the headers of the specified name. + */ + getHeaders(name: string): Array; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + hasHeader(name: string): boolean; + /** + * Replace the the given header by the given value. + * @param name - header name + * @param value - header value + */ + setHeader(name: string, value: string | Array): void; + /** + * The Via header field indicates the transport used for the transaction + * and identifies the location where the response is to be sent. A Via + * header field value is added only after the transport that will be + * used to reach the next hop has been selected (which may involve the + * usage of the procedures in [4]). + * + * When the UAC creates a request, it MUST insert a Via into that + * request. The protocol name and protocol version in the header field + * MUST be SIP and 2.0, respectively. The Via header field value MUST + * contain a branch parameter. This parameter is used to identify the + * transaction created by that request. This parameter is used by both + * the client and the server. + * https://tools.ietf.org/html/rfc3261#section-8.1.1.7 + * @param branchParameter - The branch parameter. + * @param scheme - The scheme. + */ + setViaHeader(branch: string, scheme?: string): void; + toString(): string; +} diff --git a/lib/core/messages/outgoing-request-message.js b/lib/core/messages/outgoing-request-message.js new file mode 100644 index 000000000..5033ca908 --- /dev/null +++ b/lib/core/messages/outgoing-request-message.js @@ -0,0 +1,240 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var name_addr_header_1 = require("./name-addr-header"); +var utils_1 = require("./utils"); +/** + * Outgoing SIP request message. + * @public + */ +var OutgoingRequestMessage = /** @class */ (function () { + function OutgoingRequestMessage(method, ruri, fromURI, toURI, options, extraHeaders, body) { + this.headers = {}; + this.extraHeaders = []; + this.options = OutgoingRequestMessage.getDefaultOptions(); + // Options - merge a deep copy + if (options) { + this.options = tslib_1.__assign({}, this.options, options); + if (this.options.optionTags && this.options.optionTags.length) { + this.options.optionTags = this.options.optionTags.slice(); + } + if (this.options.routeSet && this.options.routeSet.length) { + this.options.routeSet = this.options.routeSet.slice(); + } + } + // Extra headers - deep copy + if (extraHeaders && extraHeaders.length) { + this.extraHeaders = extraHeaders.slice(); + } + // Body - deep copy + if (body) { + // TODO: internal representation should be Body + // this.body = { ...body }; + this.body = { + body: body.content, + contentType: body.contentType + }; + } + // Method + this.method = method; + // RURI + this.ruri = ruri.clone(); + // From + this.fromURI = fromURI.clone(); + this.fromTag = this.options.fromTag ? this.options.fromTag : utils_1.newTag(); + this.from = OutgoingRequestMessage.makeNameAddrHeader(this.fromURI, this.options.fromDisplayName, this.fromTag); + // To + this.toURI = toURI.clone(); + this.toTag = this.options.toTag; + this.to = OutgoingRequestMessage.makeNameAddrHeader(this.toURI, this.options.toDisplayName, this.toTag); + // Call-ID + this.callId = this.options.callId ? this.options.callId : this.options.callIdPrefix + utils_1.createRandomToken(15); + // CSeq + this.cseq = this.options.cseq; + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + this.setHeader("route", this.options.routeSet); + this.setHeader("via", ""); + this.setHeader("to", this.to.toString()); + this.setHeader("from", this.from.toString()); + this.setHeader("cseq", this.cseq + " " + this.method); + this.setHeader("call-id", this.callId); + this.setHeader("max-forwards", "70"); + } + /** Get a copy of the default options. */ + OutgoingRequestMessage.getDefaultOptions = function () { + return { + callId: "", + callIdPrefix: "", + cseq: 1, + toDisplayName: "", + toTag: "", + fromDisplayName: "", + fromTag: "", + forceRport: false, + hackViaTcp: false, + optionTags: ["outbound"], + routeSet: [], + userAgentString: "sip.js", + viaHost: "" + }; + }; + OutgoingRequestMessage.makeNameAddrHeader = function (uri, displayName, tag) { + var parameters = {}; + if (tag) { + parameters.tag = tag; + } + return new name_addr_header_1.NameAddrHeader(uri, displayName, parameters); + }; + /** + * Get the value of the given header name at the given position. + * @param name - header name + * @returns Returns the specified header, undefined if header doesn't exist. + */ + OutgoingRequestMessage.prototype.getHeader = function (name) { + var header = this.headers[utils_1.headerize(name)]; + if (header) { + if (header[0]) { + return header[0]; + } + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _i = 0, _a = this.extraHeaders; _i < _a.length; _i++) { + var exHeader = _a[_i]; + if (regexp.test(exHeader)) { + return exHeader.substring(exHeader.indexOf(":") + 1).trim(); + } + } + } + return; + }; + /** + * Get the header/s of the given name. + * @param name - header name + * @returns Array with all the headers of the specified name. + */ + OutgoingRequestMessage.prototype.getHeaders = function (name) { + var result = []; + var headerArray = this.headers[utils_1.headerize(name)]; + if (headerArray) { + for (var _i = 0, headerArray_1 = headerArray; _i < headerArray_1.length; _i++) { + var headerPart = headerArray_1[_i]; + result.push(headerPart); + } + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _a = 0, _b = this.extraHeaders; _a < _b.length; _a++) { + var exHeader = _b[_a]; + if (regexp.test(exHeader)) { + result.push(exHeader.substring(exHeader.indexOf(":") + 1).trim()); + } + } + } + return result; + }; + /** + * Verify the existence of the given header. + * @param name - header name + * @returns true if header with given name exists, false otherwise + */ + OutgoingRequestMessage.prototype.hasHeader = function (name) { + if (this.headers[utils_1.headerize(name)]) { + return true; + } + else { + var regexp = new RegExp("^\\s*" + name + "\\s*:", "i"); + for (var _i = 0, _a = this.extraHeaders; _i < _a.length; _i++) { + var extraHeader = _a[_i]; + if (regexp.test(extraHeader)) { + return true; + } + } + } + return false; + }; + /** + * Replace the the given header by the given value. + * @param name - header name + * @param value - header value + */ + OutgoingRequestMessage.prototype.setHeader = function (name, value) { + this.headers[utils_1.headerize(name)] = (value instanceof Array) ? value : [value]; + }; + /** + * The Via header field indicates the transport used for the transaction + * and identifies the location where the response is to be sent. A Via + * header field value is added only after the transport that will be + * used to reach the next hop has been selected (which may involve the + * usage of the procedures in [4]). + * + * When the UAC creates a request, it MUST insert a Via into that + * request. The protocol name and protocol version in the header field + * MUST be SIP and 2.0, respectively. The Via header field value MUST + * contain a branch parameter. This parameter is used to identify the + * transaction created by that request. This parameter is used by both + * the client and the server. + * https://tools.ietf.org/html/rfc3261#section-8.1.1.7 + * @param branchParameter - The branch parameter. + * @param scheme - The scheme. + */ + OutgoingRequestMessage.prototype.setViaHeader = function (branch, scheme) { + if (scheme === void 0) { scheme = "WSS"; } + // FIXME: Hack + if (this.options.hackViaTcp) { + scheme = "TCP"; + } + var via = "SIP/2.0/" + scheme; + via += " " + this.options.viaHost + ";branch=" + branch; + if (this.options.forceRport) { + via += ";rport"; + } + this.setHeader("via", via); + this.branch = branch; + }; + OutgoingRequestMessage.prototype.toString = function () { + var msg = ""; + msg += this.method + " " + this.ruri.toRaw() + " SIP/2.0\r\n"; + for (var header in this.headers) { + if (this.headers[header]) { + for (var _i = 0, _a = this.headers[header]; _i < _a.length; _i++) { + var headerPart = _a[_i]; + msg += header + ": " + headerPart + "\r\n"; + } + } + } + for (var _b = 0, _c = this.extraHeaders; _b < _c.length; _b++) { + var header = _c[_b]; + msg += header.trim() + "\r\n"; + } + msg += "Supported: " + this.options.optionTags.join(", ") + "\r\n"; + msg += "User-Agent: " + this.options.userAgentString + "\r\n"; + if (this.body) { + if (typeof this.body === "string") { + msg += "Content-Length: " + utils_1.str_utf8_length(this.body) + "\r\n\r\n"; + msg += this.body; + } + else { + if (this.body.body && this.body.contentType) { + msg += "Content-Type: " + this.body.contentType + "\r\n"; + msg += "Content-Length: " + utils_1.str_utf8_length(this.body.body) + "\r\n\r\n"; + msg += this.body.body; + } + else { + msg += "Content-Length: " + 0 + "\r\n\r\n"; + } + } + } + else { + msg += "Content-Length: " + 0 + "\r\n\r\n"; + } + return msg; + }; + return OutgoingRequestMessage; +}()); +exports.OutgoingRequestMessage = OutgoingRequestMessage; diff --git a/lib/core/messages/outgoing-request.d.ts b/lib/core/messages/outgoing-request.d.ts new file mode 100644 index 000000000..ba8f2caec --- /dev/null +++ b/lib/core/messages/outgoing-request.d.ts @@ -0,0 +1,59 @@ +import { Body } from "./body"; +import { IncomingResponse } from "./incoming-response"; +import { OutgoingRequestMessage } from "./outgoing-request-message"; +/** + * A SIP message sent from a local client to a remote server, + * for the purpose of invoking a particular operation. + * https://tools.ietf.org/html/rfc3261#section-7.1 + */ +export interface OutgoingRequest { + /** Delegate providing custom handling of this outgoing request. */ + delegate?: OutgoingRequestDelegate; + /** The outgoing message. */ + readonly message: OutgoingRequestMessage; + /** + * Destroy request. + */ + dispose(): void; + /** + * Sends a CANCEL message targeting this request to the UAS. + * @param reason Reason for canceling request. + * @param options Request options bucket. + */ + cancel(reason?: string, options?: RequestOptions): void; +} +/** Delegate providing custom handling of outgoing requests. */ +export interface OutgoingRequestDelegate { + /** + * Received a 2xx positive final response to this request. + * @param response Incoming response. + */ + onAccept?(response: IncomingResponse): void; + /** + * Received a 1xx provisional response to this request. Excluding 100 responses. + * @param response Incoming response. + */ + onProgress?(response: IncomingResponse): void; + /** + * Received a 3xx negative final response to this request. + * @param response Incoming response. + */ + onRedirect?(response: IncomingResponse): void; + /** + * Received a 4xx, 5xx, or 6xx negative final response to this request. + * @param response Incoming response. + */ + onReject?(response: IncomingResponse): void; + /** + * Received a 100 provisional response. + * @param response Incoming response. + */ + onTrying?(response: IncomingResponse): void; +} +/** Request options bucket. */ +export interface RequestOptions { + /** Extra headers to include in the message. */ + extraHeaders?: Array; + /** Body to include in the message. */ + body?: Body; +} diff --git a/lib/core/messages/outgoing-request.js b/lib/core/messages/outgoing-request.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/messages/outgoing-request.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/messages/outgoing-response.d.ts b/lib/core/messages/outgoing-response.d.ts new file mode 100644 index 000000000..0f054db2b --- /dev/null +++ b/lib/core/messages/outgoing-response.d.ts @@ -0,0 +1,37 @@ +import { Body } from "./body"; +import { IncomingRequestMessage } from "./incoming-request-message"; +/** + * A SIP message sent from a local server to a remote client, + * for indicating the status of a request sent from the + * client to the server. + * https://tools.ietf.org/html/rfc3261#section-7.2 + */ +export interface OutgoingResponse { + /** The outgoing message. */ + readonly message: string; +} +/** Response options bucket. */ +export interface ResponseOptions { + /** Status code of the response. */ + statusCode: number; + /** Reason phrase of the response. */ + reasonPhrase?: string; + /** To tag of the response. If not provided, one is generated. */ + toTag?: string; + /** User agent string for User-Agent header. */ + userAgent?: string; + /** Support options tags for Supported header. */ + supported?: Array; + /** Extra headers to include in the message. */ + extraHeaders?: Array; + /** Body to include in the message. */ + body?: Body; +} +/** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + */ +export declare function constructOutgoingResponse(message: IncomingRequestMessage, options: ResponseOptions): OutgoingResponse; diff --git a/lib/core/messages/outgoing-response.js b/lib/core/messages/outgoing-response.js new file mode 100644 index 000000000..16170b883 --- /dev/null +++ b/lib/core/messages/outgoing-response.js @@ -0,0 +1,128 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var utils_1 = require("./utils"); +/** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + */ +function constructOutgoingResponse(message, options) { + var CRLF = "\r\n"; + if (options.statusCode < 100 || options.statusCode > 699) { + throw new TypeError("Invalid statusCode: " + options.statusCode); + } + var reasonPhrase = options.reasonPhrase ? options.reasonPhrase : utils_1.getReasonPhrase(options.statusCode); + // SIP responses are distinguished from requests by having a Status-Line + // as their start-line. A Status-Line consists of the protocol version + // followed by a numeric Status-Code and its associated textual phrase, + // with each element separated by a single SP character. + // https://tools.ietf.org/html/rfc3261#section-7.2 + var response = "SIP/2.0 " + options.statusCode + " " + reasonPhrase + CRLF; + // One largely non-method-specific guideline for the generation of + // responses is that UASs SHOULD NOT issue a provisional response for a + // non-INVITE request. Rather, UASs SHOULD generate a final response to + // a non-INVITE request as soon as possible. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.1 + if (options.statusCode >= 100 && options.statusCode < 200) { + // TODO + } + // When a 100 (Trying) response is generated, any Timestamp header field + // present in the request MUST be copied into this 100 (Trying) + // response. If there is a delay in generating the response, the UAS + // SHOULD add a delay value into the Timestamp value in the response. + // This value MUST contain the difference between the time of sending of + // the response and receipt of the request, measured in seconds. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.1 + if (options.statusCode === 100) { + // TODO + } + // The From field of the response MUST equal the From header field of + // the request. The Call-ID header field of the response MUST equal the + // Call-ID header field of the request. The CSeq header field of the + // response MUST equal the CSeq field of the request. The Via header + // field values in the response MUST equal the Via header field values + // in the request and MUST maintain the same ordering. + // https://tools.ietf.org/html/rfc3261#section-8.2.6.2 + var fromHeader = "From: " + message.getHeader("From") + CRLF; + var callIdHeader = "Call-ID: " + message.callId + CRLF; + var cSeqHeader = "CSeq: " + message.cseq + " " + message.method + CRLF; + var viaHeaders = message.getHeaders("via").reduce(function (previous, current) { + return previous + "Via: " + current + CRLF; + }, ""); + // If a request contained a To tag in the request, the To header field + // in the response MUST equal that of the request. However, if the To + // header field in the request did not contain a tag, the URI in the To + // header field in the response MUST equal the URI in the To header + // field; additionally, the UAS MUST add a tag to the To header field in + // the response (with the exception of the 100 (Trying) response, in + // which a tag MAY be present). This serves to identify the UAS that is + // responding, possibly resulting in a component of a dialog ID. The + // same tag MUST be used for all responses to that request, both final + // and provisional (again excepting the 100 (Trying)). + // https://tools.ietf.org/html/rfc3261#section-8.2.6.2 + var toHeader = "To: " + message.getHeader("to"); + if (options.statusCode > 100 && !message.parseHeader("to").hasParam("tag")) { + var toTag = options.toTag; + if (!toTag) { + // Stateless UAS Behavior... + // o To header tags MUST be generated for responses in a stateless + // manner - in a manner that will generate the same tag for the + // same request consistently. For information on tag construction + // see Section 19.3. + // https://tools.ietf.org/html/rfc3261#section-8.2.7 + toTag = utils_1.newTag(); // FIXME: newTag() currently generates random tags + } + toHeader += ";tag=" + toTag; + } + toHeader += CRLF; + // FIXME: TODO: needs review... moved to InviteUserAgentServer (as it is specific to that) + // let recordRouteHeaders = ""; + // if (request.method === C.INVITE && statusCode > 100 && statusCode <= 200) { + // recordRouteHeaders = request.getHeaders("record-route").reduce((previous, current) => { + // return previous + "Record-Route: " + current + CRLF; + // }, ""); + // } + // FIXME: TODO: needs review... + var supportedHeader = ""; + if (options.supported) { + supportedHeader = "Supported: " + options.supported.join(", ") + CRLF; + } + // FIXME: TODO: needs review... + var userAgentHeader = ""; + if (options.userAgent) { + userAgentHeader = "User-Agent: " + options.userAgent + CRLF; + } + var extensionHeaders = ""; + if (options.extraHeaders) { + extensionHeaders = options.extraHeaders.reduce(function (previous, current) { + return previous + current.trim() + CRLF; + }, ""); + } + // The relative order of header fields with different field names is not + // significant. However, it is RECOMMENDED that header fields which are + // needed for proxy processing (Via, Route, Record-Route, Proxy-Require, + // Max-Forwards, and Proxy-Authorization, for example) appear towards + // the top of the message to facilitate rapid parsing. + // https://tools.ietf.org/html/rfc3261#section-7.3.1 + // response += recordRouteHeaders; + response += viaHeaders; + response += fromHeader; + response += toHeader; + response += cSeqHeader; + response += callIdHeader; + response += supportedHeader; + response += userAgentHeader; + response += extensionHeaders; + if (options.body) { + response += "Content-Type: " + options.body.contentType + CRLF; + response += "Content-Length: " + utils_1.str_utf8_length(options.body.content) + CRLF + CRLF; + response += options.body.content; + } + else { + response += "Content-Length: " + 0 + CRLF + CRLF; + } + return { message: response }; +} +exports.constructOutgoingResponse = constructOutgoingResponse; diff --git a/lib/core/messages/parameters.d.ts b/lib/core/messages/parameters.d.ts new file mode 100644 index 000000000..d3ea87100 --- /dev/null +++ b/lib/core/messages/parameters.d.ts @@ -0,0 +1,16 @@ +/** + * @internal + */ +export declare class Parameters { + parameters: { + [name: string]: string; + }; + constructor(parameters: { + [name: string]: string; + }); + setParam(key: string, value: any): void; + getParam(key: string): string | undefined; + hasParam(key: string): boolean; + deleteParam(parameter: string): any; + clearParams(): void; +} diff --git a/lib/core/messages/parameters.js b/lib/core/messages/parameters.js new file mode 100644 index 000000000..9d627f274 --- /dev/null +++ b/lib/core/messages/parameters.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * @internal + */ +var Parameters = /** @class */ (function () { + function Parameters(parameters) { + this.parameters = {}; + for (var param in parameters) { + if (parameters.hasOwnProperty(param)) { + this.setParam(param, parameters[param]); + } + } + } + Parameters.prototype.setParam = function (key, value) { + if (key) { + this.parameters[key.toLowerCase()] = (typeof value === "undefined" || value === null) ? null : value.toString(); + } + }; + Parameters.prototype.getParam = function (key) { + if (key) { + return this.parameters[key.toLowerCase()]; + } + }; + Parameters.prototype.hasParam = function (key) { + if (key) { + return !!this.parameters.hasOwnProperty(key.toLowerCase()); + } + return false; + }; + Parameters.prototype.deleteParam = function (parameter) { + parameter = parameter.toLowerCase(); + if (this.parameters.hasOwnProperty(parameter)) { + var value = this.parameters[parameter]; + delete this.parameters[parameter]; + return value; + } + }; + Parameters.prototype.clearParams = function () { + this.parameters = {}; + }; + return Parameters; +}()); +exports.Parameters = Parameters; diff --git a/lib/core/messages/uri.d.ts b/lib/core/messages/uri.d.ts new file mode 100644 index 000000000..6a9fc4609 --- /dev/null +++ b/lib/core/messages/uri.d.ts @@ -0,0 +1,38 @@ +import { Parameters } from "./parameters"; +/** + * URI. + * @public + */ +export declare class URI extends Parameters { + private headers; + private normal; + private raw; + /** + * Constructor + * @param scheme + * @param user + * @param host + * @param port + * @param parameters + * @param headers + */ + constructor(scheme: string, user: string, host: string, port?: number, parameters?: any, headers?: any); + scheme: string; + user: string | undefined; + host: string; + readonly aor: string; + port: number | undefined; + setHeader(name: string, value: any): void; + getHeader(name: string): string | undefined; + hasHeader(name: string): boolean; + deleteHeader(header: string): any; + clearHeaders(): void; + clone(): URI; + toRaw(): string; + toString(): string; + private readonly _normal; + private readonly _raw; + private _toString; + private escapeUser; + private headerize; +} diff --git a/lib/core/messages/uri.js b/lib/core/messages/uri.js new file mode 100644 index 000000000..bb6ad1510 --- /dev/null +++ b/lib/core/messages/uri.js @@ -0,0 +1,205 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var parameters_1 = require("./parameters"); +/** + * URI. + * @public + */ +var URI = /** @class */ (function (_super) { + tslib_1.__extends(URI, _super); + /** + * Constructor + * @param scheme + * @param user + * @param host + * @param port + * @param parameters + * @param headers + */ + function URI(scheme, user, host, port, parameters, headers) { + var _this = _super.call(this, parameters) || this; + _this.headers = {}; + // Checks + if (!host) { + throw new TypeError('missing or invalid "host" parameter'); + } + // Initialize parameters + scheme = scheme || "sip"; + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + _this.setHeader(header, headers[header]); + } + } + // Raw URI + _this.raw = { + scheme: scheme, + user: user, + host: host, + port: port + }; + // Normalized URI + _this.normal = { + scheme: scheme.toLowerCase(), + user: user, + host: host.toLowerCase(), + port: port + }; + return _this; + } + Object.defineProperty(URI.prototype, "scheme", { + get: function () { return this.normal.scheme; }, + set: function (value) { + this.raw.scheme = value; + this.normal.scheme = value.toLowerCase(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "user", { + get: function () { return this.normal.user; }, + set: function (value) { + this.normal.user = this.raw.user = value; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "host", { + get: function () { return this.normal.host; }, + set: function (value) { + this.raw.host = value; + this.normal.host = value.toLowerCase(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "aor", { + get: function () { return this.normal.user + "@" + this.normal.host; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "port", { + get: function () { return this.normal.port; }, + set: function (value) { + this.normal.port = this.raw.port = value === 0 ? value : value; + }, + enumerable: true, + configurable: true + }); + URI.prototype.setHeader = function (name, value) { + this.headers[this.headerize(name)] = (value instanceof Array) ? value : [value]; + }; + URI.prototype.getHeader = function (name) { + if (name) { + return this.headers[this.headerize(name)]; + } + }; + URI.prototype.hasHeader = function (name) { + return !!name && !!this.headers.hasOwnProperty(this.headerize(name)); + }; + URI.prototype.deleteHeader = function (header) { + header = this.headerize(header); + if (this.headers.hasOwnProperty(header)) { + var value = this.headers[header]; + delete this.headers[header]; + return value; + } + }; + URI.prototype.clearHeaders = function () { + this.headers = {}; + }; + URI.prototype.clone = function () { + return new URI(this._raw.scheme, this._raw.user || "", this._raw.host, this._raw.port, JSON.parse(JSON.stringify(this.parameters)), JSON.parse(JSON.stringify(this.headers))); + }; + URI.prototype.toRaw = function () { + return this._toString(this._raw); + }; + URI.prototype.toString = function () { + return this._toString(this._normal); + }; + Object.defineProperty(URI.prototype, "_normal", { + get: function () { return this.normal; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(URI.prototype, "_raw", { + get: function () { return this.raw; }, + enumerable: true, + configurable: true + }); + URI.prototype._toString = function (uri) { + var uriString = uri.scheme + ":"; + // add slashes if it's not a sip(s) URI + if (!uri.scheme.toLowerCase().match("^sips?$")) { + uriString += "//"; + } + if (uri.user) { + uriString += this.escapeUser(uri.user) + "@"; + } + uriString += uri.host; + if (uri.port || uri.port === 0) { + uriString += ":" + uri.port; + } + for (var parameter in this.parameters) { + if (this.parameters.hasOwnProperty(parameter)) { + uriString += ";" + parameter; + if (this.parameters[parameter] !== null) { + uriString += "=" + this.parameters[parameter]; + } + } + } + var headers = []; + for (var header in this.headers) { + if (this.headers.hasOwnProperty(header)) { + for (var idx in this.headers[header]) { + if (this.headers[header].hasOwnProperty(idx)) { + headers.push(header + "=" + this.headers[header][idx]); + } + } + } + } + if (headers.length > 0) { + uriString += "?" + headers.join("&"); + } + return uriString; + }; + // The following two functions were copied from Utils to break a circular dependency + /* + * Hex-escape a SIP URI user. + * @private + * @param {String} user + */ + URI.prototype.escapeUser = function (user) { + // Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F). + return encodeURIComponent(decodeURIComponent(user)) + .replace(/%3A/ig, ":") + .replace(/%2B/ig, "+") + .replace(/%3F/ig, "?") + .replace(/%2F/ig, "/"); + }; + URI.prototype.headerize = function (str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; + }; + return URI; +}(parameters_1.Parameters)); +exports.URI = URI; diff --git a/lib/core/messages/utils.d.ts b/lib/core/messages/utils.d.ts new file mode 100644 index 000000000..7a2aad24f --- /dev/null +++ b/lib/core/messages/utils.d.ts @@ -0,0 +1,24 @@ +/** + * @param size - + * @param base - + * @internal + */ +export declare function createRandomToken(size: number, base?: number): string; +/** + * @internal + */ +export declare function getReasonPhrase(code: number): string; +/** + * @internal + */ +export declare function newTag(): string; +/** + * @param str - + * @internal + */ +export declare function headerize(str: string): string; +/** + * @param str - + * @internal + */ +export declare function str_utf8_length(str: string): number; diff --git a/lib/core/messages/utils.js b/lib/core/messages/utils.js new file mode 100644 index 000000000..7cf513f87 --- /dev/null +++ b/lib/core/messages/utils.js @@ -0,0 +1,145 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * @param size - + * @param base - + * @internal + */ +function createRandomToken(size, base) { + if (base === void 0) { base = 32; } + var token = ""; + for (var i = 0; i < size; i++) { + var r = Math.floor(Math.random() * base); + token += r.toString(base); + } + return token; +} +exports.createRandomToken = createRandomToken; +/** + * @internal + */ +function getReasonPhrase(code) { + return REASON_PHRASE[code] || ""; +} +exports.getReasonPhrase = getReasonPhrase; +/** + * @internal + */ +function newTag() { + return createRandomToken(10); +} +exports.newTag = newTag; +/** + * @param str - + * @internal + */ +function headerize(str) { + var exceptions = { + "Call-Id": "Call-ID", + "Cseq": "CSeq", + "Min-Se": "Min-SE", + "Rack": "RAck", + "Rseq": "RSeq", + "Www-Authenticate": "WWW-Authenticate", + }; + var name = str.toLowerCase().replace(/_/g, "-").split("-"); + var parts = name.length; + var hname = ""; + for (var part = 0; part < parts; part++) { + if (part !== 0) { + hname += "-"; + } + hname += name[part].charAt(0).toUpperCase() + name[part].substring(1); + } + if (exceptions[hname]) { + hname = exceptions[hname]; + } + return hname; +} +exports.headerize = headerize; +/** + * @param str - + * @internal + */ +function str_utf8_length(str) { + return encodeURIComponent(str).replace(/%[A-F\d]{2}/g, "U").length; +} +exports.str_utf8_length = str_utf8_length; +/** + * SIP Response Reasons + * DOC: http://www.iana.org/assignments/sip-parameters + * @internal + */ +var REASON_PHRASE = { + 100: "Trying", + 180: "Ringing", + 181: "Call Is Being Forwarded", + 182: "Queued", + 183: "Session Progress", + 199: "Early Dialog Terminated", + 200: "OK", + 202: "Accepted", + 204: "No Notification", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Moved Temporarily", + 305: "Use Proxy", + 380: "Alternative Service", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 410: "Gone", + 412: "Conditional Request Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Unsupported URI Scheme", + 417: "Unknown Resource-Priority", + 420: "Bad Extension", + 421: "Extension Required", + 422: "Session Interval Too Small", + 423: "Interval Too Brief", + 428: "Use Identity Header", + 429: "Provide Referrer Identity", + 430: "Flow Failed", + 433: "Anonymity Disallowed", + 436: "Bad Identity-Info", + 437: "Unsupported Certificate", + 438: "Invalid Identity Header", + 439: "First Hop Lacks Outbound Support", + 440: "Max-Breadth Exceeded", + 469: "Bad Info Package", + 470: "Consent Needed", + 478: "Unresolvable Destination", + 480: "Temporarily Unavailable", + 481: "Call/Transaction Does Not Exist", + 482: "Loop Detected", + 483: "Too Many Hops", + 484: "Address Incomplete", + 485: "Ambiguous", + 486: "Busy Here", + 487: "Request Terminated", + 488: "Not Acceptable Here", + 489: "Bad Event", + 491: "Request Pending", + 493: "Undecipherable", + 494: "Security Agreement Required", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Server Time-out", + 505: "Version Not Supported", + 513: "Message Too Large", + 580: "Precondition Failure", + 600: "Busy Everywhere", + 603: "Decline", + 604: "Does Not Exist Anywhere", + 606: "Not Acceptable" +}; diff --git a/lib/core/session/index.d.ts b/lib/core/session/index.d.ts new file mode 100644 index 000000000..765f8ba7f --- /dev/null +++ b/lib/core/session/index.d.ts @@ -0,0 +1,2 @@ +export * from "./session"; +export * from "./session-delegate"; diff --git a/lib/core/session/index.js b/lib/core/session/index.js new file mode 100644 index 000000000..1ca6145d7 --- /dev/null +++ b/lib/core/session/index.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./session"), exports); diff --git a/lib/core/session/session-delegate.d.ts b/lib/core/session/session-delegate.d.ts new file mode 100644 index 000000000..8ba981c8e --- /dev/null +++ b/lib/core/session/session-delegate.d.ts @@ -0,0 +1,49 @@ +import { IncomingAckRequest, IncomingByeRequest, IncomingInfoRequest, IncomingInviteRequest, IncomingNotifyRequest, IncomingPrackRequest, IncomingReferRequest } from "../messages"; +export interface SessionDelegate { + /** + * Receive ACK request. + * @param request Incoming ACK request. + */ + onAck?(request: IncomingAckRequest): void; + /** + * Timeout waiting for ACK request. + * If no handler is provided the Session will terminated with a BYE. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + */ + onAckTimeout?(): void; + /** + * Receive BYE request. + * https://tools.ietf.org/html/rfc3261#section-15.1.2 + * @param request Incoming BYE request. + */ + onBye?(request: IncomingByeRequest): void; + /** + * Receive INFO request. + * @param request Incoming INFO request. + */ + onInfo?(request: IncomingInfoRequest): void; + /** + * Receive re-INVITE request. + * https://tools.ietf.org/html/rfc3261#section-14.2 + * @param request Incoming INVITE request. + */ + onInvite?(request: IncomingInviteRequest): void; + /** + * Receive NOTIFY request. + * https://tools.ietf.org/html/rfc6665#section-4.1.3 + * @param request Incoming NOTIFY request. + */ + onNotify?(request: IncomingNotifyRequest): void; + /** + * Receive PRACK request. + * https://tools.ietf.org/html/rfc3262#section-3 + * @param request Incoming PRACK request. + */ + onPrack?(request: IncomingPrackRequest): void; + /** + * Receive REFER request. + * https://tools.ietf.org/html/rfc3515#section-2.4.2 + * @param request Incoming REFER request. + */ + onRefer?(request: IncomingReferRequest): void; +} diff --git a/lib/core/session/session-delegate.js b/lib/core/session/session-delegate.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/session/session-delegate.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/session/session.d.ts b/lib/core/session/session.d.ts new file mode 100644 index 000000000..01b4230b5 --- /dev/null +++ b/lib/core/session/session.d.ts @@ -0,0 +1,131 @@ +import { Body, OutgoingByeRequest, OutgoingInfoRequest, OutgoingInviteRequest, OutgoingInviteRequestDelegate, OutgoingNotifyRequest, OutgoingPrackRequest, OutgoingReferRequest, OutgoingRequestDelegate, RequestOptions, URI } from "../messages"; +import { SessionDelegate } from "./session-delegate"; +/** + * https://tools.ietf.org/html/rfc3261#section-13 + */ +export interface Session { + /** Session delegate. */ + delegate: SessionDelegate | undefined; + /** The session id. Equal to callId + localTag + remoteTag. */ + readonly id: string; + /** Call Id. */ + readonly callId: string; + /** Local Tag. */ + readonly localTag: string; + /** Local URI. */ + readonly localURI: URI; + /** Remote Tag. */ + readonly remoteTag: string; + /** Remote Target. */ + readonly remoteTarget: URI; + /** Remote URI. */ + readonly remoteURI: URI; + /** Session state. */ + readonly sessionState: SessionState; + /** Current state of the offer/answer exchange. */ + readonly signalingState: SignalingState; + /** The current answer if signalingState is stable. Otherwise undefined. */ + readonly answer: Body | undefined; + /** The current offer if signalingState is not initial or closed. Otherwise undefined. */ + readonly offer: Body | undefined; + /** + * Destroy session. + */ + dispose(): void; + /** + * Send a BYE request. + * Terminating a session. + * https://tools.ietf.org/html/rfc3261#section-15 + * @param delegate Request delegate. + * @param options Options bucket. + * @returns A promise which resolves when a 2xx response to the BYE is received. + * @throws {RequestFailedReason} If a non-2xx final response to the BYE is received. + */ + bye(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingByeRequest; + /** + * Send an INFO request. + * Exchange information during a session. + * https://tools.ietf.org/html/rfc6086#section-4.2.1 + * @param delegate Request delegate. + * @param options Options bucket. + * @returns A promise which resolves when a 2xx response to the BYE is received. + * @throws {RequestFailedReason} If a non-2xx final response to the BYE is received. + */ + info(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingInfoRequest; + /** + * Send re-INVITE request. + * Modifying a session. + * https://tools.ietf.org/html/rfc3261#section-14.1 + * @param delegate Request delegate. + * @param options Options bucket. + * @returns A promise which resolves when a 2xx response to the INVITE is received. + * @throws {PendingRequestError} If there is a re-invite "pending". + * @throws {RequestFailedReason} If a non-2xx final response to the INVITE is received. + */ + invite(delegate?: OutgoingInviteRequestDelegate, options?: RequestOptions): OutgoingInviteRequest; + /** + * Send NOTIFY request. + * Inform referrer of transfer progress. + * The use of this is limited to the implicit creation of subscription by REFER (historical). + * Otherwise, notifiers MUST NOT create subscriptions except upon receipt of a SUBSCRIBE request. + * https://tools.ietf.org/html/rfc3515#section-3.7 + * @param delegate Request delegate. + * @param options Options bucket. + * @returns A promise which resolves when a 2xx response to the NOTIFY is received. + * @throws {RequestFailedReason} If a non-2xx final response to the NOTIFY is received. + */ + notify(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingNotifyRequest; + /** + * Send PRACK request. + * Acknowledge a reliable provisional response. + * https://tools.ietf.org/html/rfc3262#section-4 + * @param delegate Request delegate. + * @param options Options bucket. + * @returns A promise which resolves when a 2xx response to the PRACK is received. + * @throws {RequestFailedReason} If a non-2xx final response to the PRACK is received. + */ + prack(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingPrackRequest; + /** + * Send REFER request (in dialog). + * Transfer a session. + * https://tools.ietf.org/html/rfc3515#section-2.4.1 + * @param delegate Request delegate. + * @param options Options bucket. + * @returns A promise which resolves when a 2xx response to the REFER is received. + * @throws {RequestFailedReason} If a non-2xx final response to the REFER is received. + */ + refer(delegate?: OutgoingRequestDelegate, options?: RequestOptions): OutgoingReferRequest; +} +/** + * Session state. + * https://tools.ietf.org/html/rfc3261#section-13 + */ +export declare enum SessionState { + Initial = "Initial", + Early = "Early", + AckWait = "AckWait", + Confirmed = "Confirmed", + Terminated = "Terminated" +} +/** + * Offer/Answer State + * + * Offer Answer RFC Ini Est Early + * ------------------------------------------------------------------- + * 1. INVITE Req. 2xx INVITE Resp. RFC 3261 Y Y N + * 2. 2xx INVITE Resp. ACK Req. RFC 3261 Y Y N + * 3. INVITE Req. 1xx-rel INVITE Resp. RFC 3262 Y Y N + * 4. 1xx-rel INVITE Resp. PRACK Req. RFC 3262 Y Y N + * 5. PRACK Req. 200 PRACK Resp. RFC 3262 N Y Y + * 6. UPDATE Req. 2xx UPDATE Resp. RFC 3311 N Y Y + * + * Table 1: Summary of SIP Usage of the Offer/Answer Model + * https://tools.ietf.org/html/rfc6337#section-2.2 + */ +export declare enum SignalingState { + Initial = "Initial", + HaveLocalOffer = "HaveLocalOffer", + HaveRemoteOffer = "HaveRemoteOffer", + Stable = "Stable", + Closed = "Closed" +} diff --git a/lib/core/session/session.js b/lib/core/session/session.js new file mode 100644 index 000000000..87293f7c8 --- /dev/null +++ b/lib/core/session/session.js @@ -0,0 +1,37 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Session state. + * https://tools.ietf.org/html/rfc3261#section-13 + */ +var SessionState; +(function (SessionState) { + SessionState["Initial"] = "Initial"; + SessionState["Early"] = "Early"; + SessionState["AckWait"] = "AckWait"; + SessionState["Confirmed"] = "Confirmed"; + SessionState["Terminated"] = "Terminated"; +})(SessionState = exports.SessionState || (exports.SessionState = {})); +/** + * Offer/Answer State + * + * Offer Answer RFC Ini Est Early + * ------------------------------------------------------------------- + * 1. INVITE Req. 2xx INVITE Resp. RFC 3261 Y Y N + * 2. 2xx INVITE Resp. ACK Req. RFC 3261 Y Y N + * 3. INVITE Req. 1xx-rel INVITE Resp. RFC 3262 Y Y N + * 4. 1xx-rel INVITE Resp. PRACK Req. RFC 3262 Y Y N + * 5. PRACK Req. 200 PRACK Resp. RFC 3262 N Y Y + * 6. UPDATE Req. 2xx UPDATE Resp. RFC 3311 N Y Y + * + * Table 1: Summary of SIP Usage of the Offer/Answer Model + * https://tools.ietf.org/html/rfc6337#section-2.2 + */ +var SignalingState; +(function (SignalingState) { + SignalingState["Initial"] = "Initial"; + SignalingState["HaveLocalOffer"] = "HaveLocalOffer"; + SignalingState["HaveRemoteOffer"] = "HaveRemoteOffer"; + SignalingState["Stable"] = "Stable"; + SignalingState["Closed"] = "Closed"; +})(SignalingState = exports.SignalingState || (exports.SignalingState = {})); diff --git a/lib/core/subscription/index.d.ts b/lib/core/subscription/index.d.ts new file mode 100644 index 000000000..28f539cf8 --- /dev/null +++ b/lib/core/subscription/index.d.ts @@ -0,0 +1,2 @@ +export * from "./subscription"; +export * from "./subscription-delegate"; diff --git a/lib/core/subscription/index.js b/lib/core/subscription/index.js new file mode 100644 index 000000000..b436e81e4 --- /dev/null +++ b/lib/core/subscription/index.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./subscription"), exports); diff --git a/lib/core/subscription/subscription-delegate.d.ts b/lib/core/subscription/subscription-delegate.d.ts new file mode 100644 index 000000000..24f8bda83 --- /dev/null +++ b/lib/core/subscription/subscription-delegate.d.ts @@ -0,0 +1,23 @@ +import { IncomingNotifyRequest, OutgoingSubscribeRequest } from "../messages"; +export interface SubscriptionDelegate { + /** + * Receive NOTIFY request. This includes in dialog NOTIFY requests only. + * Thus the first NOTIFY (the subscription creating NOTIFY) will not be provided. + * https://tools.ietf.org/html/rfc6665#section-4.1.3 + * @param request Incoming NOTIFY request. + */ + onNotify?(request: IncomingNotifyRequest): void; + /** + * Sent a SUBSCRIBE request. This includes "auto refresh" in dialog SUBSCRIBE requests only. + * Thus SUBSCRIBE requests triggered by calls to `refresh()` or `subscribe()` will not be provided. + * Thus the first SUBSCRIBE (the subscription creating SUBSCRIBE) will not be provided. + * @param request Outgoing SUBSCRIBE request. + */ + onRefresh?(request: OutgoingSubscribeRequest): void; + /** + * Subscription termination. This includes non-NOTIFY termination causes only. + * Thus this will not be called if a NOTIFY is the cause of termination. + * https://tools.ietf.org/html/rfc6665#section-4.4.1 + */ + onTerminated?(): void; +} diff --git a/lib/core/subscription/subscription-delegate.js b/lib/core/subscription/subscription-delegate.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/subscription/subscription-delegate.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/subscription/subscription.d.ts b/lib/core/subscription/subscription.d.ts new file mode 100644 index 000000000..f137a6585 --- /dev/null +++ b/lib/core/subscription/subscription.d.ts @@ -0,0 +1,50 @@ +import { OutgoingSubscribeRequest, OutgoingSubscribeRequestDelegate, RequestOptions } from "../messages"; +import { SubscriptionDelegate } from "./subscription-delegate"; +/** + * https://tools.ietf.org/html/rfc6665 + */ +export interface Subscription { + /** Subscription delegate. */ + delegate: SubscriptionDelegate | undefined; + /** The subscription id. */ + readonly id: string; + /** Subscription expires. Number of seconds until the subscription expires. */ + readonly subscriptionExpires: number; + /** Subscription state. */ + readonly subscriptionState: SubscriptionState; + /** If true, refresh subscription prior to expiration. Default is false. */ + autoRefresh: boolean; + /** + * Destroy subscription. + */ + dispose(): void; + /** + * Send re-SUBSCRIBE request. + * Refreshing a subscription and unsubscribing. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + * @param delegate Request delegate. + * @param options Options bucket + */ + subscribe(delegate?: OutgoingSubscribeRequestDelegate, options?: RequestOptions): OutgoingSubscribeRequest; + /** + * 4.1.2.2. Refreshing of Subscriptions + * https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + */ + refresh(): OutgoingSubscribeRequest; + /** + * 4.1.2.3. Unsubscribing + * https://tools.ietf.org/html/rfc6665#section-4.1.2.3 + */ + unsubscribe(): OutgoingSubscribeRequest; +} +/** + * Subscription state. + * https://tools.ietf.org/html/rfc6665#section-4.1.2 + */ +export declare enum SubscriptionState { + Initial = "Initial", + NotifyWait = "NotifyWait", + Pending = "Pending", + Active = "Active", + Terminated = "Terminated" +} diff --git a/lib/core/subscription/subscription.js b/lib/core/subscription/subscription.js new file mode 100644 index 000000000..df01e3fa3 --- /dev/null +++ b/lib/core/subscription/subscription.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Subscription state. + * https://tools.ietf.org/html/rfc6665#section-4.1.2 + */ +var SubscriptionState; +(function (SubscriptionState) { + SubscriptionState["Initial"] = "Initial"; + SubscriptionState["NotifyWait"] = "NotifyWait"; + SubscriptionState["Pending"] = "Pending"; + SubscriptionState["Active"] = "Active"; + SubscriptionState["Terminated"] = "Terminated"; +})(SubscriptionState = exports.SubscriptionState || (exports.SubscriptionState = {})); diff --git a/lib/core/timers.d.ts b/lib/core/timers.d.ts new file mode 100644 index 000000000..5d0e2ada5 --- /dev/null +++ b/lib/core/timers.d.ts @@ -0,0 +1,16 @@ +export declare const Timers: { + T1: number; + T2: number; + T4: number; + TIMER_B: number; + TIMER_D: number; + TIMER_F: number; + TIMER_H: number; + TIMER_I: number; + TIMER_J: number; + TIMER_K: number; + TIMER_L: number; + TIMER_M: number; + TIMER_N: number; + PROVISIONAL_RESPONSE_INTERVAL: number; +}; diff --git a/lib/core/timers.js b/lib/core/timers.js new file mode 100644 index 000000000..e363818d7 --- /dev/null +++ b/lib/core/timers.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var T1 = 500; +var T2 = 4000; +var T4 = 5000; +exports.Timers = { + T1: T1, + T2: T2, + T4: T4, + TIMER_B: 64 * T1, + TIMER_D: 0 * T1, + TIMER_F: 64 * T1, + TIMER_H: 64 * T1, + TIMER_I: 0 * T4, + TIMER_J: 0 * T1, + TIMER_K: 0 * T4, + TIMER_L: 64 * T1, + TIMER_M: 64 * T1, + TIMER_N: 64 * T1, + PROVISIONAL_RESPONSE_INTERVAL: 60000 // See RFC 3261 Section 13.3.1.1 +}; diff --git a/lib/core/transactions/client-transaction.d.ts b/lib/core/transactions/client-transaction.d.ts new file mode 100644 index 000000000..a847cd76b --- /dev/null +++ b/lib/core/transactions/client-transaction.d.ts @@ -0,0 +1,44 @@ +import { IncomingResponseMessage, OutgoingRequestMessage } from "../messages"; +import { Transport } from "../transport"; +import { Transaction } from "./transaction"; +import { TransactionState } from "./transaction-state"; +import { ClientTransactionUser } from "./transaction-user"; +/** + * Client Transaction + * + * The client transaction provides its functionality through the + * maintenance of a state machine. + * + * The TU communicates with the client transaction through a simple + * interface. When the TU wishes to initiate a new transaction, it + * creates a client transaction and passes it the SIP request to send + * and an IP address, port, and transport to which to send it. The + * client transaction begins execution of its state machine. Valid + * responses are passed up to the TU from the client transaction. + * https://tools.ietf.org/html/rfc3261#section-17.1 + */ +export declare abstract class ClientTransaction extends Transaction { + private _request; + protected user: ClientTransactionUser; + private static makeId; + protected constructor(_request: OutgoingRequestMessage, transport: Transport, user: ClientTransactionUser, state: TransactionState, loggerCategory: string); + /** The outgoing request the transaction handling. */ + readonly request: OutgoingRequestMessage; + /** + * Receive incoming responses from the transport which match this transaction. + * Responses will be delivered to the transaction user as necessary. + * @param response The incoming response. + */ + abstract receiveResponse(response: IncomingResponseMessage): void; + /** + * A 408 to non-INVITE will always arrive too late to be useful ([3]), + * The client already has full knowledge of the timeout. The only + * information this message would convey is whether or not the server + * believed the transaction timed out. However, with the current design + * of the NIT, a client cannot do anything with this knowledge. Thus, + * the 408 is simply wasting network resources and contributes to the + * response bombardment illustrated in [3]. + * https://tools.ietf.org/html/rfc4320#section-4.1 + */ + protected onRequestTimeout(): void; +} diff --git a/lib/core/transactions/client-transaction.js b/lib/core/transactions/client-transaction.js new file mode 100644 index 000000000..eabe9826a --- /dev/null +++ b/lib/core/transactions/client-transaction.js @@ -0,0 +1,72 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transaction_1 = require("./transaction"); +/** + * Client Transaction + * + * The client transaction provides its functionality through the + * maintenance of a state machine. + * + * The TU communicates with the client transaction through a simple + * interface. When the TU wishes to initiate a new transaction, it + * creates a client transaction and passes it the SIP request to send + * and an IP address, port, and transport to which to send it. The + * client transaction begins execution of its state machine. Valid + * responses are passed up to the TU from the client transaction. + * https://tools.ietf.org/html/rfc3261#section-17.1 + */ +var ClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(ClientTransaction, _super); + function ClientTransaction(_request, transport, user, state, loggerCategory) { + var _this = _super.call(this, transport, user, ClientTransaction.makeId(_request), state, loggerCategory) || this; + _this._request = _request; + _this.user = user; + // The Via header field indicates the transport used for the transaction + // and identifies the location where the response is to be sent. A Via + // header field value is added only after the transport that will be + // used to reach the next hop has been selected (which may involve the + // usage of the procedures in [4]). + // https://tools.ietf.org/html/rfc3261#section-8.1.1.7 + // FIXME: Transport's server property is not typed (as of writing this). + var scheme = transport.server && transport.server.scheme ? transport.server.scheme : undefined; + _request.setViaHeader(_this.id, scheme); + return _this; + } + ClientTransaction.makeId = function (request) { + if (request.method === "CANCEL") { + if (!request.branch) { + throw new Error("Outgoing CANCEL request without a branch."); + } + return request.branch; + } + else { + return "z9hG4bK" + Math.floor(Math.random() * 10000000); + } + }; + Object.defineProperty(ClientTransaction.prototype, "request", { + /** The outgoing request the transaction handling. */ + get: function () { + return this._request; + }, + enumerable: true, + configurable: true + }); + /** + * A 408 to non-INVITE will always arrive too late to be useful ([3]), + * The client already has full knowledge of the timeout. The only + * information this message would convey is whether or not the server + * believed the transaction timed out. However, with the current design + * of the NIT, a client cannot do anything with this knowledge. Thus, + * the 408 is simply wasting network resources and contributes to the + * response bombardment illustrated in [3]. + * https://tools.ietf.org/html/rfc4320#section-4.1 + */ + ClientTransaction.prototype.onRequestTimeout = function () { + if (this.user.onRequestTimeout) { + this.user.onRequestTimeout(); + } + }; + return ClientTransaction; +}(transaction_1.Transaction)); +exports.ClientTransaction = ClientTransaction; diff --git a/lib/core/transactions/index.d.ts b/lib/core/transactions/index.d.ts new file mode 100644 index 000000000..c4f66d3f7 --- /dev/null +++ b/lib/core/transactions/index.d.ts @@ -0,0 +1,10 @@ +export * from "./client-transaction"; +export * from "./invite-client-transaction"; +export * from "./invite-server-transaction"; +export * from "./non-invite-client-transaction"; +export * from "./non-invite-server-transaction"; +export * from "./invite-client-transaction"; +export * from "./server-transaction"; +export * from "./transaction-state"; +export * from "./transaction-user"; +export * from "./transaction"; diff --git a/lib/core/transactions/index.js b/lib/core/transactions/index.js new file mode 100644 index 000000000..db74c52eb --- /dev/null +++ b/lib/core/transactions/index.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./client-transaction"), exports); +tslib_1.__exportStar(require("./invite-client-transaction"), exports); +tslib_1.__exportStar(require("./invite-server-transaction"), exports); +tslib_1.__exportStar(require("./non-invite-client-transaction"), exports); +tslib_1.__exportStar(require("./non-invite-server-transaction"), exports); +tslib_1.__exportStar(require("./invite-client-transaction"), exports); +tslib_1.__exportStar(require("./server-transaction"), exports); +tslib_1.__exportStar(require("./transaction-state"), exports); +tslib_1.__exportStar(require("./transaction"), exports); diff --git a/lib/core/transactions/invite-client-transaction.d.ts b/lib/core/transactions/invite-client-transaction.d.ts new file mode 100644 index 000000000..ca38744c5 --- /dev/null +++ b/lib/core/transactions/invite-client-transaction.d.ts @@ -0,0 +1,115 @@ +import { TransportError } from "../exceptions"; +import { IncomingResponseMessage, OutgoingRequestMessage } from "../messages"; +import { Transport } from "../transport"; +import { ClientTransaction } from "./client-transaction"; +import { ClientTransactionUser } from "./transaction-user"; +/** + * INVITE Client Transaction + * + * The INVITE transaction consists of a three-way handshake. The client + * transaction sends an INVITE, the server transaction sends responses, + * and the client transaction sends an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + */ +export declare class InviteClientTransaction extends ClientTransaction { + private B; + private D; + private M; + /** + * Map of 2xx to-tag => ACK. + * If value is not undefined, value is the ACK which was sent. + * If key exists but value is undefined, a 2xx was received but the ACK not yet sent. + * Otherwise, a 2xx was not (yet) received for this transaction. + */ + private ackRetransmissionCache; + /** + * Constructor. + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + * @param request The outgoing INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + constructor(request: OutgoingRequestMessage, transport: Transport, user: ClientTransactionUser); + /** + * Destructor. + */ + dispose(): void; + /** Transaction kind. Deprecated. */ + readonly kind: string; + /** + * ACK a 2xx final response. + * + * The transaction includes the ACK only if the final response was not a 2xx response (the + * transaction will generate and send the ACK to the transport automagically). If the + * final response was a 2xx, the ACK is not considered part of the transaction (the + * transaction user needs to generate and send the ACK). + * + * This library is not strictly RFC compliant with regard to ACK handling for 2xx final + * responses. Specifically, retransmissions of ACKs to a 2xx final responses is handled + * by the transaction layer (instead of the UAC core). The "standard" approach is for + * the UAC core to receive all 2xx responses and manage sending ACK retransmissions to + * the transport directly. Herein the transaction layer manages sending ACKs to 2xx responses + * and any retransmissions of those ACKs as needed. + * + * @param ack The outgoing ACK request. + */ + ackResponse(ack: OutgoingRequestMessage): void; + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + receiveResponse(response: IncomingResponseMessage): void; + /** + * The client transaction SHOULD inform the TU that a transport failure + * has occurred, and the client transaction SHOULD transition directly + * to the "Terminated" state. The TU will handle the failover + * mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error The error. + */ + protected onTransportError(error: TransportError): void; + /** For logging. */ + protected typeToString(): string; + private ack; + /** + * Execute a state transition. + * @param newState New state. + */ + private stateTransition; + /** + * When timer A fires, the client transaction MUST retransmit the + * request by passing it to the transport layer, and MUST reset the + * timer with a value of 2*T1. + * When timer A fires 2*T1 seconds later, the request MUST be + * retransmitted again (assuming the client transaction is still in this + * state). This process MUST continue so that the request is + * retransmitted with intervals that double after each transmission. + * These retransmissions SHOULD only be done while the client + * transaction is in the "Calling" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + private timer_A; + /** + * If the client transaction is still in the "Calling" state when timer + * B fires, the client transaction SHOULD inform the TU that a timeout + * has occurred. The client transaction MUST NOT generate an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + private timer_B; + /** + * If Timer D fires while the client transaction is in the "Completed" state, + * the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + private timer_D; + /** + * If Timer M fires while the client transaction is in the "Accepted" + * state, the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + private timer_M; +} diff --git a/lib/core/transactions/invite-client-transaction.js b/lib/core/transactions/invite-client-transaction.js new file mode 100644 index 000000000..5ea36838f --- /dev/null +++ b/lib/core/transactions/invite-client-transaction.js @@ -0,0 +1,485 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var timers_1 = require("../timers"); +var client_transaction_1 = require("./client-transaction"); +var transaction_state_1 = require("./transaction-state"); +/** + * INVITE Client Transaction + * + * The INVITE transaction consists of a three-way handshake. The client + * transaction sends an INVITE, the server transaction sends responses, + * and the client transaction sends an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + */ +var InviteClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(InviteClientTransaction, _super); + /** + * Constructor. + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.1 + * @param request The outgoing INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + function InviteClientTransaction(request, transport, user) { + var _this = _super.call(this, request, transport, user, transaction_state_1.TransactionState.Calling, "sip.transaction.ict") || this; + /** + * Map of 2xx to-tag => ACK. + * If value is not undefined, value is the ACK which was sent. + * If key exists but value is undefined, a 2xx was received but the ACK not yet sent. + * Otherwise, a 2xx was not (yet) received for this transaction. + */ + _this.ackRetransmissionCache = new Map(); + // FIXME: Timer A for unreliable transport not implemented + // + // If an unreliable transport is being used, the client transaction + // MUST start timer A with a value of T1. If a reliable transport is being used, + // the client transaction SHOULD NOT start timer A (Timer A controls request retransmissions). + // For any transport, the client transaction MUST start timer B with a value + // of 64*T1 seconds (Timer B controls transaction timeouts). + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + // + // While not spelled out in the RFC, Timer B is the maximum amount of time that a sender + // will wait for an INVITE message to be acknowledged (a SIP response message is received). + // So Timer B should be cleared when the transaction state proceeds from "Calling". + _this.B = setTimeout(function () { return _this.timer_B(); }, timers_1.Timers.TIMER_B); + _this.send(request.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send initial outgoing request."); + }); + return _this; + } + /** + * Destructor. + */ + InviteClientTransaction.prototype.dispose = function () { + if (this.B) { + clearTimeout(this.B); + this.B = undefined; + } + if (this.D) { + clearTimeout(this.D); + this.D = undefined; + } + if (this.M) { + clearTimeout(this.M); + this.M = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(InviteClientTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "ict"; + }, + enumerable: true, + configurable: true + }); + /** + * ACK a 2xx final response. + * + * The transaction includes the ACK only if the final response was not a 2xx response (the + * transaction will generate and send the ACK to the transport automagically). If the + * final response was a 2xx, the ACK is not considered part of the transaction (the + * transaction user needs to generate and send the ACK). + * + * This library is not strictly RFC compliant with regard to ACK handling for 2xx final + * responses. Specifically, retransmissions of ACKs to a 2xx final responses is handled + * by the transaction layer (instead of the UAC core). The "standard" approach is for + * the UAC core to receive all 2xx responses and manage sending ACK retransmissions to + * the transport directly. Herein the transaction layer manages sending ACKs to 2xx responses + * and any retransmissions of those ACKs as needed. + * + * @param ack The outgoing ACK request. + */ + InviteClientTransaction.prototype.ackResponse = function (ack) { + var _this = this; + var toTag = ack.toTag; + if (!toTag) { + throw new Error("To tag undefined."); + } + var id = "z9hG4bK" + Math.floor(Math.random() * 10000000); + // FIXME: Transport's server property is not typed (as of writing this). + var scheme = this.transport.server && this.transport.server.scheme ? this.transport.server.scheme : undefined; + ack.setViaHeader(id, scheme); + this.ackRetransmissionCache.set(toTag, ack); // Add to ACK retransmission cache + this.send(ack.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send ACK to 2xx response."); + }); + }; + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + InviteClientTransaction.prototype.receiveResponse = function (response) { + var _this = this; + var statusCode = response.statusCode; + if (!statusCode || statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Calling: + // If the client transaction receives a provisional response while in + // the "Calling" state, it transitions to the "Proceeding" state. In the + // "Proceeding" state, the client transaction SHOULD NOT retransmit the + // request any longer. Furthermore, the provisional response MUST be + // passed to the TU. Any further provisional responses MUST be passed + // up to the TU while in the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + if (statusCode >= 100 && statusCode <= 199) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When a 2xx response is received while in either the "Calling" or + // "Proceeding" states, the client transaction MUST transition to + // the "Accepted" state... The 2xx response MUST be passed up to the TU. + // The client transaction MUST NOT generate an ACK to the 2xx response -- its + // handling is delegated to the TU. A UAC core will send an ACK to + // the 2xx response using a new transaction. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + this.stateTransition(transaction_state_1.TransactionState.Accepted); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When in either the "Calling" or "Proceeding" states, reception of + // a response with status code from 300-699 MUST cause the client + // transaction to transition to "Completed". The client transaction + // MUST pass the received response up to the TU, and the client + // transaction MUST generate an ACK request, even if the transport is + // reliable (guidelines for constructing the ACK from the response + // are given in Section 17.1.1.3), and then pass the ACK to the + // transport layer for transmission. The ACK MUST be sent to the + // same address, port, and transport to which the original request was sent. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.ack(response); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // In the "Proceeding" state, the client transaction SHOULD NOT retransmit the + // request any longer. Furthermore, the provisional response MUST be + // passed to the TU. Any further provisional responses MUST be passed + // up to the TU while in the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + if (statusCode >= 100 && statusCode <= 199) { + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When a 2xx response is received while in either the "Calling" or "Proceeding" states, + // the client transaction MUST transition to the "Accepted" state... + // The 2xx response MUST be passed up to the TU. The client + // transaction MUST NOT generate an ACK to the 2xx response -- its + // handling is delegated to the TU. A UAC core will send an ACK to + // the 2xx response using a new transaction. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + this.stateTransition(transaction_state_1.TransactionState.Accepted); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // When in either the "Calling" or "Proceeding" states, reception of + // a response with status code from 300-699 MUST cause the client + // transaction to transition to "Completed". The client transaction + // MUST pass the received response up to the TU, and the client + // transaction MUST generate an ACK request, even if the transport is + // reliable (guidelines for constructing the ACK from the response + // are given in Section 17.1.1.3), and then pass the ACK to the + // transport layer for transmission. The ACK MUST be sent to the + // same address, port, and transport to which the original request was sent. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.ack(response); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // The purpose of the "Accepted" state is to allow the client + // transaction to continue to exist to receive, and pass to the TU, + // any retransmissions of the 2xx response and any additional 2xx + // responses from other branches of the INVITE if it forked + // downstream. Timer M reflects the amount of time that the + // transaction user will wait for such messages. + // + // Any 2xx responses that match this client transaction and that are + // received while in the "Accepted" state MUST be passed up to the + // TU. The client transaction MUST NOT generate an ACK to the 2xx + // response. The client transaction takes no further action. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 200 && statusCode <= 299) { + // NOTE: This implementation herein is intentionally not RFC compliant. + // While the first 2xx response for a given branch is passed up to the TU, + // retransmissions of 2xx responses are absorbed and the ACK associated + // with the original response is resent. This approach is taken because + // our current transaction users are not currently in a good position to + // deal with 2xx retransmission. This SHOULD NOT cause any compliance issues - ;) + // + // If we don't have a cache hit, pass the response to the TU. + if (!this.ackRetransmissionCache.has(response.toTag)) { + this.ackRetransmissionCache.set(response.toTag, undefined); // Prime the ACK cache + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // If we have a cache hit, try pulling the ACK from cache and retransmitting it. + var ack = this.ackRetransmissionCache.get(response.toTag); + if (ack) { + this.send(ack.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of ACK to 2xx response."); + }); + return; + } + // If an ACK was not found in cache then we have received a retransmitted 2xx + // response before the TU responded to the original response (we don't have an ACK yet). + // So discard this response under the assumption that the TU will eventually + // get us a ACK for the original response. + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Any retransmissions of a response with status code 300-699 that + // are received while in the "Completed" state MUST cause the ACK to + // be re-passed to the transport layer for retransmission, but the + // newly received response MUST NOT be passed up to the TU. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (statusCode >= 300 && statusCode <= 699) { + this.ack(response); + return; + } + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + // Any response received that does not match an existing client + // transaction state machine is simply dropped. (Implementations are, + // of course, free to log or do other implementation-specific things + // with such responses, but the implementer should be sure to consider + // the impact of large numbers of malicious stray responses.) + // https://tools.ietf.org/html/rfc6026#section-7.2 + var message = "Received unexpected " + statusCode + " response while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * The client transaction SHOULD inform the TU that a transport failure + * has occurred, and the client transaction SHOULD transition directly + * to the "Terminated" state. The TU will handle the failover + * mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error The error. + */ + InviteClientTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + InviteClientTransaction.prototype.typeToString = function () { + return "INVITE client transaction"; + }; + InviteClientTransaction.prototype.ack = function (response) { + var _this = this; + // The ACK request constructed by the client transaction MUST contain + // values for the Call-ID, From, and Request-URI that are equal to the + // values of those header fields in the request passed to the transport + // by the client transaction (call this the "original request"). The To + // header field in the ACK MUST equal the To header field in the + // response being acknowledged, and therefore will usually differ from + // the To header field in the original request by the addition of the + // tag parameter. The ACK MUST contain a single Via header field, and + // this MUST be equal to the top Via header field of the original + // request. The CSeq header field in the ACK MUST contain the same + // value for the sequence number as was present in the original request, + // but the method parameter MUST be equal to "ACK". + // + // If the INVITE request whose response is being acknowledged had Route + // header fields, those header fields MUST appear in the ACK. This is + // to ensure that the ACK can be routed properly through any downstream + // stateless proxies. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.3 + var ruri = this.request.ruri; + var callId = this.request.callId; + var cseq = this.request.cseq; + var from = this.request.getHeader("from"); + var to = response.getHeader("to"); + var via = this.request.getHeader("via"); + var route = this.request.getHeader("route"); + if (!from) { + throw new Error("From undefined."); + } + if (!to) { + throw new Error("To undefined."); + } + if (!via) { + throw new Error("Via undefined."); + } + var ack = "ACK " + ruri + " SIP/2.0\r\n"; + if (route) { + ack += "Route: " + route + "\r\n"; + } + ack += "Via: " + via + "\r\n"; + ack += "To: " + to + "\r\n"; + ack += "From: " + from + "\r\n"; + ack += "Call-ID: " + callId + "\r\n"; + ack += "CSeq: " + cseq + " ACK\r\n"; + ack += "Max-Forwards: 70\r\n"; + ack += "Content-Length: 0\r\n\r\n"; + // TOOO: "User-Agent" header + this.send(ack).catch(function (error) { + _this.logTransportError(error, "Failed to send ACK to non-2xx response."); + }); + return; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + InviteClientTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Calling: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Calling) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Accepted: + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Calling && + this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Calling && + this.state !== transaction_state_1.TransactionState.Accepted && + this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // While not spelled out in the RFC, Timer B is the maximum amount of time that a sender + // will wait for an INVITE message to be acknowledged (a SIP response message is received). + // So Timer B should be cleared when the transaction state proceeds from "Calling". + if (this.B) { + clearTimeout(this.B); + this.B = undefined; + } + if (newState === transaction_state_1.TransactionState.Proceeding) { + // Timers have no effect on "Proceeding" state. + // In the "Proceeding" state, the client transaction + // SHOULD NOT retransmit the request any longer. + // https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + } + // The client transaction MUST start Timer D when it enters the "Completed" state + // for any reason, with a value of at least 32 seconds for unreliable transports, + // and a value of zero seconds for reliable transports. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (newState === transaction_state_1.TransactionState.Completed) { + this.D = setTimeout(function () { return _this.timer_D(); }, timers_1.Timers.TIMER_D); + } + // The client transaction MUST transition to the "Accepted" state, + // and Timer M MUST be started with a value of 64*T1. + // https://tools.ietf.org/html/rfc6026#section-8.4 + if (newState === transaction_state_1.TransactionState.Accepted) { + this.M = setTimeout(function () { return _this.timer_M(); }, timers_1.Timers.TIMER_M); + } + // Once the transaction is in the "Terminated" state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * When timer A fires, the client transaction MUST retransmit the + * request by passing it to the transport layer, and MUST reset the + * timer with a value of 2*T1. + * When timer A fires 2*T1 seconds later, the request MUST be + * retransmitted again (assuming the client transaction is still in this + * state). This process MUST continue so that the request is + * retransmitted with intervals that double after each transmission. + * These retransmissions SHOULD only be done while the client + * transaction is in the "Calling" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + InviteClientTransaction.prototype.timer_A = function () { + // TODO + }; + /** + * If the client transaction is still in the "Calling" state when timer + * B fires, the client transaction SHOULD inform the TU that a timeout + * has occurred. The client transaction MUST NOT generate an ACK. + * https://tools.ietf.org/html/rfc3261#section-17.1.1.2 + */ + InviteClientTransaction.prototype.timer_B = function () { + this.logger.debug("Timer B expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Calling) { + this.onRequestTimeout(); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer D fires while the client transaction is in the "Completed" state, + * the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + InviteClientTransaction.prototype.timer_D = function () { + this.logger.debug("Timer D expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer M fires while the client transaction is in the "Accepted" + * state, the client transaction MUST move to the "Terminated" state. + * https://tools.ietf.org/html/rfc6026#section-8.4 + */ + InviteClientTransaction.prototype.timer_M = function () { + this.logger.debug("Timer M expired for INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Accepted) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return InviteClientTransaction; +}(client_transaction_1.ClientTransaction)); +exports.InviteClientTransaction = InviteClientTransaction; diff --git a/lib/core/transactions/invite-server-transaction.d.ts b/lib/core/transactions/invite-server-transaction.d.ts new file mode 100644 index 000000000..8eed00350 --- /dev/null +++ b/lib/core/transactions/invite-server-transaction.d.ts @@ -0,0 +1,125 @@ +import { IncomingRequestMessage } from "../messages"; +import { Transport } from "../transport"; +import { ServerTransaction } from "./server-transaction"; +import { ServerTransactionUser } from "./transaction-user"; +/** + * INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ +export declare class InviteServerTransaction extends ServerTransaction { + private lastFinalResponse; + private lastProvisionalResponse; + private H; + private I; + private L; + /** + * FIXME: This should not be here. It should be in the UAS. + * + * If the UAS desires an extended period of time to answer the INVITE, + * it will need to ask for an "extension" in order to prevent proxies + * from canceling the transaction. A proxy has the option of canceling + * a transaction when there is a gap of 3 minutes between responses in a + * transaction. To prevent cancellation, the UAS MUST send a non-100 + * provisional response at every minute, to handle the possibility of + * lost provisional responses. + * + * An INVITE transaction can go on for extended durations when the + * user is placed on hold, or when interworking with PSTN systems + * which allow communications to take place without answering the + * call. The latter is common in Interactive Voice Response (IVR) + * systems. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.1 + */ + private progressExtensionTimer; + /** + * Constructor. + * Upon construction, a "100 Trying" reply will be immediately sent. + * After construction the transaction will be in the "proceeding" state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + * @param request Incoming INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + constructor(request: IncomingRequestMessage, transport: Transport, user: ServerTransactionUser); + /** + * Destructor. + */ + dispose(): void; + /** Transaction kind. Deprecated. */ + readonly kind: string; + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + receiveRequest(request: IncomingRequestMessage): void; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of response. + * @param response Response. + */ + receiveResponse(statusCode: number, response: string): void; + /** + * Retransmit the last 2xx response. This is a noop if not in the "accepted" state. + */ + retransmitAcceptedResponse(): void; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and MUST remain in the current state. + * https://tools.ietf.org/html/rfc6026#section-8.8 + */ + protected onTransportError(error: Error): void; + /** For logging. */ + protected typeToString(): string; + /** + * Execute a state transition. + * @param newState New state. + */ + private stateTransition; + /** + * FIXME: UAS Provisional Retransmission Timer. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + private startProgressExtensionTimer; + /** + * FIXME: UAS Provisional Retransmission Timer id. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + private stopProgressExtensionTimer; + /** + * While in the "Proceeding" state, if the TU passes a response with status code + * from 300 to 699 to the server transaction, the response MUST be passed to the + * transport layer for transmission, and the state machine MUST enter the "Completed" state. + * For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for + * reliable transports. If timer G fires, the response is passed to the transport layer once + * more for retransmission, and timer G is set to fire in MIN(2*T1, T2) seconds. From then on, + * when timer G fires, the response is passed to the transport again for transmission, and + * timer G is reset with a value that doubles, unless that value exceeds T2, in which case + * it is reset with the value of T2. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + private timer_G; + /** + * If timer H fires while in the "Completed" state, it implies that the ACK was never received. + * In this case, the server transaction MUST transition to the "Terminated" state, and MUST + * indicate to the TU that a transaction failure has occurred. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + private timer_H; + /** + * Once timer I fires, the server MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + private timer_I; + /** + * When Timer L fires and the state machine is in the "Accepted" state, the machine MUST + * transition to the "Terminated" state. Once the transaction is in the "Terminated" state, + * it MUST be destroyed immediately. Timer L reflects the amount of time the server + * transaction could receive 2xx responses for retransmission from the + * TU while it is waiting to receive an ACK. + * https://tools.ietf.org/html/rfc6026#section-7.1 + * https://tools.ietf.org/html/rfc6026#section-8.7 + */ + private timer_L; +} diff --git a/lib/core/transactions/invite-server-transaction.js b/lib/core/transactions/invite-server-transaction.js new file mode 100644 index 000000000..275e17988 --- /dev/null +++ b/lib/core/transactions/invite-server-transaction.js @@ -0,0 +1,389 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var timers_1 = require("../timers"); +var server_transaction_1 = require("./server-transaction"); +var transaction_state_1 = require("./transaction-state"); +/** + * INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ +var InviteServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(InviteServerTransaction, _super); + /** + * Constructor. + * Upon construction, a "100 Trying" reply will be immediately sent. + * After construction the transaction will be in the "proceeding" state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + * @param request Incoming INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + function InviteServerTransaction(request, transport, user) { + return _super.call(this, request, transport, user, transaction_state_1.TransactionState.Proceeding, "sip.transaction.ist") || this; + } + /** + * Destructor. + */ + InviteServerTransaction.prototype.dispose = function () { + this.stopProgressExtensionTimer(); + if (this.H) { + clearTimeout(this.H); + this.H = undefined; + } + if (this.I) { + clearTimeout(this.I); + this.I = undefined; + } + if (this.L) { + clearTimeout(this.L); + this.L = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(InviteServerTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "ist"; + }, + enumerable: true, + configurable: true + }); + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + InviteServerTransaction.prototype.receiveRequest = function (request) { + var _this = this; + switch (this.state) { + case transaction_state_1.TransactionState.Proceeding: + // If a request retransmission is received while in the "Proceeding" state, the most + // recent provisional response that was received from the TU MUST be passed to the + // transport layer for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE) { + if (this.lastProvisionalResponse) { + this.send(this.lastProvisionalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + } + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // While in the "Accepted" state, any retransmissions of the INVITE + // received will match this transaction state machine and will be + // absorbed by the machine without changing its state. These + // retransmissions are not passed onto the TU. + // https://tools.ietf.org/html/rfc6026#section-7.1 + if (request.method === messages_1.C.INVITE) { + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Furthermore, while in the "Completed" state, if a request retransmission is + // received, the server SHOULD pass the response to the transport for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE) { + if (!this.lastFinalResponse) { + throw new Error("Last final response undefined."); + } + this.send(this.lastFinalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of final response."); + }); + return; + } + // If an ACK is received while the server transaction is in the "Completed" state, + // the server transaction MUST transition to the "Confirmed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.ACK) { + this.stateTransition(transaction_state_1.TransactionState.Confirmed); + return; + } + break; + case transaction_state_1.TransactionState.Confirmed: + // The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, + // triggered from retransmissions of the final response. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (request.method === messages_1.C.INVITE || request.method === messages_1.C.ACK) { + return; + } + break; + case transaction_state_1.TransactionState.Terminated: + // For good measure absorb any additional messages that arrive (should not happen). + if (request.method === messages_1.C.INVITE || request.method === messages_1.C.ACK) { + return; + } + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "INVITE server transaction received unexpected " + request.method + " request while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of response. + * @param response Response. + */ + InviteServerTransaction.prototype.receiveResponse = function (statusCode, response) { + var _this = this; + if (statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Proceeding: + // The TU passes any number of provisional responses to the server + // transaction. So long as the server transaction is in the + // "Proceeding" state, each of these MUST be passed to the transport + // layer for transmission. They are not sent reliably by the + // transaction layer (they are not retransmitted by it) and do not cause + // a change in the state of the server transaction. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (statusCode >= 100 && statusCode <= 199) { + this.lastProvisionalResponse = response; + // Start the progress extension timer only for a non-100 provisional response. + if (statusCode > 100) { + this.startProgressExtensionTimer(); // FIXME: remove + } + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 1xx response."); + }); + return; + } + // If, while in the "Proceeding" state, the TU passes a 2xx response + // to the server transaction, the server transaction MUST pass this + // response to the transport layer for transmission. It is not + // retransmitted by the server transaction; retransmissions of 2xx + // responses are handled by the TU. The server transaction MUST then + // transition to the "Accepted" state. + // https://tools.ietf.org/html/rfc6026#section-8.5 + if (statusCode >= 200 && statusCode <= 299) { + this.lastFinalResponse = response; + this.stateTransition(transaction_state_1.TransactionState.Accepted); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + return; + } + // While in the "Proceeding" state, if the TU passes a response with + // status code from 300 to 699 to the server transaction, the response + // MUST be passed to the transport layer for transmission, and the state + // machine MUST enter the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (statusCode >= 300 && statusCode <= 699) { + this.lastFinalResponse = response; + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send non-2xx final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Accepted: + // While in the "Accepted" state, if the TU passes a 2xx response, + // the server transaction MUST pass the response to the transport layer for transmission. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (statusCode >= 200 && statusCode <= 299) { + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Completed: + break; + case transaction_state_1.TransactionState.Confirmed: + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "INVITE server transaction received unexpected " + statusCode + " response from TU while in state " + this.state + "."; + this.logger.error(message); + throw new Error(message); + }; + /** + * Retransmit the last 2xx response. This is a noop if not in the "accepted" state. + */ + InviteServerTransaction.prototype.retransmitAcceptedResponse = function () { + var _this = this; + if (this.state === transaction_state_1.TransactionState.Accepted && this.lastFinalResponse) { + this.send(this.lastFinalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send 2xx response."); + }); + } + }; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and MUST remain in the current state. + * https://tools.ietf.org/html/rfc6026#section-8.8 + */ + InviteServerTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + }; + /** For logging. */ + InviteServerTransaction.prototype.typeToString = function () { + return "INVITE server transaction"; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + InviteServerTransaction.prototype.stateTransition = function (newState) { + var _this = this; + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Proceeding: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Accepted: + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Confirmed: + if (this.state !== transaction_state_1.TransactionState.Completed) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Accepted && + this.state !== transaction_state_1.TransactionState.Completed && + this.state !== transaction_state_1.TransactionState.Confirmed) { + invalidStateTransition(); + } + break; + default: + invalidStateTransition(); + } + // On any state transition, stop resending provisonal responses + this.stopProgressExtensionTimer(); + // The purpose of the "Accepted" state is to absorb retransmissions of an accepted INVITE request. + // Any such retransmissions are absorbed entirely within the server transaction. + // They are not passed up to the TU since any downstream UAS cores that accepted the request have + // taken responsibility for reliability and will already retransmit their 2xx responses if necessary. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Accepted) { + this.L = setTimeout(function () { return _this.timer_L(); }, timers_1.Timers.TIMER_L); + } + // When the "Completed" state is entered, timer H MUST be set to fire in 64*T1 seconds for all transports. + // Timer H determines when the server transaction abandons retransmitting the response. + // If an ACK is received while the server transaction is in the "Completed" state, + // the server transaction MUST transition to the "Confirmed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (newState === transaction_state_1.TransactionState.Completed) { + // FIXME: Missing timer G for unreliable transports. + this.H = setTimeout(function () { return _this.timer_H(); }, timers_1.Timers.TIMER_H); + } + // The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, + // triggered from retransmissions of the final response. When this state is entered, timer I + // is set to fire in T4 seconds for unreliable transports, and zero seconds for reliable + // transports. Once timer I fires, the server MUST transition to the "Terminated" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.1 + if (newState === transaction_state_1.TransactionState.Confirmed) { + // FIXME: This timer is not getting set correctly for unreliable transports. + this.I = setTimeout(function () { return _this.timer_I(); }, timers_1.Timers.TIMER_I); + } + // Once the transaction is in the "Terminated" state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc6026#section-8.7 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * FIXME: UAS Provisional Retransmission Timer. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + InviteServerTransaction.prototype.startProgressExtensionTimer = function () { + var _this = this; + // Start the progress extension timer only for the first non-100 provisional response. + if (this.progressExtensionTimer === undefined) { + this.progressExtensionTimer = setInterval(function () { + _this.logger.debug("Progress extension timer expired for INVITE server transaction " + _this.id + "."); + if (!_this.lastProvisionalResponse) { + throw new Error("Last provisional response undefined."); + } + _this.send(_this.lastProvisionalResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + }, timers_1.Timers.PROVISIONAL_RESPONSE_INTERVAL); + } + }; + /** + * FIXME: UAS Provisional Retransmission Timer id. See RFC 3261 Section 13.3.1.1 + * This is in the wrong place. This is not a transaction level thing. It's a UAS level thing. + */ + InviteServerTransaction.prototype.stopProgressExtensionTimer = function () { + if (this.progressExtensionTimer !== undefined) { + clearInterval(this.progressExtensionTimer); + this.progressExtensionTimer = undefined; + } + }; + /** + * While in the "Proceeding" state, if the TU passes a response with status code + * from 300 to 699 to the server transaction, the response MUST be passed to the + * transport layer for transmission, and the state machine MUST enter the "Completed" state. + * For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for + * reliable transports. If timer G fires, the response is passed to the transport layer once + * more for retransmission, and timer G is set to fire in MIN(2*T1, T2) seconds. From then on, + * when timer G fires, the response is passed to the transport again for transmission, and + * timer G is reset with a value that doubles, unless that value exceeds T2, in which case + * it is reset with the value of T2. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_G = function () { + // TODO + }; + /** + * If timer H fires while in the "Completed" state, it implies that the ACK was never received. + * In this case, the server transaction MUST transition to the "Terminated" state, and MUST + * indicate to the TU that a transaction failure has occurred. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_H = function () { + this.logger.debug("Timer H expired for INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.logger.warn("ACK to negative final response was never received, terminating transaction."); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * Once timer I fires, the server MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.1 + */ + InviteServerTransaction.prototype.timer_I = function () { + this.logger.debug("Timer I expired for INVITE server transaction " + this.id + "."); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + }; + /** + * When Timer L fires and the state machine is in the "Accepted" state, the machine MUST + * transition to the "Terminated" state. Once the transaction is in the "Terminated" state, + * it MUST be destroyed immediately. Timer L reflects the amount of time the server + * transaction could receive 2xx responses for retransmission from the + * TU while it is waiting to receive an ACK. + * https://tools.ietf.org/html/rfc6026#section-7.1 + * https://tools.ietf.org/html/rfc6026#section-8.7 + */ + InviteServerTransaction.prototype.timer_L = function () { + this.logger.debug("Timer L expired for INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Accepted) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return InviteServerTransaction; +}(server_transaction_1.ServerTransaction)); +exports.InviteServerTransaction = InviteServerTransaction; diff --git a/lib/core/transactions/non-invite-client-transaction.d.ts b/lib/core/transactions/non-invite-client-transaction.d.ts new file mode 100644 index 000000000..d4beb4e72 --- /dev/null +++ b/lib/core/transactions/non-invite-client-transaction.d.ts @@ -0,0 +1,68 @@ +import { IncomingResponseMessage, OutgoingRequestMessage } from "../messages"; +import { Transport } from "../transport"; +import { ClientTransaction } from "./client-transaction"; +import { ClientTransactionUser } from "./transaction-user"; +/** + * Non-INVITE Client Transaction + * + * Non-INVITE transactions do not make use of ACK. + * They are simple request-response interactions. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + */ +export declare class NonInviteClientTransaction extends ClientTransaction { + private F; + private K; + /** + * Constructor + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + * @param request The outgoing Non-INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + constructor(request: OutgoingRequestMessage, transport: Transport, user: ClientTransactionUser); + /** + * Destructor. + */ + dispose(): void; + /** Transaction kind. Deprecated. */ + readonly kind: string; + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + receiveResponse(response: IncomingResponseMessage): void; + /** + * The client transaction SHOULD inform the TU that a transport failure has occurred, + * and the client transaction SHOULD transition directly to the "Terminated" state. + * The TU will handle the failover mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error Trasnsport error + */ + protected onTransportError(error: Error): void; + /** For logging. */ + protected typeToString(): string; + /** + * Execute a state transition. + * @param newState New state. + */ + private stateTransition; + /** + * If Timer F fires while the client transaction is still in the + * "Trying" state, the client transaction SHOULD inform the TU about the + * timeout, and then it SHOULD enter the "Terminated" state. + * If timer F fires while in the "Proceeding" state, the TU MUST be informed of + * a timeout, and the client transaction MUST transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + private timer_F; + /** + * If Timer K fires while in this (COMPLETED) state, the client transaction + * MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + private timer_K; +} diff --git a/lib/core/transactions/non-invite-client-transaction.js b/lib/core/transactions/non-invite-client-transaction.js new file mode 100644 index 000000000..5c40fe55f --- /dev/null +++ b/lib/core/transactions/non-invite-client-transaction.js @@ -0,0 +1,245 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var timers_1 = require("../timers"); +var client_transaction_1 = require("./client-transaction"); +var transaction_state_1 = require("./transaction-state"); +/** + * Non-INVITE Client Transaction + * + * Non-INVITE transactions do not make use of ACK. + * They are simple request-response interactions. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + */ +var NonInviteClientTransaction = /** @class */ (function (_super) { + tslib_1.__extends(NonInviteClientTransaction, _super); + /** + * Constructor + * Upon construction, the outgoing request's Via header is updated by calling `setViaHeader`. + * Then `toString` is called on the outgoing request and the message is sent via the transport. + * After construction the transaction will be in the "calling" state and the transaction id + * will equal the branch parameter set in the Via header of the outgoing request. + * https://tools.ietf.org/html/rfc3261#section-17.1.2 + * @param request The outgoing Non-INVITE request. + * @param transport The transport. + * @param user The transaction user. + */ + function NonInviteClientTransaction(request, transport, user) { + var _this = _super.call(this, request, transport, user, transaction_state_1.TransactionState.Trying, "sip.transaction.nict") || this; + // FIXME: Timer E for unreliable transports not implemented. + // + // The "Trying" state is entered when the TU initiates a new client + // transaction with a request. When entering this state, the client + // transaction SHOULD set timer F to fire in 64*T1 seconds. The request + // MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + _this.F = setTimeout(function () { return _this.timer_F(); }, timers_1.Timers.TIMER_F); + _this.send(request.toString()).catch(function (error) { + _this.logTransportError(error, "Failed to send initial outgoing request."); + }); + return _this; + } + /** + * Destructor. + */ + NonInviteClientTransaction.prototype.dispose = function () { + if (this.F) { + clearTimeout(this.F); + this.F = undefined; + } + if (this.K) { + clearTimeout(this.K); + this.K = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(NonInviteClientTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "nict"; + }, + enumerable: true, + configurable: true + }); + /** + * Handler for incoming responses from the transport which match this transaction. + * @param response The incoming response. + */ + NonInviteClientTransaction.prototype.receiveResponse = function (response) { + var statusCode = response.statusCode; + if (!statusCode || statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // If a provisional response is received while in the "Trying" state, the + // response MUST be passed to the TU, and then the client transaction + // SHOULD move to the "Proceeding" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 100 && statusCode <= 199) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + // If a final response (status codes 200-699) is received while in the + // "Trying" state, the response MUST be passed to the TU, and the + // client transaction MUST transition to the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + if (statusCode === 408) { + this.onRequestTimeout(); + return; + } + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // If a provisional response is received while in the "Proceeding" state, + // the response MUST be passed to the TU. (From Figure 6) + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 100 && statusCode <= 199) { + if (this.user.receiveResponse) { + return this.user.receiveResponse(response); + } + } + // If a final response (status codes 200-699) is received while in the + // "Proceeding" state, the response MUST be passed to the TU, and the + // client transaction MUST transition to the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + if (statusCode === 408) { + this.onRequestTimeout(); + return; + } + if (this.user.receiveResponse) { + this.user.receiveResponse(response); + } + return; + } + case transaction_state_1.TransactionState.Completed: + // The "Completed" state exists to buffer any additional response + // retransmissions that may be received (which is why the client + // transaction remains there only for unreliable transports). + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + return; + case transaction_state_1.TransactionState.Terminated: + // For good measure just absorb additional response retransmissions. + return; + default: + throw new Error("Invalid state " + this.state); + } + var message = "Non-INVITE client transaction received unexpected " + statusCode + " response while in state " + this.state + "."; + this.logger.warn(message); + return; + }; + /** + * The client transaction SHOULD inform the TU that a transport failure has occurred, + * and the client transaction SHOULD transition directly to the "Terminated" state. + * The TU will handle the failover mechanisms described in [4]. + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * @param error Trasnsport error + */ + NonInviteClientTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + NonInviteClientTransaction.prototype.typeToString = function () { + return "non-INVITE client transaction"; + }; + /** + * Execute a state transition. + * @param newState New state. + */ + NonInviteClientTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Trying: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Trying) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Trying && + this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Trying && + this.state !== transaction_state_1.TransactionState.Proceeding && + this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // Once the client transaction enters the "Completed" state, it MUST set + // Timer K to fire in T4 seconds for unreliable transports, and zero + // seconds for reliable transports The "Completed" state exists to + // buffer any additional response retransmissions that may be received + // (which is why the client transaction remains there only for unreliable transports). + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (newState === transaction_state_1.TransactionState.Completed) { + if (this.F) { + clearTimeout(this.F); + this.F = undefined; + } + this.K = setTimeout(function () { return _this.timer_K(); }, timers_1.Timers.TIMER_K); + } + // Once the transaction is in the terminated state, it MUST be destroyed immediately. + // https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + // Update state. + this.setState(newState); + }; + /** + * If Timer F fires while the client transaction is still in the + * "Trying" state, the client transaction SHOULD inform the TU about the + * timeout, and then it SHOULD enter the "Terminated" state. + * If timer F fires while in the "Proceeding" state, the TU MUST be informed of + * a timeout, and the client transaction MUST transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + NonInviteClientTransaction.prototype.timer_F = function () { + this.logger.debug("Timer F expired for non-INVITE client transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Trying || this.state === transaction_state_1.TransactionState.Proceeding) { + this.onRequestTimeout(); + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + /** + * If Timer K fires while in this (COMPLETED) state, the client transaction + * MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + NonInviteClientTransaction.prototype.timer_K = function () { + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return NonInviteClientTransaction; +}(client_transaction_1.ClientTransaction)); +exports.NonInviteClientTransaction = NonInviteClientTransaction; diff --git a/lib/core/transactions/non-invite-server-transaction.d.ts b/lib/core/transactions/non-invite-server-transaction.d.ts new file mode 100644 index 000000000..38e693ba4 --- /dev/null +++ b/lib/core/transactions/non-invite-server-transaction.d.ts @@ -0,0 +1,55 @@ +import { IncomingRequestMessage } from "../messages"; +import { Transport } from "../transport"; +import { ServerTransaction } from "./server-transaction"; +import { ServerTransactionUser } from "./transaction-user"; +/** + * Non-INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ +export declare class NonInviteServerTransaction extends ServerTransaction { + private lastResponse; + private J; + /** + * Constructor. + * After construction the transaction will be in the "trying": state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + * @param request Incoming Non-INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + constructor(request: IncomingRequestMessage, transport: Transport, user: ServerTransactionUser); + /** + * Destructor. + */ + dispose(): void; + /** Transaction kind. Deprecated. */ + readonly kind: string; + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + receiveRequest(request: IncomingRequestMessage): void; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of repsonse. 101-199 not allowed per RFC 4320. + * @param response Response to send. + */ + receiveResponse(statusCode: number, response: string): void; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and SHOULD transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.2.4 + */ + protected onTransportError(error: Error): void; + /** For logging. */ + protected typeToString(): string; + private stateTransition; + /** + * The server transaction remains in this state until Timer J fires, + * at which point it MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ + private timer_J; +} diff --git a/lib/core/transactions/non-invite-server-transaction.js b/lib/core/transactions/non-invite-server-transaction.js new file mode 100644 index 000000000..35c7fdfb2 --- /dev/null +++ b/lib/core/transactions/non-invite-server-transaction.js @@ -0,0 +1,229 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var timers_1 = require("../timers"); +var server_transaction_1 = require("./server-transaction"); +var transaction_state_1 = require("./transaction-state"); +/** + * Non-INVITE Server Transaction + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ +var NonInviteServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(NonInviteServerTransaction, _super); + /** + * Constructor. + * After construction the transaction will be in the "trying": state and the transaction + * `id` will equal the branch parameter set in the Via header of the incoming request. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + * @param request Incoming Non-INVITE request from the transport. + * @param transport The transport. + * @param user The transaction user. + */ + function NonInviteServerTransaction(request, transport, user) { + return _super.call(this, request, transport, user, transaction_state_1.TransactionState.Trying, "sip.transaction.nist") || this; + } + /** + * Destructor. + */ + NonInviteServerTransaction.prototype.dispose = function () { + if (this.J) { + clearTimeout(this.J); + this.J = undefined; + } + _super.prototype.dispose.call(this); + }; + Object.defineProperty(NonInviteServerTransaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + return "nist"; + }, + enumerable: true, + configurable: true + }); + /** + * Receive requests from transport matching this transaction. + * @param request Request matching this transaction. + */ + NonInviteServerTransaction.prototype.receiveRequest = function (request) { + var _this = this; + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // Once in the "Trying" state, any further request retransmissions are discarded. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + break; + case transaction_state_1.TransactionState.Proceeding: + // If a retransmission of the request is received while in the "Proceeding" state, + // the most recently sent provisional response MUST be passed to the transport layer for retransmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (!this.lastResponse) { + throw new Error("Last response undefined."); + } + this.send(this.lastResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of provisional response."); + }); + break; + case transaction_state_1.TransactionState.Completed: + // While in the "Completed" state, the server transaction MUST pass the final response to the transport + // layer for retransmission whenever a retransmission of the request is received. Any other final responses + // passed by the TU to the server transaction MUST be discarded while in the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (!this.lastResponse) { + throw new Error("Last response undefined."); + } + this.send(this.lastResponse).catch(function (error) { + _this.logTransportError(error, "Failed to send retransmission of final response."); + }); + break; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + }; + /** + * Receive responses from TU for this transaction. + * @param statusCode Status code of repsonse. 101-199 not allowed per RFC 4320. + * @param response Response to send. + */ + NonInviteServerTransaction.prototype.receiveResponse = function (statusCode, response) { + var _this = this; + if (statusCode < 100 || statusCode > 699) { + throw new Error("Invalid status code " + statusCode); + } + // An SIP element MUST NOT send any provisional response with a + // Status-Code other than 100 to a non-INVITE request. + // An SIP element MUST NOT respond to a non-INVITE request with a + // Status-Code of 100 over any unreliable transport, such as UDP, + // before the amount of time it takes a client transaction's Timer E to be reset to T2. + // An SIP element MAY respond to a non-INVITE request with a + // Status-Code of 100 over a reliable transport at any time. + // https://tools.ietf.org/html/rfc4320#section-4.1 + if (statusCode > 100 && statusCode <= 199) { + throw new Error("Provisional response other than 100 not allowed."); + } + switch (this.state) { + case transaction_state_1.TransactionState.Trying: + // While in the "Trying" state, if the TU passes a provisional response + // to the server transaction, the server transaction MUST enter the "Proceeding" state. + // The response MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + this.lastResponse = response; + if (statusCode >= 100 && statusCode < 200) { + this.stateTransition(transaction_state_1.TransactionState.Proceeding); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send provisional response."); + }); + return; + } + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Proceeding: + // Any further provisional responses that are received from the TU while + // in the "Proceeding" state MUST be passed to the transport layer for transmission. + // If the TU passes a final response (status codes 200-699) to the server while in + // the "Proceeding" state, the transaction MUST enter the "Completed" state, and + // the response MUST be passed to the transport layer for transmission. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + this.lastResponse = response; + if (statusCode >= 200 && statusCode <= 699) { + this.stateTransition(transaction_state_1.TransactionState.Completed); + this.send(response).catch(function (error) { + _this.logTransportError(error, "Failed to send final response."); + }); + return; + } + break; + case transaction_state_1.TransactionState.Completed: + // Any other final responses passed by the TU to the server + // transaction MUST be discarded while in the "Completed" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + return; + case transaction_state_1.TransactionState.Terminated: + break; + default: + throw new Error("Invalid state " + this.state); + } + var message = "Non-INVITE server transaction received unexpected " + statusCode + " response from TU while in state " + this.state + "."; + this.logger.error(message); + throw new Error(message); + }; + /** + * First, the procedures in [4] are followed, which attempt to deliver the response to a backup. + * If those should all fail, based on the definition of failure in [4], the server transaction SHOULD + * inform the TU that a failure has occurred, and SHOULD transition to the terminated state. + * https://tools.ietf.org/html/rfc3261#section-17.2.4 + */ + NonInviteServerTransaction.prototype.onTransportError = function (error) { + if (this.user.onTransportError) { + this.user.onTransportError(error); + } + this.stateTransition(transaction_state_1.TransactionState.Terminated, true); + }; + /** For logging. */ + NonInviteServerTransaction.prototype.typeToString = function () { + return "non-INVITE server transaction"; + }; + NonInviteServerTransaction.prototype.stateTransition = function (newState, dueToTransportError) { + var _this = this; + if (dueToTransportError === void 0) { dueToTransportError = false; } + // Assert valid state transitions. + var invalidStateTransition = function () { + throw new Error("Invalid state transition from " + _this.state + " to " + newState); + }; + switch (newState) { + case transaction_state_1.TransactionState.Trying: + invalidStateTransition(); + break; + case transaction_state_1.TransactionState.Proceeding: + if (this.state !== transaction_state_1.TransactionState.Trying) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Completed: + if (this.state !== transaction_state_1.TransactionState.Trying && this.state !== transaction_state_1.TransactionState.Proceeding) { + invalidStateTransition(); + } + break; + case transaction_state_1.TransactionState.Terminated: + if (this.state !== transaction_state_1.TransactionState.Proceeding && this.state !== transaction_state_1.TransactionState.Completed) { + if (!dueToTransportError) { + invalidStateTransition(); + } + } + break; + default: + invalidStateTransition(); + } + // When the server transaction enters the "Completed" state, it MUST set Timer J to fire + // in 64*T1 seconds for unreliable transports, and zero seconds for reliable transports. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (newState === transaction_state_1.TransactionState.Completed) { + this.J = setTimeout(function () { return _this.timer_J(); }, timers_1.Timers.TIMER_J); + } + // The server transaction MUST be destroyed the instant it enters the "Terminated" state. + // https://tools.ietf.org/html/rfc3261#section-17.2.2 + if (newState === transaction_state_1.TransactionState.Terminated) { + this.dispose(); + } + this.setState(newState); + }; + /** + * The server transaction remains in this state until Timer J fires, + * at which point it MUST transition to the "Terminated" state. + * https://tools.ietf.org/html/rfc3261#section-17.2.2 + */ + NonInviteServerTransaction.prototype.timer_J = function () { + this.logger.debug("Timer J expired for NON-INVITE server transaction " + this.id + "."); + if (this.state === transaction_state_1.TransactionState.Completed) { + this.stateTransition(transaction_state_1.TransactionState.Terminated); + } + }; + return NonInviteServerTransaction; +}(server_transaction_1.ServerTransaction)); +exports.NonInviteServerTransaction = NonInviteServerTransaction; diff --git a/lib/core/transactions/server-transaction.d.ts b/lib/core/transactions/server-transaction.d.ts new file mode 100644 index 000000000..6f9e80c08 --- /dev/null +++ b/lib/core/transactions/server-transaction.d.ts @@ -0,0 +1,33 @@ +import { IncomingRequestMessage } from "../messages"; +import { Transport } from "../transport"; +import { Transaction } from "./transaction"; +import { TransactionState } from "./transaction-state"; +import { ServerTransactionUser } from "./transaction-user"; +/** + * Server Transaction + * The server transaction is responsible for the delivery of requests to + * the TU and the reliable transmission of responses. It accomplishes + * this through a state machine. Server transactions are created by the + * core when a request is received, and transaction handling is desired + * for that request (this is not always the case). + * https://tools.ietf.org/html/rfc3261#section-17.2 + */ +export declare abstract class ServerTransaction extends Transaction { + private _request; + protected user: ServerTransactionUser; + protected constructor(_request: IncomingRequestMessage, transport: Transport, user: ServerTransactionUser, state: TransactionState, loggerCategory: string); + /** The incoming request the transaction handling. */ + readonly request: IncomingRequestMessage; + /** + * Receive incoming requests from the transport which match this transaction. + * @param request The incoming request. + */ + abstract receiveRequest(request: IncomingRequestMessage): void; + /** + * Receive outgoing responses to this request from the transaction user. + * Responses will be delivered to the transport as necessary. + * @param statusCode Response status code. + * @param response Response. + */ + abstract receiveResponse(statusCode: number, response: string): void; +} diff --git a/lib/core/transactions/server-transaction.js b/lib/core/transactions/server-transaction.js new file mode 100644 index 000000000..d723c1688 --- /dev/null +++ b/lib/core/transactions/server-transaction.js @@ -0,0 +1,32 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transaction_1 = require("./transaction"); +/** + * Server Transaction + * The server transaction is responsible for the delivery of requests to + * the TU and the reliable transmission of responses. It accomplishes + * this through a state machine. Server transactions are created by the + * core when a request is received, and transaction handling is desired + * for that request (this is not always the case). + * https://tools.ietf.org/html/rfc3261#section-17.2 + */ +var ServerTransaction = /** @class */ (function (_super) { + tslib_1.__extends(ServerTransaction, _super); + function ServerTransaction(_request, transport, user, state, loggerCategory) { + var _this = _super.call(this, transport, user, _request.viaBranch, state, loggerCategory) || this; + _this._request = _request; + _this.user = user; + return _this; + } + Object.defineProperty(ServerTransaction.prototype, "request", { + /** The incoming request the transaction handling. */ + get: function () { + return this._request; + }, + enumerable: true, + configurable: true + }); + return ServerTransaction; +}(transaction_1.Transaction)); +exports.ServerTransaction = ServerTransaction; diff --git a/lib/core/transactions/transaction-state.d.ts b/lib/core/transactions/transaction-state.d.ts new file mode 100644 index 000000000..b4e366429 --- /dev/null +++ b/lib/core/transactions/transaction-state.d.ts @@ -0,0 +1,10 @@ +/** Transaction state. */ +export declare enum TransactionState { + Accepted = "Accepted", + Calling = "Calling", + Completed = "Completed", + Confirmed = "Confirmed", + Proceeding = "Proceeding", + Terminated = "Terminated", + Trying = "Trying" +} diff --git a/lib/core/transactions/transaction-state.js b/lib/core/transactions/transaction-state.js new file mode 100644 index 000000000..3f88d67ed --- /dev/null +++ b/lib/core/transactions/transaction-state.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** Transaction state. */ +var TransactionState; +(function (TransactionState) { + TransactionState["Accepted"] = "Accepted"; + TransactionState["Calling"] = "Calling"; + TransactionState["Completed"] = "Completed"; + TransactionState["Confirmed"] = "Confirmed"; + TransactionState["Proceeding"] = "Proceeding"; + TransactionState["Terminated"] = "Terminated"; + TransactionState["Trying"] = "Trying"; +})(TransactionState = exports.TransactionState || (exports.TransactionState = {})); diff --git a/lib/core/transactions/transaction-user.d.ts b/lib/core/transactions/transaction-user.d.ts new file mode 100644 index 000000000..60e1c9572 --- /dev/null +++ b/lib/core/transactions/transaction-user.d.ts @@ -0,0 +1,68 @@ +import { TransportError } from "../exceptions"; +import { LoggerFactory } from "../log"; +import { IncomingResponseMessage } from "../messages"; +import { TransactionState } from "./transaction-state"; +/** + * Transaction User (TU) Interface + * The layer of protocol processing that resides above the transaction layer. + * Transaction users include the UAC core, UAS core, and proxy core. + * https://tools.ietf.org/html/rfc3261#section-5 + * https://tools.ietf.org/html/rfc3261#section-6 + */ +export interface TransactionUser { + /** + * Logger factory. + */ + loggerFactory: LoggerFactory; + /** + * Callback for notification of transaction state changes. + * + * Not called when transaction is constructed, so there is + * no notification of entering the initial transaction state. + * Otherwise, called once for each transaction state change. + * State changes adhere to the following RFCs. + * https://tools.ietf.org/html/rfc3261#section-17 + * https://tools.ietf.org/html/rfc6026 + */ + onStateChange?: (newState: TransactionState) => void; + /** + * Callback for notification of a transport error. + * + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + * https://tools.ietf.org/html/rfc3261#section-17.1.4 + * https://tools.ietf.org/html/rfc3261#section-17.2.4 + * https://tools.ietf.org/html/rfc6026 + */ + onTransportError?: (error: TransportError) => void; +} +/** + * UAC core Transaction User inteface. + */ +export interface ClientTransactionUser extends TransactionUser { + /** + * Callback for request timeout error. + * + * When a timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + * TU MUST be informed of a timeout. + * https://tools.ietf.org/html/rfc3261#section-17.1.2.2 + */ + onRequestTimeout?: () => void; + /** + * Callback for delegation of valid response handling. + * + * Valid responses are passed up to the TU from the client transaction. + * https://tools.ietf.org/html/rfc3261#section-17.1 + */ + receiveResponse?: (response: IncomingResponseMessage) => void; +} +/** + * UAS core Transaction User interface. + */ +export interface ServerTransactionUser extends TransactionUser { +} diff --git a/lib/core/transactions/transaction-user.js b/lib/core/transactions/transaction-user.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/transactions/transaction-user.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/transactions/transaction.d.ts b/lib/core/transactions/transaction.d.ts new file mode 100644 index 000000000..9ea9f54e9 --- /dev/null +++ b/lib/core/transactions/transaction.d.ts @@ -0,0 +1,60 @@ +/// +import { EventEmitter } from "events"; +import { TransportError } from "../exceptions"; +import { Logger } from "../log"; +import { Transport } from "../transport"; +import { TransactionState } from "./transaction-state"; +import { TransactionUser } from "./transaction-user"; +/** + * Transaction + * + * SIP is a transactional protocol: interactions between components take + * place in a series of independent message exchanges. Specifically, a + * SIP transaction consists of a single request and any responses to + * that request, which include zero or more provisional responses and + * one or more final responses. In the case of a transaction where the + * request was an INVITE (known as an INVITE transaction), the + * transaction also includes the ACK only if the final response was not + * a 2xx response. If the response was a 2xx, the ACK is not considered + * part of the transaction. + * https://tools.ietf.org/html/rfc3261#section-17 + */ +export declare abstract class Transaction extends EventEmitter { + private _transport; + private _user; + private _id; + private _state; + protected logger: Logger; + protected constructor(_transport: Transport, _user: TransactionUser, _id: string, _state: TransactionState, loggerCategory: string); + /** + * Destructor. + * Once the transaction is in the "terminated" state, it is destroyed + * immediately and there is no need to call `dispose`. However, if a + * transaction needs to be ended prematurely, the transaction user may + * do so by calling this method (for example, perhaps the UA is shutting down). + * No state transition will occur upon calling this method, all outstanding + * transmission timers will be cancelled, and use of the transaction after + * calling `dispose` is undefined. + */ + dispose(): void; + /** Transaction id. */ + readonly id: string; + /** Transaction kind. Deprecated. */ + readonly kind: string; + /** Transaction state. */ + readonly state: TransactionState; + /** Transaction transport. */ + readonly transport: Transport; + /** Subscribe to 'stateChanged' event. */ + on(name: "stateChanged", callback: () => void): this; + protected logTransportError(error: TransportError, message: string): void; + protected abstract onTransportError(error: TransportError): void; + /** + * Pass message to transport for transmission. If transport fails, + * the transaction user is notified by callback to onTransportError(). + * @throws {TransportError} If transport fails. + */ + protected send(message: string): Promise; + protected setState(state: TransactionState): void; + protected typeToString(): string; +} diff --git a/lib/core/transactions/transaction.js b/lib/core/transactions/transaction.js new file mode 100644 index 000000000..a5b346c85 --- /dev/null +++ b/lib/core/transactions/transaction.js @@ -0,0 +1,122 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +var exceptions_1 = require("../exceptions"); +/** + * Transaction + * + * SIP is a transactional protocol: interactions between components take + * place in a series of independent message exchanges. Specifically, a + * SIP transaction consists of a single request and any responses to + * that request, which include zero or more provisional responses and + * one or more final responses. In the case of a transaction where the + * request was an INVITE (known as an INVITE transaction), the + * transaction also includes the ACK only if the final response was not + * a 2xx response. If the response was a 2xx, the ACK is not considered + * part of the transaction. + * https://tools.ietf.org/html/rfc3261#section-17 + */ +var Transaction = /** @class */ (function (_super) { + tslib_1.__extends(Transaction, _super); + function Transaction(_transport, _user, _id, _state, loggerCategory) { + var _this = _super.call(this) || this; + _this._transport = _transport; + _this._user = _user; + _this._id = _id; + _this._state = _state; + _this.logger = _user.loggerFactory.getLogger(loggerCategory, _id); + _this.logger.debug("Constructing " + _this.typeToString() + " with id " + _this.id + "."); + return _this; + } + /** + * Destructor. + * Once the transaction is in the "terminated" state, it is destroyed + * immediately and there is no need to call `dispose`. However, if a + * transaction needs to be ended prematurely, the transaction user may + * do so by calling this method (for example, perhaps the UA is shutting down). + * No state transition will occur upon calling this method, all outstanding + * transmission timers will be cancelled, and use of the transaction after + * calling `dispose` is undefined. + */ + Transaction.prototype.dispose = function () { + this.logger.debug("Destroyed " + this.typeToString() + " with id " + this.id + "."); + }; + Object.defineProperty(Transaction.prototype, "id", { + /** Transaction id. */ + get: function () { + return this._id; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "kind", { + /** Transaction kind. Deprecated. */ + get: function () { + throw new Error("Invalid kind."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "state", { + /** Transaction state. */ + get: function () { + return this._state; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transaction.prototype, "transport", { + /** Transaction transport. */ + get: function () { + return this._transport; + }, + enumerable: true, + configurable: true + }); + Transaction.prototype.on = function (name, callback) { return _super.prototype.on.call(this, name, callback); }; + Transaction.prototype.logTransportError = function (error, message) { + this.logger.error(error.message); + this.logger.error("Transport error occurred in " + this.typeToString() + " with id " + this.id + "."); + this.logger.error(message); + }; + /** + * Pass message to transport for transmission. If transport fails, + * the transaction user is notified by callback to onTransportError(). + * @throws {TransportError} If transport fails. + */ + Transaction.prototype.send = function (message) { + var _this = this; + return this.transport.send(message).catch(function (error) { + // FIXME: Transport is not, yet, typed and it is not clear + // yet what send() may or may not send our way. So for now, + // make sure we convert it to a TransportError if need be. + if (error instanceof exceptions_1.TransportError) { + _this.onTransportError(error); + return; + } + var transportError; + if (error && typeof error.message === "string") { + transportError = new exceptions_1.TransportError(error.message); + } + else { + transportError = new exceptions_1.TransportError(); + } + _this.onTransportError(transportError); + throw transportError; + }); + }; + Transaction.prototype.setState = function (state) { + this.logger.debug("State change to \"" + state + "\" on " + this.typeToString() + " with id " + this.id + "."); + this._state = state; + if (this._user.onStateChange) { + this._user.onStateChange(state); + } + this.emit("stateChanged"); + }; + Transaction.prototype.typeToString = function () { + return "UnknownType"; + }; + return Transaction; +}(events_1.EventEmitter)); +exports.Transaction = Transaction; diff --git a/lib/core/transport.d.ts b/lib/core/transport.d.ts new file mode 100644 index 000000000..f7da8dbb1 --- /dev/null +++ b/lib/core/transport.d.ts @@ -0,0 +1,73 @@ +/// +import { EventEmitter } from "events"; +import { Logger } from "./log"; +/** + * Transport + * @remarks + * Abstract transport layer base class. + * @param logger - Logger. + * @param options - Options bucket. + * @public + */ +export declare abstract class Transport extends EventEmitter { + server: any; + protected logger: Logger; + constructor(logger: Logger, options: any); + /** + * Returns the promise designated by the child layer then emits a connected event. + * Automatically emits an event upon resolution, unless overrideEvent is set. If you + * override the event in this fashion, you should emit it in your implementation of connectPromise + * @param options - Options bucket. + */ + connect(options?: any): Promise; + /** + * Returns true if the transport is connected + */ + abstract isConnected(): boolean; + /** + * Sends a message then emits a 'messageSent' event. Automatically emits an + * event upon resolution, unless data.overrideEvent is set. If you override + * the event in this fashion, you should emit it in your implementation of sendPromise + * @param msg - Message. + * @param options - Options bucket. + */ + send(msg: string, options?: any): Promise; + /** + * Returns the promise designated by the child layer then emits a + * disconnected event. Automatically emits an event upon resolution, + * unless overrideEvent is set. If you override the event in this fashion, + * you should emit it in your implementation of disconnectPromise + * @param options - Options bucket + */ + disconnect(options?: any): Promise; + afterConnected(callback: () => void): void; + /** + * Returns a promise which resolves once the UA is connected. DEPRECATION WARNING: just use afterConnected() + */ + waitForConnected(): Promise; + /** + * Called by connect, must return a promise + * promise must resolve to an object. object supports 1 parameter: overrideEvent - Boolean + * @param options - Options bucket. + */ + protected abstract connectPromise(options: any): Promise; + /** + * Called by send, must return a promise + * promise must resolve to an object. object supports 2 parameters: msg - string (mandatory) + * and overrideEvent - Boolean (optional) + * @param msg - Message. + * @param options - Options bucket. + */ + protected abstract sendPromise(msg: string, options?: any): Promise; + /** + * Called by disconnect, must return a promise + * promise must resolve to an object. object supports 1 parameter: overrideEvent - Boolean + * @param options - Options bucket. + */ + protected abstract disconnectPromise(options: any): Promise; + /** + * To be called when a message is received + * @param e - Event + */ + protected abstract onMessage(e: any): void; +} diff --git a/lib/core/transport.js b/lib/core/transport.js new file mode 100644 index 000000000..7b5a0044c --- /dev/null +++ b/lib/core/transport.js @@ -0,0 +1,88 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var events_1 = require("events"); +/** + * Transport + * @remarks + * Abstract transport layer base class. + * @param logger - Logger. + * @param options - Options bucket. + * @public + */ +var Transport = /** @class */ (function (_super) { + tslib_1.__extends(Transport, _super); + function Transport(logger, options) { + var _this = _super.call(this) || this; + _this.logger = logger; + return _this; + } + /** + * Returns the promise designated by the child layer then emits a connected event. + * Automatically emits an event upon resolution, unless overrideEvent is set. If you + * override the event in this fashion, you should emit it in your implementation of connectPromise + * @param options - Options bucket. + */ + Transport.prototype.connect = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.connectPromise(options).then(function (data) { + if (!data.overrideEvent) { + _this.emit("connected"); + } + }); + }; + /** + * Sends a message then emits a 'messageSent' event. Automatically emits an + * event upon resolution, unless data.overrideEvent is set. If you override + * the event in this fashion, you should emit it in your implementation of sendPromise + * @param msg - Message. + * @param options - Options bucket. + */ + Transport.prototype.send = function (msg, options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.sendPromise(msg).then(function (data) { + if (!data.overrideEvent) { + _this.emit("messageSent", data.msg); + } + }); + }; + /** + * Returns the promise designated by the child layer then emits a + * disconnected event. Automatically emits an event upon resolution, + * unless overrideEvent is set. If you override the event in this fashion, + * you should emit it in your implementation of disconnectPromise + * @param options - Options bucket + */ + Transport.prototype.disconnect = function (options) { + var _this = this; + if (options === void 0) { options = {}; } + return this.disconnectPromise(options).then(function (data) { + if (!data.overrideEvent) { + _this.emit("disconnected"); + } + }); + }; + Transport.prototype.afterConnected = function (callback) { + if (this.isConnected()) { + callback(); + } + else { + this.once("connected", callback); + } + }; + /** + * Returns a promise which resolves once the UA is connected. DEPRECATION WARNING: just use afterConnected() + */ + Transport.prototype.waitForConnected = function () { + var _this = this; + // tslint:disable-next-line:no-console + console.warn("DEPRECATION WARNING Transport.waitForConnected(): use afterConnected() instead"); + return new Promise(function (resolve) { + _this.afterConnected(resolve); + }); + }; + return Transport; +}(events_1.EventEmitter)); +exports.Transport = Transport; diff --git a/lib/core/user-agent-core/allowed-methods.d.ts b/lib/core/user-agent-core/allowed-methods.d.ts new file mode 100644 index 000000000..c0f99a550 --- /dev/null +++ b/lib/core/user-agent-core/allowed-methods.d.ts @@ -0,0 +1,4 @@ +/** + * FIXME: TODO: Should be configurable/variable. + */ +export declare const AllowedMethods: string[]; diff --git a/lib/core/user-agent-core/allowed-methods.js b/lib/core/user-agent-core/allowed-methods.js new file mode 100644 index 000000000..7ae5cdd6d --- /dev/null +++ b/lib/core/user-agent-core/allowed-methods.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = require("../messages"); +/** + * FIXME: TODO: Should be configurable/variable. + */ +exports.AllowedMethods = [ + messages_1.C.ACK, + messages_1.C.BYE, + messages_1.C.CANCEL, + messages_1.C.INFO, + messages_1.C.INVITE, + messages_1.C.MESSAGE, + messages_1.C.NOTIFY, + messages_1.C.OPTIONS, + messages_1.C.PRACK, + messages_1.C.REFER, + messages_1.C.SUBSCRIBE +]; diff --git a/lib/core/user-agent-core/index.d.ts b/lib/core/user-agent-core/index.d.ts new file mode 100644 index 000000000..189fc40b8 --- /dev/null +++ b/lib/core/user-agent-core/index.d.ts @@ -0,0 +1,3 @@ +export * from "./user-agent-core"; +export * from "./user-agent-core-configuration"; +export * from "./user-agent-core-delegate"; diff --git a/lib/core/user-agent-core/index.js b/lib/core/user-agent-core/index.js new file mode 100644 index 000000000..e87aff642 --- /dev/null +++ b/lib/core/user-agent-core/index.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./user-agent-core"), exports); diff --git a/lib/core/user-agent-core/user-agent-core-configuration.d.ts b/lib/core/user-agent-core/user-agent-core-configuration.d.ts new file mode 100644 index 000000000..682082623 --- /dev/null +++ b/lib/core/user-agent-core/user-agent-core-configuration.d.ts @@ -0,0 +1,94 @@ +import { LoggerFactory } from "../log"; +import { DigestAuthentication, URI } from "../messages"; +import { Transport } from "../transport"; +/** + * User agent contact. + * @remarks + * https://tools.ietf.org/html/rfc3261#section-8.1.1.8 + * This is ported from UA.contact. + * FIXME: TODO: This is not a great rep for Contact + * and is used in a kinda hacky way herein. + */ +export interface Contact { + pubGruu: URI | undefined; + tempGruu: URI | undefined; + uri: URI; + toString: (options?: any) => string; +} +/** + * User agent core configuration. + */ +export interface UserAgentCoreConfiguration { + /** + * Address-of-Record (AOR). + * @remarks + * https://tools.ietf.org/html/rfc3261#section-6 + */ + aor: URI; + /** + * Contact. + * @remarks + * https://tools.ietf.org/html/rfc3261#section-8.1.1.8 + */ + contact: Contact; + /** + * From header display name. + */ + displayName: string; + /** + * Logger factory. + */ + loggerFactory: LoggerFactory; + /** + * Force Via header field transport to TCP. + */ + hackViaTcp: boolean; + /** + * Preloaded route set. + */ + routeSet: Array; + /** + * Unique instance id. + */ + sipjsId: string; + /** + * Option tags of supported SIP extenstions. + */ + supportedOptionTags: Array; + /** + * Option tags of supported SIP extenstions. + * Used in resposnes. + * @remarks + * FIXME: Make this go away. + */ + supportedOptionTagsResponse: Array; + /** + * User-Agent header field value. + * @remarks + * https://tools.ietf.org/html/rfc3261#section-20.41 + */ + userAgentHeaderFieldValue: string | undefined; + /** + * Force use of "rport" Via header field parameter. + * @remarks + * https://www.ietf.org/rfc/rfc3581.txt + */ + viaForceRport: boolean; + /** + * Via header field host name or network address. + * @remarks + * The Via header field indicates the path taken by the request so far + * and indicates the path that should be followed in routing responses. + */ + viaHost: string; + /** + * DEPRECATED + * Authentication factory function. + */ + authenticationFactory(): DigestAuthentication | undefined; + /** + * DEPRECATED: This is a hack to get around `Transport` + * requiring the `UA` to start for construction. + */ + transportAccessor(): Transport | undefined; +} diff --git a/lib/core/user-agent-core/user-agent-core-configuration.js b/lib/core/user-agent-core/user-agent-core-configuration.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/user-agent-core/user-agent-core-configuration.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/user-agent-core/user-agent-core-delegate.d.ts b/lib/core/user-agent-core/user-agent-core-delegate.d.ts new file mode 100644 index 000000000..a8400fc2b --- /dev/null +++ b/lib/core/user-agent-core/user-agent-core-delegate.d.ts @@ -0,0 +1,31 @@ +import { IncomingInviteRequest, IncomingMessageRequest, IncomingNotifyRequest, IncomingReferRequest, IncomingSubscribeRequest } from "../messages"; +/** + * User agent core delegate. + */ +export interface UserAgentCoreDelegate { + /** + * Receive INVITE request. + * @param request Incoming INVITE request. + */ + onInvite?(request: IncomingInviteRequest): void; + /** + * Receive MESSAGE request. + * @param request Incoming MESSAGE request. + */ + onMessage?(request: IncomingMessageRequest): void; + /** + * DEPRECATED. Receive NOTIFY request. + * @param message Incoming NOTIFY request. + */ + onNotify?(request: IncomingNotifyRequest): void; + /** + * Receive REFER request. + * @param request Incoming REFER request. + */ + onRefer?(request: IncomingReferRequest): void; + /** + * Receive SUBSCRIBE request. + * @param request Incoming SUBSCRIBE request. + */ + onSubscribe?(request: IncomingSubscribeRequest): void; +} diff --git a/lib/core/user-agent-core/user-agent-core-delegate.js b/lib/core/user-agent-core/user-agent-core-delegate.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/core/user-agent-core/user-agent-core-delegate.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/core/user-agent-core/user-agent-core.d.ts b/lib/core/user-agent-core/user-agent-core.d.ts new file mode 100644 index 000000000..556b696ae --- /dev/null +++ b/lib/core/user-agent-core/user-agent-core.d.ts @@ -0,0 +1,176 @@ +import { Dialog } from "../dialogs"; +import { LoggerFactory } from "../log"; +import { Body, IncomingRequestMessage, IncomingResponseMessage, OutgoingInviteRequest, OutgoingInviteRequestDelegate, OutgoingMessageRequest, OutgoingPublishRequest, OutgoingRegisterRequest, OutgoingRequest, OutgoingRequestDelegate, OutgoingRequestMessage, OutgoingRequestMessageOptions, OutgoingResponse, OutgoingSubscribeRequest, OutgoingSubscribeRequestDelegate, ResponseOptions, URI } from "../messages"; +import { Transport } from "../transport"; +import { SubscribeUserAgentClient, UserAgentClient, UserAgentServer } from "../user-agents"; +import { UserAgentCoreConfiguration } from "./user-agent-core-configuration"; +import { UserAgentCoreDelegate } from "./user-agent-core-delegate"; +/** + * Core: Core designates the functions specific to a particular type + * of SIP entity, i.e., specific to either a stateful or stateless + * proxy, a user agent or registrar. All cores, except those for + * the stateless proxy, are transaction users. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAC Core: The set of processing functions required of a UAC that + * reside above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAS Core: The set of processing functions required at a UAS that + * resides above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +export declare class UserAgentCore { + /** Configuration. */ + configuration: UserAgentCoreConfiguration; + /** Delegate. */ + delegate: UserAgentCoreDelegate; + /** Dialogs. */ + dialogs: Map; + /** Subscribers. */ + subscribers: Map; + /** UACs. */ + userAgentClients: Map; + /** UASs. */ + userAgentServers: Map; + private logger; + /** + * Constructor. + * @param configuration Configuration. + * @param delegate Delegate. + */ + constructor(configuration: UserAgentCoreConfiguration, delegate?: UserAgentCoreDelegate); + /** Destructor. */ + dispose(): void; + /** Reset. */ + reset(): void; + /** Logger factory. */ + readonly loggerFactory: LoggerFactory; + /** Transport. */ + readonly transport: Transport; + /** + * Send INVITE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + invite(request: OutgoingRequestMessage, delegate?: OutgoingInviteRequestDelegate): OutgoingInviteRequest; + /** + * Send MESSAGE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + message(request: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate): OutgoingMessageRequest; + /** + * Send PUBLISH. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + publish(request: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate): OutgoingPublishRequest; + /** + * Send REGISTER. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + register(request: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate): OutgoingRegisterRequest; + /** + * Send SUBSCRIBE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + subscribe(request: OutgoingRequestMessage, delegate?: OutgoingSubscribeRequestDelegate): OutgoingSubscribeRequest; + /** + * Send a request. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + request(request: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate): OutgoingRequest; + /** + * Outgoing request message factory function. + * @param method Method. + * @param requestURI Request-URI. + * @param fromURI From URI. + * @param toURI To URI. + * @param options Request options. + * @param extraHeaders Extra headers to add. + * @param body Message body. + */ + makeOutgoingRequestMessage(method: string, requestURI: URI, fromURI: URI, toURI: URI, options: OutgoingRequestMessageOptions, extraHeaders?: Array, body?: Body): OutgoingRequestMessage; + /** + * Handle an incoming request message from the transport. + * @param message Incoming request message from transport layer. + */ + receiveIncomingRequestFromTransport(message: IncomingRequestMessage): void; + /** + * Handle an incoming response message from the transport. + * @param message Incoming response message from transport layer. + */ + receiveIncomingResponseFromTransport(message: IncomingResponseMessage): void; + /** + * A stateless UAS is a UAS that does not maintain transaction state. + * It replies to requests normally, but discards any state that would + * ordinarily be retained by a UAS after a response has been sent. If a + * stateless UAS receives a retransmission of a request, it regenerates + * the response and re-sends it, just as if it were replying to the first + * instance of the request. A UAS cannot be stateless unless the request + * processing for that method would always result in the same response + * if the requests are identical. This rules out stateless registrars, + * for example. Stateless UASs do not use a transaction layer; they + * receive requests directly from the transport layer and send responses + * directly to the transport layer. + * https://tools.ietf.org/html/rfc3261#section-8.2.7 + * @param message Incoming request message to reply to. + * @param statusCode Status code to reply with. + */ + replyStateless(message: IncomingRequestMessage, options: ResponseOptions): OutgoingResponse; + /** + * In Section 18.2.1, replace the last paragraph with: + * + * Next, the server transport attempts to match the request to a + * server transaction. It does so using the matching rules described + * in Section 17.2.3. If a matching server transaction is found, the + * request is passed to that transaction for processing. If no match + * is found, the request is passed to the core, which may decide to + * construct a new server transaction for that request. + * https://tools.ietf.org/html/rfc6026#section-8.10 + * @param message Incoming request message from transport layer. + */ + private receiveRequestFromTransport; + /** + * UAC and UAS procedures depend strongly on two factors. First, based + * on whether the request or response is inside or outside of a dialog, + * and second, based on the method of a request. Dialogs are discussed + * thoroughly in Section 12; they represent a peer-to-peer relationship + * between user agents and are established by specific SIP methods, such + * as INVITE. + * @param message Incoming request message. + */ + private receiveRequest; + /** + * Once a dialog has been established between two UAs, either of them + * MAY initiate new transactions as needed within the dialog. The UA + * sending the request will take the UAC role for the transaction. The + * UA receiving the request will take the UAS role. Note that these may + * be different roles than the UAs held during the transaction that + * established the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2 + * @param message Incoming request message. + */ + private receiveInsideDialogRequest; + /** + * Assuming all of the checks in the previous subsections are passed, + * the UAS processing becomes method-specific. + * https://tools.ietf.org/html/rfc3261#section-8.2.5 + * @param message Incoming request message. + */ + private receiveOutsideDialogRequest; + /** + * Responses are first processed by the transport layer and then passed + * up to the transaction layer. The transaction layer performs its + * processing and then passes the response up to the TU. The majority + * of response processing in the TU is method specific. However, there + * are some general behaviors independent of the method. + * https://tools.ietf.org/html/rfc3261#section-8.1.3 + * @param message Incoming response message from transport layer. + */ + private receiveResponseFromTransport; +} diff --git a/lib/core/user-agent-core/user-agent-core.js b/lib/core/user-agent-core/user-agent-core.js new file mode 100644 index 000000000..65a72ff5b --- /dev/null +++ b/lib/core/user-agent-core/user-agent-core.js @@ -0,0 +1,791 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agents_1 = require("../user-agents"); +var allowed_methods_1 = require("./allowed-methods"); +/** + * This is ported from UA.C.ACCEPTED_BODY_TYPES. + * FIXME: TODO: Should be configurable/variable. + */ +var acceptedBodyTypes = [ + "application/sdp", + "application/dtmf-relay" +]; +/** + * Core: Core designates the functions specific to a particular type + * of SIP entity, i.e., specific to either a stateful or stateless + * proxy, a user agent or registrar. All cores, except those for + * the stateless proxy, are transaction users. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAC Core: The set of processing functions required of a UAC that + * reside above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + * + * UAS Core: The set of processing functions required at a UAS that + * resides above the transaction and transport layers. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentCore = /** @class */ (function () { + /** + * Constructor. + * @param configuration Configuration. + * @param delegate Delegate. + */ + function UserAgentCore(configuration, delegate) { + if (delegate === void 0) { delegate = {}; } + /** UACs. */ + this.userAgentClients = new Map(); + /** UASs. */ + this.userAgentServers = new Map(); + this.configuration = configuration; + this.delegate = delegate; + this.dialogs = new Map(); + this.subscribers = new Map(); + this.logger = configuration.loggerFactory.getLogger("sip.user-agent-core"); + } + /** Destructor. */ + UserAgentCore.prototype.dispose = function () { + this.reset(); + }; + /** Reset. */ + UserAgentCore.prototype.reset = function () { + this.dialogs.forEach(function (dialog) { return dialog.dispose(); }); + this.dialogs.clear(); + this.subscribers.forEach(function (subscriber) { return subscriber.dispose(); }); + this.subscribers.clear(); + this.userAgentClients.forEach(function (uac) { return uac.dispose(); }); + this.userAgentClients.clear(); + this.userAgentServers.forEach(function (uac) { return uac.dispose(); }); + this.userAgentServers.clear(); + }; + Object.defineProperty(UserAgentCore.prototype, "loggerFactory", { + /** Logger factory. */ + get: function () { + return this.configuration.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentCore.prototype, "transport", { + /** Transport. */ + get: function () { + var transport = this.configuration.transportAccessor(); + if (!transport) { + throw new Error("Transport undefined."); + } + return transport; + }, + enumerable: true, + configurable: true + }); + /** + * Send INVITE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.invite = function (request, delegate) { + return new user_agents_1.InviteUserAgentClient(this, request, delegate); + }; + /** + * Send MESSAGE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.message = function (request, delegate) { + return new user_agents_1.MessageUserAgentClient(this, request, delegate); + }; + /** + * Send PUBLISH. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.publish = function (request, delegate) { + return new user_agents_1.PublishUserAgentClient(this, request, delegate); + }; + /** + * Send REGISTER. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.register = function (request, delegate) { + return new user_agents_1.RegisterUserAgentClient(this, request, delegate); + }; + /** + * Send SUBSCRIBE. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.subscribe = function (request, delegate) { + return new user_agents_1.SubscribeUserAgentClient(this, request, delegate); + }; + /** + * Send a request. + * @param request Outgoing request. + * @param delegate Request delegate. + */ + UserAgentCore.prototype.request = function (request, delegate) { + return new user_agents_1.UserAgentClient(transactions_1.NonInviteClientTransaction, this, request, delegate); + }; + /** + * Outgoing request message factory function. + * @param method Method. + * @param requestURI Request-URI. + * @param fromURI From URI. + * @param toURI To URI. + * @param options Request options. + * @param extraHeaders Extra headers to add. + * @param body Message body. + */ + UserAgentCore.prototype.makeOutgoingRequestMessage = function (method, requestURI, fromURI, toURI, options, extraHeaders, body) { + // default values from user agent configuration + var callIdPrefix = this.configuration.sipjsId; + var fromDisplayName = this.configuration.displayName; + var forceRport = this.configuration.viaForceRport; + var hackViaTcp = this.configuration.hackViaTcp; + var optionTags = this.configuration.supportedOptionTags.slice(); + if (method === messages_1.C.REGISTER) { + optionTags.push("path", "gruu"); + } + if (method === messages_1.C.INVITE && (this.configuration.contact.pubGruu || this.configuration.contact.tempGruu)) { + optionTags.push("gruu"); + } + var routeSet = this.configuration.routeSet; + var userAgentString = this.configuration.userAgentHeaderFieldValue; + var viaHost = this.configuration.viaHost; + var defaultOptions = { + callIdPrefix: callIdPrefix, + forceRport: forceRport, + fromDisplayName: fromDisplayName, + hackViaTcp: hackViaTcp, + optionTags: optionTags, + routeSet: routeSet, + userAgentString: userAgentString, + viaHost: viaHost, + }; + // merge provided options with default options + var requestOptions = tslib_1.__assign({}, defaultOptions, options); + return new messages_1.OutgoingRequestMessage(method, requestURI, fromURI, toURI, requestOptions, extraHeaders, body); + }; + /** + * Handle an incoming request message from the transport. + * @param message Incoming request message from transport layer. + */ + UserAgentCore.prototype.receiveIncomingRequestFromTransport = function (message) { + this.receiveRequestFromTransport(message); + }; + /** + * Handle an incoming response message from the transport. + * @param message Incoming response message from transport layer. + */ + UserAgentCore.prototype.receiveIncomingResponseFromTransport = function (message) { + this.receiveResponseFromTransport(message); + }; + /** + * A stateless UAS is a UAS that does not maintain transaction state. + * It replies to requests normally, but discards any state that would + * ordinarily be retained by a UAS after a response has been sent. If a + * stateless UAS receives a retransmission of a request, it regenerates + * the response and re-sends it, just as if it were replying to the first + * instance of the request. A UAS cannot be stateless unless the request + * processing for that method would always result in the same response + * if the requests are identical. This rules out stateless registrars, + * for example. Stateless UASs do not use a transaction layer; they + * receive requests directly from the transport layer and send responses + * directly to the transport layer. + * https://tools.ietf.org/html/rfc3261#section-8.2.7 + * @param message Incoming request message to reply to. + * @param statusCode Status code to reply with. + */ + UserAgentCore.prototype.replyStateless = function (message, options) { + var userAgent = this.configuration.userAgentHeaderFieldValue; + var supported = this.configuration.supportedOptionTagsResponse; + options = tslib_1.__assign({}, options, { userAgent: userAgent, supported: supported }); + var response = messages_1.constructOutgoingResponse(message, options); + this.transport.send(response.message); + return response; + }; + /** + * In Section 18.2.1, replace the last paragraph with: + * + * Next, the server transport attempts to match the request to a + * server transaction. It does so using the matching rules described + * in Section 17.2.3. If a matching server transaction is found, the + * request is passed to that transaction for processing. If no match + * is found, the request is passed to the core, which may decide to + * construct a new server transaction for that request. + * https://tools.ietf.org/html/rfc6026#section-8.10 + * @param message Incoming request message from transport layer. + */ + UserAgentCore.prototype.receiveRequestFromTransport = function (message) { + // When a request is received from the network by the server, it has to + // be matched to an existing transaction. This is accomplished in the + // following manner. + // + // The branch parameter in the topmost Via header field of the request + // is examined. If it is present and begins with the magic cookie + // "z9hG4bK", the request was generated by a client transaction + // compliant to this specification. Therefore, the branch parameter + // will be unique across all transactions sent by that client. The + // request matches a transaction if: + // + // 1. the branch parameter in the request is equal to the one in the + // top Via header field of the request that created the + // transaction, and + // + // 2. the sent-by value in the top Via of the request is equal to the + // one in the request that created the transaction, and + // + // 3. the method of the request matches the one that created the + // transaction, except for ACK, where the method of the request + // that created the transaction is INVITE. + // + // This matching rule applies to both INVITE and non-INVITE transactions + // alike. + // + // The sent-by value is used as part of the matching process because + // there could be accidental or malicious duplication of branch + // parameters from different clients. + // https://tools.ietf.org/html/rfc3261#section-17.2.3 + var transactionId = message.viaBranch; // FIXME: Currently only using rule 1... + var uas = this.userAgentServers.get(transactionId); + // When receiving an ACK that matches an existing INVITE server + // transaction and that does not contain a branch parameter containing + // the magic cookie defined in RFC 3261, the matching transaction MUST + // be checked to see if it is in the "Accepted" state. If it is, then + // the ACK must be passed directly to the transaction user instead of + // being absorbed by the transaction state machine. This is necessary + // as requests from RFC 2543 clients will not include a unique branch + // parameter, and the mechanisms for calculating the transaction ID from + // such a request will be the same for both INVITE and ACKs. + // https://tools.ietf.org/html/rfc6026#section-6 + // Any ACKs received from the network while in the "Accepted" state MUST be + // passed directly to the TU and not absorbed. + // https://tools.ietf.org/html/rfc6026#section-7.1 + if (message.method === messages_1.C.ACK) { + if (uas && uas.transaction.state === transactions_1.TransactionState.Accepted) { + if (uas instanceof user_agents_1.InviteUserAgentServer) { + // These are ACKs matching an INVITE server transaction. + // These should never happen with RFC 3261 compliant user agents + // (would be a broken ACK to negative final response or something) + // but is apparently how RFC 2543 user agents do things. + // We are not currently supporting this case. + // NOTE: Not backwards compatible with RFC 2543 (no support for strict-routing). + this.logger.warn("Discarding out of dialog ACK after 2xx response sent on transaction " + transactionId + "."); + return; + } + } + } + // The CANCEL method requests that the TU at the server side cancel a + // pending transaction. The TU determines the transaction to be + // cancelled by taking the CANCEL request, and then assuming that the + // request method is anything but CANCEL or ACK and applying the + // transaction matching procedures of Section 17.2.3. The matching + // transaction is the one to be cancelled. + // https://tools.ietf.org/html/rfc3261#section-9.2 + if (message.method === messages_1.C.CANCEL) { + if (uas) { + // Regardless of the method of the original request, as long as the + // CANCEL matched an existing transaction, the UAS answers the CANCEL + // request itself with a 200 (OK) response. + // https://tools.ietf.org/html/rfc3261#section-9.2 + this.replyStateless(message, { statusCode: 200 }); + // If the transaction for the original request still exists, the behavior + // of the UAS on receiving a CANCEL request depends on whether it has already + // sent a final response for the original request. If it has, the CANCEL + // request has no effect on the processing of the original request, no + // effect on any session state, and no effect on the responses generated + // for the original request. If the UAS has not issued a final response + // for the original request, its behavior depends on the method of the + // original request. If the original request was an INVITE, the UAS + // SHOULD immediately respond to the INVITE with a 487 (Request + // Terminated). + // https://tools.ietf.org/html/rfc3261#section-9.2 + if (uas.transaction instanceof transactions_1.InviteServerTransaction && + uas.transaction.state === transactions_1.TransactionState.Proceeding) { + if (uas instanceof user_agents_1.InviteUserAgentServer) { + uas.receiveCancel(message); + } + // A CANCEL request has no impact on the processing of + // transactions with any other method defined in this specification. + // https://tools.ietf.org/html/rfc3261#section-9.2 + } + } + else { + // If the UAS did not find a matching transaction for the CANCEL + // according to the procedure above, it SHOULD respond to the CANCEL + // with a 481 (Call Leg/Transaction Does Not Exist). + // https://tools.ietf.org/html/rfc3261#section-9.2 + this.replyStateless(message, { statusCode: 481 }); + } + return; + } + // If a matching server transaction is found, the request is passed to that + // transaction for processing. + // https://tools.ietf.org/html/rfc6026#section-8.10 + if (uas) { + uas.transaction.receiveRequest(message); + return; + } + // If no match is found, the request is passed to the core, which may decide to + // construct a new server transaction for that request. + // https://tools.ietf.org/html/rfc6026#section-8.10 + this.receiveRequest(message); + return; + }; + /** + * UAC and UAS procedures depend strongly on two factors. First, based + * on whether the request or response is inside or outside of a dialog, + * and second, based on the method of a request. Dialogs are discussed + * thoroughly in Section 12; they represent a peer-to-peer relationship + * between user agents and are established by specific SIP methods, such + * as INVITE. + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveRequest = function (message) { + // 8.2 UAS Behavior + // UASs SHOULD process the requests in the order of the steps that + // follow in this section (that is, starting with authentication, then + // inspecting the method, the header fields, and so on throughout the + // remainder of this section). + // https://tools.ietf.org/html/rfc3261#section-8.2 + // 8.2.1 Method Inspection + // Once a request is authenticated (or authentication is skipped), the + // UAS MUST inspect the method of the request. If the UAS recognizes + // but does not support the method of a request, it MUST generate a 405 + // (Method Not Allowed) response. Procedures for generating responses + // are described in Section 8.2.6. The UAS MUST also add an Allow + // header field to the 405 (Method Not Allowed) response. The Allow + // header field MUST list the set of methods supported by the UAS + // generating the message. + // https://tools.ietf.org/html/rfc3261#section-8.2.1 + if (!allowed_methods_1.AllowedMethods.includes(message.method)) { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + this.replyStateless(message, { + statusCode: 405, + extraHeaders: [allowHeader] + }); + return; + } + // 8.2.2 Header Inspection + // https://tools.ietf.org/html/rfc3261#section-8.2.2 + if (!message.ruri) { // FIXME: A request message should always have an ruri + throw new Error("Request-URI undefined."); + } + // 8.2.2.1 To and Request-URI + // If the Request-URI uses a scheme not supported by the UAS, it SHOULD + // reject the request with a 416 (Unsupported URI Scheme) response. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + if (message.ruri.scheme !== "sip") { + this.replyStateless(message, { statusCode: 416 }); + return; + } + // 8.2.2.1 To and Request-URI + // If the Request-URI does not identify an address that the + // UAS is willing to accept requests for, it SHOULD reject + // the request with a 404 (Not Found) response. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + var ruri = message.ruri; + var ruriMatches = function (uri) { + return !!uri && uri.user === ruri.user; + }; + if (!ruriMatches(this.configuration.aor) && + !(ruriMatches(this.configuration.contact.uri) || + ruriMatches(this.configuration.contact.pubGruu) || + ruriMatches(this.configuration.contact.tempGruu))) { + this.logger.warn("Request-URI does not point to us."); + if (message.method !== messages_1.C.ACK) { + this.replyStateless(message, { statusCode: 404 }); + } + return; + } + // 8.2.2.1 To and Request-URI + // Other potential sources of received Request-URIs include + // the Contact header fields of requests and responses sent by the UA + // that establish or refresh dialogs. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.1 + if (message.method === messages_1.C.INVITE) { + if (!message.hasHeader("Contact")) { + this.replyStateless(message, { + statusCode: 400, + reasonPhrase: "Missing Contact Header" + }); + return; + } + } + // 8.2.2.2 Merged Requests + // If the request has no tag in the To header field, the UAS core MUST + // check the request against ongoing transactions. If the From tag, + // Call-ID, and CSeq exactly match those associated with an ongoing + // transaction, but the request does not match that transaction (based + // on the matching rules in Section 17.2.3), the UAS core SHOULD + // generate a 482 (Loop Detected) response and pass it to the server + // transaction. + // + // The same request has arrived at the UAS more than once, following + // different paths, most likely due to forking. The UAS processes + // the first such request received and responds with a 482 (Loop + // Detected) to the rest of them. + // https://tools.ietf.org/html/rfc3261#section-8.2.2.2 + if (!message.toTag) { + var transactionId = message.viaBranch; + if (!this.userAgentServers.has(transactionId)) { + var mergedRequest = Array.from(this.userAgentServers.values()) + .some(function (uas) { + return uas.transaction.request.fromTag === message.fromTag && + uas.transaction.request.callId === message.callId && + uas.transaction.request.cseq === message.cseq; + }); + if (mergedRequest) { + this.replyStateless(message, { statusCode: 482 }); + return; + } + } + } + // 8.2.2.3 Require + // https://tools.ietf.org/html/rfc3261#section-8.2.2.3 + // TODO + // 8.2.3 Content Processing + // https://tools.ietf.org/html/rfc3261#section-8.2.3 + // TODO + // 8.2.4 Applying Extensions + // https://tools.ietf.org/html/rfc3261#section-8.2.4 + // TODO + // 8.2.5 Processing the Request + // Assuming all of the checks in the previous subsections are passed, + // the UAS processing becomes method-specific. + // https://tools.ietf.org/html/rfc3261#section-8.2.5 + // The UAS will receive the request from the transaction layer. If the + // request has a tag in the To header field, the UAS core computes the + // dialog identifier corresponding to the request and compares it with + // existing dialogs. If there is a match, this is a mid-dialog request. + // In that case, the UAS first applies the same processing rules for + // requests outside of a dialog, discussed in Section 8.2. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (message.toTag) { + this.receiveInsideDialogRequest(message); + } + else { + this.receiveOutsideDialogRequest(message); + } + return; + }; + /** + * Once a dialog has been established between two UAs, either of them + * MAY initiate new transactions as needed within the dialog. The UA + * sending the request will take the UAC role for the transaction. The + * UA receiving the request will take the UAS role. Note that these may + * be different roles than the UAs held during the transaction that + * established the dialog. + * https://tools.ietf.org/html/rfc3261#section-12.2 + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveInsideDialogRequest = function (message) { + // NOTIFY requests are matched to such SUBSCRIBE requests if they + // contain the same "Call-ID", a "To" header field "tag" parameter that + // matches the "From" header field "tag" parameter of the SUBSCRIBE + // request, and the same "Event" header field. Rules for comparisons of + // the "Event" header fields are described in Section 8.2.1. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (message.method === messages_1.C.NOTIFY) { + var event_1 = message.parseHeader("Event"); + if (!event_1 || !event_1.event) { + this.replyStateless(message, { statusCode: 489 }); + return; + } + // FIXME: Subscriber id should also matching on event id. + var subscriberId = message.callId + message.toTag + event_1.event; + var subscriber = this.subscribers.get(subscriberId); + if (subscriber) { + var uas = new user_agents_1.NotifyUserAgentServer(this, message); + subscriber.onNotify(uas); + return; + } + } + // Requests sent within a dialog, as any other requests, are atomic. If + // a particular request is accepted by the UAS, all the state changes + // associated with it are performed. If the request is rejected, none + // of the state changes are performed. + // + // Note that some requests, such as INVITEs, affect several pieces of + // state. + // + // The UAS will receive the request from the transaction layer. If the + // request has a tag in the To header field, the UAS core computes the + // dialog identifier corresponding to the request and compares it with + // existing dialogs. If there is a match, this is a mid-dialog request. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + var dialogId = message.callId + message.toTag + message.fromTag; + var dialog = this.dialogs.get(dialogId); + if (dialog) { + // [Sip-implementors] Reg. SIP reinvite, UPDATE and OPTIONS + // You got the question right. + // + // And you got the right answer too. :-) + // + // Thanks, + // Paul + // + // Robert Sparks wrote: + // > So I've lost track of the question during the musing. + // > + // > I _think_ the fundamental question being asked is this: + // > + // > Is an endpoint required to reject (with a 481) an OPTIONS request that + // > arrives with at to-tag but does not match any existing dialog state. + // > (Assuming some earlier requirement hasn't forced another error code). Or + // > is it OK if it just sends + // > a 200 OK anyhow. + // > + // > My take on the collection of specs is that its _not_ ok for it to send + // > the 200 OK anyhow and that it is required to send + // > the 481. I base this primarily on these sentences from 11.2 in 3261: + // > + // > The response to an OPTIONS is constructed using the standard rules + // > for a SIP response as discussed in Section 8.2.6. The response code + // > chosen MUST be the same that would have been chosen had the request + // > been an INVITE. + // > + // > Did I miss the point of the question? + // > + // > On May 15, 2008, at 12:48 PM, Paul Kyzivat wrote: + // > + // >> [Including Robert in hopes of getting his insight on this.] + // https://lists.cs.columbia.edu/pipermail/sip-implementors/2008-May/019178.html + // + // Requests that do not change in any way the state of a dialog may be + // received within a dialog (for example, an OPTIONS request). They are + // processed as if they had been received outside the dialog. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + if (message.method === messages_1.C.OPTIONS) { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var acceptHeader = "Accept: " + acceptedBodyTypes.toString(); + this.replyStateless(message, { + statusCode: 200, + extraHeaders: [allowHeader, acceptHeader] + }); + return; + } + // Pass the incoming request to the dialog for further handling. + dialog.receiveRequest(message); + return; + } + // The most important behaviors of a stateless UAS are the following: + // ... + // o A stateless UAS MUST ignore ACK requests. + // ... + // https://tools.ietf.org/html/rfc3261#section-8.2.7 + if (message.method === messages_1.C.ACK) { + // If a final response to an INVITE was sent statelessly, + // the corresponding ACK: + // - will not match an existing transaction + // - may have tag in the To header field + // - not not match any existing dialogs + // Absorb unmatched ACKs. + return; + } + // If the request has a tag in the To header field, but the dialog + // identifier does not match any existing dialogs, the UAS may have + // crashed and restarted, or it may have received a request for a + // different (possibly failed) UAS (the UASs can construct the To tags + // so that a UAS can identify that the tag was for a UAS for which it is + // providing recovery). Another possibility is that the incoming + // request has been simply mis-routed. Based on the To tag, the UAS MAY + // either accept or reject the request. Accepting the request for + // acceptable To tags provides robustness, so that dialogs can persist + // even through crashes. UAs wishing to support this capability must + // take into consideration some issues such as choosing monotonically + // increasing CSeq sequence numbers even across reboots, reconstructing + // the route set, and accepting out-of-range RTP timestamps and sequence + // numbers. + // + // If the UAS wishes to reject the request because it does not wish to + // recreate the dialog, it MUST respond to the request with a 481 + // (Call/Transaction Does Not Exist) status code and pass that to the + // server transaction. + // https://tools.ietf.org/html/rfc3261#section-12.2.2 + this.replyStateless(message, { statusCode: 481 }); + return; + }; + /** + * Assuming all of the checks in the previous subsections are passed, + * the UAS processing becomes method-specific. + * https://tools.ietf.org/html/rfc3261#section-8.2.5 + * @param message Incoming request message. + */ + UserAgentCore.prototype.receiveOutsideDialogRequest = function (message) { + switch (message.method) { + case messages_1.C.ACK: + // Absorb stray out of dialog ACKs + break; + case messages_1.C.BYE: + // If the BYE does not match an existing dialog, the UAS core SHOULD + // generate a 481 (Call/Transaction Does Not Exist) response and pass + // that to the server transaction. This rule means that a BYE sent + // without tags by a UAC will be rejected. + // https://tools.ietf.org/html/rfc3261#section-15.1.2 + this.replyStateless(message, { statusCode: 481 }); + break; + case messages_1.C.CANCEL: + throw new Error("Unexpected out of dialog request method " + message.method + "."); + break; + case messages_1.C.INFO: + // Use of the INFO method does not constitute a separate dialog usage. + // INFO messages are always part of, and share the fate of, an invite + // dialog usage [RFC5057]. INFO messages cannot be sent as part of + // other dialog usages, or outside an existing dialog. + // https://tools.ietf.org/html/rfc6086#section-1 + this.replyStateless(message, { statusCode: 405 }); // Should never happen + break; + case messages_1.C.INVITE: + // https://tools.ietf.org/html/rfc3261#section-13.3.1 + { + var uas = new user_agents_1.InviteUserAgentServer(this, message); + this.delegate.onInvite ? + this.delegate.onInvite(uas) : + uas.reject(); + } + break; + case messages_1.C.MESSAGE: + // MESSAGE requests are discouraged inside a dialog. Implementations + // are restricted from creating a usage for the purpose of carrying a + // sequence of MESSAGE requests (though some implementations use it that + // way, against the standard recommendation). + // https://tools.ietf.org/html/rfc5057#section-5.3 + { + var uas = new user_agents_1.MessageUserAgentServer(this, message); + this.delegate.onMessage ? + this.delegate.onMessage(uas) : + uas.accept(); + } + break; + case messages_1.C.NOTIFY: + // Obsoleted by: RFC 6665 + // If any non-SUBSCRIBE mechanisms are defined to create subscriptions, + // it is the responsibility of the parties defining those mechanisms to + // ensure that correlation of a NOTIFY message to the corresponding + // subscription is possible. Designers of such mechanisms are also + // warned to make a distinction between sending a NOTIFY message to a + // subscriber who is aware of the subscription, and sending a NOTIFY + // message to an unsuspecting node. The latter behavior is invalid, and + // MUST receive a "481 Subscription does not exist" response (unless + // some other 400- or 500-class error code is more applicable), as + // described in section 3.2.4. In other words, knowledge of a + // subscription must exist in both the subscriber and the notifier to be + // valid, even if installed via a non-SUBSCRIBE mechanism. + // https://tools.ietf.org/html/rfc3265#section-3.2 + // + // NOTIFY requests are sent to inform subscribers of changes in state to + // which the subscriber has a subscription. Subscriptions are created + // using the SUBSCRIBE method. In legacy implementations, it is + // possible that other means of subscription creation have been used. + // However, this specification does not allow the creation of + // subscriptions except through SUBSCRIBE requests and (for backwards- + // compatibility) REFER requests [RFC3515]. + // https://tools.ietf.org/html/rfc6665#section-3.2 + { + var uas = new user_agents_1.NotifyUserAgentServer(this, message); + this.delegate.onNotify ? + this.delegate.onNotify(uas) : + this.replyStateless(message, { statusCode: 405 }); + } + break; + case messages_1.C.OPTIONS: + // https://tools.ietf.org/html/rfc3261#section-11.2 + { + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + var acceptHeader = "Accept: " + acceptedBodyTypes.toString(); + this.replyStateless(message, { + statusCode: 200, + extraHeaders: [allowHeader, acceptHeader] + }); + } + break; + case messages_1.C.REFER: + // https://tools.ietf.org/html/rfc3515#section-2.4.2 + { + var uas = new user_agents_1.ReferUserAgentServer(this, message); + this.delegate.onRefer ? + this.delegate.onRefer(uas) : + this.replyStateless(message, { statusCode: 405 }); + } + break; + case messages_1.C.SUBSCRIBE: + // https://tools.ietf.org/html/rfc6665#section-4.2 + { + var uas = new user_agents_1.SubscribeUserAgentServer(this, message); + this.delegate.onSubscribe ? + this.delegate.onSubscribe(uas) : + uas.reject(); + } + break; + default: + throw new Error("Unexpected out of dialog request method " + message.method + "."); + } + return; + }; + /** + * Responses are first processed by the transport layer and then passed + * up to the transaction layer. The transaction layer performs its + * processing and then passes the response up to the TU. The majority + * of response processing in the TU is method specific. However, there + * are some general behaviors independent of the method. + * https://tools.ietf.org/html/rfc3261#section-8.1.3 + * @param message Incoming response message from transport layer. + */ + UserAgentCore.prototype.receiveResponseFromTransport = function (message) { + // 8.1.3.1 Transaction Layer Errors + // https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + // Handled by transaction layer callbacks. + // 8.1.3.2 Unrecognized Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + // TODO + // 8.1.3.3 Vias + // https://tools.ietf.org/html/rfc3261#section-8.1.3.3 + if (message.getHeaders("via").length > 1) { + this.logger.warn("More than one Via header field present in the response, dropping"); + return; + } + // 8.1.3.4 Processing 3xx Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.4 + // TODO + // 8.1.3.5 Processing 4xx Responses + // https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + // TODO + // When the transport layer in the client receives a response, it has to + // determine which client transaction will handle the response, so that + // the processing of Sections 17.1.1 and 17.1.2 can take place. The + // branch parameter in the top Via header field is used for this + // purpose. A response matches a client transaction under two + // conditions: + // + // 1. If the response has the same value of the branch parameter in + // the top Via header field as the branch parameter in the top + // Via header field of the request that created the transaction. + // + // 2. If the method parameter in the CSeq header field matches the + // method of the request that created the transaction. The + // method is needed since a CANCEL request constitutes a + // different transaction, but shares the same value of the branch + // parameter. + // https://tools.ietf.org/html/rfc3261#section-17.1.3 + var userAgentClientId = message.viaBranch + message.method; + var userAgentClient = this.userAgentClients.get(userAgentClientId); + // The client transport uses the matching procedures of Section + // 17.1.3 to attempt to match the response to an existing + // transaction. If there is a match, the response MUST be passed to + // that transaction. Otherwise, any element other than a stateless + // proxy MUST silently discard the response. + // https://tools.ietf.org/html/rfc6026#section-8.9 + if (userAgentClient) { + userAgentClient.transaction.receiveResponse(message); + } + else { + this.logger.warn("Discarding unmatched " + message.statusCode + " response to " + message.method + " " + userAgentClientId + "."); + } + }; + return UserAgentCore; +}()); +exports.UserAgentCore = UserAgentCore; diff --git a/lib/core/user-agents/bye-user-agent-client.d.ts b/lib/core/user-agents/bye-user-agent-client.d.ts new file mode 100644 index 000000000..32682e220 --- /dev/null +++ b/lib/core/user-agents/bye-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { SessionDialog } from "../dialogs"; +import { OutgoingByeRequest, OutgoingRequestDelegate, RequestOptions } from "../messages"; +import { UserAgentClient } from "./user-agent-client"; +export declare class ByeUserAgentClient extends UserAgentClient implements OutgoingByeRequest { + constructor(dialog: SessionDialog, delegate?: OutgoingRequestDelegate, options?: RequestOptions); +} diff --git a/lib/core/user-agents/bye-user-agent-client.js b/lib/core/user-agents/bye-user-agent-client.js new file mode 100644 index 000000000..a87320460 --- /dev/null +++ b/lib/core/user-agents/bye-user-agent-client.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var ByeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ByeUserAgentClient, _super); + function ByeUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.BYE, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.dispose(); + return _this; + } + return ByeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ByeUserAgentClient = ByeUserAgentClient; diff --git a/lib/core/user-agents/bye-user-agent-server.d.ts b/lib/core/user-agents/bye-user-agent-server.d.ts new file mode 100644 index 000000000..920ec82e4 --- /dev/null +++ b/lib/core/user-agents/bye-user-agent-server.d.ts @@ -0,0 +1,6 @@ +import { SessionDialog } from "../dialogs"; +import { IncomingByeRequest, IncomingRequestDelegate, IncomingRequestMessage } from "../messages"; +import { UserAgentServer } from "./user-agent-server"; +export declare class ByeUserAgentServer extends UserAgentServer implements IncomingByeRequest { + constructor(dialog: SessionDialog, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); +} diff --git a/lib/core/user-agents/bye-user-agent-server.js b/lib/core/user-agents/bye-user-agent-server.js new file mode 100644 index 000000000..988f1766a --- /dev/null +++ b/lib/core/user-agents/bye-user-agent-server.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var ByeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ByeUserAgentServer, _super); + function ByeUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return ByeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ByeUserAgentServer = ByeUserAgentServer; diff --git a/lib/core/user-agents/cancel-user-agent-client.d.ts b/lib/core/user-agents/cancel-user-agent-client.d.ts new file mode 100644 index 000000000..1a4453da7 --- /dev/null +++ b/lib/core/user-agents/cancel-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { OutgoingCancelRequest, OutgoingRequestDelegate, OutgoingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentClient } from "./user-agent-client"; +export declare class CancelUserAgentClient extends UserAgentClient implements OutgoingCancelRequest { + constructor(core: UserAgentCore, message: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate); +} diff --git a/lib/core/user-agents/cancel-user-agent-client.js b/lib/core/user-agents/cancel-user-agent-client.js new file mode 100644 index 000000000..c63bcb169 --- /dev/null +++ b/lib/core/user-agents/cancel-user-agent-client.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var CancelUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(CancelUserAgentClient, _super); + function CancelUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return CancelUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.CancelUserAgentClient = CancelUserAgentClient; diff --git a/lib/core/user-agents/index.d.ts b/lib/core/user-agents/index.d.ts new file mode 100644 index 000000000..1e66f345e --- /dev/null +++ b/lib/core/user-agents/index.d.ts @@ -0,0 +1,24 @@ +export * from "./bye-user-agent-client"; +export * from "./bye-user-agent-server"; +export * from "./cancel-user-agent-client"; +export * from "./info-user-agent-server"; +export * from "./invite-user-agent-client"; +export * from "./invite-user-agent-server"; +export * from "./message-user-agent-client"; +export * from "./message-user-agent-server"; +export * from "./notify-user-agent-client"; +export * from "./notify-user-agent-server"; +export * from "./publish-user-agent-client"; +export * from "./prack-user-agent-client"; +export * from "./prack-user-agent-server"; +export * from "./re-invite-user-agent-client"; +export * from "./re-invite-user-agent-server"; +export * from "./re-subscribe-user-agent-client"; +export * from "./re-subscribe-user-agent-server"; +export * from "./refer-user-agent-client"; +export * from "./refer-user-agent-server"; +export * from "./register-user-agent-client"; +export * from "./subscribe-user-agent-client"; +export * from "./subscribe-user-agent-server"; +export * from "./user-agent-client"; +export * from "./user-agent-server"; diff --git a/lib/core/user-agents/index.js b/lib/core/user-agents/index.js new file mode 100644 index 000000000..4a84d981c --- /dev/null +++ b/lib/core/user-agents/index.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +tslib_1.__exportStar(require("./bye-user-agent-client"), exports); +tslib_1.__exportStar(require("./bye-user-agent-server"), exports); +tslib_1.__exportStar(require("./cancel-user-agent-client"), exports); +tslib_1.__exportStar(require("./info-user-agent-server"), exports); +tslib_1.__exportStar(require("./invite-user-agent-client"), exports); +tslib_1.__exportStar(require("./invite-user-agent-server"), exports); +tslib_1.__exportStar(require("./message-user-agent-client"), exports); +tslib_1.__exportStar(require("./message-user-agent-server"), exports); +tslib_1.__exportStar(require("./notify-user-agent-client"), exports); +tslib_1.__exportStar(require("./notify-user-agent-server"), exports); +tslib_1.__exportStar(require("./publish-user-agent-client"), exports); +tslib_1.__exportStar(require("./prack-user-agent-client"), exports); +tslib_1.__exportStar(require("./prack-user-agent-server"), exports); +tslib_1.__exportStar(require("./re-invite-user-agent-client"), exports); +tslib_1.__exportStar(require("./re-invite-user-agent-server"), exports); +tslib_1.__exportStar(require("./re-subscribe-user-agent-client"), exports); +tslib_1.__exportStar(require("./re-subscribe-user-agent-server"), exports); +tslib_1.__exportStar(require("./refer-user-agent-client"), exports); +tslib_1.__exportStar(require("./refer-user-agent-server"), exports); +tslib_1.__exportStar(require("./register-user-agent-client"), exports); +tslib_1.__exportStar(require("./subscribe-user-agent-client"), exports); +tslib_1.__exportStar(require("./subscribe-user-agent-server"), exports); +tslib_1.__exportStar(require("./user-agent-client"), exports); +tslib_1.__exportStar(require("./user-agent-server"), exports); diff --git a/lib/core/user-agents/info-user-agent-client.d.ts b/lib/core/user-agents/info-user-agent-client.d.ts new file mode 100644 index 000000000..54d04ce12 --- /dev/null +++ b/lib/core/user-agents/info-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { SessionDialog } from "../dialogs"; +import { OutgoingInfoRequest, OutgoingRequestDelegate, RequestOptions } from "../messages"; +import { UserAgentClient } from "./user-agent-client"; +export declare class InfoUserAgentClient extends UserAgentClient implements OutgoingInfoRequest { + constructor(dialog: SessionDialog, delegate?: OutgoingRequestDelegate, options?: RequestOptions); +} diff --git a/lib/core/user-agents/info-user-agent-client.js b/lib/core/user-agents/info-user-agent-client.js new file mode 100644 index 000000000..f8671b189 --- /dev/null +++ b/lib/core/user-agents/info-user-agent-client.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var InfoUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(InfoUserAgentClient, _super); + function InfoUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.INFO, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return InfoUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.InfoUserAgentClient = InfoUserAgentClient; diff --git a/lib/core/user-agents/info-user-agent-server.d.ts b/lib/core/user-agents/info-user-agent-server.d.ts new file mode 100644 index 000000000..029acfaa1 --- /dev/null +++ b/lib/core/user-agents/info-user-agent-server.d.ts @@ -0,0 +1,6 @@ +import { SessionDialog } from "../dialogs"; +import { IncomingInfoRequest, IncomingRequestDelegate, IncomingRequestMessage } from "../messages"; +import { UserAgentServer } from "./user-agent-server"; +export declare class InfoUserAgentServer extends UserAgentServer implements IncomingInfoRequest { + constructor(dialog: SessionDialog, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); +} diff --git a/lib/core/user-agents/info-user-agent-server.js b/lib/core/user-agents/info-user-agent-server.js new file mode 100644 index 000000000..769db8f4f --- /dev/null +++ b/lib/core/user-agents/info-user-agent-server.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var InfoUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(InfoUserAgentServer, _super); + function InfoUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return InfoUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.InfoUserAgentServer = InfoUserAgentServer; diff --git a/lib/core/user-agents/invite-user-agent-client.d.ts b/lib/core/user-agents/invite-user-agent-client.d.ts new file mode 100644 index 000000000..7a635f8fb --- /dev/null +++ b/lib/core/user-agents/invite-user-agent-client.d.ts @@ -0,0 +1,26 @@ +import { IncomingResponseMessage, OutgoingInviteRequest, OutgoingInviteRequestDelegate, OutgoingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentClient } from "./user-agent-client"; +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.2 UAC Processing + * https://tools.ietf.org/html/rfc3261#section-13.2 + */ +export declare class InviteUserAgentClient extends UserAgentClient implements OutgoingInviteRequest { + delegate: OutgoingInviteRequestDelegate | undefined; + private confirmedDialogAcks; + private confirmedDialogs; + private earlyDialogs; + constructor(core: UserAgentCore, message: OutgoingRequestMessage, delegate?: OutgoingInviteRequestDelegate); + dispose(): void; + /** + * Once the INVITE has been passed to the INVITE client transaction, the + * UAC waits for responses for the INVITE. + * https://tools.ietf.org/html/rfc3261#section-13.2.2 + * @param incomingResponse Incoming response to INVITE request. + */ + protected receiveResponse(message: IncomingResponseMessage): void; +} diff --git a/lib/core/user-agents/invite-user-agent-client.js b/lib/core/user-agents/invite-user-agent-client.js new file mode 100644 index 000000000..f71fb3353 --- /dev/null +++ b/lib/core/user-agents/invite-user-agent-client.js @@ -0,0 +1,283 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var dialogs_1 = require("../dialogs"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.2 UAC Processing + * https://tools.ietf.org/html/rfc3261#section-13.2 + */ +var InviteUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(InviteUserAgentClient, _super); + function InviteUserAgentClient(core, message, delegate) { + var _this = _super.call(this, transactions_1.InviteClientTransaction, core, message, delegate) || this; + _this.confirmedDialogAcks = new Map(); + _this.confirmedDialogs = new Map(); + _this.earlyDialogs = new Map(); + _this.delegate = delegate; + return _this; + } + InviteUserAgentClient.prototype.dispose = function () { + // The UAC core considers the INVITE transaction completed 64*T1 seconds + // after the reception of the first 2xx response. At this point all the + // early dialogs that have not transitioned to established dialogs are + // terminated. Once the INVITE transaction is considered completed by + // the UAC core, no more new 2xx responses are expected to arrive. + // + // If, after acknowledging any 2xx response to an INVITE, the UAC does + // not want to continue with that dialog, then the UAC MUST terminate + // the dialog by sending a BYE request as described in Section 15. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + _super.prototype.dispose.call(this); + }; + /** + * Once the INVITE has been passed to the INVITE client transaction, the + * UAC waits for responses for the INVITE. + * https://tools.ietf.org/html/rfc3261#section-13.2.2 + * @param incomingResponse Incoming response to INVITE request. + */ + InviteUserAgentClient.prototype.receiveResponse = function (message) { + var _this = this; + if (!this.authenticationGuard(message)) { + return; + } + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + return; + case /^1[0-9]{2}$/.test(statusCode): + // Zero, one or multiple provisional responses may arrive before one or + // more final responses are received. Provisional responses for an + // INVITE request can create "early dialogs". If a provisional response + // has a tag in the To field, and if the dialog ID of the response does + // not match an existing dialog, one is constructed using the procedures + // defined in Section 12.1.2. + // + // The early dialog will only be needed if the UAC needs to send a + // request to its peer within the dialog before the initial INVITE + // transaction completes. Header fields present in a provisional + // response are applicable as long as the dialog is in the early state + // (for example, an Allow header field in a provisional response + // contains the methods that can be used in the dialog while this is in + // the early state). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.1 + { + // Provisional without to tag, no dialog to create. + if (!message.toTag) { + this.logger.warn("Non-100 1xx INVITE response received without a to tag, dropping."); + return; + } + // Compute dialog state. + var dialogState = dialogs_1.Dialog.initialDialogStateForUserAgentClient(this.message, message); + // Have existing early dialog or create a new one. + var earlyDialog = this.earlyDialogs.get(dialogState.id); + if (!earlyDialog) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + earlyDialog = new dialogs_1.SessionDialog(transaction, this.core, dialogState); + this.earlyDialogs.set(earlyDialog.id, earlyDialog); + } + // Guard against out of order reliable provisional responses. + // Note that this is where the rseq tracking is done. + if (!earlyDialog.reliableSequenceGuard(message)) { + this.logger.warn("1xx INVITE reliable response received out of order, dropping."); + return; + } + // Update dialog signaling state if need be. + earlyDialog.signalingStateTransition(message); + // Pass response to delegate. + var session_1 = earlyDialog; + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ + message: message, + session: session_1, + prack: function (options) { + var outgoingPrackRequest = session_1.prack(undefined, options); + return outgoingPrackRequest; + } + }); + } + } + return; + case /^2[0-9]{2}$/.test(statusCode): + // Multiple 2xx responses may arrive at the UAC for a single INVITE + // request due to a forking proxy. Each response is distinguished by + // the tag parameter in the To header field, and each represents a + // distinct dialog, with a distinct dialog identifier. + // + // If the dialog identifier in the 2xx response matches the dialog + // identifier of an existing dialog, the dialog MUST be transitioned to + // the "confirmed" state, and the route set for the dialog MUST be + // recomputed based on the 2xx response using the procedures of Section + // 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + // constructed using the procedures of Section 12.1.2. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + { + // Compute dialog state. + var dialogState = dialogs_1.Dialog.initialDialogStateForUserAgentClient(this.message, message); + // NOTE: Currently our transaction layer is caching the 2xx ACKs and + // handling retransmissions of the ACK which is an approach which is + // not to spec. In any event, this block is intended to provide a to + // spec implementation of ACK retransmissions, but it should not be + // hit currently. + var dialog = this.confirmedDialogs.get(dialogState.id); + if (dialog) { + // Once the ACK has been constructed, the procedures of [4] are used to + // determine the destination address, port and transport. However, the + // request is passed to the transport layer directly for transmission, + // rather than a client transaction. This is because the UAC core + // handles retransmissions of the ACK, not the transaction layer. The + // ACK MUST be passed to the client transport every time a + // retransmission of the 2xx final response that triggered the ACK + // arrives. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + var outgoingAckRequest = this.confirmedDialogAcks.get(dialogState.id); + if (outgoingAckRequest) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Client transaction not instance of InviteClientTransaction."); + } + transaction.ackResponse(outgoingAckRequest.message); + } + else { + // If still waiting for an ACK, drop the retransmission of the 2xx final response. + } + return; + } + // If the dialog identifier in the 2xx response matches the dialog + // identifier of an existing dialog, the dialog MUST be transitioned to + // the "confirmed" state, and the route set for the dialog MUST be + // recomputed based on the 2xx response using the procedures of Section + // 12.2.1.2. Otherwise, a new dialog in the "confirmed" state MUST be + // constructed using the procedures of Section 12.1.2. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + dialog = this.earlyDialogs.get(dialogState.id); + if (dialog) { + dialog.confirm(); + dialog.recomputeRouteSet(message); + this.earlyDialogs.delete(dialog.id); + this.confirmedDialogs.set(dialog.id, dialog); + } + else { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteClientTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + dialog = new dialogs_1.SessionDialog(transaction, this.core, dialogState); + this.confirmedDialogs.set(dialog.id, dialog); + } + // Update dialog signaling state if need be. + dialog.signalingStateTransition(message); + // Session Initiated! :) + var session_2 = dialog; + // The UAC core MUST generate an ACK request for each 2xx received from + // the transaction layer. The header fields of the ACK are constructed + // in the same way as for any request sent within a dialog (see Section + // 12) with the exception of the CSeq and the header fields related to + // authentication. The sequence number of the CSeq header field MUST be + // the same as the INVITE being acknowledged, but the CSeq method MUST + // be ACK. The ACK MUST contain the same credentials as the INVITE. If + // the 2xx contains an offer (based on the rules above), the ACK MUST + // carry an answer in its body. If the offer in the 2xx response is not + // acceptable, the UAC core MUST generate a valid answer in the ACK and + // then send a BYE immediately. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.4 + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ + message: message, + session: session_2, + ack: function (options) { + var outgoingAckRequest = session_2.ack(options); + _this.confirmedDialogAcks.set(session_2.id, outgoingAckRequest); + return outgoingAckRequest; + } + }); + } + else { + var outgoingAckRequest = session_2.ack(); + this.confirmedDialogAcks.set(session_2.id, outgoingAckRequest); + } + } + return; + case /^3[0-9]{2}$/.test(statusCode): + // 12.3 Termination of a Dialog + // + // Independent of the method, if a request outside of a dialog generates + // a non-2xx final response, any early dialogs created through + // provisional responses to that request are terminated. The mechanism + // for terminating confirmed dialogs is method specific. In this + // specification, the BYE method terminates a session and the dialog + // associated with it. See Section 15 for details. + // https://tools.ietf.org/html/rfc3261#section-12.3 + // All early dialogs are considered terminated upon reception of the + // non-2xx final response. + // + // After having received the non-2xx final response the UAC core + // considers the INVITE transaction completed. The INVITE client + // transaction handles the generation of ACKs for the response (see + // Section 17). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + // A 3xx response may contain one or more Contact header field values + // providing new addresses where the callee might be reachable. + // Depending on the status code of the 3xx response (see Section 21.3), + // the UAC MAY choose to try those new addresses. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.2 + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + return; + case /^[4-6][0-9]{2}$/.test(statusCode): + // 12.3 Termination of a Dialog + // + // Independent of the method, if a request outside of a dialog generates + // a non-2xx final response, any early dialogs created through + // provisional responses to that request are terminated. The mechanism + // for terminating confirmed dialogs is method specific. In this + // specification, the BYE method terminates a session and the dialog + // associated with it. See Section 15 for details. + // https://tools.ietf.org/html/rfc3261#section-12.3 + // All early dialogs are considered terminated upon reception of the + // non-2xx final response. + // + // After having received the non-2xx final response the UAC core + // considers the INVITE transaction completed. The INVITE client + // transaction handles the generation of ACKs for the response (see + // Section 17). + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + this.earlyDialogs.forEach(function (earlyDialog) { return earlyDialog.dispose(); }); + this.earlyDialogs.clear(); + // A single non-2xx final response may be received for the INVITE. 4xx, + // 5xx and 6xx responses may contain a Contact header field value + // indicating the location where additional information about the error + // can be found. Subsequent final responses (which would only arrive + // under error conditions) MUST be ignored. + // https://tools.ietf.org/html/rfc3261#section-13.2.2.3 + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + return; + default: + throw new Error("Invalid status code " + statusCode); + } + throw new Error("Executing what should be an unreachable code path receiving " + statusCode + " response."); + }; + return InviteUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.InviteUserAgentClient = InviteUserAgentClient; diff --git a/lib/core/user-agents/invite-user-agent-server.d.ts b/lib/core/user-agents/invite-user-agent-server.d.ts new file mode 100644 index 000000000..8bbae4a07 --- /dev/null +++ b/lib/core/user-agents/invite-user-agent-server.d.ts @@ -0,0 +1,73 @@ +import { IncomingInviteRequest, IncomingRequestDelegate, IncomingRequestMessage, OutgoingResponse, OutgoingResponseWithSession, ResponseOptions, URI } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentServer } from "./user-agent-server"; +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.3 UAS Processing + * https://tools.ietf.org/html/rfc3261#section-13.3 + */ +export declare class InviteUserAgentServer extends UserAgentServer implements IncomingInviteRequest { + protected core: UserAgentCore; + /** The confirmed dialog, if any. */ + private confirmedDialog; + /** The early dialog, if any. */ + private earlyDialog; + constructor(core: UserAgentCore, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); + dispose(): void; + /** + * 13.3.1.4 The INVITE is Accepted + * The UAS core generates a 2xx response. This response establishes a + * dialog, and therefore follows the procedures of Section 12.1.1 in + * addition to those of Section 8.2.6. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + * @param options Accept options bucket. + */ + accept(options?: ResponseOptions): OutgoingResponseWithSession; + /** + * 13.3.1.1 Progress + * If the UAS is not able to answer the invitation immediately, it can + * choose to indicate some kind of progress to the UAC (for example, an + * indication that a phone is ringing). This is accomplished with a + * provisional response between 101 and 199. These provisional + * responses establish early dialogs and therefore follow the procedures + * of Section 12.1.1 in addition to those of Section 8.2.6. A UAS MAY + * send as many provisional responses as it likes. Each of these MUST + * indicate the same dialog ID. However, these will not be delivered + * reliably. + * + * If the UAS desires an extended period of time to answer the INVITE, + * it will need to ask for an "extension" in order to prevent proxies + * from canceling the transaction. A proxy has the option of canceling + * a transaction when there is a gap of 3 minutes between responses in a + * transaction. To prevent cancellation, the UAS MUST send a non-100 + * provisional response at every minute, to handle the possibility of + * lost provisional responses. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.1 + * @param options Progress options bucket. + */ + progress(options?: ResponseOptions): OutgoingResponseWithSession; + /** + * 13.3.1.2 The INVITE is Redirected + * If the UAS decides to redirect the call, a 3xx response is sent. A + * 300 (Multiple Choices), 301 (Moved Permanently) or 302 (Moved + * Temporarily) response SHOULD contain a Contact header field + * containing one or more URIs of new addresses to be tried. The + * response is passed to the INVITE server transaction, which will deal + * with its retransmissions. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.2 + * @param options Reject options bucket. + */ + redirect(contacts: Array, options?: ResponseOptions): OutgoingResponse; + /** + * 13.3.1.3 The INVITE is Rejected + * A common scenario occurs when the callee is currently not willing or + * able to take additional calls at this end system. A 486 (Busy Here) + * SHOULD be returned in such a scenario. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.3 + * @param options Reject options bucket. + */ + reject(options?: ResponseOptions): OutgoingResponse; +} diff --git a/lib/core/user-agents/invite-user-agent-server.js b/lib/core/user-agents/invite-user-agent-server.js new file mode 100644 index 000000000..d8a5152ee --- /dev/null +++ b/lib/core/user-agents/invite-user-agent-server.js @@ -0,0 +1,225 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var dialogs_1 = require("../dialogs"); +var exceptions_1 = require("../exceptions"); +var session_1 = require("../session"); +var transactions_1 = require("../transactions"); +var allowed_methods_1 = require("../user-agent-core/allowed-methods"); +var user_agent_server_1 = require("./user-agent-server"); +/** + * 13 Initiating a Session + * https://tools.ietf.org/html/rfc3261#section-13 + * 13.1 Overview + * https://tools.ietf.org/html/rfc3261#section-13.1 + * 13.3 UAS Processing + * https://tools.ietf.org/html/rfc3261#section-13.3 + */ +var InviteUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(InviteUserAgentServer, _super); + function InviteUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.InviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + InviteUserAgentServer.prototype.dispose = function () { + if (this.earlyDialog) { + this.earlyDialog.dispose(); + } + _super.prototype.dispose.call(this); + }; + /** + * 13.3.1.4 The INVITE is Accepted + * The UAS core generates a 2xx response. This response establishes a + * dialog, and therefore follows the procedures of Section 12.1.1 in + * addition to those of Section 8.2.6. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + * @param options Accept options bucket. + */ + InviteUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (!this.acceptable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not acceptable in state " + this.transaction.state + "."); + } + // This response establishes a dialog... + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!this.confirmedDialog) { + if (this.earlyDialog) { + this.earlyDialog.confirm(); + this.confirmedDialog = this.earlyDialog; + this.earlyDialog = undefined; + } + else { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteServerTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + var state = dialogs_1.Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag); + this.confirmedDialog = new dialogs_1.SessionDialog(transaction, this.core, state); + } + } + // When a UAS responds to a request with a response that establishes a + // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route + // header field values from the request into the response (including the + // URIs, URI parameters, and any Record-Route header field parameters, + // whether they are known or unknown to the UAS) and MUST maintain the + // order of those values. The UAS MUST add a Contact header field to + // the response. The Contact header field contains an address where the + // UAS would like to be contacted for subsequent requests in the dialog + // (which includes the ACK for a 2xx response in the case of an INVITE). + // Generally, the host portion of this URI is the IP address or FQDN of + // the host. The URI provided in the Contact header field MUST be a SIP + // or SIPS URI. If the request that initiated the dialog contained a + // SIPS URI in the Request-URI or in the top Record-Route header field + // value, if there was any, or the Contact header field if there was no + // Record-Route header field, the Contact header field in the response + // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the + // same URI can be used in messages outside this dialog). The same way, + // the scope of the URI in the Contact header field of the INVITE is not + // limited to this dialog either. It can therefore be used in messages + // to the UAC even outside this dialog. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var recordRouteHeader = this.message + .getHeaders("record-route") + .map(function (header) { return "Record-Route: " + header; }); + var contactHeader = "Contact: " + this.core.configuration.contact.toString(); + // A 2xx response to an INVITE SHOULD contain the Allow header field and + // the Supported header field, and MAY contain the Accept header field. + // Including these header fields allows the UAC to determine the + // features and extensions supported by the UAS for the duration of the + // call, without probing. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + // FIXME: TODO: This should not be hard coded. + var allowHeader = "Allow: " + allowed_methods_1.AllowedMethods.toString(); + // FIXME: TODO: Supported header (see reply()) + // FIXME: TODO: Accept header + // If the INVITE request contained an offer, and the UAS had not yet + // sent an answer, the 2xx MUST contain an answer. If the INVITE did + // not contain an offer, the 2xx MUST contain an offer if the UAS had + // not yet sent an offer. + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!options.body) { + if (this.confirmedDialog.signalingState === session_1.SignalingState.Initial || + this.confirmedDialog.signalingState === session_1.SignalingState.HaveRemoteOffer) { + throw new Error("Response must have a body."); + } + } + // FIXME: TODO: Guard offer/answer + options.statusCode = options.statusCode || 200; + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); + options.extraHeaders.push(allowHeader); + options.extraHeaders.push(contactHeader); + var response = _super.prototype.accept.call(this, options); + var session = this.confirmedDialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.confirmedDialog.signalingStateTransition(options.body); + } + return result; + }; + /** + * 13.3.1.1 Progress + * If the UAS is not able to answer the invitation immediately, it can + * choose to indicate some kind of progress to the UAC (for example, an + * indication that a phone is ringing). This is accomplished with a + * provisional response between 101 and 199. These provisional + * responses establish early dialogs and therefore follow the procedures + * of Section 12.1.1 in addition to those of Section 8.2.6. A UAS MAY + * send as many provisional responses as it likes. Each of these MUST + * indicate the same dialog ID. However, these will not be delivered + * reliably. + * + * If the UAS desires an extended period of time to answer the INVITE, + * it will need to ask for an "extension" in order to prevent proxies + * from canceling the transaction. A proxy has the option of canceling + * a transaction when there is a gap of 3 minutes between responses in a + * transaction. To prevent cancellation, the UAS MUST send a non-100 + * provisional response at every minute, to handle the possibility of + * lost provisional responses. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.1 + * @param options Progress options bucket. + */ + InviteUserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + if (!this.progressable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not progressable in state " + this.transaction.state + "."); + } + // This response establishes a dialog... + // https://tools.ietf.org/html/rfc3261#section-13.3.1.4 + if (!this.earlyDialog) { + var transaction = this.transaction; + if (!(transaction instanceof transactions_1.InviteServerTransaction)) { + throw new Error("Transaction not instance of InviteClientTransaction."); + } + var state = dialogs_1.Dialog.initialDialogStateForUserAgentServer(this.message, this.toTag, true); + this.earlyDialog = new dialogs_1.SessionDialog(transaction, this.core, state); + } + // When a UAS responds to a request with a response that establishes a + // dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route + // header field values from the request into the response (including the + // URIs, URI parameters, and any Record-Route header field parameters, + // whether they are known or unknown to the UAS) and MUST maintain the + // order of those values. The UAS MUST add a Contact header field to + // the response. The Contact header field contains an address where the + // UAS would like to be contacted for subsequent requests in the dialog + // (which includes the ACK for a 2xx response in the case of an INVITE). + // Generally, the host portion of this URI is the IP address or FQDN of + // the host. The URI provided in the Contact header field MUST be a SIP + // or SIPS URI. If the request that initiated the dialog contained a + // SIPS URI in the Request-URI or in the top Record-Route header field + // value, if there was any, or the Contact header field if there was no + // Record-Route header field, the Contact header field in the response + // MUST be a SIPS URI. The URI SHOULD have global scope (that is, the + // same URI can be used in messages outside this dialog). The same way, + // the scope of the URI in the Contact header field of the INVITE is not + // limited to this dialog either. It can therefore be used in messages + // to the UAC even outside this dialog. + // https://tools.ietf.org/html/rfc3261#section-12.1.1 + var recordRouteHeader = this.message + .getHeaders("record-route") + .map(function (header) { return "Record-Route: " + header; }); + var contactHeader = "Contact: " + this.core.configuration.contact; + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(recordRouteHeader); + options.extraHeaders.push(contactHeader); + var response = _super.prototype.progress.call(this, options); + var session = this.earlyDialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.earlyDialog.signalingStateTransition(options.body); + } + return result; + }; + /** + * 13.3.1.2 The INVITE is Redirected + * If the UAS decides to redirect the call, a 3xx response is sent. A + * 300 (Multiple Choices), 301 (Moved Permanently) or 302 (Moved + * Temporarily) response SHOULD contain a Contact header field + * containing one or more URIs of new addresses to be tried. The + * response is passed to the INVITE server transaction, which will deal + * with its retransmissions. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.2 + * @param options Reject options bucket. + */ + InviteUserAgentServer.prototype.redirect = function (contacts, options) { + if (options === void 0) { options = { statusCode: 302 }; } + return _super.prototype.redirect.call(this, contacts, options); + }; + /** + * 13.3.1.3 The INVITE is Rejected + * A common scenario occurs when the callee is currently not willing or + * able to take additional calls at this end system. A 486 (Busy Here) + * SHOULD be returned in such a scenario. + * https://tools.ietf.org/html/rfc3261#section-13.3.1.3 + * @param options Reject options bucket. + */ + InviteUserAgentServer.prototype.reject = function (options) { + if (options === void 0) { options = { statusCode: 486 }; } + return _super.prototype.reject.call(this, options); + }; + return InviteUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.InviteUserAgentServer = InviteUserAgentServer; diff --git a/lib/core/user-agents/message-user-agent-client.d.ts b/lib/core/user-agents/message-user-agent-client.d.ts new file mode 100644 index 000000000..e7047d713 --- /dev/null +++ b/lib/core/user-agents/message-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { OutgoingMessageRequest, OutgoingRequestDelegate, OutgoingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentClient } from "./user-agent-client"; +export declare class MessageUserAgentClient extends UserAgentClient implements OutgoingMessageRequest { + constructor(core: UserAgentCore, message: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate); +} diff --git a/lib/core/user-agents/message-user-agent-client.js b/lib/core/user-agents/message-user-agent-client.js new file mode 100644 index 000000000..a60af5b5d --- /dev/null +++ b/lib/core/user-agents/message-user-agent-client.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var MessageUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(MessageUserAgentClient, _super); + function MessageUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return MessageUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.MessageUserAgentClient = MessageUserAgentClient; diff --git a/lib/core/user-agents/message-user-agent-server.d.ts b/lib/core/user-agents/message-user-agent-server.d.ts new file mode 100644 index 000000000..b1205d5cb --- /dev/null +++ b/lib/core/user-agents/message-user-agent-server.d.ts @@ -0,0 +1,7 @@ +import { IncomingMessageRequest, IncomingRequestDelegate, IncomingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentServer } from "./user-agent-server"; +export declare class MessageUserAgentServer extends UserAgentServer implements IncomingMessageRequest { + protected core: UserAgentCore; + constructor(core: UserAgentCore, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); +} diff --git a/lib/core/user-agents/message-user-agent-server.js b/lib/core/user-agents/message-user-agent-server.js new file mode 100644 index 000000000..d25e081a7 --- /dev/null +++ b/lib/core/user-agents/message-user-agent-server.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var MessageUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(MessageUserAgentServer, _super); + function MessageUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + return MessageUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.MessageUserAgentServer = MessageUserAgentServer; diff --git a/lib/core/user-agents/notify-user-agent-client.d.ts b/lib/core/user-agents/notify-user-agent-client.d.ts new file mode 100644 index 000000000..ddab646bb --- /dev/null +++ b/lib/core/user-agents/notify-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { SessionDialog } from "../dialogs"; +import { OutgoingNotifyRequest, OutgoingRequestDelegate, RequestOptions } from "../messages"; +import { UserAgentClient } from "./user-agent-client"; +export declare class NotifyUserAgentClient extends UserAgentClient implements OutgoingNotifyRequest { + constructor(dialog: SessionDialog, delegate?: OutgoingRequestDelegate, options?: RequestOptions); +} diff --git a/lib/core/user-agents/notify-user-agent-client.js b/lib/core/user-agents/notify-user-agent-client.js new file mode 100644 index 000000000..de6b37f73 --- /dev/null +++ b/lib/core/user-agents/notify-user-agent-client.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var NotifyUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(NotifyUserAgentClient, _super); + function NotifyUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.NOTIFY, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return NotifyUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.NotifyUserAgentClient = NotifyUserAgentClient; diff --git a/lib/core/user-agents/notify-user-agent-server.d.ts b/lib/core/user-agents/notify-user-agent-server.d.ts new file mode 100644 index 000000000..2fa9d9393 --- /dev/null +++ b/lib/core/user-agents/notify-user-agent-server.d.ts @@ -0,0 +1,12 @@ +import { Dialog } from "../dialogs"; +import { IncomingNotifyRequest, IncomingRequestDelegate, IncomingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentServer } from "./user-agent-server"; +export declare class NotifyUserAgentServer extends UserAgentServer implements IncomingNotifyRequest { + /** + * NOTIFY UAS constructor. + * @param dialogOrCore Dialog for in dialog NOTIFY, UserAgentCore for out of dialog NOTIFY (deprecated). + * @param message Incoming NOTIFY request message. + */ + constructor(dialogOrCore: Dialog | UserAgentCore, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); +} diff --git a/lib/core/user-agents/notify-user-agent-server.js b/lib/core/user-agents/notify-user-agent-server.js new file mode 100644 index 000000000..2cbee5788 --- /dev/null +++ b/lib/core/user-agents/notify-user-agent-server.js @@ -0,0 +1,26 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var NotifyUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(NotifyUserAgentServer, _super); + /** + * NOTIFY UAS constructor. + * @param dialogOrCore Dialog for in dialog NOTIFY, UserAgentCore for out of dialog NOTIFY (deprecated). + * @param message Incoming NOTIFY request message. + */ + function NotifyUserAgentServer(dialogOrCore, message, delegate) { + var _this = this; + var userAgentCore = instanceOfDialog(dialogOrCore) ? + dialogOrCore.userAgentCore : + dialogOrCore; + _this = _super.call(this, transactions_1.NonInviteServerTransaction, userAgentCore, message, delegate) || this; + return _this; + } + return NotifyUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.NotifyUserAgentServer = NotifyUserAgentServer; +function instanceOfDialog(object) { + return object.userAgentCore !== undefined; +} diff --git a/lib/core/user-agents/prack-user-agent-client.d.ts b/lib/core/user-agents/prack-user-agent-client.d.ts new file mode 100644 index 000000000..0f4a0f6bc --- /dev/null +++ b/lib/core/user-agents/prack-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { SessionDialog } from "../dialogs"; +import { OutgoingPrackRequest, OutgoingRequestDelegate, RequestOptions } from "../messages"; +import { UserAgentClient } from "./user-agent-client"; +export declare class PrackUserAgentClient extends UserAgentClient implements OutgoingPrackRequest { + constructor(dialog: SessionDialog, delegate?: OutgoingRequestDelegate, options?: RequestOptions); +} diff --git a/lib/core/user-agents/prack-user-agent-client.js b/lib/core/user-agents/prack-user-agent-client.js new file mode 100644 index 000000000..a401ea948 --- /dev/null +++ b/lib/core/user-agents/prack-user-agent-client.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var PrackUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(PrackUserAgentClient, _super); + function PrackUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.PRACK, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.signalingStateTransition(message); + return _this; + } + return PrackUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.PrackUserAgentClient = PrackUserAgentClient; diff --git a/lib/core/user-agents/prack-user-agent-server.d.ts b/lib/core/user-agents/prack-user-agent-server.d.ts new file mode 100644 index 000000000..46c143e9c --- /dev/null +++ b/lib/core/user-agents/prack-user-agent-server.d.ts @@ -0,0 +1,12 @@ +import { SessionDialog } from "../dialogs"; +import { IncomingPrackRequest, IncomingRequestDelegate, IncomingRequestMessage, OutgoingResponse, ResponseOptions } from "../messages"; +import { UserAgentServer } from "./user-agent-server"; +export declare class PrackUserAgentServer extends UserAgentServer implements IncomingPrackRequest { + private dialog; + constructor(dialog: SessionDialog, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + accept(options?: ResponseOptions): OutgoingResponse; +} diff --git a/lib/core/user-agents/prack-user-agent-server.js b/lib/core/user-agents/prack-user-agent-server.js new file mode 100644 index 000000000..38ef5b2ef --- /dev/null +++ b/lib/core/user-agents/prack-user-agent-server.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var PrackUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(PrackUserAgentServer, _super); + function PrackUserAgentServer(dialog, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + // Update dialog signaling state with offer/answer in body + dialog.signalingStateTransition(message); + _this.dialog = dialog; + return _this; + } + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + PrackUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (options.body) { + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(options.body); + } + return _super.prototype.accept.call(this, options); + }; + return PrackUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.PrackUserAgentServer = PrackUserAgentServer; diff --git a/lib/core/user-agents/publish-user-agent-client.d.ts b/lib/core/user-agents/publish-user-agent-client.d.ts new file mode 100644 index 000000000..8543cd774 --- /dev/null +++ b/lib/core/user-agents/publish-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { OutgoingPublishRequest, OutgoingRequestDelegate, OutgoingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentClient } from "./user-agent-client"; +export declare class PublishUserAgentClient extends UserAgentClient implements OutgoingPublishRequest { + constructor(core: UserAgentCore, message: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate); +} diff --git a/lib/core/user-agents/publish-user-agent-client.js b/lib/core/user-agents/publish-user-agent-client.js new file mode 100644 index 000000000..c3825ecbb --- /dev/null +++ b/lib/core/user-agents/publish-user-agent-client.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var PublishUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(PublishUserAgentClient, _super); + function PublishUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return PublishUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.PublishUserAgentClient = PublishUserAgentClient; diff --git a/lib/core/user-agents/re-invite-user-agent-client.d.ts b/lib/core/user-agents/re-invite-user-agent-client.d.ts new file mode 100644 index 000000000..cea52b92a --- /dev/null +++ b/lib/core/user-agents/re-invite-user-agent-client.d.ts @@ -0,0 +1,15 @@ +import { SessionDialog } from "../dialogs"; +import { IncomingResponseMessage, OutgoingInviteRequest, OutgoingInviteRequestDelegate, RequestOptions } from "../messages"; +import { UserAgentClient } from "./user-agent-client"; +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.1 UAC Behavior + * https://tools.ietf.org/html/rfc3261#section-14.1 + */ +export declare class ReInviteUserAgentClient extends UserAgentClient implements OutgoingInviteRequest { + delegate: OutgoingInviteRequestDelegate | undefined; + private dialog; + constructor(dialog: SessionDialog, delegate?: OutgoingInviteRequestDelegate, options?: RequestOptions); + protected receiveResponse(message: IncomingResponseMessage): void; +} diff --git a/lib/core/user-agents/re-invite-user-agent-client.js b/lib/core/user-agents/re-invite-user-agent-client.js new file mode 100644 index 000000000..18825fc5a --- /dev/null +++ b/lib/core/user-agents/re-invite-user-agent-client.js @@ -0,0 +1,106 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.1 UAC Behavior + * https://tools.ietf.org/html/rfc3261#section-14.1 + */ +var ReInviteUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReInviteUserAgentClient, _super); + function ReInviteUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.INVITE, options); + _this = _super.call(this, transactions_1.InviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + _this.delegate = delegate; + dialog.signalingStateTransition(message); + // FIXME: TODO: next line obviously needs to be improved... + dialog.reinviteUserAgentClient = _this; // let the dialog know re-invite request sent + _this.dialog = dialog; + return _this; + } + ReInviteUserAgentClient.prototype.receiveResponse = function (message) { + var _this = this; + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + break; + case /^1[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ + message: message, + session: this.dialog, + prack: function (options) { + throw new Error("Unimplemented."); + } + }); + } + break; + case /^2[0-9]{2}$/.test(statusCode): + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(message); + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ + message: message, + session: this.dialog, + ack: function (options) { + var outgoingAckRequest = _this.dialog.ack(options); + return outgoingAckRequest; + } + }); + } + break; + case /^3[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + break; + case /^[4-6][0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + else { + // If a UA receives a non-2xx final response to a re-INVITE, the session + // parameters MUST remain unchanged, as if no re-INVITE had been issued. + // Note that, as stated in Section 12.2.1.2, if the non-2xx final + // response is a 481 (Call/Transaction Does Not Exist), or a 408 + // (Request Timeout), or no response at all is received for the re- + // INVITE (that is, a timeout is returned by the INVITE client + // transaction), the UAC will terminate the dialog. + // + // If a UAC receives a 491 response to a re-INVITE, it SHOULD start a + // timer with a value T chosen as follows: + // + // 1. If the UAC is the owner of the Call-ID of the dialog ID + // (meaning it generated the value), T has a randomly chosen value + // between 2.1 and 4 seconds in units of 10 ms. + // + // 2. If the UAC is not the owner of the Call-ID of the dialog ID, T + // has a randomly chosen value of between 0 and 2 seconds in units + // of 10 ms. + // + // When the timer fires, the UAC SHOULD attempt the re-INVITE once more, + // if it still desires for that session modification to take place. For + // example, if the call was already hung up with a BYE, the re-INVITE + // would not take place. + // https://tools.ietf.org/html/rfc3261#section-14.1 + // FIXME: TODO: The above. + } + break; + default: + throw new Error("Invalid status code " + statusCode); + } + }; + return ReInviteUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReInviteUserAgentClient = ReInviteUserAgentClient; diff --git a/lib/core/user-agents/re-invite-user-agent-server.d.ts b/lib/core/user-agents/re-invite-user-agent-server.d.ts new file mode 100644 index 000000000..43b82962f --- /dev/null +++ b/lib/core/user-agents/re-invite-user-agent-server.d.ts @@ -0,0 +1,23 @@ +import { SessionDialog } from "../dialogs"; +import { IncomingInviteRequest, IncomingRequestDelegate, IncomingRequestMessage, OutgoingResponseWithSession, ResponseOptions } from "../messages"; +import { UserAgentServer } from "./user-agent-server"; +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.2 UAS Behavior + * https://tools.ietf.org/html/rfc3261#section-14.2 + */ +export declare class ReInviteUserAgentServer extends UserAgentServer implements IncomingInviteRequest { + private dialog; + constructor(dialog: SessionDialog, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + accept(options?: ResponseOptions): OutgoingResponseWithSession; + /** + * Update the dialog signaling state on a 1xx response. + * @param options Progress options bucket. + */ + progress(options?: ResponseOptions): OutgoingResponseWithSession; +} diff --git a/lib/core/user-agents/re-invite-user-agent-server.js b/lib/core/user-agents/re-invite-user-agent-server.js new file mode 100644 index 000000000..491f56cd2 --- /dev/null +++ b/lib/core/user-agents/re-invite-user-agent-server.js @@ -0,0 +1,68 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +/** + * 14 Modifying an Existing Session + * https://tools.ietf.org/html/rfc3261#section-14 + * 14.2 UAS Behavior + * https://tools.ietf.org/html/rfc3261#section-14.2 + */ +var ReInviteUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReInviteUserAgentServer, _super); + function ReInviteUserAgentServer(dialog, message, delegate) { + var _this = _super.call(this, transactions_1.InviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + dialog.reinviteUserAgentServer = _this; + _this.dialog = dialog; + return _this; + } + /** + * Update the dialog signaling state on a 2xx response. + * @param options Options bucket. + */ + ReInviteUserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + // FIXME: The next two lines SHOULD go away, but I suppose it's technically harmless... + // These are here because some versions of SIP.js prior to 0.13.8 set the route set + // of all in dialog ACKs based on the Record-Route headers in the associated 2xx + // response. While this worked for dialog forming 2xx responses, it was technically + // broken for re-INVITE ACKS as it only worked if the UAS populated the Record-Route + // headers in the re-INVITE 2xx response (which is not required and a waste of bandwidth + // as the should be ignored if present in re-INVITE ACKS) and the UAS populated + // the Record-Route headers with the correct values (would be weird not too, but...). + // Anyway, for now the technically useless Record-Route headers are being added + // to maintain "backwards compatibility" with the older broken versions of SIP.js. + options.extraHeaders = options.extraHeaders || []; + options.extraHeaders = options.extraHeaders.concat(this.dialog.routeSet.map(function (route) { return "Record-Route: " + route; })); + // Send and return the response + var response = _super.prototype.accept.call(this, options); + var session = this.dialog; + var result = tslib_1.__assign({}, response, { session: session }); + if (options.body) { + // Update dialog signaling state with offer/answer in body + this.dialog.signalingStateTransition(options.body); + } + // Update dialog + this.dialog.reConfirm(); + return result; + }; + /** + * Update the dialog signaling state on a 1xx response. + * @param options Progress options bucket. + */ + ReInviteUserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + // Send and return the response + var response = _super.prototype.progress.call(this, options); + var session = this.dialog; + var result = tslib_1.__assign({}, response, { session: session }); + // Update dialog signaling state + if (options.body) { + this.dialog.signalingStateTransition(options.body); + } + return result; + }; + return ReInviteUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReInviteUserAgentServer = ReInviteUserAgentServer; diff --git a/lib/core/user-agents/re-subscribe-user-agent-client.d.ts b/lib/core/user-agents/re-subscribe-user-agent-client.d.ts new file mode 100644 index 000000000..8ff23c748 --- /dev/null +++ b/lib/core/user-agents/re-subscribe-user-agent-client.d.ts @@ -0,0 +1,13 @@ +import { SubscriptionDialog } from "../dialogs"; +import { IncomingResponseMessage, OutgoingRequestDelegate, OutgoingSubscribeRequest, RequestOptions } from "../messages"; +import { UserAgentClient } from "./user-agent-client"; +export declare class ReSubscribeUserAgentClient extends UserAgentClient implements OutgoingSubscribeRequest { + private dialog; + constructor(dialog: SubscriptionDialog, delegate?: OutgoingRequestDelegate, options?: RequestOptions); + waitNotifyStop(): void; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + protected receiveResponse(message: IncomingResponseMessage): void; +} diff --git a/lib/core/user-agents/re-subscribe-user-agent-client.js b/lib/core/user-agents/re-subscribe-user-agent-client.js new file mode 100644 index 000000000..544df6b83 --- /dev/null +++ b/lib/core/user-agents/re-subscribe-user-agent-client.js @@ -0,0 +1,69 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var ReSubscribeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReSubscribeUserAgentClient, _super); + function ReSubscribeUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.SUBSCRIBE, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + _this.dialog = dialog; + return _this; + } + ReSubscribeUserAgentClient.prototype.waitNotifyStop = function () { + // TODO: Placeholder. Not utilized currently. + return; + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + ReSubscribeUserAgentClient.prototype.receiveResponse = function (message) { + if (message.statusCode && message.statusCode >= 200 && message.statusCode < 300) { + // The "Expires" header field in a 200-class response to SUBSCRIBE + // request indicates the actual duration for which the subscription will + // remain active (unless refreshed). The received value might be + // smaller than the value indicated in the SUBSCRIBE request but cannot + // be larger; see Section 4.2.1 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + var expires = message.getHeader("Expires"); + if (!expires) { + this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE"); + } + else { + var subscriptionExpiresReceived = Number(expires); + if (this.dialog.subscriptionExpires > subscriptionExpiresReceived) { + this.dialog.subscriptionExpires = subscriptionExpiresReceived; + } + } + } + if (message.statusCode && message.statusCode >= 400 && message.statusCode < 700) { + // If a SUBSCRIBE request to refresh a subscription receives a 404, 405, + // 410, 416, 480-485, 489, 501, or 604 response, the subscriber MUST + // consider the subscription terminated. (See [RFC5057] for further + // details and notes about the effect of error codes on dialogs and + // usages within dialog, such as subscriptions). If the subscriber + // wishes to re-subscribe to the state, he does so by composing an + // unrelated initial SUBSCRIBE request with a freshly generated Call-ID + // and a new, unique "From" tag (see Section 4.1.2.1). + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + var errorCodes = [404, 405, 410, 416, 480, 481, 482, 483, 484, 485, 489, 501, 604]; + if (errorCodes.includes(message.statusCode)) { + this.dialog.terminate(); + } + // If a SUBSCRIBE request to refresh a subscription fails with any error + // code other than those listed above, the original subscription is + // still considered valid for the duration of the most recently known + // "Expires" value as negotiated by the most recent successful SUBSCRIBE + // transaction, or as communicated by a NOTIFY request in its + // "Subscription-State" header field "expires" parameter. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.2 + } + _super.prototype.receiveResponse.call(this, message); + }; + return ReSubscribeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReSubscribeUserAgentClient = ReSubscribeUserAgentClient; diff --git a/lib/core/user-agents/re-subscribe-user-agent-server.d.ts b/lib/core/user-agents/re-subscribe-user-agent-server.d.ts new file mode 100644 index 000000000..cfda1f85d --- /dev/null +++ b/lib/core/user-agents/re-subscribe-user-agent-server.d.ts @@ -0,0 +1,6 @@ +import { Dialog } from "../dialogs"; +import { IncomingRequestDelegate, IncomingRequestMessage, IncomingSubscribeRequest } from "../messages"; +import { UserAgentServer } from "./user-agent-server"; +export declare class ReSubscribeUserAgentServer extends UserAgentServer implements IncomingSubscribeRequest { + constructor(dialog: Dialog, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); +} diff --git a/lib/core/user-agents/re-subscribe-user-agent-server.js b/lib/core/user-agents/re-subscribe-user-agent-server.js new file mode 100644 index 000000000..b3670cfcb --- /dev/null +++ b/lib/core/user-agents/re-subscribe-user-agent-server.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var ReSubscribeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReSubscribeUserAgentServer, _super); + function ReSubscribeUserAgentServer(dialog, message, delegate) { + return _super.call(this, transactions_1.NonInviteServerTransaction, dialog.userAgentCore, message, delegate) || this; + } + return ReSubscribeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReSubscribeUserAgentServer = ReSubscribeUserAgentServer; diff --git a/lib/core/user-agents/refer-user-agent-client.d.ts b/lib/core/user-agents/refer-user-agent-client.d.ts new file mode 100644 index 000000000..ffda0b191 --- /dev/null +++ b/lib/core/user-agents/refer-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { SessionDialog } from "../dialogs"; +import { OutgoingReferRequest, OutgoingRequestDelegate, RequestOptions } from "../messages"; +import { UserAgentClient } from "./user-agent-client"; +export declare class ReferUserAgentClient extends UserAgentClient implements OutgoingReferRequest { + constructor(dialog: SessionDialog, delegate?: OutgoingRequestDelegate, options?: RequestOptions); +} diff --git a/lib/core/user-agents/refer-user-agent-client.js b/lib/core/user-agents/refer-user-agent-client.js new file mode 100644 index 000000000..1d564f4b7 --- /dev/null +++ b/lib/core/user-agents/refer-user-agent-client.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var ReferUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(ReferUserAgentClient, _super); + function ReferUserAgentClient(dialog, delegate, options) { + var _this = this; + var message = dialog.createOutgoingRequestMessage(messages_1.C.REFER, options); + _this = _super.call(this, transactions_1.NonInviteClientTransaction, dialog.userAgentCore, message, delegate) || this; + return _this; + } + return ReferUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.ReferUserAgentClient = ReferUserAgentClient; diff --git a/lib/core/user-agents/refer-user-agent-server.d.ts b/lib/core/user-agents/refer-user-agent-server.d.ts new file mode 100644 index 000000000..e540da697 --- /dev/null +++ b/lib/core/user-agents/refer-user-agent-server.d.ts @@ -0,0 +1,12 @@ +import { SessionDialog } from "../dialogs"; +import { IncomingReferRequest, IncomingRequestDelegate, IncomingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentServer } from "./user-agent-server"; +export declare class ReferUserAgentServer extends UserAgentServer implements IncomingReferRequest { + /** + * REFER UAS constructor. + * @param dialogOrCore Dialog for in dialog REFER, UserAgentCore for out of dialog REFER. + * @param message Incoming REFER request message. + */ + constructor(dialogOrCore: SessionDialog | UserAgentCore, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); +} diff --git a/lib/core/user-agents/refer-user-agent-server.js b/lib/core/user-agents/refer-user-agent-server.js new file mode 100644 index 000000000..a31de5b81 --- /dev/null +++ b/lib/core/user-agents/refer-user-agent-server.js @@ -0,0 +1,26 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var ReferUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(ReferUserAgentServer, _super); + /** + * REFER UAS constructor. + * @param dialogOrCore Dialog for in dialog REFER, UserAgentCore for out of dialog REFER. + * @param message Incoming REFER request message. + */ + function ReferUserAgentServer(dialogOrCore, message, delegate) { + var _this = this; + var userAgentCore = instanceOfSessionDialog(dialogOrCore) ? + dialogOrCore.userAgentCore : + dialogOrCore; + _this = _super.call(this, transactions_1.NonInviteServerTransaction, userAgentCore, message, delegate) || this; + return _this; + } + return ReferUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.ReferUserAgentServer = ReferUserAgentServer; +function instanceOfSessionDialog(object) { + return object.userAgentCore !== undefined; +} diff --git a/lib/core/user-agents/register-user-agent-client.d.ts b/lib/core/user-agents/register-user-agent-client.d.ts new file mode 100644 index 000000000..140756e8a --- /dev/null +++ b/lib/core/user-agents/register-user-agent-client.d.ts @@ -0,0 +1,6 @@ +import { OutgoingRegisterRequest, OutgoingRequestDelegate, OutgoingRequestMessage } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentClient } from "./user-agent-client"; +export declare class RegisterUserAgentClient extends UserAgentClient implements OutgoingRegisterRequest { + constructor(core: UserAgentCore, message: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate); +} diff --git a/lib/core/user-agents/register-user-agent-client.js b/lib/core/user-agents/register-user-agent-client.js new file mode 100644 index 000000000..018cab37a --- /dev/null +++ b/lib/core/user-agents/register-user-agent-client.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +var RegisterUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(RegisterUserAgentClient, _super); + function RegisterUserAgentClient(core, message, delegate) { + return _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + } + return RegisterUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.RegisterUserAgentClient = RegisterUserAgentClient; diff --git a/lib/core/user-agents/subscribe-user-agent-client.d.ts b/lib/core/user-agents/subscribe-user-agent-client.d.ts new file mode 100644 index 000000000..e69de3b1a --- /dev/null +++ b/lib/core/user-agents/subscribe-user-agent-client.d.ts @@ -0,0 +1,62 @@ +import { IncomingResponseMessage, OutgoingRequestMessage, OutgoingSubscribeRequest, OutgoingSubscribeRequestDelegate } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { NotifyUserAgentServer } from "./notify-user-agent-server"; +import { UserAgentClient } from "./user-agent-client"; +/** + * 4.1. Subscriber Behavior + * https://tools.ietf.org/html/rfc6665#section-4.1 + * + * User agent client for installation of a single subscription per SUBSCRIBE request. + * TODO: Support for installation of multiple subscriptions on forked SUBSCRIBE reqeuests. + */ +export declare class SubscribeUserAgentClient extends UserAgentClient implements OutgoingSubscribeRequest { + delegate: OutgoingSubscribeRequestDelegate | undefined; + /** Dialog created upon receiving the first NOTIFY. */ + private dialog; + /** Identifier of this user agent client. */ + private subscriberId; + /** When the subscription expires. Starts as requested expires and updated on 200 and NOTIFY. */ + private subscriptionExpires; + /** The requested expires for the subscription. */ + private subscriptionExpiresRequested; + /** Subscription event being targeted. */ + private subscriptionEvent; + /** Subscription state. */ + private subscriptionState; + /** Timer N Id. */ + private N; + constructor(core: UserAgentCore, message: OutgoingRequestMessage, delegate?: OutgoingSubscribeRequestDelegate); + /** + * Destructor. + * Note that Timer N may live on waiting for an initial NOTIFY and + * the delegate may still receive that NOTIFY. If you don't want + * that behavior then either clear the delegate so the delegate + * doesn't get called (a 200 will be sent in response to the NOTIFY) + * or call `waitNotifyStop` which will clear Timer N and remove this + * UAC from the core (a 481 will be sent in response to the NOTIFY). + */ + dispose(): void; + /** + * Handle out of dialog NOTIFY assoicated with SUBSCRIBE request. + * This is the first NOTIFY received after the SUBSCRIBE request. + * @param uas User agent server handling the subscription creating NOTIFY. + */ + onNotify(uas: NotifyUserAgentServer): void; + waitNotifyStart(): void; + waitNotifyStop(): void; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + protected receiveResponse(message: IncomingResponseMessage): void; + /** + * To ensure that subscribers do not wait indefinitely for a + * subscription to be established, a subscriber starts a Timer N, set to + * 64*T1, when it sends a SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription failed, and cleans up any state associated with the + * subscription attempt. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + */ + private timer_N; +} diff --git a/lib/core/user-agents/subscribe-user-agent-client.js b/lib/core/user-agents/subscribe-user-agent-client.js new file mode 100644 index 000000000..02b7c9fa3 --- /dev/null +++ b/lib/core/user-agents/subscribe-user-agent-client.js @@ -0,0 +1,294 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var subscription_dialog_1 = require("../dialogs/subscription-dialog"); +var subscription_1 = require("../subscription"); +var timers_1 = require("../timers"); +var transactions_1 = require("../transactions"); +var user_agent_client_1 = require("./user-agent-client"); +/** + * 4.1. Subscriber Behavior + * https://tools.ietf.org/html/rfc6665#section-4.1 + * + * User agent client for installation of a single subscription per SUBSCRIBE request. + * TODO: Support for installation of multiple subscriptions on forked SUBSCRIBE reqeuests. + */ +var SubscribeUserAgentClient = /** @class */ (function (_super) { + tslib_1.__extends(SubscribeUserAgentClient, _super); + function SubscribeUserAgentClient(core, message, delegate) { + var _this = this; + // Get event from request message. + var event = message.getHeader("Event"); + if (!event) { + throw new Error("Event undefined"); + } + // Get expires from reqeust message. + var expires = message.getHeader("Expires"); + if (!expires) { + throw new Error("Expires undefined"); + } + _this = _super.call(this, transactions_1.NonInviteClientTransaction, core, message, delegate) || this; + _this.delegate = delegate; + // FIXME: Subscriber id should also be matching on event id. + _this.subscriberId = message.callId + message.fromTag + event; + _this.subscriptionExpiresRequested = _this.subscriptionExpires = Number(expires); + _this.subscriptionEvent = event; + _this.subscriptionState = subscription_1.SubscriptionState.NotifyWait; + // Start waiting for a NOTIFY we can use to create a subscription. + _this.waitNotifyStart(); + return _this; + } + /** + * Destructor. + * Note that Timer N may live on waiting for an initial NOTIFY and + * the delegate may still receive that NOTIFY. If you don't want + * that behavior then either clear the delegate so the delegate + * doesn't get called (a 200 will be sent in response to the NOTIFY) + * or call `waitNotifyStop` which will clear Timer N and remove this + * UAC from the core (a 481 will be sent in response to the NOTIFY). + */ + SubscribeUserAgentClient.prototype.dispose = function () { + _super.prototype.dispose.call(this); + }; + /** + * Handle out of dialog NOTIFY assoicated with SUBSCRIBE request. + * This is the first NOTIFY received after the SUBSCRIBE request. + * @param uas User agent server handling the subscription creating NOTIFY. + */ + SubscribeUserAgentClient.prototype.onNotify = function (uas) { + // NOTIFY requests are matched to such SUBSCRIBE requests if they + // contain the same "Call-ID", a "To" header field "tag" parameter that + // matches the "From" header field "tag" parameter of the SUBSCRIBE + // request, and the same "Event" header field. Rules for comparisons of + // the "Event" header fields are described in Section 8.2.1. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + var event = uas.message.parseHeader("Event").event; + if (!event || event !== this.subscriptionEvent) { + this.logger.warn("Failed to parse event."); + uas.reject({ statusCode: 489 }); + return; + } + // NOTIFY requests MUST contain "Subscription-State" header fields that + // indicate the status of the subscription. + // https://tools.ietf.org/html/rfc6665#section-4.1.3 + var subscriptionState = uas.message.parseHeader("Subscription-State"); + if (!subscriptionState || !subscriptionState.state) { + this.logger.warn("Failed to parse subscription state."); + uas.reject({ statusCode: 489 }); + return; + } + // Validate subscription state. + var state = subscriptionState.state; + switch (state) { + case "pending": + break; + case "active": + break; + case "terminated": + break; + default: + this.logger.warn("Invalid subscription state " + state); + uas.reject({ statusCode: 489 }); + return; + } + // Dialogs usages are created upon completion of a NOTIFY transaction + // for a new subscription, unless the NOTIFY request contains a + // "Subscription-State" of "terminated." + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (state !== "terminated") { + // The Contact header field MUST be present and contain exactly one SIP + // or SIPS URI in any request that can result in the establishment of a + // dialog. + // https://tools.ietf.org/html/rfc3261#section-8.1.1.8 + var contact = uas.message.parseHeader("contact"); + if (!contact) { + this.logger.warn("Failed to parse contact."); + uas.reject({ statusCode: 489 }); + return; + } + } + // In accordance with the rules for proxying non-INVITE requests as + // defined in [RFC3261], successful SUBSCRIBE requests will receive only + // one 200-class response; however, due to forking, the subscription may + // have been accepted by multiple nodes. The subscriber MUST therefore + // be prepared to receive NOTIFY requests with "From:" tags that differ + // from the "To:" tag received in the SUBSCRIBE 200-class response. + // + // If multiple NOTIFY requests are received in different dialogs in + // response to a single SUBSCRIBE request, each dialog represents a + // different destination to which the SUBSCRIBE request was forked. + // Subscriber handling in such situations varies by event package; see + // Section 5.4.9 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.4 + // Each event package MUST specify whether forked SUBSCRIBE requests are + // allowed to install multiple subscriptions. + // + // If such behavior is not allowed, the first potential dialog- + // establishing message will create a dialog. All subsequent NOTIFY + // requests that correspond to the SUBSCRIBE request (i.e., have + // matching "To", "From", "Call-ID", and "Event" header fields, as well + // as "From" header field "tag" parameter and "Event" header field "id" + // parameter) but that do not match the dialog would be rejected with a + // 481 response. Note that the 200-class response to the SUBSCRIBE + // request can arrive after a matching NOTIFY request has been received; + // such responses might not correlate to the same dialog established by + // the NOTIFY request. Except as required to complete the SUBSCRIBE + // transaction, such non-matching 200-class responses are ignored. + // + // If installing of multiple subscriptions by way of a single forked + // SUBSCRIBE request is allowed, the subscriber establishes a new dialog + // towards each notifier by returning a 200-class response to each + // NOTIFY request. Each dialog is then handled as its own entity and is + // refreshed independently of the other dialogs. + // + // In the case that multiple subscriptions are allowed, the event + // package MUST specify whether merging of the notifications to form a + // single state is required, and how such merging is to be performed. + // Note that it is possible that some event packages may be defined in + // such a way that each dialog is tied to a mutually exclusive state + // that is unaffected by the other dialogs; this MUST be clearly stated + // if it is the case. + // https://tools.ietf.org/html/rfc6665#section-5.4.9 + // *** NOTE: This implementation is only for event packages which + // do not allow forked requests to install muliple subscriptions. + // As such and in accordance with the specificaiton, we stop waiting + // and any future NOTIFY requests will be rejected with a 481. + if (this.dialog) { + throw new Error("Dialog already created. This implementation only supports install of single subscriptions."); + } + this.waitNotifyStop(); + // Update expires. + this.subscriptionExpires = + subscriptionState.expires ? + Math.min(this.subscriptionExpires, Math.max(subscriptionState.expires, 0)) : + this.subscriptionExpires; + // Update subscriptoin state. + switch (state) { + case "pending": + this.subscriptionState = subscription_1.SubscriptionState.Pending; + break; + case "active": + this.subscriptionState = subscription_1.SubscriptionState.Active; + break; + case "terminated": + this.subscriptionState = subscription_1.SubscriptionState.Terminated; + break; + default: + throw new Error("Unrecognized state " + state + "."); + } + // Dialogs usages are created upon completion of a NOTIFY transaction + // for a new subscription, unless the NOTIFY request contains a + // "Subscription-State" of "terminated." + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + if (this.subscriptionState !== subscription_1.SubscriptionState.Terminated) { + // Because the dialog usage is established by the NOTIFY request, the + // route set at the subscriber is taken from the NOTIFY request itself, + // as opposed to the route set present in the 200-class response to the + // SUBSCRIBE request. + // https://tools.ietf.org/html/rfc6665#section-4.4.1 + var dialogState = subscription_dialog_1.SubscriptionDialog.initialDialogStateForSubscription(this.message, uas.message); + // Subscription Initiated! :) + this.dialog = new subscription_dialog_1.SubscriptionDialog(this.subscriptionEvent, this.subscriptionExpires, this.subscriptionState, this.core, dialogState); + } + // Delegate. + if (this.delegate && this.delegate.onNotify) { + var request = uas; + var subscription = this.dialog; + this.delegate.onNotify({ request: request, subscription: subscription }); + } + else { + uas.accept(); + } + }; + SubscribeUserAgentClient.prototype.waitNotifyStart = function () { + var _this = this; + if (!this.N) { + // Add ourselves to the core's subscriber map. + // This allows the core to route out of dialog NOTIFY messages to us. + this.core.subscribers.set(this.subscriberId, this); + this.N = setTimeout(function () { return _this.timer_N(); }, timers_1.Timers.TIMER_N); + } + }; + SubscribeUserAgentClient.prototype.waitNotifyStop = function () { + if (this.N) { + // Remove ourselves to the core's subscriber map. + // Any future out of dialog NOTIFY messages will be rejected with a 481. + this.core.subscribers.delete(this.subscriberId); + clearTimeout(this.N); + this.N = undefined; + } + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + SubscribeUserAgentClient.prototype.receiveResponse = function (message) { + if (!this.authenticationGuard(message)) { + return; + } + if (message.statusCode && message.statusCode >= 200 && message.statusCode < 300) { + // The "Expires" header field in a 200-class response to SUBSCRIBE + // request indicates the actual duration for which the subscription will + // remain active (unless refreshed). The received value might be + // smaller than the value indicated in the SUBSCRIBE request but cannot + // be larger; see Section 4.2.1 for details. + // https://tools.ietf.org/html/rfc6665#section-4.1.2.1 + // The "Expires" values present in SUBSCRIBE 200-class responses behave + // in the same way as they do in REGISTER responses: the server MAY + // shorten the interval but MUST NOT lengthen it. + // + // If the duration specified in a SUBSCRIBE request is unacceptably + // short, the notifier may be able to send a 423 response, as + // described earlier in this section. + // + // 200-class responses to SUBSCRIBE requests will not generally contain + // any useful information beyond subscription duration; their primary + // purpose is to serve as a reliability mechanism. State information + // will be communicated via a subsequent NOTIFY request from the + // notifier. + // https://tools.ietf.org/html/rfc6665#section-4.2.1.1 + var expires = message.getHeader("Expires"); + if (!expires) { + this.logger.warn("Expires header missing in a 200-class response to SUBSCRIBE"); + } + else { + var subscriptionExpiresReceived = Number(expires); + if (subscriptionExpiresReceived > this.subscriptionExpiresRequested) { + this.logger.warn("Expires header in a 200-class response to SUBSCRIBE with a higher value than the one in the request"); + } + if (subscriptionExpiresReceived < this.subscriptionExpires) { + this.subscriptionExpires = subscriptionExpiresReceived; + } + } + // If a NOTIFY arrived before 200-class response a dialog may have been created. + // Updated the dialogs expiration only if this indicates earlier expiration. + if (this.dialog) { + if (this.dialog.subscriptionExpires > this.subscriptionExpires) { + this.dialog.subscriptionExpires = this.subscriptionExpires; + } + } + } + if (message.statusCode && message.statusCode >= 300 && message.statusCode < 700) { + this.waitNotifyStop(); // No NOTIFY will be sent after a negative final response. + } + _super.prototype.receiveResponse.call(this, message); + }; + /** + * To ensure that subscribers do not wait indefinitely for a + * subscription to be established, a subscriber starts a Timer N, set to + * 64*T1, when it sends a SUBSCRIBE request. If this Timer N expires + * prior to the receipt of a NOTIFY request, the subscriber considers + * the subscription failed, and cleans up any state associated with the + * subscription attempt. + * https://tools.ietf.org/html/rfc6665#section-4.1.2.4 + */ + SubscribeUserAgentClient.prototype.timer_N = function () { + this.logger.warn("Timer N expired for SUBSCRIBE user agent client. Timed out waiting for NOTIFY."); + this.waitNotifyStop(); + if (this.delegate && this.delegate.onNotifyTimeout) { + this.delegate.onNotifyTimeout(); + } + }; + return SubscribeUserAgentClient; +}(user_agent_client_1.UserAgentClient)); +exports.SubscribeUserAgentClient = SubscribeUserAgentClient; diff --git a/lib/core/user-agents/subscribe-user-agent-server.d.ts b/lib/core/user-agents/subscribe-user-agent-server.d.ts new file mode 100644 index 000000000..179d28c15 --- /dev/null +++ b/lib/core/user-agents/subscribe-user-agent-server.d.ts @@ -0,0 +1,7 @@ +import { IncomingRequestDelegate, IncomingRequestMessage, IncomingSubscribeRequest } from "../messages"; +import { UserAgentCore } from "../user-agent-core"; +import { UserAgentServer } from "./user-agent-server"; +export declare class SubscribeUserAgentServer extends UserAgentServer implements IncomingSubscribeRequest { + protected core: UserAgentCore; + constructor(core: UserAgentCore, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate); +} diff --git a/lib/core/user-agents/subscribe-user-agent-server.js b/lib/core/user-agents/subscribe-user-agent-server.js new file mode 100644 index 000000000..85e28078d --- /dev/null +++ b/lib/core/user-agents/subscribe-user-agent-server.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var transactions_1 = require("../transactions"); +var user_agent_server_1 = require("./user-agent-server"); +var SubscribeUserAgentServer = /** @class */ (function (_super) { + tslib_1.__extends(SubscribeUserAgentServer, _super); + function SubscribeUserAgentServer(core, message, delegate) { + var _this = _super.call(this, transactions_1.NonInviteServerTransaction, core, message, delegate) || this; + _this.core = core; + return _this; + } + return SubscribeUserAgentServer; +}(user_agent_server_1.UserAgentServer)); +exports.SubscribeUserAgentServer = SubscribeUserAgentServer; diff --git a/lib/core/user-agents/user-agent-client.d.ts b/lib/core/user-agents/user-agent-client.d.ts new file mode 100644 index 000000000..067d6d868 --- /dev/null +++ b/lib/core/user-agents/user-agent-client.d.ts @@ -0,0 +1,85 @@ +import { Logger, LoggerFactory } from "../log"; +import { IncomingResponseMessage, OutgoingRequest, OutgoingRequestDelegate, OutgoingRequestMessage, RequestOptions } from "../messages"; +import { ClientTransaction, ClientTransactionUser } from "../transactions"; +import { Transport } from "../transport"; +import { UserAgentCore } from "../user-agent-core"; +declare type ClientTransactionConstructor = new (message: OutgoingRequestMessage, transport: Transport, user: ClientTransactionUser) => ClientTransaction; +export declare class UserAgentClient implements OutgoingRequest { + private transactionConstructor; + protected core: UserAgentCore; + message: OutgoingRequestMessage; + delegate?: OutgoingRequestDelegate | undefined; + protected logger: Logger; + private _transaction; + private credentials; + private challenged; + private stale; + constructor(transactionConstructor: ClientTransactionConstructor, core: UserAgentCore, message: OutgoingRequestMessage, delegate?: OutgoingRequestDelegate | undefined); + dispose(): void; + readonly loggerFactory: LoggerFactory; + /** The transaction associated with this request. */ + readonly transaction: ClientTransaction; + /** + * Since requests other than INVITE are responded to immediately, sending a + * CANCEL for a non-INVITE request would always create a race condition. + * A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + * https://tools.ietf.org/html/rfc3261#section-9.1 + * @param options Cancel options bucket. + */ + cancel(reason?: string, options?: RequestOptions): OutgoingRequestMessage; + /** + * If a 401 (Unauthorized) or 407 (Proxy Authentication Required) + * response is received, the UAC SHOULD follow the authorization + * procedures of Section 22.2 and Section 22.3 to retry the request with + * credentials. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + * 22 Usage of HTTP Authentication + * https://tools.ietf.org/html/rfc3261#section-22 + * 22.1 Framework + * https://tools.ietf.org/html/rfc3261#section-22.1 + * 22.2 User-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.2 + * 22.3 Proxy-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.3 + * + * FIXME: This "guard for and retry the request with credentials" + * implementation is not complete and at best minimally passable. + * @param response The incoming response to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise the request is retried with credentials and current request processing must stop. + */ + protected authenticationGuard(message: IncomingResponseMessage): boolean; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + protected receiveResponse(message: IncomingResponseMessage): void; + private init; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + private onRequestTimeout; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + private onTransportError; +} +export {}; diff --git a/lib/core/user-agents/user-agent-client.js b/lib/core/user-agents/user-agent-client.js new file mode 100644 index 000000000..64d3a794d --- /dev/null +++ b/lib/core/user-agents/user-agent-client.js @@ -0,0 +1,310 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var messages_1 = require("../messages"); +var transactions_1 = require("../transactions"); +/* + * User Agent Client (UAC): A user agent client is a logical entity + * that creates a new request, and then uses the client + * transaction state machinery to send it. The role of UAC lasts + * only for the duration of that transaction. In other words, if + * a piece of software initiates a request, it acts as a UAC for + * the duration of that transaction. If it receives a request + * later, it assumes the role of a user agent server for the + * processing of that transaction. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentClient = /** @class */ (function () { + function UserAgentClient(transactionConstructor, core, message, delegate) { + this.transactionConstructor = transactionConstructor; + this.core = core; + this.message = message; + this.delegate = delegate; + this.challenged = false; + this.stale = false; + this.logger = this.loggerFactory.getLogger("sip.user-agent-client"); + this.init(); + } + UserAgentClient.prototype.dispose = function () { + this.transaction.dispose(); + }; + Object.defineProperty(UserAgentClient.prototype, "loggerFactory", { + get: function () { + return this.core.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentClient.prototype, "transaction", { + /** The transaction associated with this request. */ + get: function () { + if (!this._transaction) { + throw new Error("Transaction undefined."); + } + return this._transaction; + }, + enumerable: true, + configurable: true + }); + /** + * Since requests other than INVITE are responded to immediately, sending a + * CANCEL for a non-INVITE request would always create a race condition. + * A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + * https://tools.ietf.org/html/rfc3261#section-9.1 + * @param options Cancel options bucket. + */ + UserAgentClient.prototype.cancel = function (reason, options) { + var _this = this; + if (options === void 0) { options = {}; } + if (!this.transaction) { + throw new Error("Transaction undefined."); + } + if (!this.message.to) { + throw new Error("To undefined."); + } + if (!this.message.from) { + throw new Error("From undefined."); + } + // The following procedures are used to construct a CANCEL request. The + // Request-URI, Call-ID, To, the numeric part of CSeq, and From header + // fields in the CANCEL request MUST be identical to those in the + // request being cancelled, including tags. A CANCEL constructed by a + // client MUST have only a single Via header field value matching the + // top Via value in the request being cancelled. Using the same values + // for these header fields allows the CANCEL to be matched with the + // request it cancels (Section 9.2 indicates how such matching occurs). + // However, the method part of the CSeq header field MUST have a value + // of CANCEL. This allows it to be identified and processed as a + // transaction in its own right (See Section 17). + // https://tools.ietf.org/html/rfc3261#section-9.1 + var message = this.core.makeOutgoingRequestMessage(messages_1.C.CANCEL, this.message.ruri, this.message.from.uri, this.message.to.uri, { + toTag: this.message.toTag, + fromTag: this.message.fromTag, + callId: this.message.callId, + cseq: this.message.cseq + }, options.extraHeaders); + // TODO: Revisit this. + // The CANCEL needs to use the same branch parameter so that + // it matches the INVITE transaction, but this is a hacky way to do this. + // Or at the very least not well documented. If the the branch parameter + // is set on the outgoing request, the transaction will use it. + // Otherwise the transaction will make a new one. + message.branch = this.message.branch; + if (this.message.headers.Route) { + message.headers.Route = this.message.headers.Route; + } + if (reason) { + message.setHeader("Reason", reason); + } + // If no provisional response has been received, the CANCEL request MUST + // NOT be sent; rather, the client MUST wait for the arrival of a + // provisional response before sending the request. If the original + // request has generated a final response, the CANCEL SHOULD NOT be + // sent, as it is an effective no-op, since CANCEL has no effect on + // requests that have already generated a final response. + // https://tools.ietf.org/html/rfc3261#section-9.1 + if (this.transaction.state === transactions_1.TransactionState.Proceeding) { + var uac = new UserAgentClient(transactions_1.NonInviteClientTransaction, this.core, message); + } + else { + this.transaction.once("stateChanged", function () { + if (_this.transaction && _this.transaction.state === transactions_1.TransactionState.Proceeding) { + var uac = new UserAgentClient(transactions_1.NonInviteClientTransaction, _this.core, message); + } + }); + } + return message; + }; + /** + * If a 401 (Unauthorized) or 407 (Proxy Authentication Required) + * response is received, the UAC SHOULD follow the authorization + * procedures of Section 22.2 and Section 22.3 to retry the request with + * credentials. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + * 22 Usage of HTTP Authentication + * https://tools.ietf.org/html/rfc3261#section-22 + * 22.1 Framework + * https://tools.ietf.org/html/rfc3261#section-22.1 + * 22.2 User-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.2 + * 22.3 Proxy-to-User Authentication + * https://tools.ietf.org/html/rfc3261#section-22.3 + * + * FIXME: This "guard for and retry the request with credentials" + * implementation is not complete and at best minimally passable. + * @param response The incoming response to guard. + * @returns True if the program execution is to continue in the branch in question. + * Otherwise the request is retried with credentials and current request processing must stop. + */ + UserAgentClient.prototype.authenticationGuard = function (message) { + var statusCode = message.statusCode; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + // If a 401 (Unauthorized) or 407 (Proxy Authentication Required) + // response is received, the UAC SHOULD follow the authorization + // procedures of Section 22.2 and Section 22.3 to retry the request with + // credentials. + // https://tools.ietf.org/html/rfc3261#section-8.1.3.5 + if (statusCode !== 401 && statusCode !== 407) { + return true; + } + // Get and parse the appropriate WWW-Authenticate or Proxy-Authenticate header. + var challenge; + var authorizationHeaderName; + if (statusCode === 401) { + challenge = message.parseHeader("www-authenticate"); + authorizationHeaderName = "authorization"; + } + else { + challenge = message.parseHeader("proxy-authenticate"); + authorizationHeaderName = "proxy-authorization"; + } + // Verify it seems a valid challenge. + if (!challenge) { + this.logger.warn(statusCode + " with wrong or missing challenge, cannot authenticate"); + return true; + } + // Avoid infinite authentications. + if (this.challenged && (this.stale || challenge.stale !== true)) { + this.logger.warn(statusCode + " apparently in authentication loop, cannot authenticate"); + return true; + } + // Get credentials. + if (!this.credentials) { + this.credentials = this.core.configuration.authenticationFactory(); + if (!this.credentials) { + this.logger.warn("Unable to obtain credentials, cannot authenticate"); + return true; + } + } + // Verify that the challenge is really valid. + if (!this.credentials.authenticate(this.message, challenge)) { + return true; + } + this.challenged = true; + if (challenge.stale) { + this.stale = true; + } + var cseq = this.message.cseq += 1; + this.message.setHeader("cseq", cseq + " " + this.message.method); + this.message.setHeader(authorizationHeaderName, this.credentials.toString()); + // Calling init (again) will swap out our existing client transaction with a new one. + // FIXME: HACK: An assumption is being made here that there is nothing that needs to + // be cleaned up beyond the client transaction which is being replaced. For example, + // it is assumed that no early dialogs have been created. + this.init(); + return false; + }; + /** + * Receive a response from the transaction layer. + * @param message Incoming response message. + */ + UserAgentClient.prototype.receiveResponse = function (message) { + if (!this.authenticationGuard(message)) { + return; + } + var statusCode = message.statusCode ? message.statusCode.toString() : ""; + if (!statusCode) { + throw new Error("Response status code undefined."); + } + switch (true) { + case /^100$/.test(statusCode): + if (this.delegate && this.delegate.onTrying) { + this.delegate.onTrying({ message: message }); + } + break; + case /^1[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onProgress) { + this.delegate.onProgress({ message: message }); + } + break; + case /^2[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onAccept) { + this.delegate.onAccept({ message: message }); + } + break; + case /^3[0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onRedirect) { + this.delegate.onRedirect({ message: message }); + } + break; + case /^[4-6][0-9]{2}$/.test(statusCode): + if (this.delegate && this.delegate.onReject) { + this.delegate.onReject({ message: message }); + } + break; + default: + throw new Error("Invalid status code " + statusCode); + } + }; + UserAgentClient.prototype.init = function () { + var _this = this; + // We are the transaction user. + var user = { + loggerFactory: this.loggerFactory, + onRequestTimeout: function () { return _this.onRequestTimeout(); }, + onStateChange: function (newState) { + if (newState === transactions_1.TransactionState.Terminated) { + // Remove the terminated transaction from the core. + _this.core.userAgentClients.delete(userAgentClientId); + // FIXME: HACK: Our transaction may have been swapped out with a new one + // post authentication (see above), so make sure to only to dispose of + // ourselves if this terminating transaction is our current transaction. + if (transaction === _this._transaction) { + _this.dispose(); + } + } + }, + onTransportError: function (error) { return _this.onTransportError(error); }, + receiveResponse: function (message) { return _this.receiveResponse(message); } + }; + // Create a new transaction with us as the user. + var transaction = new this.transactionConstructor(this.message, this.core.transport, user); + this._transaction = transaction; + // Add the new transaction to the core. + var userAgentClientId = transaction.id + transaction.request.method; + this.core.userAgentClients.set(userAgentClientId, this); + }; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + UserAgentClient.prototype.onRequestTimeout = function () { + this.logger.warn("User agent client request timed out. Generating internal 408 Request Timeout."); + var message = new messages_1.IncomingResponseMessage(); + message.statusCode = 408; + message.reasonPhrase = "Request Timeout"; + this.receiveResponse(message); + return; + }; + /** + * 8.1.3.1 Transaction Layer Errors + * In some cases, the response returned by the transaction layer will + * not be a SIP message, but rather a transaction layer error. When a + * timeout error is received from the transaction layer, it MUST be + * treated as if a 408 (Request Timeout) status code has been received. + * If a fatal transport error is reported by the transport layer + * (generally, due to fatal ICMP errors in UDP or connection failures in + * TCP), the condition MUST be treated as a 503 (Service Unavailable) + * status code. + * https://tools.ietf.org/html/rfc3261#section-8.1.3.1 + */ + UserAgentClient.prototype.onTransportError = function (error) { + this.logger.error(error.message); + this.logger.error("User agent client request transport error. Generating internal 503 Service Unavailable."); + var message = new messages_1.IncomingResponseMessage(); + message.statusCode = 503; + message.reasonPhrase = "Service Unavailable"; + this.receiveResponse(message); + }; + return UserAgentClient; +}()); +exports.UserAgentClient = UserAgentClient; diff --git a/lib/core/user-agents/user-agent-server.d.ts b/lib/core/user-agents/user-agent-server.d.ts new file mode 100644 index 000000000..a893c80fe --- /dev/null +++ b/lib/core/user-agents/user-agent-server.d.ts @@ -0,0 +1,76 @@ +import { Logger, LoggerFactory } from "../log"; +import { IncomingRequest, IncomingRequestDelegate, IncomingRequestMessage, OutgoingResponse, ResponseOptions, URI } from "../messages"; +import { ServerTransaction, ServerTransactionUser } from "../transactions"; +import { Transport } from "../transport"; +import { UserAgentCore } from "../user-agent-core"; +declare type ServerTransactionConstructor = new (message: IncomingRequestMessage, transport: Transport, user: ServerTransactionUser) => ServerTransaction; +/** + * User Agent Server (UAS): A user agent server is a logical entity + * that generates a response to a SIP request. The response + * accepts, rejects, or redirects the request. This role lasts + * only for the duration of that transaction. In other words, if + * a piece of software responds to a request, it acts as a UAS for + * the duration of that transaction. If it generates a request + * later, it assumes the role of a user agent client for the + * processing of that transaction. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +export declare class UserAgentServer implements IncomingRequest { + private transactionConstructor; + protected core: UserAgentCore; + message: IncomingRequestMessage; + delegate?: IncomingRequestDelegate | undefined; + protected logger: Logger; + protected toTag: string; + private _transaction; + constructor(transactionConstructor: ServerTransactionConstructor, core: UserAgentCore, message: IncomingRequestMessage, delegate?: IncomingRequestDelegate | undefined); + dispose(): void; + readonly loggerFactory: LoggerFactory; + /** The transaction associated with this request. */ + readonly transaction: ServerTransaction; + accept(options?: ResponseOptions): OutgoingResponse; + progress(options?: ResponseOptions): OutgoingResponse; + redirect(contacts: Array, options?: ResponseOptions): OutgoingResponse; + reject(options?: ResponseOptions): OutgoingResponse; + trying(options?: ResponseOptions): OutgoingResponse; + /** + * If the UAS did not find a matching transaction for the CANCEL + * according to the procedure above, it SHOULD respond to the CANCEL + * with a 481 (Call Leg/Transaction Does Not Exist). If the transaction + * for the original request still exists, the behavior of the UAS on + * receiving a CANCEL request depends on whether it has already sent a + * final response for the original request. If it has, the CANCEL + * request has no effect on the processing of the original request, no + * effect on any session state, and no effect on the responses generated + * for the original request. If the UAS has not issued a final response + * for the original request, its behavior depends on the method of the + * original request. If the original request was an INVITE, the UAS + * SHOULD immediately respond to the INVITE with a 487 (Request + * Terminated). A CANCEL request has no impact on the processing of + * transactions with any other method defined in this specification. + * https://tools.ietf.org/html/rfc3261#section-9.2 + * @param request Incoming CANCEL request. + */ + receiveCancel(message: IncomingRequestMessage): void; + protected readonly acceptable: boolean; + protected readonly progressable: boolean; + protected readonly redirectable: boolean; + protected readonly rejectable: boolean; + protected readonly tryingable: boolean; + /** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * + * Once all procedures associated with the creation of a response have + * been completed, the UAS hands the response back to the server + * transaction from which it received the request. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + * @param statusCode Status code to reply with. + * @param options Reply options bucket. + */ + private reply; + private init; +} +export {}; diff --git a/lib/core/user-agents/user-agent-server.js b/lib/core/user-agents/user-agent-server.js new file mode 100644 index 000000000..fa3074c73 --- /dev/null +++ b/lib/core/user-agents/user-agent-server.js @@ -0,0 +1,258 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var exceptions_1 = require("../exceptions"); +var messages_1 = require("../messages"); +var utils_1 = require("../messages/utils"); +var transactions_1 = require("../transactions"); +/** + * User Agent Server (UAS): A user agent server is a logical entity + * that generates a response to a SIP request. The response + * accepts, rejects, or redirects the request. This role lasts + * only for the duration of that transaction. In other words, if + * a piece of software responds to a request, it acts as a UAS for + * the duration of that transaction. If it generates a request + * later, it assumes the role of a user agent client for the + * processing of that transaction. + * https://tools.ietf.org/html/rfc3261#section-6 + */ +var UserAgentServer = /** @class */ (function () { + function UserAgentServer(transactionConstructor, core, message, delegate) { + this.transactionConstructor = transactionConstructor; + this.core = core; + this.message = message; + this.delegate = delegate; + this.logger = this.loggerFactory.getLogger("sip.user-agent-server"); + this.toTag = message.toTag ? message.toTag : utils_1.newTag(); + this.init(); + } + UserAgentServer.prototype.dispose = function () { + this.transaction.dispose(); + }; + Object.defineProperty(UserAgentServer.prototype, "loggerFactory", { + get: function () { + return this.core.loggerFactory; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "transaction", { + /** The transaction associated with this request. */ + get: function () { + if (!this._transaction) { + throw new Error("Transaction undefined."); + } + return this._transaction; + }, + enumerable: true, + configurable: true + }); + UserAgentServer.prototype.accept = function (options) { + if (options === void 0) { options = { statusCode: 200 }; } + if (!this.acceptable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not acceptable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 200 || statusCode > 299) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.progress = function (options) { + if (options === void 0) { options = { statusCode: 180 }; } + if (!this.progressable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not progressable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 101 || statusCode > 199) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.redirect = function (contacts, options) { + if (options === void 0) { options = { statusCode: 302 }; } + if (!this.redirectable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not redirectable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 300 || statusCode > 399) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var contactHeaders = new Array(); + contacts.forEach(function (contact) { return contactHeaders.push("Contact: " + contact.toString()); }); + options.extraHeaders = (options.extraHeaders || []).concat(contactHeaders); + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.reject = function (options) { + if (options === void 0) { options = { statusCode: 480 }; } + if (!this.rejectable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not rejectable in state " + this.transaction.state + "."); + } + var statusCode = options.statusCode; + if (statusCode < 400 || statusCode > 699) { + throw new TypeError("Invalid statusCode: " + statusCode); + } + var response = this.reply(options); + return response; + }; + UserAgentServer.prototype.trying = function (options) { + if (!this.tryingable) { + throw new exceptions_1.TransactionStateError(this.message.method + " not tryingable in state " + this.transaction.state + "."); + } + var response = this.reply({ statusCode: 100 }); + return response; + }; + /** + * If the UAS did not find a matching transaction for the CANCEL + * according to the procedure above, it SHOULD respond to the CANCEL + * with a 481 (Call Leg/Transaction Does Not Exist). If the transaction + * for the original request still exists, the behavior of the UAS on + * receiving a CANCEL request depends on whether it has already sent a + * final response for the original request. If it has, the CANCEL + * request has no effect on the processing of the original request, no + * effect on any session state, and no effect on the responses generated + * for the original request. If the UAS has not issued a final response + * for the original request, its behavior depends on the method of the + * original request. If the original request was an INVITE, the UAS + * SHOULD immediately respond to the INVITE with a 487 (Request + * Terminated). A CANCEL request has no impact on the processing of + * transactions with any other method defined in this specification. + * https://tools.ietf.org/html/rfc3261#section-9.2 + * @param request Incoming CANCEL request. + */ + UserAgentServer.prototype.receiveCancel = function (message) { + // Note: Currently CANCEL is being handled as a special case. + // No UAS is created to handle the CANCEL and the response to + // it CANCEL is being handled statelessly by the user agent core. + // As such, there is currently no way to externally impact the + // response to the a CANCEL request. + if (this.delegate && this.delegate.onCancel) { + this.delegate.onCancel(message); + } + }; + Object.defineProperty(UserAgentServer.prototype, "acceptable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Proceeding || + this.transaction.state === transactions_1.TransactionState.Accepted); + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "progressable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return false; // https://tools.ietf.org/html/rfc4320#section-4.1 + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "redirectable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "rejectable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return (this.transaction.state === transactions_1.TransactionState.Trying || + this.transaction.state === transactions_1.TransactionState.Proceeding); + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(UserAgentServer.prototype, "tryingable", { + get: function () { + if (this.transaction instanceof transactions_1.InviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Proceeding; + } + if (this.transaction instanceof transactions_1.NonInviteServerTransaction) { + return this.transaction.state === transactions_1.TransactionState.Trying; + } + throw new Error("Unknown transaction type."); + }, + enumerable: true, + configurable: true + }); + /** + * When a UAS wishes to construct a response to a request, it follows + * the general procedures detailed in the following subsections. + * Additional behaviors specific to the response code in question, which + * are not detailed in this section, may also be required. + * + * Once all procedures associated with the creation of a response have + * been completed, the UAS hands the response back to the server + * transaction from which it received the request. + * https://tools.ietf.org/html/rfc3261#section-8.2.6 + * @param statusCode Status code to reply with. + * @param options Reply options bucket. + */ + UserAgentServer.prototype.reply = function (options) { + if (!options.toTag && options.statusCode !== 100) { + options.toTag = this.toTag; + } + options.userAgent = options.userAgent || this.core.configuration.userAgentHeaderFieldValue; + options.supported = options.supported || this.core.configuration.supportedOptionTagsResponse; + var response = messages_1.constructOutgoingResponse(this.message, options); + this.transaction.receiveResponse(options.statusCode, response.message); + return response; + }; + UserAgentServer.prototype.init = function () { + var _this = this; + // We are the transaction user. + var user = { + loggerFactory: this.loggerFactory, + onStateChange: function (newState) { + if (newState === transactions_1.TransactionState.Terminated) { + // Remove the terminated transaction from the core. + _this.core.userAgentServers.delete(userAgentServerId); + _this.dispose(); + } + }, + onTransportError: function (error) { + _this.logger.error(error.message); + if (_this.delegate && _this.delegate.onTransportError) { + _this.delegate.onTransportError(error); + } + else { + _this.logger.error("User agent server response transport error."); + } + } + }; + // Create a new transaction with us as the user. + var transaction = new this.transactionConstructor(this.message, this.core.transport, user); + this._transaction = transaction; + // Add the new transaction to the core. + var userAgentServerId = transaction.id; + this.core.userAgentServers.set(transaction.id, this); + }; + return UserAgentServer; +}()); +exports.UserAgentServer = UserAgentServer; diff --git a/lib/grammar/dist/grammar.d.ts b/lib/grammar/dist/grammar.d.ts new file mode 100644 index 000000000..eb9f72b05 --- /dev/null +++ b/lib/grammar/dist/grammar.d.ts @@ -0,0 +1,50 @@ +export interface IFilePosition { + offset: number; + line: number; + column: number; +} +export interface IFileRange { + start: IFilePosition; + end: IFilePosition; +} +export interface ILiteralExpectation { + type: "literal"; + text: string; + ignoreCase: boolean; +} +export interface IClassParts extends Array { +} +export interface IClassExpectation { + type: "class"; + parts: IClassParts; + inverted: boolean; + ignoreCase: boolean; +} +export interface IAnyExpectation { + type: "any"; +} +export interface IEndExpectation { + type: "end"; +} +export interface IOtherExpectation { + type: "other"; + description: string; +} +export declare type Expectation = ILiteralExpectation | IClassExpectation | IAnyExpectation | IEndExpectation | IOtherExpectation; +export declare class SyntaxError extends Error { + static buildMessage(expected: Expectation[], found: string | null): string; + message: string; + expected: Expectation[]; + found: string | null; + location: IFileRange; + name: string; + constructor(message: string, expected: Expectation[], found: string | null, location: IFileRange); +} +export interface IParseOptions { + filename?: string; + startRule?: string; + tracer?: any; + [key: string]: any; +} +export declare type ParseFunction = (input: string, options?: IParseOptions) => any; +export declare const parse: ParseFunction; diff --git a/lib/grammar/dist/grammar.js b/lib/grammar/dist/grammar.js new file mode 100644 index 000000000..c951c3c83 --- /dev/null +++ b/lib/grammar/dist/grammar.js @@ -0,0 +1,1508 @@ +"use strict"; +// tslint:disable:interface-name +// tslint:disable: trailing-comma +// tslint:disable: object-literal-sort-keys +// tslint:disable: max-line-length +// tslint:disable: only-arrow-functions +// tslint:disable: one-variable-per-declaration +// tslint:disable: no-consecutive-blank-lines +// tslint:disable: align +// tslint:disable: radix +// tslint:disable: quotemark +// tslint:disable: semicolon +// tslint:disable: object-literal-shorthand +// tslint:disable: variable-name +// tslint:disable: no-var-keyword +// tslint:disable: whitespace +// tslint:disable: curly +// tslint:disable: prefer-const +// tslint:disable: object-literal-key-quotes +// tslint:disable: no-string-literal +// tslint:disable: one-line +// tslint:disable: no-unused-expression +// tslint:disable: space-before-function-paren +// tslint:disable: arrow-return-shorthand +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +// Generated by PEG.js v. 0.10.0 (ts-pegjs plugin v. 0.2.5 ) +// +// https://pegjs.org/ https://github.com/metadevpro/ts-pegjs +var name_addr_header_1 = require("../../core/messages/name-addr-header"); +var uri_1 = require("../../core/messages/uri"); +var SyntaxError = /** @class */ (function (_super) { + tslib_1.__extends(SyntaxError, _super); + function SyntaxError(message, expected, found, location) { + var _this = _super.call(this) || this; + _this.message = message; + _this.expected = expected; + _this.found = found; + _this.location = location; + _this.name = "SyntaxError"; + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(_this, SyntaxError); + } + return _this; + } + SyntaxError.buildMessage = function (expected, found) { + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); + } + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); + } + function describeExpectation(expectation) { + switch (expectation.type) { + case "literal": + return "\"" + literalEscape(expectation.text) + "\""; + case "class": + var escapedParts = expectation.parts.map(function (part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + case "any": + return "any character"; + case "end": + return "end of input"; + case "other": + return expectation.description; + } + } + function describeExpected(expected1) { + var descriptions = expected1.map(describeExpectation); + var i; + var j; + descriptions.sort(); + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + switch (descriptions.length) { + case 1: + return descriptions[0]; + case 2: + return descriptions[0] + " or " + descriptions[1]; + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + function describeFound(found1) { + return found1 ? "\"" + literalEscape(found1) + "\"" : "end of input"; + } + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; + }; + return SyntaxError; +}(Error)); +exports.SyntaxError = SyntaxError; +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + var peg$FAILED = {}; + var peg$startRuleIndices = { Contact: 119, Name_Addr_Header: 156, Record_Route: 176, Request_Response: 81, SIP_URI: 45, Subscription_State: 186, Supported: 191, Require: 182, Via: 194, absoluteURI: 84, Call_ID: 118, Content_Disposition: 130, Content_Length: 135, Content_Type: 136, CSeq: 146, displayName: 122, Event: 149, From: 151, host: 52, Max_Forwards: 154, Min_SE: 213, Proxy_Authenticate: 157, quoted_string: 40, Refer_To: 178, Replaces: 179, Session_Expires: 210, stun_URI: 217, To: 192, turn_URI: 223, uuid: 226, WWW_Authenticate: 209, challenge: 158, sipfrag: 230, Referred_By: 231 }; + var peg$startRuleIndex = 119; + var peg$consts = [ + "\r\n", + peg$literalExpectation("\r\n", false), + /^[0-9]/, + peg$classExpectation([["0", "9"]], false, false), + /^[a-zA-Z]/, + peg$classExpectation([["a", "z"], ["A", "Z"]], false, false), + /^[0-9a-fA-F]/, + peg$classExpectation([["0", "9"], ["a", "f"], ["A", "F"]], false, false), + /^[\0-\xFF]/, + peg$classExpectation([["\0", "\xFF"]], false, false), + /^["]/, + peg$classExpectation(["\""], false, false), + " ", + peg$literalExpectation(" ", false), + "\t", + peg$literalExpectation("\t", false), + /^[a-zA-Z0-9]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false), + ";", + peg$literalExpectation(";", false), + "/", + peg$literalExpectation("/", false), + "?", + peg$literalExpectation("?", false), + ":", + peg$literalExpectation(":", false), + "@", + peg$literalExpectation("@", false), + "&", + peg$literalExpectation("&", false), + "=", + peg$literalExpectation("=", false), + "+", + peg$literalExpectation("+", false), + "$", + peg$literalExpectation("$", false), + ",", + peg$literalExpectation(",", false), + "-", + peg$literalExpectation("-", false), + "_", + peg$literalExpectation("_", false), + ".", + peg$literalExpectation(".", false), + "!", + peg$literalExpectation("!", false), + "~", + peg$literalExpectation("~", false), + "*", + peg$literalExpectation("*", false), + "'", + peg$literalExpectation("'", false), + "(", + peg$literalExpectation("(", false), + ")", + peg$literalExpectation(")", false), + "%", + peg$literalExpectation("%", false), + function () { return " "; }, + function () { return ':'; }, + /^[!-~]/, + peg$classExpectation([["!", "~"]], false, false), + /^[\x80-\uFFFF]/, + peg$classExpectation([["\x80", "\uFFFF"]], false, false), + /^[\x80-\xBF]/, + peg$classExpectation([["\x80", "\xBF"]], false, false), + /^[a-f]/, + peg$classExpectation([["a", "f"]], false, false), + "`", + peg$literalExpectation("`", false), + "<", + peg$literalExpectation("<", false), + ">", + peg$literalExpectation(">", false), + "\\", + peg$literalExpectation("\\", false), + "[", + peg$literalExpectation("[", false), + "]", + peg$literalExpectation("]", false), + "{", + peg$literalExpectation("{", false), + "}", + peg$literalExpectation("}", false), + function () { return "*"; }, + function () { return "/"; }, + function () { return "="; }, + function () { return "("; }, + function () { return ")"; }, + function () { return ">"; }, + function () { return "<"; }, + function () { return ","; }, + function () { return ";"; }, + function () { return ":"; }, + function () { return "\""; }, + /^[!-']/, + peg$classExpectation([["!", "'"]], false, false), + /^[*-[]/, + peg$classExpectation([["*", "["]], false, false), + /^[\]-~]/, + peg$classExpectation([["]", "~"]], false, false), + function (contents) { + return contents; + }, + /^[#-[]/, + peg$classExpectation([["#", "["]], false, false), + /^[\0-\t]/, + peg$classExpectation([["\0", "\t"]], false, false), + /^[\x0B-\f]/, + peg$classExpectation([["\x0B", "\f"]], false, false), + /^[\x0E-\x7F]/, + peg$classExpectation([["\x0E", "\x7F"]], false, false), + function () { + options = options || { data: {} }; + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + }, + function () { + options = options || { data: {} }; + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + delete options.data.uri_params; + if (options.startRule === 'SIP_URI') { + options.data = options.data.uri; + } + }, + "sips", + peg$literalExpectation("sips", true), + "sip", + peg$literalExpectation("sip", true), + function (uri_scheme) { + options = options || { data: {} }; + options.data.scheme = uri_scheme; + }, + function () { + options = options || { data: {} }; + options.data.user = decodeURIComponent(text().slice(0, -1)); + }, + function () { + options = options || { data: {} }; + options.data.password = text(); + }, + function () { + options = options || { data: {} }; + options.data.host = text(); + return options.data.host; + }, + function () { + options = options || { data: {} }; + options.data.host_type = 'domain'; + return text(); + }, + /^[a-zA-Z0-9_\-]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false), + /^[a-zA-Z0-9\-]/, + peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "-"], false, false), + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv6'; + return text(); + }, + "::", + peg$literalExpectation("::", false), + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv6'; + return text(); + }, + function () { + options = options || { data: {} }; + options.data.host_type = 'IPv4'; + return text(); + }, + "25", + peg$literalExpectation("25", false), + /^[0-5]/, + peg$classExpectation([["0", "5"]], false, false), + "2", + peg$literalExpectation("2", false), + /^[0-4]/, + peg$classExpectation([["0", "4"]], false, false), + "1", + peg$literalExpectation("1", false), + /^[1-9]/, + peg$classExpectation([["1", "9"]], false, false), + function (port) { + options = options || { data: {} }; + port = parseInt(port.join('')); + options.data.port = port; + return port; + }, + "transport=", + peg$literalExpectation("transport=", true), + "udp", + peg$literalExpectation("udp", true), + "tcp", + peg$literalExpectation("tcp", true), + "sctp", + peg$literalExpectation("sctp", true), + "tls", + peg$literalExpectation("tls", true), + function (transport) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['transport'] = transport.toLowerCase(); + }, + "user=", + peg$literalExpectation("user=", true), + "phone", + peg$literalExpectation("phone", true), + "ip", + peg$literalExpectation("ip", true), + function (user) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['user'] = user.toLowerCase(); + }, + "method=", + peg$literalExpectation("method=", true), + function (method) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['method'] = method; + }, + "ttl=", + peg$literalExpectation("ttl=", true), + function (ttl) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['ttl'] = ttl; + }, + "maddr=", + peg$literalExpectation("maddr=", true), + function (maddr) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['maddr'] = maddr; + }, + "lr", + peg$literalExpectation("lr", true), + function () { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + options.data.uri_params['lr'] = undefined; + }, + function (param, value) { + options = options || { data: {} }; + if (!options.data.uri_params) + options.data.uri_params = {}; + if (value === null) { + value = undefined; + } + else { + value = value[1]; + } + options.data.uri_params[param.toLowerCase()] = value; + }, + function (hname, hvalue) { + hname = hname.join('').toLowerCase(); + hvalue = hvalue.join(''); + options = options || { data: {} }; + if (!options.data.uri_headers) + options.data.uri_headers = {}; + if (!options.data.uri_headers[hname]) { + options.data.uri_headers[hname] = [hvalue]; + } + else { + options.data.uri_headers[hname].push(hvalue); + } + }, + function () { + options = options || { data: {} }; + // lots of tests fail if this isn't guarded... + if (options.startRule === 'Refer_To') { + options.data.uri = new uri_1.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers); + delete options.data.scheme; + delete options.data.user; + delete options.data.host; + delete options.data.host_type; + delete options.data.port; + delete options.data.uri_params; + } + }, + "//", + peg$literalExpectation("//", false), + function () { + options = options || { data: {} }; + options.data.scheme = text(); + }, + peg$literalExpectation("SIP", true), + function () { + options = options || { data: {} }; + options.data.sip_version = text(); + }, + "INVITE", + peg$literalExpectation("INVITE", false), + "ACK", + peg$literalExpectation("ACK", false), + "VXACH", + peg$literalExpectation("VXACH", false), + "OPTIONS", + peg$literalExpectation("OPTIONS", false), + "BYE", + peg$literalExpectation("BYE", false), + "CANCEL", + peg$literalExpectation("CANCEL", false), + "REGISTER", + peg$literalExpectation("REGISTER", false), + "SUBSCRIBE", + peg$literalExpectation("SUBSCRIBE", false), + "NOTIFY", + peg$literalExpectation("NOTIFY", false), + "REFER", + peg$literalExpectation("REFER", false), + "PUBLISH", + peg$literalExpectation("PUBLISH", false), + function () { + options = options || { data: {} }; + options.data.method = text(); + return options.data.method; + }, + function (status_code) { + options = options || { data: {} }; + options.data.status_code = parseInt(status_code.join('')); + }, + function () { + options = options || { data: {} }; + options.data.reason_phrase = text(); + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + function () { + var idx, length; + options = options || { data: {} }; + length = options.data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (options.data.multi_header[idx].parsed === null) { + options.data = null; + break; + } + } + if (options.data !== null) { + options.data = options.data.multi_header; + } + else { + options.data = -1; + } + }, + function () { + var header; + options = options || { data: {} }; + if (!options.data.multi_header) + options.data.multi_header = []; + try { + header = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + delete options.data.uri; + delete options.data.displayName; + delete options.data.params; + } + catch (e) { + header = null; + } + options.data.multi_header.push({ 'position': peg$currPos, + 'offset': location().start.offset, + 'parsed': header + }); + }, + function (displayName) { + displayName = text().trim(); + if (displayName[0] === '\"') { + displayName = displayName.substring(1, displayName.length - 1); + } + options = options || { data: {} }; + options.data.displayName = displayName; + }, + "q", + peg$literalExpectation("q", true), + function (q) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['q'] = q; + }, + "expires", + peg$literalExpectation("expires", true), + function (expires) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + options.data.params['expires'] = expires; + }, + function (delta_seconds) { + return parseInt(delta_seconds.join('')); + }, + "0", + peg$literalExpectation("0", false), + function () { + return parseFloat(text()); + }, + function (param, value) { + options = options || { data: {} }; + if (!options.data.params) + options.data.params = {}; + if (value === null) { + value = undefined; + } + else { + value = value[1]; + } + options.data.params[param.toLowerCase()] = value; + }, + "render", + peg$literalExpectation("render", true), + "session", + peg$literalExpectation("session", true), + "icon", + peg$literalExpectation("icon", true), + "alert", + peg$literalExpectation("alert", true), + function () { + options = options || { data: {} }; + if (options.startRule === 'Content_Disposition') { + options.data.type = text().toLowerCase(); + } + }, + "handling", + peg$literalExpectation("handling", true), + "optional", + peg$literalExpectation("optional", true), + "required", + peg$literalExpectation("required", true), + function (length) { + options = options || { data: {} }; + options.data = parseInt(length.join('')); + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + "text", + peg$literalExpectation("text", true), + "image", + peg$literalExpectation("image", true), + "audio", + peg$literalExpectation("audio", true), + "video", + peg$literalExpectation("video", true), + "application", + peg$literalExpectation("application", true), + "message", + peg$literalExpectation("message", true), + "multipart", + peg$literalExpectation("multipart", true), + "x-", + peg$literalExpectation("x-", true), + function (cseq_value) { + options = options || { data: {} }; + options.data.value = parseInt(cseq_value.join('')); + }, + function (expires) { options = options || { data: {} }; options.data = expires; }, + function (event_type) { + options = options || { data: {} }; + options.data.event = event_type.toLowerCase(); + }, + function () { + options = options || { data: {} }; + var tag = options.data.tag; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + if (tag) { + options.data.setParam('tag', tag); + } + }, + "tag", + peg$literalExpectation("tag", true), + function (tag) { options = options || { data: {} }; options.data.tag = tag; }, + function (forwards) { + options = options || { data: {} }; + options.data = parseInt(forwards.join('')); + }, + function (min_expires) { options = options || { data: {} }; options.data = min_expires; }, + function () { + options = options || { data: {} }; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + }, + "digest", + peg$literalExpectation("Digest", true), + "realm", + peg$literalExpectation("realm", true), + function (realm) { options = options || { data: {} }; options.data.realm = realm; }, + "domain", + peg$literalExpectation("domain", true), + "nonce", + peg$literalExpectation("nonce", true), + function (nonce) { options = options || { data: {} }; options.data.nonce = nonce; }, + "opaque", + peg$literalExpectation("opaque", true), + function (opaque) { options = options || { data: {} }; options.data.opaque = opaque; }, + "stale", + peg$literalExpectation("stale", true), + "true", + peg$literalExpectation("true", true), + function () { options = options || { data: {} }; options.data.stale = true; }, + "false", + peg$literalExpectation("false", true), + function () { options = options || { data: {} }; options.data.stale = false; }, + "algorithm", + peg$literalExpectation("algorithm", true), + "md5", + peg$literalExpectation("MD5", true), + "md5-sess", + peg$literalExpectation("MD5-sess", true), + function (algorithm) { + options = options || { data: {} }; + options.data.algorithm = algorithm.toUpperCase(); + }, + "qop", + peg$literalExpectation("qop", true), + "auth-int", + peg$literalExpectation("auth-int", true), + "auth", + peg$literalExpectation("auth", true), + function (qop_value) { + options = options || { data: {} }; + options.data.qop || (options.data.qop = []); + options.data.qop.push(qop_value.toLowerCase()); + }, + function (rack_value) { + options = options || { data: {} }; + options.data.value = parseInt(rack_value.join('')); + }, + function () { + var idx, length; + options = options || { data: {} }; + length = options.data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (options.data.multi_header[idx].parsed === null) { + options.data = null; + break; + } + } + if (options.data !== null) { + options.data = options.data.multi_header; + } + else { + options.data = -1; + } + }, + function () { + var header; + options = options || { data: {} }; + if (!options.data.multi_header) + options.data.multi_header = []; + try { + header = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + delete options.data.uri; + delete options.data.displayName; + delete options.data.params; + } + catch (e) { + header = null; + } + options.data.multi_header.push({ 'position': peg$currPos, + 'offset': location().start.offset, + 'parsed': header + }); + }, + function () { + options = options || { data: {} }; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + }, + function () { + options = options || { data: {} }; + if (!(options.data.replaces_from_tag && options.data.replaces_to_tag)) { + options.data = -1; + } + }, + function () { + options = options || { data: {} }; + options.data = { + call_id: options.data + }; + }, + "from-tag", + peg$literalExpectation("from-tag", true), + function (from_tag) { + options = options || { data: {} }; + options.data.replaces_from_tag = from_tag; + }, + "to-tag", + peg$literalExpectation("to-tag", true), + function (to_tag) { + options = options || { data: {} }; + options.data.replaces_to_tag = to_tag; + }, + "early-only", + peg$literalExpectation("early-only", true), + function () { + options = options || { data: {} }; + options.data.early_only = true; + }, + function (head, r) { return r; }, + function (head, tail) { return list(head, tail); }, + function (value) { + options = options || { data: {} }; + if (options.startRule === 'Require') { + options.data = value || []; + } + }, + function (rseq_value) { + options = options || { data: {} }; + options.data.value = parseInt(rseq_value.join('')); + }, + "active", + peg$literalExpectation("active", true), + "pending", + peg$literalExpectation("pending", true), + "terminated", + peg$literalExpectation("terminated", true), + function () { + options = options || { data: {} }; + options.data.state = text(); + }, + "reason", + peg$literalExpectation("reason", true), + function (reason) { + options = options || { data: {} }; + if (typeof reason !== 'undefined') + options.data.reason = reason; + }, + function (expires) { + options = options || { data: {} }; + if (typeof expires !== 'undefined') + options.data.expires = expires; + }, + "retry_after", + peg$literalExpectation("retry_after", true), + function (retry_after) { + options = options || { data: {} }; + if (typeof retry_after !== 'undefined') + options.data.retry_after = retry_after; + }, + "deactivated", + peg$literalExpectation("deactivated", true), + "probation", + peg$literalExpectation("probation", true), + "rejected", + peg$literalExpectation("rejected", true), + "timeout", + peg$literalExpectation("timeout", true), + "giveup", + peg$literalExpectation("giveup", true), + "noresource", + peg$literalExpectation("noresource", true), + "invariant", + peg$literalExpectation("invariant", true), + function (value) { + options = options || { data: {} }; + if (options.startRule === 'Supported') { + options.data = value || []; + } + }, + function () { + options = options || { data: {} }; + var tag = options.data.tag; + options.data = new name_addr_header_1.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params); + if (tag) { + options.data.setParam('tag', tag); + } + }, + "ttl", + peg$literalExpectation("ttl", true), + function (via_ttl_value) { + options = options || { data: {} }; + options.data.ttl = via_ttl_value; + }, + "maddr", + peg$literalExpectation("maddr", true), + function (via_maddr) { + options = options || { data: {} }; + options.data.maddr = via_maddr; + }, + "received", + peg$literalExpectation("received", true), + function (via_received) { + options = options || { data: {} }; + options.data.received = via_received; + }, + "branch", + peg$literalExpectation("branch", true), + function (via_branch) { + options = options || { data: {} }; + options.data.branch = via_branch; + }, + "rport", + peg$literalExpectation("rport", true), + function (response_port) { + options = options || { data: {} }; + if (typeof response_port !== 'undefined') + options.data.rport = response_port.join(''); + }, + function (via_protocol) { + options = options || { data: {} }; + options.data.protocol = via_protocol; + }, + peg$literalExpectation("UDP", true), + peg$literalExpectation("TCP", true), + peg$literalExpectation("TLS", true), + peg$literalExpectation("SCTP", true), + function (via_transport) { + options = options || { data: {} }; + options.data.transport = via_transport; + }, + function () { + options = options || { data: {} }; + options.data.host = text(); + }, + function (via_sent_by_port) { + options = options || { data: {} }; + options.data.port = parseInt(via_sent_by_port.join('')); + }, + function (ttl) { + return parseInt(ttl.join('')); + }, + function (deltaSeconds) { + options = options || { data: {} }; + if (options.startRule === 'Session_Expires') { + options.data.deltaSeconds = deltaSeconds; + } + }, + "refresher", + peg$literalExpectation("refresher", false), + "uas", + peg$literalExpectation("uas", false), + "uac", + peg$literalExpectation("uac", false), + function (endpoint) { + options = options || { data: {} }; + if (options.startRule === 'Session_Expires') { + options.data.refresher = endpoint; + } + }, + function (deltaSeconds) { + options = options || { data: {} }; + if (options.startRule === 'Min_SE') { + options.data = deltaSeconds; + } + }, + "stuns", + peg$literalExpectation("stuns", true), + "stun", + peg$literalExpectation("stun", true), + function (scheme) { + options = options || { data: {} }; + options.data.scheme = scheme; + }, + function (host) { + options = options || { data: {} }; + options.data.host = host; + }, + "?transport=", + peg$literalExpectation("?transport=", false), + "turns", + peg$literalExpectation("turns", true), + "turn", + peg$literalExpectation("turn", true), + function (transport) { + options = options || { data: {} }; + options.data.transport = transport; + }, + function () { + options = options || { data: {} }; + options.data = text(); + }, + "Referred-By", + peg$literalExpectation("Referred-By", false), + "b", + peg$literalExpectation("b", false), + "cid", + peg$literalExpectation("cid", false) + ]; + var peg$bytecode = [ + peg$decode("2 \"\"6 7!"), + peg$decode("4\"\"\"5!7#"), + peg$decode("4$\"\"5!7%"), + peg$decode("4&\"\"5!7'"), + peg$decode(";'.# &;("), + peg$decode("4(\"\"5!7)"), + peg$decode("4*\"\"5!7+"), + peg$decode("2,\"\"6,7-"), + peg$decode("2.\"\"6.7/"), + peg$decode("40\"\"5!71"), + peg$decode("22\"\"6273.\x89 &24\"\"6475.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode(";).# &;,"), + peg$decode("2F\"\"6F7G.} &2H\"\"6H7I.q &2J\"\"6J7K.e &2L\"\"6L7M.Y &2N\"\"6N7O.M &2P\"\"6P7Q.A &2R\"\"6R7S.5 &2T\"\"6T7U.) &2V\"\"6V7W"), + peg$decode("%%2X\"\"6X7Y/5#;#/,$;#/#$+#)(#'#(\"'#&'#/\"!&,)"), + peg$decode("%%$;$0#*;$&/,#; /#$+\")(\"'#&'#.\" &\"/=#$;$/�#*;$&&&#/'$8\":Z\" )(\"'#&'#"), + peg$decode(";..\" &\""), + peg$decode("%$;'.# &;(0)*;'.# &;(&/?#28\"\"6879/0$;//'$8#:[# )(#'#(\"'#&'#"), + peg$decode("%%$;2/�#*;2&&&#/g#$%$;.0#*;.&/,#;2/#$+\")(\"'#&'#0=*%$;.0#*;.&/,#;2/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/\"!&,)"), + peg$decode("4\\\"\"5!7].# &;3"), + peg$decode("4^\"\"5!7_"), + peg$decode("4`\"\"5!7a"), + peg$decode(";!.) &4b\"\"5!7c"), + peg$decode("%$;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x9E#0\x9B*;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"), + peg$decode("%$;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x92#0\x8F*;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"), + peg$decode("2T\"\"6T7U.\xE3 &2V\"\"6V7W.\xD7 &2f\"\"6f7g.\xCB &2h\"\"6h7i.\xBF &2:\"\"6:7;.\xB3 &2D\"\"6D7E.\xA7 &22\"\"6273.\x9B &28\"\"6879.\x8F &2j\"\"6j7k.\x83 &;&.} &24\"\"6475.q &2l\"\"6l7m.e &2n\"\"6n7o.Y &26\"\"6677.M &2>\"\"6>7?.A &2p\"\"6p7q.5 &2r\"\"6r7s.) &;'.# &;("), + peg$decode("%$;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s/\u0134#0\u0131*;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s&&&#/\"!&,)"), + peg$decode("%;//?#2P\"\"6P7Q/0$;//'$8#:t# )(#'#(\"'#&'#"), + peg$decode("%;//?#24\"\"6475/0$;//'$8#:u# )(#'#(\"'#&'#"), + peg$decode("%;//?#2>\"\"6>7?/0$;//'$8#:v# )(#'#(\"'#&'#"), + peg$decode("%;//?#2T\"\"6T7U/0$;//'$8#:w# )(#'#(\"'#&'#"), + peg$decode("%;//?#2V\"\"6V7W/0$;//'$8#:x# )(#'#(\"'#&'#"), + peg$decode("%2h\"\"6h7i/0#;//'$8\":y\" )(\"'#&'#"), + peg$decode("%;//6#2f\"\"6f7g/'$8\":z\" )(\"'#&'#"), + peg$decode("%;//?#2D\"\"6D7E/0$;//'$8#:{# )(#'#(\"'#&'#"), + peg$decode("%;//?#22\"\"6273/0$;//'$8#:|# )(#'#(\"'#&'#"), + peg$decode("%;//?#28\"\"6879/0$;//'$8#:}# )(#'#(\"'#&'#"), + peg$decode("%;//0#;&/'$8\":~\" )(\"'#&'#"), + peg$decode("%;&/0#;//'$8\":~\" )(\"'#&'#"), + peg$decode("%;=/T#$;G.) &;K.# &;F0/*;G.) &;K.# &;F&/,$;>/#$+#)(#'#(\"'#&'#"), + peg$decode("4\x7F\"\"5!7\x80.A &4\x81\"\"5!7\x82.5 &4\x83\"\"5!7\x84.) &;3.# &;."), + peg$decode("%%;//Q#;&/H$$;J.# &;K0)*;J.# &;K&/,$;&/#$+$)($'#(#'#(\"'#&'#/\"!&,)"), + peg$decode("%;//]#;&/T$%$;J.# &;K0)*;J.# &;K&/\"!&,)/1$;&/($8$:\x85$!!)($'#(#'#(\"'#&'#"), + peg$decode(";..G &2L\"\"6L7M.; &4\x86\"\"5!7\x87./ &4\x83\"\"5!7\x84.# &;3"), + peg$decode("%2j\"\"6j7k/J#4\x88\"\"5!7\x89.5 &4\x8A\"\"5!7\x8B.) &4\x8C\"\"5!7\x8D/#$+\")(\"'#&'#"), + peg$decode("%;N/M#28\"\"6879/>$;O.\" &\"/0$;S/'$8$:\x8E$ )($'#(#'#(\"'#&'#"), + peg$decode("%;N/d#28\"\"6879/U$;O.\" &\"/G$;S/>$;_/5$;l.\" &\"/'$8&:\x8F& )(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%3\x90\"\"5$7\x91.) &3\x92\"\"5#7\x93/' 8!:\x94!! )"), + peg$decode("%;P/]#%28\"\"6879/,#;R/#$+\")(\"'#&'#.\" &\"/6$2:\"\"6:7;/'$8#:\x95# )(#'#(\"'#&'#"), + peg$decode("$;+.) &;-.# &;Q/2#0/*;+.) &;-.# &;Q&&&#"), + peg$decode("2<\"\"6<7=.q &2>\"\"6>7?.e &2@\"\"6@7A.Y &2B\"\"6B7C.M &2D\"\"6D7E.A &22\"\"6273.5 &26\"\"6677.) &24\"\"6475"), + peg$decode("%$;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E0e*;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E&/& 8!:\x96! )"), + peg$decode("%;T/J#%28\"\"6879/,#;^/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%;U.) &;\\.# &;X/& 8!:\x97! )"), + peg$decode("%$%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#0<*%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#&/D#;W/;$2J\"\"6J7K.\" &\"/'$8#:\x98# )(#'#(\"'#&'#"), + peg$decode("$4\x99\"\"5!7\x9A/,#0)*4\x99\"\"5!7\x9A&&&#"), + peg$decode("%4$\"\"5!7%/?#$4\x9B\"\"5!7\x9C0)*4\x9B\"\"5!7\x9C&/#$+\")(\"'#&'#"), + peg$decode("%2l\"\"6l7m/?#;Y/6$2n\"\"6n7o/'$8#:\x9D# )(#'#(\"'#&'#"), + peg$decode("%%;Z/\xB3#28\"\"6879/\xA4$;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+-)(-'#(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0790 &%2\x9E\"\"6\x9E7\x9F/\xA4#;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+,)(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u06F9 &%2\x9E\"\"6\x9E7\x9F/\x8C#;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u067A &%2\x9E\"\"6\x9E7\x9F/t#;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0613 &%2\x9E\"\"6\x9E7\x9F/\\#;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+&)(&'#(%'#($'#(#'#(\"'#&'#.\u05C4 &%2\x9E\"\"6\x9E7\x9F/D#;Z/;$28\"\"6879/,$;[/#$+$)($'#(#'#(\"'#&'#.\u058D &%2\x9E\"\"6\x9E7\x9F/,#;[/#$+\")(\"'#&'#.\u056E &%2\x9E\"\"6\x9E7\x9F/,#;Z/#$+\")(\"'#&'#.\u054F &%;Z/\x9B#2\x9E\"\"6\x9E7\x9F/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$++)(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u04C7 &%;Z/\xAA#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x83$2\x9E\"\"6\x9E7\x9F/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0430 &%;Z/\xB9#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x92$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/k$2\x9E\"\"6\x9E7\x9F/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+))()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u038A &%;Z/\xC8#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA1$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/z$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/S$2\x9E\"\"6\x9E7\x9F/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u02D5 &%;Z/\xD7#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;[/#$+')(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0211 &%;Z/\xFE#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xD7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;Z/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0126 &%;Z/\u011C#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xF5$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xCE$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x80$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/Y$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/2$2\x9E\"\"6\x9E7\x9F/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#/& 8!:\xA0! )"), + peg$decode("%;#/M#;#.\" &\"/?$;#.\" &\"/1$;#.\" &\"/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%;Z/;#28\"\"6879/,$;Z/#$+#)(#'#(\"'#&'#.# &;\\"), + peg$decode("%;]/o#2J\"\"6J7K/`$;]/W$2J\"\"6J7K/H$;]/?$2J\"\"6J7K/0$;]/'$8':\xA1' )(''#(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%2\xA2\"\"6\xA27\xA3/2#4\xA4\"\"5!7\xA5/#$+\")(\"'#&'#.\x98 &%2\xA6\"\"6\xA67\xA7/;#4\xA8\"\"5!7\xA9/,$;!/#$+#)(#'#(\"'#&'#.j &%2\xAA\"\"6\xAA7\xAB/5#;!/,$;!/#$+#)(#'#(\"'#&'#.B &%4\xAC\"\"5!7\xAD/,#;!/#$+\")(\"'#&'#.# &;!"), + peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\xAE!! )"), + peg$decode("$%22\"\"6273/,#;`/#$+\")(\"'#&'#0<*%22\"\"6273/,#;`/#$+\")(\"'#&'#&"), + peg$decode(";a.A &;b.; &;c.5 &;d./ &;e.) &;f.# &;g"), + peg$decode("%3\xAF\"\"5*7\xB0/a#3\xB1\"\"5#7\xB2.G &3\xB3\"\"5#7\xB4.; &3\xB5\"\"5$7\xB6./ &3\xB7\"\"5#7\xB8.# &;6/($8\":\xB9\"! )(\"'#&'#"), + peg$decode("%3\xBA\"\"5%7\xBB/I#3\xBC\"\"5%7\xBD./ &3\xBE\"\"5\"7\xBF.# &;6/($8\":\xC0\"! )(\"'#&'#"), + peg$decode("%3\xC1\"\"5'7\xC2/1#;\x90/($8\":\xC3\"! )(\"'#&'#"), + peg$decode("%3\xC4\"\"5$7\xC5/1#;\xF0/($8\":\xC6\"! )(\"'#&'#"), + peg$decode("%3\xC7\"\"5&7\xC8/1#;T/($8\":\xC9\"! )(\"'#&'#"), + peg$decode("%3\xCA\"\"5\"7\xCB/N#%2>\"\"6>7?/,#;6/#$+\")(\"'#&'#.\" &\"/'$8\":\xCC\" )(\"'#&'#"), + peg$decode("%;h/P#%2>\"\"6>7?/,#;i/#$+\")(\"'#&'#.\" &\"/)$8\":\xCD\"\"! )(\"'#&'#"), + peg$decode("%$;j/�#*;j&&&#/\"!&,)"), + peg$decode("%$;j/�#*;j&&&#/\"!&,)"), + peg$decode(";k.) &;+.# &;-"), + peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &28\"\"6879.A &2<\"\"6<7=.5 &2@\"\"6@7A.) &2B\"\"6B7C"), + peg$decode("%26\"\"6677/n#;m/e$$%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#0<*%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#&/#$+#)(#'#(\"'#&'#"), + peg$decode("%;n/A#2>\"\"6>7?/2$;o/)$8#:\xCE#\"\" )(#'#(\"'#&'#"), + peg$decode("$;p.) &;+.# &;-/2#0/*;p.) &;+.# &;-&&&#"), + peg$decode("$;p.) &;+.# &;-0/*;p.) &;+.# &;-&"), + peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &26\"\"6677.A &28\"\"6879.5 &2@\"\"6@7A.) &2B\"\"6B7C"), + peg$decode(";\x91.# &;r"), + peg$decode("%;\x90/G#;'/>$;s/5$;'/,$;\x84/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode(";M.# &;t"), + peg$decode("%;\x7F/E#28\"\"6879/6$;u.# &;x/'$8#:\xCF# )(#'#(\"'#&'#"), + peg$decode("%;v.# &;w/J#%26\"\"6677/,#;\x83/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%2\xD0\"\"6\xD07\xD1/:#;\x80/1$;w.\" &\"/#$+#)(#'#(\"'#&'#"), + peg$decode("%24\"\"6475/,#;{/#$+\")(\"'#&'#"), + peg$decode("%;z/3#$;y0#*;y&/#$+\")(\"'#&'#"), + peg$decode(";*.) &;+.# &;-"), + peg$decode(";+.\x8F &;-.\x89 &22\"\"6273.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode("%;|/e#$%24\"\"6475/,#;|/#$+\")(\"'#&'#0<*%24\"\"6475/,#;|/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("%$;~0#*;~&/e#$%22\"\"6273/,#;}/#$+\")(\"'#&'#0<*%22\"\"6273/,#;}/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("$;~0#*;~&"), + peg$decode(";+.w &;-.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"), + peg$decode("%%;\"/\x87#$;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K0M*;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K&/#$+\")(\"'#&'#/& 8!:\xD2! )"), + peg$decode(";\x81.# &;\x82"), + peg$decode("%%;O/2#2:\"\"6:7;/#$+\")(\"'#&'#.\" &\"/,#;S/#$+\")(\"'#&'#.\" &\""), + peg$decode("$;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A/\x8C#0\x89*;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A&&&#"), + peg$decode("$;y0#*;y&"), + peg$decode("%3\x92\"\"5#7\xD3/q#24\"\"6475/b$$;!/�#*;!&&&#/L$2J\"\"6J7K/=$$;!/�#*;!&&&#/'$8%:\xD4% )(%'#($'#(#'#(\"'#&'#"), + peg$decode("2\xD5\"\"6\xD57\xD6"), + peg$decode("2\xD7\"\"6\xD77\xD8"), + peg$decode("2\xD9\"\"6\xD97\xDA"), + peg$decode("2\xDB\"\"6\xDB7\xDC"), + peg$decode("2\xDD\"\"6\xDD7\xDE"), + peg$decode("2\xDF\"\"6\xDF7\xE0"), + peg$decode("2\xE1\"\"6\xE17\xE2"), + peg$decode("2\xE3\"\"6\xE37\xE4"), + peg$decode("2\xE5\"\"6\xE57\xE6"), + peg$decode("2\xE7\"\"6\xE77\xE8"), + peg$decode("2\xE9\"\"6\xE97\xEA"), + peg$decode("%;\x85.Y &;\x86.S &;\x88.M &;\x89.G &;\x8A.A &;\x8B.; &;\x8C.5 &;\x8F./ &;\x8D.) &;\x8E.# &;6/& 8!:\xEB! )"), + peg$decode("%;\x84/G#;'/>$;\x92/5$;'/,$;\x94/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%;\x93/' 8!:\xEC!! )"), + peg$decode("%;!/5#;!/,$;!/#$+#)(#'#(\"'#&'#"), + peg$decode("%$;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(0G*;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(&/& 8!:\xED! )"), + peg$decode("%;\xB6/Y#$%;A/,#;\xB6/#$+\")(\"'#&'#06*%;A/,#;\xB6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"), + peg$decode("%;9/N#%2:\"\"6:7;/,#;9/#$+\")(\"'#&'#.\" &\"/'$8\":\xEE\" )(\"'#&'#"), + peg$decode("%;:.c &%;\x98/Y#$%;A/,#;\x98/#$+\")(\"'#&'#06*%;A/,#;\x98/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/& 8!:\xEF! )"), + peg$decode("%;L.# &;\x99/]#$%;B/,#;\x9B/#$+\")(\"'#&'#06*%;B/,#;\x9B/#$+\")(\"'#&'#&/'$8\":\xF0\" )(\"'#&'#"), + peg$decode("%;\x9A.\" &\"/>#;@/5$;M/,$;?/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%%;6/Y#$%;./,#;6/#$+\")(\"'#&'#06*%;./,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#.# &;H/' 8!:\xF1!! )"), + peg$decode(";\x9C.) &;\x9D.# &;\xA0"), + peg$decode("%3\xF2\"\"5!7\xF3/:#;$;\xCF/5$;./,$;\x90/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%$;!/�#*;!&&&#/' 8!:\u014B!! )"), + peg$decode("%;\xD1/]#$%;A/,#;\xD1/#$+\")(\"'#&'#06*%;A/,#;\xD1/#$+\")(\"'#&'#&/'$8\":\u014C\" )(\"'#&'#"), + peg$decode("%;\x99/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014D\" )(\"'#&'#"), + peg$decode("%;L.O &;\x99.I &%;@.\" &\"/:#;t/1$;?.\" &\"/#$+#)(#'#(\"'#&'#/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014E\" )(\"'#&'#"), + peg$decode("%;\xD4/]#$%;B/,#;\xD5/#$+\")(\"'#&'#06*%;B/,#;\xD5/#$+\")(\"'#&'#&/'$8\":\u014F\" )(\"'#&'#"), + peg$decode("%;\x96/& 8!:\u0150! )"), + peg$decode("%3\u0151\"\"5(7\u0152/:#;$;6/5$;;/,$;\xEC/#$+%)(%'#($'#(#'#(\"'#&'#"), + peg$decode("%3\x92\"\"5#7\xD3.# &;6/' 8!:\u018B!! )"), + peg$decode("%3\xB1\"\"5#7\u018C.G &3\xB3\"\"5#7\u018D.; &3\xB7\"\"5#7\u018E./ &3\xB5\"\"5$7\u018F.# &;6/' 8!:\u0190!! )"), + peg$decode("%;\xEE/D#%;C/,#;\xEF/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"), + peg$decode("%;U.) &;\\.# &;X/& 8!:\u0191! )"), + peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\u0192!! )"), + peg$decode("%%;!/?#;!.\" &\"/1$;!.\" &\"/#$+#)(#'#(\"'#&'#/' 8!:\u0193!! )"), + peg$decode(";\xBE"), + peg$decode("%;\x9E/^#$%;B/,#;\xF3/#$+\")(\"'#&'#06*%;B/,#;\xF3/#$+\")(\"'#&'#&/($8\":\u0194\"!!)(\"'#&'#"), + peg$decode(";\xF4.# &;\xA0"), + peg$decode("%2\u0195\"\"6\u01957\u0196/L#;\"\"6>7?"), + peg$decode("%;\u0100/b#28\"\"6879/S$;\xFB/J$%2\u01A3\"\"6\u01A37\u01A4/,#;\xEC/#$+\")(\"'#&'#.\" &\"/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%3\u01A5\"\"5%7\u01A6.) &3\u01A7\"\"5$7\u01A8/' 8!:\u01A1!! )"), + peg$decode("%3\xB1\"\"5#7\xB2.6 &3\xB3\"\"5#7\xB4.* &$;+0#*;+&/' 8!:\u01A9!! )"), + peg$decode("%;\u0104/\x87#2F\"\"6F7G/x$;\u0103/o$2F\"\"6F7G/`$;\u0103/W$2F\"\"6F7G/H$;\u0103/?$2F\"\"6F7G/0$;\u0105/'$8):\u01AA) )()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#"), + peg$decode("%;#/>#;#/5$;#/,$;#/#$+$)($'#(#'#(\"'#&'#"), + peg$decode("%;\u0103/,#;\u0103/#$+\")(\"'#&'#"), + peg$decode("%;\u0103/5#;\u0103/,$;\u0103/#$+#)(#'#(\"'#&'#"), + peg$decode("%;q/T#$;m0#*;m&/D$%; /,#;\xF8/#$+\")(\"'#&'#.\" &\"/#$+#)(#'#(\"'#&'#"), + peg$decode("%2\u01AB\"\"6\u01AB7\u01AC.) &2\u01AD\"\"6\u01AD7\u01AE/w#;0/n$;\u0108/e$$%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#0<*%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#"), + peg$decode(";\x99.# &;L"), + peg$decode("%2\u01AF\"\"6\u01AF7\u01B0/5#; peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + peg$maxFailExpected.push(expected1); + } + function peg$buildSimpleError(message, location1) { + return new SyntaxError(message, [], "", location1); + } + function peg$buildStructuredError(expected1, found, location1) { + return new SyntaxError(SyntaxError.buildMessage(expected1, found), expected1, found, location1); + } + function peg$decode(s) { + return s.split("").map(function (ch) { return ch.charCodeAt(0) - 32; }); + } + function peg$parseRule(index) { + var bc = peg$bytecode[index]; + var ip = 0; + var ips = []; + var end = bc.length; + var ends = []; + var stack = []; + var params; + while (true) { + while (ip < end) { + switch (bc[ip]) { + case 0: + stack.push(peg$consts[bc[ip + 1]]); + ip += 2; + break; + case 1: + stack.push(undefined); + ip++; + break; + case 2: + stack.push(null); + ip++; + break; + case 3: + stack.push(peg$FAILED); + ip++; + break; + case 4: + stack.push([]); + ip++; + break; + case 5: + stack.push(peg$currPos); + ip++; + break; + case 6: + stack.pop(); + ip++; + break; + case 7: + peg$currPos = stack.pop(); + ip++; + break; + case 8: + stack.length -= bc[ip + 1]; + ip += 2; + break; + case 9: + stack.splice(-2, 1); + ip++; + break; + case 10: + stack[stack.length - 2].push(stack.pop()); + ip++; + break; + case 11: + stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1])); + ip += 2; + break; + case 12: + stack.push(input.substring(stack.pop(), peg$currPos)); + ip++; + break; + case 13: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1]) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 14: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1] === peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 15: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (stack[stack.length - 1] !== peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 16: + if (stack[stack.length - 1] !== peg$FAILED) { + ends.push(end); + ips.push(ip); + end = ip + 2 + bc[ip + 1]; + ip += 2; + } + else { + ip += 2 + bc[ip + 1]; + } + break; + case 17: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + if (input.length > peg$currPos) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } + else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + break; + case 18: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 19: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 20: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } + else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + break; + case 21: + stack.push(input.substr(peg$currPos, bc[ip + 1])); + peg$currPos += bc[ip + 1]; + ip += 2; + break; + case 22: + stack.push(peg$consts[bc[ip + 1]]); + peg$currPos += peg$consts[bc[ip + 1]].length; + ip += 2; + break; + case 23: + stack.push(peg$FAILED); + if (peg$silentFails === 0) { + peg$fail(peg$consts[bc[ip + 1]]); + } + ip += 2; + break; + case 24: + peg$savedPos = stack[stack.length - 1 - bc[ip + 1]]; + ip += 2; + break; + case 25: + peg$savedPos = peg$currPos; + ip++; + break; + case 26: + params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]) + .map(function (p) { return stack[stack.length - 1 - p]; }); + stack.splice(stack.length - bc[ip + 2], bc[ip + 2], peg$consts[bc[ip + 1]].apply(null, params)); + ip += 4 + bc[ip + 3]; + break; + case 27: + stack.push(peg$parseRule(bc[ip + 1])); + ip += 2; + break; + case 28: + peg$silentFails++; + ip++; + break; + case 29: + peg$silentFails--; + ip++; + break; + default: + throw new Error("Invalid opcode: " + bc[ip] + "."); + } + } + if (ends.length > 0) { + end = ends.pop(); + ip = ips.pop(); + } + else { + break; + } + } + return stack[0]; + } + options.data = {}; // Object to which header attributes will be assigned during parsing + function list(head, tail) { + return [head].concat(tail); + } + peg$result = peg$parseRule(peg$startRuleIndex); + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } + else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + throw peg$buildStructuredError(peg$maxFailExpected, peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)); + } +} +exports.parse = peg$parse; diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 000000000..25ec63ee9 --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,31 @@ +export { DigestAuthentication, Grammar, IncomingRequestMessage as IncomingRequest, IncomingResponseMessage as IncomingResponse, LoggerFactory, NameAddrHeader, OutgoingRequestMessage as OutgoingRequest, Timers, Transport, URI } from "./core"; +export { ClientContext } from "./ClientContext"; +export { C } from "./Constants"; +export { DialogStatus, SessionStatus, TypeStrings, UAStatus } from "./Enums"; +export { Exceptions } from "./Exceptions"; +export { Parser } from "./Parser"; +export { PublishContext } from "./PublishContext"; +export { ReferClientContext, ReferServerContext } from "./ReferContext"; +export { RegisterContext } from "./RegisterContext"; +export { ServerContext } from "./ServerContext"; +export { InviteClientContext, InviteServerContext, Session } from "./Session"; +export { SessionDescriptionHandlerFactory, SessionDescriptionHandlerFactoryOptions } from "./session-description-handler-factory"; +export { SessionDescriptionHandler, SessionDescriptionHandlerModifier, SessionDescriptionHandlerModifiers, SessionDescriptionHandlerOptions } from "./session-description-handler"; +export { Subscription } from "./Subscription"; +import { InviteClientTransaction, InviteServerTransaction, NonInviteClientTransaction, NonInviteServerTransaction } from "./core/transactions"; +declare const Transactions: { + InviteClientTransaction: typeof InviteClientTransaction; + InviteServerTransaction: typeof InviteServerTransaction; + NonInviteClientTransaction: typeof NonInviteClientTransaction; + NonInviteServerTransaction: typeof NonInviteServerTransaction; +}; +export { Transactions }; +export { makeUserAgentCoreConfigurationFromUA, UA } from "./UA"; +export { Utils } from "./Utils"; +import * as Web from "./Web/index"; +export { Web }; +declare const name: any; +declare const version: any; +export { name, version }; +import * as Core from "./core/index"; +export { Core }; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 000000000..a2323648c --- /dev/null +++ b/lib/index.js @@ -0,0 +1,65 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var core_1 = require("./core"); +exports.DigestAuthentication = core_1.DigestAuthentication; +exports.Grammar = core_1.Grammar; +exports.IncomingRequest = core_1.IncomingRequestMessage; +exports.IncomingResponse = core_1.IncomingResponseMessage; +exports.LoggerFactory = core_1.LoggerFactory; +exports.NameAddrHeader = core_1.NameAddrHeader; +exports.OutgoingRequest = core_1.OutgoingRequestMessage; +exports.Timers = core_1.Timers; +exports.Transport = core_1.Transport; +exports.URI = core_1.URI; +var ClientContext_1 = require("./ClientContext"); +exports.ClientContext = ClientContext_1.ClientContext; +var Constants_1 = require("./Constants"); +exports.C = Constants_1.C; +var Enums_1 = require("./Enums"); +exports.DialogStatus = Enums_1.DialogStatus; +exports.SessionStatus = Enums_1.SessionStatus; +exports.TypeStrings = Enums_1.TypeStrings; +exports.UAStatus = Enums_1.UAStatus; +var Exceptions_1 = require("./Exceptions"); +exports.Exceptions = Exceptions_1.Exceptions; +var Parser_1 = require("./Parser"); +exports.Parser = Parser_1.Parser; +var PublishContext_1 = require("./PublishContext"); +exports.PublishContext = PublishContext_1.PublishContext; +var ReferContext_1 = require("./ReferContext"); +exports.ReferClientContext = ReferContext_1.ReferClientContext; +exports.ReferServerContext = ReferContext_1.ReferServerContext; +var RegisterContext_1 = require("./RegisterContext"); +exports.RegisterContext = RegisterContext_1.RegisterContext; +var ServerContext_1 = require("./ServerContext"); +exports.ServerContext = ServerContext_1.ServerContext; +var Session_1 = require("./Session"); +exports.InviteClientContext = Session_1.InviteClientContext; +exports.InviteServerContext = Session_1.InviteServerContext; +exports.Session = Session_1.Session; +var Subscription_1 = require("./Subscription"); +exports.Subscription = Subscription_1.Subscription; +var transactions_1 = require("./core/transactions"); +var Transactions = { + InviteClientTransaction: transactions_1.InviteClientTransaction, + InviteServerTransaction: transactions_1.InviteServerTransaction, + NonInviteClientTransaction: transactions_1.NonInviteClientTransaction, + NonInviteServerTransaction: transactions_1.NonInviteServerTransaction +}; +exports.Transactions = Transactions; +var UA_1 = require("./UA"); +exports.makeUserAgentCoreConfigurationFromUA = UA_1.makeUserAgentCoreConfigurationFromUA; +exports.UA = UA_1.UA; +var Utils_1 = require("./Utils"); +exports.Utils = Utils_1.Utils; +var Web = tslib_1.__importStar(require("./Web/index")); +exports.Web = Web; +// tslint:disable-next-line:no-var-requires +var pkg = require("../package.json"); +var name = pkg.title; +exports.name = name; +var version = pkg.version; +exports.version = version; +var Core = tslib_1.__importStar(require("./core/index")); +exports.Core = Core; diff --git a/lib/session-description-handler-factory.d.ts b/lib/session-description-handler-factory.d.ts new file mode 100644 index 000000000..1190223b3 --- /dev/null +++ b/lib/session-description-handler-factory.d.ts @@ -0,0 +1,14 @@ +import { InviteClientContext, InviteServerContext } from "./Session"; +import { SessionDescriptionHandler } from "./session-description-handler"; +/** + * The SessionDescriptionHandlerFactory interface SIP.js is expecting. + */ +export interface SessionDescriptionHandlerFactory { + (session: InviteClientContext | InviteServerContext, options?: SessionDescriptionHandlerFactoryOptions): SessionDescriptionHandler; +} +/** + * SessionDescriptionHandlerFactory options. + * These options are provided as part of the UserAgent configuration + * and passed through on every call to SessionDescriptionHandlerFactory's constructor. + */ +export declare type SessionDescriptionHandlerFactoryOptions = object; diff --git a/lib/session-description-handler-factory.js b/lib/session-description-handler-factory.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/lib/session-description-handler-factory.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/session-description-handler.d.ts b/lib/session-description-handler.d.ts new file mode 100644 index 000000000..3f8464a7a --- /dev/null +++ b/lib/session-description-handler.d.ts @@ -0,0 +1,73 @@ +/** + * The SessionDescriptionHandler interface SIP.js is expecting. + */ +export interface SessionDescriptionHandler { + /** + * Destructor. + */ + close(): void; + /** + * Gets the local description from the underlying media implementation. + * @param options Options object to be used by getDescription. + * @param modifiers Array with one time use description modifiers. + * @returns Promise that resolves with the local description to be used for the session. + * @throws {ClosedSessionDescriptionHandlerError} When this method + * is called after close or when close occurs before complete. + */ + getDescription(options?: SessionDescriptionHandlerOptions, modifiers?: SessionDescriptionHandlerModifiers): Promise; + /** + * Returns true if the Session Description Handler can handle the Content-Type described by a SIP message. + * @param contentType The content type that is in the SIP Message. + * @returns True if the content type is handled by this session description handler. False otherwise. + */ + hasDescription(contentType: string): boolean; + /** + * The modifier that should be used when the session would like to place the call on hold. + * @param sessionDescription The description that will be modified. + * @returns Promise that resolves with modified SDP. + * @throws {ClosedSessionDescriptionHandlerError} When this method + * is called after close or when close occurs before complete. + */ + holdModifier(sessionDescription: RTCSessionDescriptionInit): Promise; + /** + * Sets the remote description to the underlying media implementation. + * @param sessionDescription The description provided by a SIP message to be set on the media implementation. + * @param options Options object to be used by setDescription. + * @param modifiers Array with one time use description modifiers. + * @returns Promise that resolves once the description is set. + * @throws {ClosedSessionDescriptionHandlerError} When this method + * is called after close or when close occurs before complete. + */ + setDescription(sdp: string, options?: SessionDescriptionHandlerOptions, modifiers?: SessionDescriptionHandlerModifiers): Promise; + /** + * Send DTMF via RTP (RFC 4733). + * Returns true if DTMF send is successful, false otherwise. + * @param tones A string containing DTMF digits. + * @param options Options object to be used by sendDtmf. + * @returns True if DTMF send is successful, false otherwise. + */ + sendDtmf(tones: string, options?: any): boolean; +} +export interface SessionDescriptionHandlerModifier { + (sessionDescription: RTCSessionDescriptionInit): Promise; +} +export declare type SessionDescriptionHandlerModifiers = Array; +/** + * SessionDescriptionHandler options. + * These options are provided to various UserAgent methods (invite() for example) + * and passed through on calls to getDescription() and setDescription(). + */ +export interface SessionDescriptionHandlerOptions { + modifiers?: SessionDescriptionHandlerModifiers; + constraints?: { + audio: boolean; + video: boolean; + }; +} +/** + * SIP message body and content type. + */ +export interface BodyObj { + body: string; + contentType: string; +} diff --git a/lib/session-description-handler.js b/lib/session-description-handler.js new file mode 100644 index 000000000..993644a62 --- /dev/null +++ b/lib/session-description-handler.js @@ -0,0 +1,3 @@ +"use strict"; +// tslint:disable:callable-types +Object.defineProperty(exports, "__esModule", { value: true });