├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADING.md ├── package-lock.json ├── package.json ├── prettier.config.js ├── src ├── components │ └── SipProvider │ │ └── index.ts ├── index.ts └── lib │ ├── dummyLogger.ts │ ├── enums.ts │ └── types.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git* 3 | .travis.yml 4 | prettier.config.js 5 | tsconfig.json 6 | tslint.json 7 | src 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | - '10' 5 | cache: 6 | directories: 7 | - node_modules 8 | script: 9 | - npm run lint 10 | - npm run build 11 | env: 12 | global: 13 | - COMMIT=${TRAVIS_COMMIT::8} 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `react-sip` changelog 2 | 3 | ## [0.8.1](https://github.com/callthemonline/react-sip/tree/0.8.1) (2018-10-20) 4 | 5 | [All Commits](https://github.com/callthemonline/react-sip/compare/0.8.0...0.8.1) 6 | 7 | - Upgrade dependencies including JsSIP 3.2.15 ([3564b47b](https://github.com/callthemonline/react-sip/commit/3564b47ba0215d9ef7fc4a542e8a66bf80ec5981)) 8 | - Fix TypeScript types in SipProvider ([a26ab823](https://github.com/callthemonline/react-sip/commit/a26ab823367eda93123422fdd9526aab574ac836)) 9 | 10 | ## [0.8.0](https://github.com/callthemonline/react-sip/tree/0.8.0) (2018-08-04) 11 | 12 | [All Commits](https://github.com/callthemonline/react-sip/compare/0.7.0...0.8.0) 13 | 14 | - Guard against multiple SipProviders ([#22](https://github.com/callthemonline/react-sip/pull/22)) 15 | - Switch to TypeScript, TSLint and Prettier ([#21](https://github.com/callthemonline/react-sip/pull/21)) 16 | 17 | ## [0.7.0](https://github.com/callthemonline/react-sip/tree/0.7.0) (2018-04-19) 18 | 19 | [All Commits](https://github.com/callthemonline/react-sip/compare/0.6.0...0.7.0) 20 | 21 | - Add pathname prop ([#18](https://github.com/callthemonline/react-sip/pull/18)) 22 | 23 | ## [0.6.0](https://github.com/callthemonline/react-sip/tree/0.6.0) (2018-03-24) 24 | 25 | [All Commits](https://github.com/callthemonline/react-sip/compare/0.5.0...0.6.0) 26 | 27 | - Allow force restart ICE ([#17](https://github.com/callthemonline/react-sip/pull/17)) 28 | 29 | ## [0.5.0](https://github.com/callthemonline/react-sip/tree/0.5.0) (2017-12-02) 30 | 31 | [All Commits](https://github.com/callthemonline/react-sip/compare/0.4.0...0.5.0) 32 | 33 | - Refactor `` component by changing the shape of its context and by making it possible to modify all props on fly 34 | - Expose `sipType`, `callType`, `extraHeadersType` and `iceServersType` `PropTypes` 35 | - Improve docs in `README` 36 | - add `LICENSE,`CHANGELOG`and`UPGRADING` 37 | - Automatically publish to npm from travis 38 | 39 | See [UPGRADING.md](./UPGRADING.md#04--05) for how to upgrade from 0.4 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present Alexander Kachkaev and Denis Nikulin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React SIP 2 | 3 | [![license](https://img.shields.io/github/license/callthemonline/react-sip.svg)](https://github.com/callthemonline/react-sip/blob/master/LICENSE) 4 | [![npm version](https://img.shields.io/npm/v/react-sip.svg)](https://www.npmjs.com/package/react-sip) 5 | [![npm downloads](https://img.shields.io/npm/dy/react-sip.svg)](https://www.npmjs.com/package/react-sip) 6 | [![build status](https://travis-ci.org/callthemonline/react-sip.svg?branch=master)](https://travis-ci.org/callthemonline/react-sip) 7 | 8 | React wrapper for [jssip](https://github.com/versatica/JsSIP). 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm install react-sip 14 | ``` 15 | 16 | There is no need to install `jssip` as it is a dependency of `react-sip`. 17 | 18 | ## Usage 19 | 20 | ```js 21 | import { SipProvider } from 'react-sip'; 22 | import App from './components/App'; 23 | 24 | ReactDOM.render( 25 | 45 | 46 | 47 | document.getElementById('root'), 48 | ); 49 | ``` 50 | 51 | Child components get access to this context: 52 | 53 | ```js 54 | { 55 | sip: sipType, 56 | call: callType, 57 | 58 | registerSip: PropTypes.func, 59 | unregisterSip: PropTypes.func, 60 | 61 | answerCall: PropTypes.func, 62 | startCall: PropTypes.func, 63 | stopCall: PropTypes.func, 64 | } 65 | ``` 66 | 67 | See [lib/types.ts](./src/lib/types.ts) for technical details of what `sipType` and `callType` are. 68 | An overview is given below: 69 | 70 | ### sip 71 | 72 | `sip.status` represents SIP connection status and equals to one of these values: 73 | 74 | - `'sipStatus/DISCONNECTED'` when `host`, `port` or `user` is not defined 75 | - `'sipStatus/CONNECTING'` 76 | - `'sipStatus/CONNECTED'` 77 | - `'sipStatus/REGISTERED'` after calling `registerSip` or after `'sipStatus/CONNECTED'` when `autoRegister` is true 78 | - `'sipStatus/ERROR'` in case of configuration, connection or registration problems 79 | 80 | `sip.errorType`: 81 | 82 | - `null` when `sip.status` is not `'sipStatus/ERROR'` 83 | - `'sipErrorType/CONFIGURATION'` 84 | - `'sipErrorType/CONNECTION'` 85 | - `'sipErrorType/REGISTRATION'` 86 | 87 | `sip.host`, `sip.port`, `sip.user`, `...` – ``’s props (to make them easy to be displayed in the UI). 88 | 89 | ### call 90 | 91 | `call.id` is a unique session id of the actual established voice call; `undefined` between calls 92 | 93 | `call.status` represents the status of the call: 94 | 95 | - `'callStatus/IDLE'` between calls (even when disconnected) 96 | - `'callStatus/STARTING'` active incoming or outgoing call request 97 | - `'callStatus/ACTIVE'` during ongoing call 98 | - `'callStatus/STOPPING'` during call cancelation request 99 | 100 | `call.direction` indicates the direction of the ongoing call: 101 | 102 | - `null` between calls 103 | - `'callDirection/INCOMING'` 104 | - `'callDirection/OUTGOING'` 105 | 106 | `call.counterpart` represents the call _destination_ in case of outgoing call and _caller_ for 107 | incoming calls. 108 | The format depends on the configuration of the SIP server (e.g. `"bob" <+441234567890@sip.example.com>`, `+441234567890@sip.example.com` or `bob@sip.example.com`). 109 | 110 | ### methods 111 | 112 | When `autoRegister` is set to `false`, you can call `sipRegister()` and `sipUnregister()` manually for advanced registration scenarios. 113 | 114 | To make calls, simply use these functions: 115 | 116 | - `answerCall()` 117 | - `startCall(destination)` 118 | - `stopCall()` 119 | 120 | The value for `destination` argument equals to the target SIP user without the host part (e.g. `+441234567890` or `bob`). 121 | The omitted host part is equal to host you’ve defined in `SipProvider` props (e.g. `sip.example.com`). 122 | 123 | --- 124 | 125 | The values for `sip.status`, `sip.errorType`, `call.status` and `call.direction` can be imported as constants to make typos easier to detect: 126 | 127 | ```js 128 | import { 129 | SIP_STATUS_DISCONNECTED, 130 | //SIP_STATUS_..., 131 | CALL_STATUS_IDLE, 132 | //CALL_STATUS_..., 133 | SIP_ERROR_TYPE_CONFIGURATION, 134 | //SIP_ERROR_TYPE_..., 135 | CALL_DIRECTION_INCOMING, 136 | CALL_DIRECTION_OUTGOING, 137 | } from "react-sip"; 138 | ``` 139 | 140 | Custom PropTypes types are also provided by the library: 141 | 142 | ```js 143 | import { callType, extraHeadersType, iceServersType, sipType } from "react-sip"; 144 | ``` 145 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading `react-sip` 2 | 3 | ## 0.4 → 0.5 4 | 5 | There are several breaking changes in `` due to its refactoring. 6 | The component now supports prop updates, such as new host, port, user and password already after the component has been initialised. 7 | It is now not necessary to know the connection details in advance and so they can be defined lazily. 8 | A change in connection details creates a new instance of `JsSIP` and the old object (if any) is stopped. 9 | 10 | Things to take into account while refactoring: 11 | 12 | - The shape of context has changed, so your child components that consume ``’s info or trigger callbacks require revision. 13 | - ``’s props are now passed down the tree as context to make UI easier to implement. If you're passing them to child components yourself, you can now remove this custom code in favour of what `` puts into context. 14 | - When you specify `contextTypes` in your child components, you can now `import { sipType, callType } from 'react-sip'` to make your code shorter (see [lib/types.js](./src/lib/types.js)). 15 | - ``’s `port` prop type has changed from `string` to `number`. 16 | - previously undocumented experimental `uri` prop was removed 17 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sip", 3 | "version": "0.8.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/prop-types": { 8 | "version": "15.5.6", 9 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.6.tgz", 10 | "integrity": "sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ==", 11 | "dev": true 12 | }, 13 | "@types/react": { 14 | "version": "16.4.18", 15 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.4.18.tgz", 16 | "integrity": "sha512-eFzJKEg6pdeaukVLVZ8Xb79CTl/ysX+ExmOfAAqcFlCCK5TgFDD9kWR0S18sglQ3EmM8U+80enjUqbfnUyqpdA==", 17 | "dev": true, 18 | "requires": { 19 | "@types/prop-types": "*", 20 | "csstype": "^2.2.0" 21 | } 22 | }, 23 | "ansi-regex": { 24 | "version": "2.1.1", 25 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 26 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 27 | "dev": true 28 | }, 29 | "ansi-styles": { 30 | "version": "2.2.1", 31 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 32 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 33 | "dev": true 34 | }, 35 | "argparse": { 36 | "version": "1.0.10", 37 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 38 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 39 | "dev": true, 40 | "requires": { 41 | "sprintf-js": "~1.0.2" 42 | } 43 | }, 44 | "babel-code-frame": { 45 | "version": "6.26.0", 46 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 47 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 48 | "dev": true, 49 | "requires": { 50 | "chalk": "^1.1.3", 51 | "esutils": "^2.0.2", 52 | "js-tokens": "^3.0.2" 53 | }, 54 | "dependencies": { 55 | "chalk": { 56 | "version": "1.1.3", 57 | "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 58 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 59 | "dev": true, 60 | "requires": { 61 | "ansi-styles": "^2.2.1", 62 | "escape-string-regexp": "^1.0.2", 63 | "has-ansi": "^2.0.0", 64 | "strip-ansi": "^3.0.0", 65 | "supports-color": "^2.0.0" 66 | } 67 | }, 68 | "js-tokens": { 69 | "version": "3.0.2", 70 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 71 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 72 | "dev": true 73 | } 74 | } 75 | }, 76 | "balanced-match": { 77 | "version": "1.0.0", 78 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 79 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 80 | "dev": true 81 | }, 82 | "brace-expansion": { 83 | "version": "1.1.11", 84 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 85 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 86 | "dev": true, 87 | "requires": { 88 | "balanced-match": "^1.0.0", 89 | "concat-map": "0.0.1" 90 | } 91 | }, 92 | "builtin-modules": { 93 | "version": "1.1.1", 94 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 95 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", 96 | "dev": true 97 | }, 98 | "chalk": { 99 | "version": "2.4.1", 100 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 101 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 102 | "dev": true, 103 | "requires": { 104 | "ansi-styles": "^3.2.1", 105 | "escape-string-regexp": "^1.0.5", 106 | "supports-color": "^5.3.0" 107 | }, 108 | "dependencies": { 109 | "ansi-styles": { 110 | "version": "3.2.1", 111 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 112 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 113 | "dev": true, 114 | "requires": { 115 | "color-convert": "^1.9.0" 116 | } 117 | }, 118 | "supports-color": { 119 | "version": "5.5.0", 120 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 121 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 122 | "dev": true, 123 | "requires": { 124 | "has-flag": "^3.0.0" 125 | } 126 | } 127 | } 128 | }, 129 | "color-convert": { 130 | "version": "1.9.3", 131 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 132 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 133 | "dev": true, 134 | "requires": { 135 | "color-name": "1.1.3" 136 | } 137 | }, 138 | "color-name": { 139 | "version": "1.1.3", 140 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 141 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 142 | "dev": true 143 | }, 144 | "commander": { 145 | "version": "2.19.0", 146 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", 147 | "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", 148 | "dev": true 149 | }, 150 | "concat-map": { 151 | "version": "0.0.1", 152 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 153 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 154 | "dev": true 155 | }, 156 | "cross-spawn": { 157 | "version": "5.1.0", 158 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", 159 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", 160 | "dev": true, 161 | "requires": { 162 | "lru-cache": "^4.0.1", 163 | "shebang-command": "^1.2.0", 164 | "which": "^1.2.9" 165 | } 166 | }, 167 | "csstype": { 168 | "version": "2.5.7", 169 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz", 170 | "integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw==", 171 | "dev": true 172 | }, 173 | "debug": { 174 | "version": "4.1.0", 175 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", 176 | "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", 177 | "requires": { 178 | "ms": "^2.1.1" 179 | } 180 | }, 181 | "diff": { 182 | "version": "3.5.0", 183 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 184 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 185 | "dev": true 186 | }, 187 | "escape-string-regexp": { 188 | "version": "1.0.5", 189 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 190 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 191 | "dev": true 192 | }, 193 | "esprima": { 194 | "version": "4.0.1", 195 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 196 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 197 | "dev": true 198 | }, 199 | "esutils": { 200 | "version": "2.0.2", 201 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 202 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 203 | "dev": true 204 | }, 205 | "events": { 206 | "version": "3.0.0", 207 | "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", 208 | "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" 209 | }, 210 | "execa": { 211 | "version": "0.6.3", 212 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.6.3.tgz", 213 | "integrity": "sha1-V7aaWU8IF1nGnlNw8NF7nLEWWP4=", 214 | "dev": true, 215 | "requires": { 216 | "cross-spawn": "^5.0.1", 217 | "get-stream": "^3.0.0", 218 | "is-stream": "^1.1.0", 219 | "npm-run-path": "^2.0.0", 220 | "p-finally": "^1.0.0", 221 | "signal-exit": "^3.0.0", 222 | "strip-eof": "^1.0.0" 223 | } 224 | }, 225 | "fs.realpath": { 226 | "version": "1.0.0", 227 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 228 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 229 | "dev": true 230 | }, 231 | "get-stream": { 232 | "version": "3.0.0", 233 | "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", 234 | "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", 235 | "dev": true 236 | }, 237 | "glob": { 238 | "version": "7.1.3", 239 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 240 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 241 | "dev": true, 242 | "requires": { 243 | "fs.realpath": "^1.0.0", 244 | "inflight": "^1.0.4", 245 | "inherits": "2", 246 | "minimatch": "^3.0.4", 247 | "once": "^1.3.0", 248 | "path-is-absolute": "^1.0.0" 249 | } 250 | }, 251 | "has-ansi": { 252 | "version": "2.0.0", 253 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 254 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 255 | "dev": true, 256 | "requires": { 257 | "ansi-regex": "^2.0.0" 258 | } 259 | }, 260 | "has-flag": { 261 | "version": "3.0.0", 262 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 263 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 264 | "dev": true 265 | }, 266 | "inflight": { 267 | "version": "1.0.6", 268 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 269 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 270 | "dev": true, 271 | "requires": { 272 | "once": "^1.3.0", 273 | "wrappy": "1" 274 | } 275 | }, 276 | "inherits": { 277 | "version": "2.0.3", 278 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 279 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 280 | "dev": true 281 | }, 282 | "is-stream": { 283 | "version": "1.1.0", 284 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 285 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 286 | "dev": true 287 | }, 288 | "isexe": { 289 | "version": "2.0.0", 290 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 291 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 292 | "dev": true 293 | }, 294 | "js-tokens": { 295 | "version": "4.0.0", 296 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 297 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 298 | }, 299 | "js-yaml": { 300 | "version": "3.12.0", 301 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", 302 | "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", 303 | "dev": true, 304 | "requires": { 305 | "argparse": "^1.0.7", 306 | "esprima": "^4.0.0" 307 | } 308 | }, 309 | "jssip": { 310 | "version": "3.2.15", 311 | "resolved": "https://registry.npmjs.org/jssip/-/jssip-3.2.15.tgz", 312 | "integrity": "sha512-kpKekINV3AObe6UFSTYiM5PfL5xKWTstUnG3S65vy6/GRlKhWk6XdivaH0X22RyAFPQuxskfpy99V/4MLhEdWg==", 313 | "requires": { 314 | "debug": "^4.1.0", 315 | "events": "^3.0.0", 316 | "sdp-transform": "^2.4.1" 317 | } 318 | }, 319 | "loose-envify": { 320 | "version": "1.4.0", 321 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 322 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 323 | "requires": { 324 | "js-tokens": "^3.0.0 || ^4.0.0" 325 | } 326 | }, 327 | "lru-cache": { 328 | "version": "4.1.3", 329 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", 330 | "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", 331 | "dev": true, 332 | "requires": { 333 | "pseudomap": "^1.0.2", 334 | "yallist": "^2.1.2" 335 | } 336 | }, 337 | "minimatch": { 338 | "version": "3.0.4", 339 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 340 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 341 | "dev": true, 342 | "requires": { 343 | "brace-expansion": "^1.1.7" 344 | } 345 | }, 346 | "ms": { 347 | "version": "2.1.1", 348 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 349 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 350 | }, 351 | "npm-run-path": { 352 | "version": "2.0.2", 353 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 354 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 355 | "dev": true, 356 | "requires": { 357 | "path-key": "^2.0.0" 358 | } 359 | }, 360 | "object-assign": { 361 | "version": "4.1.1", 362 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 363 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 364 | }, 365 | "once": { 366 | "version": "1.4.0", 367 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 368 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 369 | "dev": true, 370 | "requires": { 371 | "wrappy": "1" 372 | } 373 | }, 374 | "p-finally": { 375 | "version": "1.0.0", 376 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 377 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", 378 | "dev": true 379 | }, 380 | "path-is-absolute": { 381 | "version": "1.0.1", 382 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 383 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 384 | "dev": true 385 | }, 386 | "path-key": { 387 | "version": "2.0.1", 388 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 389 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 390 | "dev": true 391 | }, 392 | "path-parse": { 393 | "version": "1.0.6", 394 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 395 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 396 | "dev": true 397 | }, 398 | "prettier": { 399 | "version": "1.14.3", 400 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.3.tgz", 401 | "integrity": "sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==", 402 | "dev": true 403 | }, 404 | "prettier-check": { 405 | "version": "2.0.0", 406 | "resolved": "https://registry.npmjs.org/prettier-check/-/prettier-check-2.0.0.tgz", 407 | "integrity": "sha512-HZG53XQTJ9Cyi5hi1VFVVFxdlhITJybpZAch3ib9KqI05VUxV+F5Hip0GhSWRItrlDzVyqjSoDQ9KqIn7AHYyw==", 408 | "dev": true, 409 | "requires": { 410 | "execa": "^0.6.0" 411 | } 412 | }, 413 | "prop-types": { 414 | "version": "15.6.2", 415 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", 416 | "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", 417 | "requires": { 418 | "loose-envify": "^1.3.1", 419 | "object-assign": "^4.1.1" 420 | } 421 | }, 422 | "pseudomap": { 423 | "version": "1.0.2", 424 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 425 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", 426 | "dev": true 427 | }, 428 | "react": { 429 | "version": "16.5.2", 430 | "resolved": "https://registry.npmjs.org/react/-/react-16.5.2.tgz", 431 | "integrity": "sha512-FDCSVd3DjVTmbEAjUNX6FgfAmQ+ypJfHUsqUJOYNCBUp1h8lqmtC+0mXJ+JjsWx4KAVTkk1vKd1hLQPvEviSuw==", 432 | "dev": true, 433 | "requires": { 434 | "loose-envify": "^1.1.0", 435 | "object-assign": "^4.1.1", 436 | "prop-types": "^15.6.2", 437 | "schedule": "^0.5.0" 438 | } 439 | }, 440 | "resolve": { 441 | "version": "1.8.1", 442 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 443 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 444 | "dev": true, 445 | "requires": { 446 | "path-parse": "^1.0.5" 447 | } 448 | }, 449 | "rimraf": { 450 | "version": "2.6.2", 451 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 452 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 453 | "dev": true, 454 | "requires": { 455 | "glob": "^7.0.5" 456 | } 457 | }, 458 | "schedule": { 459 | "version": "0.5.0", 460 | "resolved": "https://registry.npmjs.org/schedule/-/schedule-0.5.0.tgz", 461 | "integrity": "sha512-HUcJicG5Ou8xfR//c2rPT0lPIRR09vVvN81T9fqfVgBmhERUbDEQoYKjpBxbueJnCPpSu2ujXzOnRQt6x9o/jw==", 462 | "dev": true, 463 | "requires": { 464 | "object-assign": "^4.1.1" 465 | } 466 | }, 467 | "sdp-transform": { 468 | "version": "2.4.1", 469 | "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.4.1.tgz", 470 | "integrity": "sha512-dCReSJ6NtCW6goKkKBEHcIknBS1pKejnMw+S3p3jl0PALPFrbjbreCyQ+CABtFxl2GXD2/05+C2q2gZP23dUWQ==" 471 | }, 472 | "semver": { 473 | "version": "5.6.0", 474 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 475 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", 476 | "dev": true 477 | }, 478 | "shebang-command": { 479 | "version": "1.2.0", 480 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 481 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 482 | "dev": true, 483 | "requires": { 484 | "shebang-regex": "^1.0.0" 485 | } 486 | }, 487 | "shebang-regex": { 488 | "version": "1.0.0", 489 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 490 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 491 | "dev": true 492 | }, 493 | "signal-exit": { 494 | "version": "3.0.2", 495 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 496 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 497 | "dev": true 498 | }, 499 | "sprintf-js": { 500 | "version": "1.0.3", 501 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 502 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 503 | "dev": true 504 | }, 505 | "strip-ansi": { 506 | "version": "3.0.1", 507 | "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 508 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 509 | "dev": true, 510 | "requires": { 511 | "ansi-regex": "^2.0.0" 512 | } 513 | }, 514 | "strip-eof": { 515 | "version": "1.0.0", 516 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 517 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", 518 | "dev": true 519 | }, 520 | "supports-color": { 521 | "version": "2.0.0", 522 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 523 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 524 | "dev": true 525 | }, 526 | "tslib": { 527 | "version": "1.9.3", 528 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", 529 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", 530 | "dev": true 531 | }, 532 | "tslint": { 533 | "version": "5.11.0", 534 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", 535 | "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", 536 | "dev": true, 537 | "requires": { 538 | "babel-code-frame": "^6.22.0", 539 | "builtin-modules": "^1.1.1", 540 | "chalk": "^2.3.0", 541 | "commander": "^2.12.1", 542 | "diff": "^3.2.0", 543 | "glob": "^7.1.1", 544 | "js-yaml": "^3.7.0", 545 | "minimatch": "^3.0.4", 546 | "resolve": "^1.3.2", 547 | "semver": "^5.3.0", 548 | "tslib": "^1.8.0", 549 | "tsutils": "^2.27.2" 550 | } 551 | }, 552 | "tslint-config-prettier": { 553 | "version": "1.15.0", 554 | "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.15.0.tgz", 555 | "integrity": "sha512-06CgrHJxJmNYVgsmeMoa1KXzQRoOdvfkqnJth6XUkNeOz707qxN0WfxfhYwhL5kXHHbYJRby2bqAPKwThlZPhw==", 556 | "dev": true 557 | }, 558 | "tsutils": { 559 | "version": "2.29.0", 560 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 561 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 562 | "dev": true, 563 | "requires": { 564 | "tslib": "^1.8.1" 565 | } 566 | }, 567 | "typescript": { 568 | "version": "3.1.3", 569 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.3.tgz", 570 | "integrity": "sha512-+81MUSyX+BaSo+u2RbozuQk/UWx6hfG0a5gHu4ANEM4sU96XbuIyAB+rWBW1u70c6a5QuZfuYICn3s2UjuHUpA==", 571 | "dev": true 572 | }, 573 | "which": { 574 | "version": "1.3.1", 575 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 576 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 577 | "dev": true, 578 | "requires": { 579 | "isexe": "^2.0.0" 580 | } 581 | }, 582 | "wrappy": { 583 | "version": "1.0.2", 584 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 585 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 586 | "dev": true 587 | }, 588 | "yallist": { 589 | "version": "2.1.2", 590 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 591 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", 592 | "dev": true 593 | } 594 | } 595 | } 596 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sip", 3 | "version": "0.8.1", 4 | "description": "React wrapper for jssip", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf dist && tsc --project .", 9 | "build:watch": "rimraf dist && tsc --project . --watch", 10 | "format": "prettier --write --ignore-path .gitignore \"{.,src/**}/{*.js,*.ts,*.md,*.json}\"", 11 | "lint": "tsc --project . --noEmit && tslint --project . && prettier-check --ignore-path .gitignore \"{.,src/**}/{*.js,*.ts,*.md,*.json}\"", 12 | "prepublish": "npm run build", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+ssh://git@github.com/callthemonline/react-sip.git" 18 | }, 19 | "keywords": [ 20 | "react", 21 | "sip", 22 | "jssip", 23 | "webrtc" 24 | ], 25 | "author": "callthemonline (https://callthem.online/)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/callthemonline/react-sip/issues" 29 | }, 30 | "homepage": "https://github.com/callthemonline/react-sip#readme", 31 | "dependencies": { 32 | "jssip": "^3.2.15", 33 | "prop-types": "^15.6.2" 34 | }, 35 | "peerDependencies": { 36 | "react": ">=15.0" 37 | }, 38 | "devDependencies": { 39 | "@types/prop-types": "^15.5.6", 40 | "@types/react": "^16.4.18", 41 | "prettier": "^1.14.3", 42 | "prettier-check": "^2.0.0", 43 | "react": "^16.5.2", 44 | "rimraf": "^2.6.2", 45 | "tslint": "^5.11.0", 46 | "tslint-config-prettier": "^1.15.0", 47 | "typescript": "^3.1.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: "always", 3 | trailingComma: "all", 4 | }; 5 | -------------------------------------------------------------------------------- /src/components/SipProvider/index.ts: -------------------------------------------------------------------------------- 1 | import * as JsSIP from "jssip"; 2 | import * as PropTypes from "prop-types"; 3 | import * as React from "react"; 4 | import dummyLogger from "../../lib/dummyLogger"; 5 | import { 6 | CALL_DIRECTION_INCOMING, 7 | CALL_DIRECTION_OUTGOING, 8 | CALL_STATUS_ACTIVE, 9 | CALL_STATUS_IDLE, 10 | CALL_STATUS_STARTING, 11 | CALL_STATUS_STOPPING, 12 | SIP_ERROR_TYPE_CONFIGURATION, 13 | SIP_ERROR_TYPE_CONNECTION, 14 | SIP_ERROR_TYPE_REGISTRATION, 15 | SIP_STATUS_CONNECTED, 16 | SIP_STATUS_CONNECTING, 17 | SIP_STATUS_DISCONNECTED, 18 | SIP_STATUS_ERROR, 19 | SIP_STATUS_REGISTERED, 20 | } from "../../lib/enums"; 21 | import { 22 | CallDirection, 23 | CallStatus, 24 | SipErrorType, 25 | SipStatus, 26 | } from "../../lib/enums"; 27 | import { 28 | callPropType, 29 | ExtraHeaders, 30 | extraHeadersPropType, 31 | IceServers, 32 | iceServersPropType, 33 | sipPropType, 34 | } from "../../lib/types"; 35 | 36 | export default class SipProvider extends React.Component< 37 | { 38 | host: string; 39 | port: number; 40 | pathname: string; 41 | user: string; 42 | password: string; 43 | autoRegister: boolean; 44 | autoAnswer: boolean; 45 | iceRestart: boolean; 46 | sessionTimersExpires: number; 47 | extraHeaders: ExtraHeaders; 48 | iceServers: IceServers; 49 | debug: boolean; 50 | }, 51 | { 52 | sipStatus: SipStatus; 53 | sipErrorType: SipErrorType | null; 54 | sipErrorMessage: string | null; 55 | callStatus: CallStatus; 56 | callDirection: CallDirection | null; 57 | callCounterpart: string | null; 58 | rtcSession; 59 | } 60 | > { 61 | public static childContextTypes = { 62 | sip: sipPropType, 63 | call: callPropType, 64 | registerSip: PropTypes.func, 65 | unregisterSip: PropTypes.func, 66 | 67 | answerCall: PropTypes.func, 68 | startCall: PropTypes.func, 69 | stopCall: PropTypes.func, 70 | }; 71 | 72 | public static propTypes = { 73 | host: PropTypes.string, 74 | port: PropTypes.number, 75 | pathname: PropTypes.string, 76 | user: PropTypes.string, 77 | password: PropTypes.string, 78 | autoRegister: PropTypes.bool, 79 | autoAnswer: PropTypes.bool, 80 | iceRestart: PropTypes.bool, 81 | sessionTimersExpires: PropTypes.number, 82 | extraHeaders: extraHeadersPropType, 83 | iceServers: iceServersPropType, 84 | debug: PropTypes.bool, 85 | 86 | children: PropTypes.node, 87 | }; 88 | 89 | public static defaultProps = { 90 | host: null, 91 | port: null, 92 | pathname: "", 93 | user: null, 94 | password: null, 95 | autoRegister: true, 96 | autoAnswer: false, 97 | iceRestart: false, 98 | sessionTimersExpires: 120, 99 | extraHeaders: { register: [], invite: [] }, 100 | iceServers: [], 101 | debug: false, 102 | 103 | children: null, 104 | }; 105 | private ua; 106 | private remoteAudio; 107 | private logger; 108 | 109 | constructor(props) { 110 | super(props); 111 | 112 | this.state = { 113 | sipStatus: SIP_STATUS_DISCONNECTED, 114 | sipErrorType: null, 115 | sipErrorMessage: null, 116 | 117 | rtcSession: null, 118 | // errorLog: [], 119 | callStatus: CALL_STATUS_IDLE, 120 | callDirection: null, 121 | callCounterpart: null, 122 | }; 123 | 124 | this.ua = null; 125 | } 126 | 127 | public getChildContext() { 128 | return { 129 | sip: { 130 | ...this.props, 131 | status: this.state.sipStatus, 132 | errorType: this.state.sipErrorType, 133 | errorMessage: this.state.sipErrorMessage, 134 | }, 135 | call: { 136 | id: "??", 137 | status: this.state.callStatus, 138 | direction: this.state.callDirection, 139 | counterpart: this.state.callCounterpart, 140 | }, 141 | registerSip: this.registerSip, 142 | unregisterSip: this.unregisterSip, 143 | 144 | answerCall: this.answerCall, 145 | startCall: this.startCall, 146 | stopCall: this.stopCall, 147 | }; 148 | } 149 | 150 | public componentDidMount() { 151 | if (window.document.getElementById("sip-provider-audio")) { 152 | throw new Error( 153 | `Creating two SipProviders in one application is forbidden. If that's not the case ` + 154 | `then check if you're using "sip-provider-audio" as id attribute for any existing ` + 155 | `element`, 156 | ); 157 | } 158 | 159 | this.remoteAudio = window.document.createElement("audio"); 160 | this.remoteAudio.id = "sip-provider-audio"; 161 | window.document.body.appendChild(this.remoteAudio); 162 | 163 | this.reconfigureDebug(); 164 | this.reinitializeJsSIP(); 165 | } 166 | 167 | public componentDidUpdate(prevProps) { 168 | if (this.props.debug !== prevProps.debug) { 169 | this.reconfigureDebug(); 170 | } 171 | if ( 172 | this.props.host !== prevProps.host || 173 | this.props.port !== prevProps.port || 174 | this.props.pathname !== prevProps.pathname || 175 | this.props.user !== prevProps.user || 176 | this.props.password !== prevProps.password || 177 | this.props.autoRegister !== prevProps.autoRegister 178 | ) { 179 | this.reinitializeJsSIP(); 180 | } 181 | } 182 | 183 | public componentWillUnmount() { 184 | this.remoteAudio.parentNode.removeChild(this.remoteAudio); 185 | delete this.remoteAudio; 186 | if (this.ua) { 187 | this.ua.stop(); 188 | this.ua = null; 189 | } 190 | } 191 | 192 | public registerSip = () => { 193 | if (this.props.autoRegister) { 194 | throw new Error( 195 | "Calling registerSip is not allowed when autoRegister === true", 196 | ); 197 | } 198 | if (this.state.sipStatus !== SIP_STATUS_CONNECTED) { 199 | throw new Error( 200 | `Calling registerSip is not allowed when sip status is ${ 201 | this.state.sipStatus 202 | } (expected ${SIP_STATUS_CONNECTED})`, 203 | ); 204 | } 205 | return this.ua.register(); 206 | }; 207 | 208 | public unregisterSip = () => { 209 | if (this.props.autoRegister) { 210 | throw new Error( 211 | "Calling registerSip is not allowed when autoRegister === true", 212 | ); 213 | } 214 | if (this.state.sipStatus !== SIP_STATUS_REGISTERED) { 215 | throw new Error( 216 | `Calling unregisterSip is not allowed when sip status is ${ 217 | this.state.sipStatus 218 | } (expected ${SIP_STATUS_CONNECTED})`, 219 | ); 220 | } 221 | return this.ua.unregister(); 222 | }; 223 | 224 | public answerCall = () => { 225 | if ( 226 | this.state.callStatus !== CALL_STATUS_STARTING || 227 | this.state.callDirection !== CALL_DIRECTION_INCOMING 228 | ) { 229 | throw new Error( 230 | `Calling answerCall() is not allowed when call status is ${ 231 | this.state.callStatus 232 | } and call direction is ${ 233 | this.state.callDirection 234 | } (expected ${CALL_STATUS_STARTING} and ${CALL_DIRECTION_INCOMING})`, 235 | ); 236 | } 237 | 238 | this.state.rtcSession.answer({ 239 | mediaConstraints: { 240 | audio: true, 241 | video: false, 242 | }, 243 | pcConfig: { 244 | iceServers: this.props.iceServers, 245 | }, 246 | }); 247 | }; 248 | 249 | public startCall = (destination) => { 250 | if (!destination) { 251 | throw new Error(`Destination must be defined (${destination} given)`); 252 | } 253 | if ( 254 | this.state.sipStatus !== SIP_STATUS_CONNECTED && 255 | this.state.sipStatus !== SIP_STATUS_REGISTERED 256 | ) { 257 | throw new Error( 258 | `Calling startCall() is not allowed when sip status is ${ 259 | this.state.sipStatus 260 | } (expected ${SIP_STATUS_CONNECTED} or ${SIP_STATUS_REGISTERED})`, 261 | ); 262 | } 263 | 264 | if (this.state.callStatus !== CALL_STATUS_IDLE) { 265 | throw new Error( 266 | `Calling startCall() is not allowed when call status is ${ 267 | this.state.callStatus 268 | } (expected ${CALL_STATUS_IDLE})`, 269 | ); 270 | } 271 | 272 | const { iceServers, sessionTimersExpires } = this.props; 273 | const extraHeaders = this.props.extraHeaders.invite; 274 | 275 | const options = { 276 | extraHeaders, 277 | mediaConstraints: { audio: true, video: false }, 278 | rtcOfferConstraints: { iceRestart: this.props.iceRestart }, 279 | pcConfig: { 280 | iceServers, 281 | }, 282 | sessionTimersExpires, 283 | }; 284 | 285 | this.ua.call(destination, options); 286 | this.setState({ callStatus: CALL_STATUS_STARTING }); 287 | }; 288 | 289 | public stopCall = () => { 290 | this.setState({ callStatus: CALL_STATUS_STOPPING }); 291 | this.ua.terminateSessions(); 292 | }; 293 | 294 | public reconfigureDebug() { 295 | const { debug } = this.props; 296 | 297 | if (debug) { 298 | JsSIP.debug.enable("JsSIP:*"); 299 | this.logger = console; 300 | } else { 301 | JsSIP.debug.disable("JsSIP:*"); 302 | this.logger = dummyLogger; 303 | } 304 | } 305 | 306 | public reinitializeJsSIP() { 307 | if (this.ua) { 308 | this.ua.stop(); 309 | this.ua = null; 310 | } 311 | 312 | const { host, port, pathname, user, password, autoRegister } = this.props; 313 | 314 | if (!host || !port || !user) { 315 | this.setState({ 316 | sipStatus: SIP_STATUS_DISCONNECTED, 317 | sipErrorType: null, 318 | sipErrorMessage: null, 319 | }); 320 | return; 321 | } 322 | 323 | try { 324 | const socket = new JsSIP.WebSocketInterface( 325 | `wss://${host}:${port}${pathname}`, 326 | ); 327 | this.ua = new JsSIP.UA({ 328 | uri: `sip:${user}@${host}`, 329 | password, 330 | sockets: [socket], 331 | register: autoRegister, 332 | }); 333 | } catch (error) { 334 | this.logger.debug("Error", error.message, error); 335 | this.setState({ 336 | sipStatus: SIP_STATUS_ERROR, 337 | sipErrorType: SIP_ERROR_TYPE_CONFIGURATION, 338 | sipErrorMessage: error.message, 339 | }); 340 | return; 341 | } 342 | 343 | const { ua } = this; 344 | ua.on("connecting", () => { 345 | this.logger.debug('UA "connecting" event'); 346 | if (this.ua !== ua) { 347 | return; 348 | } 349 | this.setState({ 350 | sipStatus: SIP_STATUS_CONNECTING, 351 | sipErrorType: null, 352 | sipErrorMessage: null, 353 | }); 354 | }); 355 | 356 | ua.on("connected", () => { 357 | this.logger.debug('UA "connected" event'); 358 | if (this.ua !== ua) { 359 | return; 360 | } 361 | this.setState({ 362 | sipStatus: SIP_STATUS_CONNECTED, 363 | sipErrorType: null, 364 | sipErrorMessage: null, 365 | }); 366 | }); 367 | 368 | ua.on("disconnected", () => { 369 | this.logger.debug('UA "disconnected" event'); 370 | if (this.ua !== ua) { 371 | return; 372 | } 373 | this.setState({ 374 | sipStatus: SIP_STATUS_ERROR, 375 | sipErrorType: SIP_ERROR_TYPE_CONNECTION, 376 | sipErrorMessage: "disconnected", 377 | }); 378 | }); 379 | 380 | ua.on("registered", (data) => { 381 | this.logger.debug('UA "registered" event', data); 382 | if (this.ua !== ua) { 383 | return; 384 | } 385 | this.setState({ 386 | sipStatus: SIP_STATUS_REGISTERED, 387 | callStatus: CALL_STATUS_IDLE, 388 | }); 389 | }); 390 | 391 | ua.on("unregistered", () => { 392 | this.logger.debug('UA "unregistered" event'); 393 | if (this.ua !== ua) { 394 | return; 395 | } 396 | if (ua.isConnected()) { 397 | this.setState({ 398 | sipStatus: SIP_STATUS_CONNECTED, 399 | callStatus: CALL_STATUS_IDLE, 400 | callDirection: null, 401 | }); 402 | } else { 403 | this.setState({ 404 | sipStatus: SIP_STATUS_DISCONNECTED, 405 | callStatus: CALL_STATUS_IDLE, 406 | callDirection: null, 407 | }); 408 | } 409 | }); 410 | 411 | ua.on("registrationFailed", (data) => { 412 | this.logger.debug('UA "registrationFailed" event'); 413 | if (this.ua !== ua) { 414 | return; 415 | } 416 | this.setState({ 417 | sipStatus: SIP_STATUS_ERROR, 418 | sipErrorType: SIP_ERROR_TYPE_REGISTRATION, 419 | sipErrorMessage: data, 420 | }); 421 | }); 422 | 423 | ua.on( 424 | "newRTCSession", 425 | ({ originator, session: rtcSession, request: rtcRequest }) => { 426 | if (!this || this.ua !== ua) { 427 | return; 428 | } 429 | 430 | // identify call direction 431 | if (originator === "local") { 432 | const foundUri = rtcRequest.to.toString(); 433 | const delimiterPosition = foundUri.indexOf(";") || null; 434 | this.setState({ 435 | callDirection: CALL_DIRECTION_OUTGOING, 436 | callStatus: CALL_STATUS_STARTING, 437 | callCounterpart: 438 | foundUri.substring(0, delimiterPosition) || foundUri, 439 | }); 440 | } else if (originator === "remote") { 441 | const foundUri = rtcRequest.from.toString(); 442 | const delimiterPosition = foundUri.indexOf(";") || null; 443 | this.setState({ 444 | callDirection: CALL_DIRECTION_INCOMING, 445 | callStatus: CALL_STATUS_STARTING, 446 | callCounterpart: 447 | foundUri.substring(0, delimiterPosition) || foundUri, 448 | }); 449 | } 450 | 451 | const { rtcSession: rtcSessionInState } = this.state; 452 | 453 | // Avoid if busy or other incoming 454 | if (rtcSessionInState) { 455 | this.logger.debug('incoming call replied with 486 "Busy Here"'); 456 | rtcSession.terminate({ 457 | status_code: 486, 458 | reason_phrase: "Busy Here", 459 | }); 460 | return; 461 | } 462 | 463 | this.setState({ rtcSession }); 464 | rtcSession.on("failed", () => { 465 | if (this.ua !== ua) { 466 | return; 467 | } 468 | 469 | this.setState({ 470 | rtcSession: null, 471 | callStatus: CALL_STATUS_IDLE, 472 | callDirection: null, 473 | callCounterpart: null, 474 | }); 475 | }); 476 | 477 | rtcSession.on("ended", () => { 478 | if (this.ua !== ua) { 479 | return; 480 | } 481 | 482 | this.setState({ 483 | rtcSession: null, 484 | callStatus: CALL_STATUS_IDLE, 485 | callDirection: null, 486 | callCounterpart: null, 487 | }); 488 | }); 489 | 490 | rtcSession.on("accepted", () => { 491 | if (this.ua !== ua) { 492 | return; 493 | } 494 | 495 | [ 496 | this.remoteAudio.srcObject, 497 | ] = rtcSession.connection.getRemoteStreams(); 498 | // const played = this.remoteAudio.play(); 499 | const played = this.remoteAudio.play(); 500 | 501 | if (typeof played !== "undefined") { 502 | played 503 | .catch(() => { 504 | /**/ 505 | }) 506 | .then(() => { 507 | setTimeout(() => { 508 | this.remoteAudio.play(); 509 | }, 2000); 510 | }); 511 | this.setState({ callStatus: CALL_STATUS_ACTIVE }); 512 | return; 513 | } 514 | 515 | setTimeout(() => { 516 | this.remoteAudio.play(); 517 | }, 2000); 518 | 519 | this.setState({ callStatus: CALL_STATUS_ACTIVE }); 520 | }); 521 | 522 | if ( 523 | this.state.callDirection === CALL_DIRECTION_INCOMING && 524 | this.props.autoAnswer 525 | ) { 526 | this.logger.log("Answer auto ON"); 527 | this.answerCall(); 528 | } else if ( 529 | this.state.callDirection === CALL_DIRECTION_INCOMING && 530 | !this.props.autoAnswer 531 | ) { 532 | this.logger.log("Answer auto OFF"); 533 | } else if (this.state.callDirection === CALL_DIRECTION_OUTGOING) { 534 | this.logger.log("OUTGOING call"); 535 | } 536 | }, 537 | ); 538 | 539 | const extraHeadersRegister = this.props.extraHeaders.register || []; 540 | if (extraHeadersRegister.length) { 541 | ua.registrator().setExtraHeaders(extraHeadersRegister); 542 | } 543 | ua.start(); 544 | } 545 | 546 | public render() { 547 | return this.props.children; 548 | } 549 | } 550 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import SipProvider from "./components/SipProvider"; 2 | 3 | export * from "./lib/enums"; 4 | export * from "./lib/types"; 5 | 6 | export { SipProvider }; 7 | -------------------------------------------------------------------------------- /src/lib/dummyLogger.ts: -------------------------------------------------------------------------------- 1 | const dummyLogger = { 2 | log: () => { 3 | /**/ 4 | }, 5 | error: () => { 6 | /**/ 7 | }, 8 | warn: () => { 9 | /**/ 10 | }, 11 | debug: () => { 12 | /**/ 13 | }, 14 | }; 15 | 16 | export default dummyLogger; 17 | -------------------------------------------------------------------------------- /src/lib/enums.ts: -------------------------------------------------------------------------------- 1 | export const SIP_STATUS_DISCONNECTED = "sipStatus/DISCONNECTED"; 2 | export const SIP_STATUS_CONNECTING = "sipStatus/CONNECTING"; 3 | export const SIP_STATUS_CONNECTED = "sipStatus/CONNECTED"; 4 | export const SIP_STATUS_REGISTERED = "sipStatus/REGISTERED"; 5 | export const SIP_STATUS_ERROR = "sipStatus/ERROR"; 6 | export type SipStatus = 7 | | "sipStatus/DISCONNECTED" 8 | | "sipStatus/CONNECTING" 9 | | "sipStatus/CONNECTED" 10 | | "sipStatus/REGISTERED" 11 | | "sipStatus/ERROR"; 12 | 13 | export const SIP_ERROR_TYPE_CONFIGURATION = "sipErrorType/CONFIGURATION"; 14 | export const SIP_ERROR_TYPE_CONNECTION = "sipErrorType/CONNECTION"; 15 | export const SIP_ERROR_TYPE_REGISTRATION = "sipErrorType/REGISTRATION"; 16 | export type SipErrorType = 17 | | "sipErrorType/CONFIGURATION" 18 | | "sipErrorType/CONNECTION" 19 | | "sipErrorType/REGISTRATION"; 20 | 21 | export const CALL_STATUS_IDLE = "callStatus/IDLE"; 22 | export const CALL_STATUS_STARTING = "callStatus/STARTING"; 23 | export const CALL_STATUS_ACTIVE = "callStatus/ACTIVE"; 24 | export const CALL_STATUS_STOPPING = "callStatus/STOPPING"; 25 | export type CallStatus = 26 | | "callStatus/IDLE" 27 | | "callStatus/STARTING" 28 | | "callStatus/ACTIVE" 29 | | "callStatus/STOPPING"; 30 | 31 | export const CALL_DIRECTION_INCOMING = "callDirection/INCOMING"; 32 | export const CALL_DIRECTION_OUTGOING = "callDirection/OUTGOING"; 33 | export type CallDirection = "callDirection/INCOMING" | "callDirection/OUTGOING"; 34 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import * as PropTypes from "prop-types"; 2 | 3 | export interface ExtraHeaders { 4 | register?: string[]; 5 | invite?: string[]; 6 | } 7 | export const extraHeadersPropType = PropTypes.objectOf( 8 | PropTypes.arrayOf(PropTypes.string), 9 | ); 10 | 11 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer 12 | export type IceServers = Array<{ 13 | urls: string | string[]; 14 | username?: string; 15 | credential?: string; 16 | credentialType?: string; 17 | password?: string; 18 | }>; 19 | export const iceServersPropType = PropTypes.arrayOf(PropTypes.object); 20 | 21 | export interface Sip { 22 | status?: string; 23 | errorType?: string; 24 | errorMessage?: string; 25 | 26 | host?: string; 27 | port?: number; 28 | user?: string; 29 | password?: string; 30 | autoRegister?: boolean; 31 | autoAnswer: boolean; 32 | sessionTimersExpires: number; 33 | extraHeaders: ExtraHeaders; 34 | iceServers: IceServers; 35 | debug: boolean; 36 | } 37 | export const sipPropType = PropTypes.shape({ 38 | status: PropTypes.string, 39 | errorType: PropTypes.string, 40 | errorMessage: PropTypes.string, 41 | 42 | host: PropTypes.string, 43 | port: PropTypes.number, 44 | user: PropTypes.string, 45 | password: PropTypes.string, 46 | autoRegister: PropTypes.bool, 47 | autoAnswer: PropTypes.bool, 48 | sessionTimersExpires: PropTypes.number, 49 | extraHeaders: extraHeadersPropType, 50 | iceServers: iceServersPropType, 51 | debug: PropTypes.bool, 52 | }); 53 | 54 | export interface Call { 55 | id: string; 56 | status: string; 57 | direction: string; 58 | counterpart: string; 59 | } 60 | export const callPropType = PropTypes.shape({ 61 | id: PropTypes.string, 62 | status: PropTypes.string, 63 | direction: PropTypes.string, 64 | counterpart: PropTypes.string, 65 | }); 66 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "declaration": true, 8 | "strictNullChecks": true, 9 | "noImplicitAny": false, 10 | "noUnusedLocals": true, 11 | "allowSyntheticDefaultImports": false, 12 | "pretty": true, 13 | "removeComments": true, 14 | "lib": ["es6", "esnext.asynciterable", "dom"], 15 | 16 | "outDir": "dist", 17 | "rootDir": "src" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-config-prettier"], 4 | "rules": { 5 | "interface-name": [true, "never-prefix"], 6 | "no-implicit-dependencies": true, 7 | "no-string-literal": false, 8 | "object-literal-sort-keys": false, 9 | "prefer-for-of": false 10 | } 11 | } 12 | --------------------------------------------------------------------------------