├── dist ├── .placeholder ├── index.test.d.ts ├── util │ ├── hold.d.ts │ ├── buttons.d.ts │ ├── ToneManager.d.ts │ ├── audio.d.ts │ ├── incomingSession.d.ts │ ├── TonePlayer.d.ts │ └── sessions.d.ts ├── reducers │ ├── index.d.ts │ ├── models.d.ts │ ├── sipAccounts.d.ts │ ├── config.d.ts │ ├── sipSessions.d.ts │ └── device.d.ts ├── assets │ ├── ring.mp3 │ ├── pause-24px.svg │ ├── call_made-24px.svg │ ├── call_received-24px.svg │ ├── arrow_forward-24px.svg │ ├── mic-24px.svg │ ├── volume_up-24px.svg │ ├── phone_forwarded-24px.svg │ ├── phone_paused-24px.svg │ ├── call-24px.svg │ ├── call-large-40px.svg │ ├── voicemail-24px.svg │ ├── phone_in_talk-24px.svg │ ├── mic_off-24px.svg │ ├── call_end-24px.svg │ ├── dialpad-24px.svg │ └── settings-24px.svg ├── components │ ├── phone │ │ ├── DialButton.d.ts │ │ ├── Hold.d.ts │ │ ├── Dialpad.d.ts │ │ ├── Incoming.d.ts │ │ ├── Mute.d.ts │ │ ├── BlindTransfer.d.ts │ │ ├── Phone.d.ts │ │ └── AttendedTransfer.d.ts │ ├── PhoneSessions.d.ts │ ├── Dialstring.d.ts │ └── Status.d.ts ├── actions │ ├── sipAccounts.d.ts │ ├── config.d.ts │ ├── device.d.ts │ └── sipSessions.d.ts ├── store │ └── configureStore.d.ts ├── lib │ └── SipAccount.d.ts ├── models.d.ts ├── index.d.ts ├── SipWrapper.d.ts └── index.css ├── .travis.yml ├── src ├── .eslintrc ├── reducers │ ├── models.ts │ ├── index.ts │ ├── sipAccounts.ts │ ├── device.ts │ ├── config.ts │ └── sipSessions.ts ├── _variables.scss ├── assets │ ├── ring.mp3 │ ├── pause-24px.svg │ ├── call_made-24px.svg │ ├── call_received-24px.svg │ ├── arrow_forward-24px.svg │ ├── mic-24px.svg │ ├── volume_up-24px.svg │ ├── phone_forwarded-24px.svg │ ├── phone_paused-24px.svg │ ├── call-24px.svg │ ├── call-large-40px.svg │ ├── voicemail-24px.svg │ ├── phone_in_talk-24px.svg │ ├── mic_off-24px.svg │ ├── call_end-24px.svg │ ├── dialpad-24px.svg │ └── settings-24px.svg ├── index.test.tsx ├── actions │ ├── sipAccounts.ts │ ├── config.ts │ ├── device.ts │ └── sipSessions.ts ├── util │ ├── buttons.ts │ ├── ToneManager.ts │ ├── hold.ts │ ├── incomingSession.ts │ ├── audio.ts │ ├── sessions.ts │ └── TonePlayer.ts ├── models.ts ├── typings.d.ts ├── components │ ├── phone │ │ ├── DialButton.tsx │ │ ├── Hold.tsx │ │ ├── BlindTransfer.tsx │ │ ├── Phone.scss │ │ ├── Dialpad.tsx │ │ ├── Incoming.tsx │ │ ├── Mute.tsx │ │ ├── Phone.tsx │ │ └── AttendedTransfer.tsx │ ├── Dialstring.scss │ ├── Status.scss │ ├── PhoneSessions.tsx │ ├── Dialstring.tsx │ └── Status.tsx ├── store │ └── configureStore.ts ├── styles.scss ├── SipWrapper.tsx ├── index.tsx └── lib │ └── SipAccount.ts ├── .eslintignore ├── example ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── src │ ├── index.js │ ├── App.test.js │ ├── index.css │ └── App.js ├── README.md ├── package.json └── package-lock.json ├── tsconfig.test.json ├── .editorconfig ├── .prettierrc ├── .gitignore ├── .eslintrc ├── tsconfig.json ├── README.md └── package.json /dist/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/index.test.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /dist/util/hold.d.ts: -------------------------------------------------------------------------------- 1 | export declare const holdAll: (id: string) => void; 2 | -------------------------------------------------------------------------------- /dist/reducers/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const reducers: any; 2 | export default reducers; 3 | -------------------------------------------------------------------------------- /src/reducers/models.ts: -------------------------------------------------------------------------------- 1 | export interface Action { 2 | type: string 3 | payload: any 4 | } 5 | -------------------------------------------------------------------------------- /dist/assets/ring.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenTelecom/react-sip-phone/HEAD/dist/assets/ring.mp3 -------------------------------------------------------------------------------- /src/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #96bdf7; 2 | $secondary-color: #356abb; 3 | $almost-black: #333132; -------------------------------------------------------------------------------- /src/assets/ring.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenTelecom/react-sip-phone/HEAD/src/assets/ring.mp3 -------------------------------------------------------------------------------- /dist/reducers/models.d.ts: -------------------------------------------------------------------------------- 1 | export interface Action { 2 | type: string; 3 | payload: any; 4 | } 5 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenTelecom/react-sip-phone/HEAD/example/public/favicon.ico -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /dist/util/buttons.d.ts: -------------------------------------------------------------------------------- 1 | export declare const getButtonLetters: (value: string) => "" | "1" | "ABC" | "DEF" | "GHI" | "JKL" | "MNO" | "PQRS" | "TUV" | "WXYZ" | "+"; 2 | -------------------------------------------------------------------------------- /src/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { ReactSipPhone } from '.' 2 | 3 | describe('ExampleComponent', () => { 4 | it('is truthy', () => { 5 | expect(ReactSipPhone).toBeTruthy() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /dist/assets/pause-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/pause-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /dist/assets/call_made-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import App from './App' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /src/assets/call_made-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/call_received-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/call_received-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/arrow_forward-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/arrow_forward-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/util/ToneManager.d.ts: -------------------------------------------------------------------------------- 1 | declare class ToneManager { 2 | currentTone: any; 3 | playRing(type: string): void; 4 | stopAll(): void; 5 | } 6 | declare const toneManager: ToneManager; 7 | export default toneManager; 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": false, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /dist/util/audio.d.ts: -------------------------------------------------------------------------------- 1 | import { Session } from 'sip.js'; 2 | export declare const setRemoteAudio: (session: Session) => void; 3 | export declare const setLocalAudio: (session: Session) => void; 4 | export declare const cleanupMedia: (sessionId: string) => void; 5 | -------------------------------------------------------------------------------- /dist/util/incomingSession.d.ts: -------------------------------------------------------------------------------- 1 | import { SessionState, Session } from 'sip.js'; 2 | export declare class IncomingSessionStateHandler { 3 | private incomingSession; 4 | constructor(incomingSession: Session); 5 | stateChange: (newState: SessionState) => void; 6 | } 7 | -------------------------------------------------------------------------------- /dist/components/phone/DialButton.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | interface Props { 3 | text: string; 4 | click: Function; 5 | letters: string; 6 | } 7 | declare const DialButton: ({ text, click, letters }: Props) => JSX.Element; 8 | export default DialButton; 9 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | It is linked to the react-sip-phone package in the parent directory for development purposes. 4 | 5 | You can run `npm install` and then `npm start` to test your package. 6 | -------------------------------------------------------------------------------- /example/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | ReactDOM.unmountComponentAtNode(div) 9 | }) 10 | -------------------------------------------------------------------------------- /dist/assets/mic-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/mic-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/volume_up-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/volume_up-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | .rpt2_cache 10 | 11 | # misc 12 | .DS_Store 13 | .env 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /dist/util/TonePlayer.d.ts: -------------------------------------------------------------------------------- 1 | export declare const playDTMF: (key: any, deviceId: string) => void; 2 | export declare const callDisconnect: (deviceId: string) => void; 3 | declare class TonePlayer { 4 | private loop; 5 | ringtone: (deviceId: string) => void; 6 | ringback: (deviceId: string) => void; 7 | stop(): void; 8 | } 9 | export default TonePlayer; 10 | -------------------------------------------------------------------------------- /src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import sipSessions from './sipSessions' 3 | import sipAccounts from './sipAccounts' 4 | import device from './device' 5 | import config from './config' 6 | 7 | const reducers: any = combineReducers({ 8 | sipAccounts, 9 | sipSessions, 10 | device, 11 | config 12 | }) 13 | 14 | export default reducers 15 | -------------------------------------------------------------------------------- /dist/actions/sipAccounts.d.ts: -------------------------------------------------------------------------------- 1 | import SIPAccount from '../lib/SipAccount'; 2 | export declare const SIPACCOUNT_UNREGISTERED = "SIPACCOUNT_UNREGISTERED"; 3 | export declare const NEW_USERAGENT = "NEW_USERAGENT"; 4 | export declare const NEW_ACCOUNT = "NEW_ACCOUNT"; 5 | export declare const setNewAccount: (account: SIPAccount) => { 6 | type: string; 7 | payload: SIPAccount; 8 | }; 9 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-sip-phone", 3 | "name": "react-sip-phone", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/actions/sipAccounts.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import SIPAccount from '../lib/SipAccount' 3 | 4 | export const SIPACCOUNT_UNREGISTERED = 'SIPACCOUNT_UNREGISTERED' 5 | export const NEW_USERAGENT = 'NEW_USERAGENT' 6 | export const NEW_ACCOUNT = 'NEW_ACCOUNT' 7 | 8 | export const setNewAccount = (account: SIPAccount) => { 9 | return { type: NEW_ACCOUNT, payload: account } 10 | } -------------------------------------------------------------------------------- /dist/assets/phone_forwarded-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/phone_forwarded-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/phone_paused-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/phone_paused-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/call-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/call-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/reducers/sipAccounts.d.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './models'; 2 | declare const sipAccounts: (state: { 3 | sipAccount: null; 4 | userAgent: null; 5 | status: string; 6 | } | undefined, action: Action) => { 7 | sipAccount: any; 8 | userAgent: null; 9 | status: string; 10 | } | { 11 | userAgent: any; 12 | sipAccount: null; 13 | status: string; 14 | }; 15 | export default sipAccounts; 16 | -------------------------------------------------------------------------------- /dist/assets/call-large-40px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/call-large-40px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/store/configureStore.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | export declare const defaultStore: import("redux").Store> & { 5 | dispatch: unknown; 6 | }; 7 | export declare const persistor: import("redux-persist").Persistor; 8 | -------------------------------------------------------------------------------- /dist/lib/SipAccount.d.ts: -------------------------------------------------------------------------------- 1 | import { SipConfig, SipCredentials } from '../models'; 2 | export default class SIPAccount { 3 | _config: SipConfig; 4 | _credentials: SipCredentials; 5 | _userAgent: any; 6 | _registerer: any; 7 | constructor(sipConfig: SipConfig, sipCredentials: SipCredentials); 8 | setupDelegate(): void; 9 | setupRegistererListener(): void; 10 | makeCall(number: string): void; 11 | listener(): void; 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/voicemail-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/voicemail-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/phone_in_talk-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/phone_in_talk-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | display: flex; 10 | justify-content: center; 11 | } 12 | 13 | code { 14 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 15 | monospace; 16 | } 17 | -------------------------------------------------------------------------------- /dist/assets/mic_off-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/mic_off-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/call_end-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/call_end-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/util/sessions.d.ts: -------------------------------------------------------------------------------- 1 | import { SessionState, Session, UserAgent } from 'sip.js'; 2 | export declare class SessionStateHandler { 3 | private session; 4 | private ua; 5 | constructor(session: Session, ua: UserAgent); 6 | stateChange: (newState: SessionState) => void; 7 | } 8 | export declare const getFullNumber: (number: string) => string; 9 | export declare const statusMask: (status: string) => "Connected" | "Calling..." | "Initial" | "Ended" | "Unknown Status"; 10 | export declare const getDurationDisplay: (duration: number) => string; 11 | -------------------------------------------------------------------------------- /src/util/buttons.ts: -------------------------------------------------------------------------------- 1 | export const getButtonLetters = (value: string) => { 2 | switch (value) { 3 | case '1': 4 | return '1' 5 | case '2': 6 | return 'ABC' 7 | case '3': 8 | return 'DEF' 9 | case '4': 10 | return 'GHI' 11 | case '5': 12 | return 'JKL' 13 | case '6': 14 | return 'MNO' 15 | case '7': 16 | return 'PQRS' 17 | case '8': 18 | return 'TUV' 19 | case '9': 20 | return 'WXYZ' 21 | case '0': 22 | return '+' 23 | default: 24 | return '' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dist/components/PhoneSessions.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PhoneConfig } from '../models'; 3 | interface Props { 4 | sessions: Object; 5 | incomingCalls: Array; 6 | phoneConfig: PhoneConfig; 7 | attendedTransfers: Array; 8 | } 9 | declare class PhoneSessions extends React.Component { 10 | render(): JSX.Element; 11 | } 12 | declare const PS: import("react-redux").ConnectedComponent & Props, "phoneConfig" | "ref" | "key">>; 13 | export default PS; 14 | -------------------------------------------------------------------------------- /dist/assets/dialpad-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/dialpad-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/models.ts: -------------------------------------------------------------------------------- 1 | export interface SipCredentials { 2 | sipuri: string 3 | password: string 4 | } 5 | 6 | export interface SipConfig { 7 | websocket: string 8 | video: boolean 9 | iceServers: Array 10 | defaultCountryCode: string 11 | noAnswerTimeout: number 12 | } 13 | 14 | export interface PhoneConfig { 15 | disabledButtons: Array 16 | disabledFeatures: Array 17 | defaultDial: string 18 | sessionsLimit: number 19 | attendedTransferLimit: number 20 | autoAnswer: boolean 21 | } 22 | 23 | export interface AppConfig { 24 | mode: string 25 | started: boolean 26 | appSize: string 27 | } -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default CSS definition for typescript, 3 | * will be overridden with file-specific definitions by rollup 4 | */ 5 | declare module '*.css' { 6 | const content: { [className: string]: string }; 7 | export default content; 8 | } 9 | declare module '*.scss'; 10 | 11 | declare module '*.mp3' { 12 | const src: string; 13 | export default src; 14 | } 15 | 16 | interface SvgrComponent extends React.StatelessComponent> {} 17 | 18 | declare module '*.svg' { 19 | const svgUrl: string; 20 | const svgComponent: SvgrComponent; 21 | export default svgUrl; 22 | export { svgComponent as ReactComponent } 23 | } 24 | -------------------------------------------------------------------------------- /dist/components/phone/Hold.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Session, UserAgent } from 'sip.js'; 3 | interface Props { 4 | session: Session; 5 | userAgent: UserAgent; 6 | holdCallRequest: Function; 7 | unHoldCallRequest: Function; 8 | onHold: Array; 9 | sessions: Array; 10 | } 11 | declare class Hold extends React.Component { 12 | hold(): void; 13 | checkHoldState(): boolean; 14 | render(): JSX.Element; 15 | } 16 | declare const _default: import("react-redux").ConnectedComponent & Props, "ref" | "key" | "session">>; 17 | export default _default; 18 | -------------------------------------------------------------------------------- /src/components/phone/DialButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import styles from './Phone.scss' 3 | 4 | interface Props { 5 | text: string 6 | click: Function 7 | letters: string 8 | } 9 | 10 | const DialButton = ({ text, click, letters }: Props) => { 11 | return ( 12 |
click()} 16 | > 17 | {text} 18 |
22 | {letters} 23 |
24 |
25 | ) 26 | } 27 | 28 | export default DialButton 29 | -------------------------------------------------------------------------------- /src/reducers/sipAccounts.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './models' 2 | import { NEW_USERAGENT, NEW_ACCOUNT } from '../actions/sipAccounts' 3 | 4 | const sipAccounts = ( 5 | state = { 6 | sipAccount: null, 7 | userAgent: null, 8 | status: '' 9 | }, 10 | action: Action 11 | ) => { 12 | const { type, payload } = action 13 | switch (type) { 14 | case NEW_ACCOUNT: 15 | return { 16 | ...state, 17 | sipAccount: action.payload 18 | } 19 | case NEW_USERAGENT: 20 | return { 21 | ...state, 22 | userAgent: payload 23 | } 24 | default: 25 | return state 26 | } 27 | } 28 | 29 | export default sipAccounts 30 | -------------------------------------------------------------------------------- /dist/models.d.ts: -------------------------------------------------------------------------------- 1 | export interface SipCredentials { 2 | sipuri: string; 3 | password: string; 4 | } 5 | export interface SipConfig { 6 | websocket: string; 7 | video: boolean; 8 | iceServers: Array; 9 | defaultCountryCode: string; 10 | noAnswerTimeout: number; 11 | } 12 | export interface PhoneConfig { 13 | disabledButtons: Array; 14 | disabledFeatures: Array; 15 | defaultDial: string; 16 | sessionsLimit: number; 17 | attendedTransferLimit: number; 18 | autoAnswer: boolean; 19 | } 20 | export interface AppConfig { 21 | mode: string; 22 | started: boolean; 23 | appSize: string; 24 | } 25 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { SipConfig, SipCredentials, PhoneConfig, AppConfig } from './models'; 3 | interface Props { 4 | width: number; 5 | height: number; 6 | name: string; 7 | phoneConfig: PhoneConfig; 8 | sipCredentials: SipCredentials; 9 | sipConfig: SipConfig; 10 | appConfig: AppConfig; 11 | containerStyle: any; 12 | } 13 | export declare const phoneStore: import("redux").Store> & { 14 | dispatch: unknown; 15 | }; 16 | export declare const ReactSipPhone: ({ name, phoneConfig, sipConfig, appConfig, sipCredentials, containerStyle }: Props) => JSX.Element; 17 | export {}; 18 | -------------------------------------------------------------------------------- /dist/components/phone/Dialpad.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Session } from 'sip.js'; 3 | interface Props { 4 | open: boolean; 5 | session: Session; 6 | deviceId: string; 7 | } 8 | declare class Dialpad extends React.Component { 9 | topRow: any; 10 | middleRow: any; 11 | bottomRow: any; 12 | constructor(props: Props); 13 | getButton(value: string): JSX.Element; 14 | handleClick(value: string): void; 15 | sendDTMF(value: string): void; 16 | render(): JSX.Element; 17 | } 18 | declare const _default: import("react-redux").ConnectedComponent & Props, "ref" | "key" | "session" | "open">>; 19 | export default _default; 20 | -------------------------------------------------------------------------------- /src/store/configureStore.ts: -------------------------------------------------------------------------------- 1 | import { composeWithDevTools } from 'redux-devtools-extension' 2 | import { createStore, applyMiddleware } from 'redux' 3 | import thunk from 'redux-thunk' 4 | import { persistStore, persistReducer } from 'redux-persist' 5 | import storage from 'redux-persist/lib/storage' 6 | import reducers from '../reducers/index' 7 | 8 | const middleware = [thunk] 9 | 10 | const persistConfig = { 11 | key: 'root', 12 | storage, 13 | whitelist: ['device'] 14 | } 15 | 16 | const persistedReducer = persistReducer(persistConfig, reducers) 17 | 18 | export const defaultStore = createStore( 19 | persistedReducer, 20 | composeWithDevTools(applyMiddleware(...middleware)) 21 | ) 22 | export const persistor = persistStore(defaultStore) 23 | -------------------------------------------------------------------------------- /dist/components/phone/Incoming.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Invitation } from 'sip.js'; 3 | interface Props { 4 | session: Invitation; 5 | autoanswer: boolean; 6 | acceptCall: Function; 7 | declineCall: Function; 8 | } 9 | declare class Incoming extends React.Component { 10 | private timer; 11 | componentDidMount(): void; 12 | componentWillUnmount(): void; 13 | handleAccept(): void; 14 | handleAutoAnswer(): void; 15 | handleDecline(): void; 16 | render(): JSX.Element; 17 | } 18 | declare const _default: import("react-redux").ConnectedComponent & Props, "ref" | "key" | "session" | "autoanswer">>; 19 | export default _default; 20 | -------------------------------------------------------------------------------- /dist/components/phone/Mute.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Session, UserAgent } from 'sip.js'; 3 | interface Props { 4 | session: Session; 5 | userAgent: UserAgent; 6 | muteRequest: Function; 7 | muteSuccess: Function; 8 | muteFail: Function; 9 | unMuteRequest: Function; 10 | unMuteSuccess: Function; 11 | unMuteFail: Function; 12 | } 13 | declare class Mute extends React.Component { 14 | state: { 15 | onMute: boolean; 16 | }; 17 | mute(): Promise | undefined; 18 | render(): JSX.Element; 19 | } 20 | declare const _default: import("react-redux").ConnectedComponent & Props, "ref" | "key" | "session">>; 21 | export default _default; 22 | -------------------------------------------------------------------------------- /dist/components/phone/BlindTransfer.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Session, UserAgent } from 'sip.js'; 3 | import SIPAccount from '../../lib/SipAccount'; 4 | interface Props { 5 | session: Session; 6 | sipAccount: SIPAccount; 7 | userAgent: UserAgent; 8 | destination: string; 9 | blindTransferRequest: Function; 10 | blindTransferSuccess: Function; 11 | blindTransferFail: Function; 12 | } 13 | declare class BlindTransfer extends React.Component { 14 | blindTransferCall(): void; 15 | render(): JSX.Element; 16 | } 17 | declare const _default: import("react-redux").ConnectedComponent & Props, "ref" | "key" | "session" | "destination">>; 18 | export default _default; 19 | -------------------------------------------------------------------------------- /src/components/Dialstring.scss: -------------------------------------------------------------------------------- 1 | @import '../styles.scss'; 2 | 3 | .dialButton { 4 | @include common-button; 5 | height: 40px; 6 | width: 40px; 7 | background: none; 8 | } 9 | 10 | .dialButtonStrict{ 11 | @include common-button; 12 | height: 80px; 13 | width: 80px; 14 | background: none; 15 | // background-color: #15c73b; 16 | } 17 | 18 | .dialInput { 19 | @include common-input; 20 | width: 100%; 21 | } 22 | 23 | .dialstringContainerStrict { 24 | display: flex; 25 | flex-direction: row; 26 | justify-content: space-between; 27 | width: 50%; 28 | padding: 0 0 0 60px; 29 | 30 | } 31 | 32 | .dialstringContainer { 33 | display: flex; 34 | flex-direction: row; 35 | justify-content: space-between; 36 | align-items: center; 37 | width: 100%; 38 | border: 2px solid $almost-black; 39 | border-radius: 100px; 40 | padding: 0 0 0 20px; 41 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react", 9 | "plugin:@typescript-eslint/eslint-recommended" 10 | ], 11 | "env": { 12 | "node": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "ecmaFeatures": { 17 | "legacyDecorators": true, 18 | "jsx": true 19 | } 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "16" 24 | } 25 | }, 26 | "rules": { 27 | "space-before-function-paren": 0, 28 | "react/prop-types": 0, 29 | "react/jsx-handler-names": 0, 30 | "react/jsx-fragments": 0, 31 | "react/no-unused-prop-types": 0, 32 | "import/export": 0 33 | // "no-unused-vars": [ 34 | // "off" 35 | // ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /dist/SipWrapper.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SipConfig, SipCredentials, PhoneConfig, AppConfig } from './models'; 3 | interface Props { 4 | sipCredentials: SipCredentials; 5 | sipConfig: SipConfig; 6 | phoneConfig: PhoneConfig; 7 | appConfig: AppConfig; 8 | setNewAccount: Function; 9 | setPhoneConfig: Function; 10 | setCredentials: Function; 11 | setAppConfig: Function; 12 | children: any; 13 | } 14 | declare class SipWrapper extends React.Component { 15 | componentDidMount(): void; 16 | initializeSip(): void; 17 | render(): JSX.Element; 18 | } 19 | declare const _default: import("react-redux").ConnectedComponent & Props, "sipCredentials" | "sipConfig" | "phoneConfig" | "appConfig" | "children" | "ref" | "key">>; 20 | export default _default; 21 | -------------------------------------------------------------------------------- /dist/reducers/config.d.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './models'; 2 | declare const config: (state: { 3 | uri: string; 4 | password: string; 5 | phoneConfig: {}; 6 | appConfig: { 7 | mode: string; 8 | started: boolean; 9 | appSize: string; 10 | }; 11 | } | undefined, action: Action) => { 12 | phoneConfig: any; 13 | uri: string; 14 | password: string; 15 | appConfig: { 16 | mode: string; 17 | started: boolean; 18 | appSize: string; 19 | }; 20 | } | { 21 | uri: any; 22 | password: any; 23 | phoneConfig: {}; 24 | appConfig: { 25 | mode: string; 26 | started: boolean; 27 | appSize: string; 28 | }; 29 | } | { 30 | appConfig: any; 31 | uri: string; 32 | password: string; 33 | phoneConfig: {}; 34 | }; 35 | export default config; 36 | -------------------------------------------------------------------------------- /dist/components/Dialstring.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import SIPAccount from '../lib/SipAccount'; 3 | import { PhoneConfig, SipConfig, AppConfig } from '../models'; 4 | interface Props { 5 | sipAccount: SIPAccount; 6 | phoneConfig: PhoneConfig; 7 | sipConfig: SipConfig; 8 | appConfig: AppConfig; 9 | sessions: Object; 10 | started: Boolean; 11 | sessionsLimitReached: Function; 12 | attendedTransfersList: Array; 13 | } 14 | declare class Dialstring extends React.Component { 15 | state: { 16 | currentDialString: string; 17 | }; 18 | handleDial(): void; 19 | checkDialstring(): boolean; 20 | render(): JSX.Element | null; 21 | } 22 | declare const D: import("react-redux").ConnectedComponent & Props, "sipConfig" | "phoneConfig" | "appConfig" | "ref" | "key">>; 23 | export default D; 24 | -------------------------------------------------------------------------------- /src/util/ToneManager.ts: -------------------------------------------------------------------------------- 1 | import TonePlayer from './TonePlayer' 2 | import { phoneStore } from '../index' 3 | 4 | class ToneManager { 5 | currentTone: any 6 | 7 | playRing(type: string) { 8 | const state = phoneStore.getState() 9 | // @ts-ignore 10 | const deviceId = state.device.primaryAudioOutput 11 | if (this.currentTone) { 12 | this.currentTone.stop() 13 | this.currentTone = undefined 14 | } 15 | if (type === 'ringback') { 16 | this.currentTone = new TonePlayer() 17 | this.currentTone.ringback(deviceId) 18 | } else if (type == 'ringtone') { 19 | this.currentTone = new TonePlayer() 20 | this.currentTone.ringtone(deviceId) 21 | } 22 | } 23 | 24 | stopAll() { 25 | if (this.currentTone) { 26 | this.currentTone.stop() 27 | this.currentTone = undefined 28 | } 29 | } 30 | } 31 | 32 | const toneManager = new ToneManager() 33 | export default toneManager 34 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sip-phone-example", 3 | "homepage": "https://JaonL.github.io/react-sip-phone", 4 | "version": "0.0.0", 5 | "private": true, 6 | "dependencies": { 7 | "react": "file:../node_modules/react", 8 | "react-dom": "file:../node_modules/react-dom", 9 | "react-scripts": "file:../node_modules/react-scripts", 10 | "react-sip-phone": "file:.." 11 | }, 12 | "scripts": { 13 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start", 14 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build", 15 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test", 16 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": "react-app" 20 | }, 21 | "browserslist": [ 22 | ">0.2%", 23 | "not dead", 24 | "not ie <= 11", 25 | "not op_mini all" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/util/hold.ts: -------------------------------------------------------------------------------- 1 | import { phoneStore } from '../index' 2 | import { 3 | SIPSESSION_HOLD_REQUEST, 4 | holdCallRequest 5 | } from '../actions/sipSessions' 6 | 7 | export const holdAll = (id: string) => { 8 | const state = phoneStore.getState() 9 | // @ts-ignore 10 | const onHolds = state.sipSessions.onHold 11 | // @ts-ignore 12 | const sessions = state.sipSessions.sessions 13 | for (const [sessionId, session] of Object.entries(sessions)) { 14 | if (onHolds.indexOf(sessionId) < 0 && sessionId !== id) { 15 | try { 16 | // @ts-ignore 17 | holdCallRequest(session) 18 | // dispatch here because class is not connected to redux actions 19 | phoneStore.dispatch({ 20 | type: SIPSESSION_HOLD_REQUEST, 21 | // @ts-ignore 22 | payload: session.id 23 | }) 24 | return 25 | } catch (err) { 26 | return 27 | } 28 | } 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /dist/reducers/sipSessions.d.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './models'; 2 | declare const sipSessions: (state: { 3 | sessions: {}; 4 | incomingCalls: never[]; 5 | stateChanged: number; 6 | onHold: never[]; 7 | attendedTransfers: never[]; 8 | } | undefined, action: Action) => { 9 | sessions: {}; 10 | incomingCalls: any[]; 11 | stateChanged: number; 12 | onHold: never[]; 13 | attendedTransfers: never[]; 14 | } | { 15 | sessions: {}; 16 | attendedTransfers: any[]; 17 | incomingCalls: never[]; 18 | stateChanged: number; 19 | onHold: never[]; 20 | } | { 21 | incomingCalls: never[]; 22 | sessions: any; 23 | stateChanged: number; 24 | onHold: never[]; 25 | attendedTransfers: never[]; 26 | } | { 27 | onHold: any[]; 28 | sessions: {}; 29 | incomingCalls: never[]; 30 | stateChanged: number; 31 | attendedTransfers: never[]; 32 | }; 33 | export default sipSessions; 34 | -------------------------------------------------------------------------------- /dist/reducers/device.d.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './models'; 2 | declare const device: (state: { 3 | audioInput: never[]; 4 | audioOutput: never[]; 5 | primaryAudioOutput: string; 6 | primaryAudioInput: string; 7 | sinkId: boolean; 8 | } | undefined, action: Action) => { 9 | audioInput: any; 10 | audioOutput: never[]; 11 | primaryAudioOutput: string; 12 | primaryAudioInput: string; 13 | sinkId: boolean; 14 | } | { 15 | audioOutput: any; 16 | audioInput: never[]; 17 | primaryAudioOutput: string; 18 | primaryAudioInput: string; 19 | sinkId: boolean; 20 | } | { 21 | primaryAudioOutput: any; 22 | audioInput: never[]; 23 | audioOutput: never[]; 24 | primaryAudioInput: string; 25 | sinkId: boolean; 26 | } | { 27 | primaryAudioInput: any; 28 | audioInput: never[]; 29 | audioOutput: never[]; 30 | primaryAudioOutput: string; 31 | sinkId: boolean; 32 | }; 33 | export default device; 34 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @import './variables'; 2 | 3 | @mixin common-button { 4 | border: 0; 5 | padding: 0; 6 | background: none; 7 | width: 60px; 8 | height: 60px; 9 | border-radius: 100px; 10 | transition: all 0.2s; 11 | transition-delay: 300ms; 12 | background-color: $primary-color; 13 | color: $almost-black; 14 | cursor: pointer; 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | align-items: center; 19 | font-size: 1em; 20 | &:focus { 21 | outline: none; 22 | box-shadow: none; 23 | } 24 | &:active { 25 | transition: none; 26 | opacity: 0.5; 27 | } 28 | } 29 | 30 | @mixin common-input { 31 | border: none; 32 | padding: none; 33 | &:focus { 34 | outline: none; 35 | } 36 | font-size: 1em; 37 | } 38 | 39 | .container { 40 | display: flex; 41 | flex-direction: column; 42 | justify-content: flex-start; 43 | align-items: center; 44 | height: 100%; 45 | font-family: Arial, Helvetica, sans-serif; 46 | } 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": [ 6 | "dom", 7 | "esnext" 8 | ], 9 | "moduleResolution": "node", 10 | "jsx": "react", 11 | "sourceMap": true, 12 | "declaration": true, 13 | "esModuleInterop": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "allowSyntheticDefaultImports": true, 22 | "target": "es5", 23 | "allowJs": true, 24 | "skipLibCheck": true, 25 | "strict": true, 26 | "forceConsistentCasingInFileNames": true, 27 | "resolveJsonModule": true, 28 | "isolatedModules": true, 29 | "noEmit": true 30 | }, 31 | "include": [ 32 | "src" 33 | ], 34 | "exclude": [ 35 | "node_modules", 36 | "dist", 37 | "example" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /dist/assets/settings-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/settings-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/components/Status.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PhoneConfig, AppConfig } from '../models'; 3 | interface Props { 4 | phoneConfig: PhoneConfig; 5 | appConfig: AppConfig; 6 | name: string; 7 | inputs: any; 8 | outputs: any; 9 | primaryInput: string; 10 | primaryOutput: string; 11 | setPrimaryInput: Function; 12 | setPrimaryOutput: Function; 13 | getInputAudioDevices: Function; 14 | getOutputAudioDevices: Function; 15 | sessions: any; 16 | sinkIdAllowed: boolean; 17 | } 18 | declare class Status extends React.Component { 19 | state: { 20 | settingsMenu: boolean; 21 | }; 22 | componentDidMount(): void; 23 | mapOptions(options: any): any; 24 | handleChangeDevice(type: string, id: string): void; 25 | render(): JSX.Element; 26 | } 27 | declare const _default: import("react-redux").ConnectedComponent & Props, "phoneConfig" | "appConfig" | "name" | "ref" | "key">>; 28 | export default _default; 29 | -------------------------------------------------------------------------------- /src/components/Status.scss: -------------------------------------------------------------------------------- 1 | @import './phone/Phone.scss'; 2 | 3 | .container { 4 | width: 100%; 5 | margin-top: 10px; 6 | height: 40px; 7 | display: flex; 8 | flex-direction: row; 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | 13 | .userString { 14 | padding: 2px; 15 | } 16 | 17 | .userStringLarge{ 18 | padding: 2px; 19 | font-size: 25px; 20 | } 21 | 22 | #settingsButton { 23 | @include common-button; 24 | background: none; 25 | height: 24px; 26 | width: 24px; 27 | padding: 2px; 28 | &.on { 29 | background-color: gray; 30 | } 31 | } 32 | 33 | #settingsMenu { 34 | flex-direction: column; 35 | transition: all 0.2s ease; 36 | height: 120px; 37 | width: 100%; 38 | justify-content: flex-start; 39 | &.closed { 40 | visibility: hidden; 41 | opacity: 0; 42 | height: 0; 43 | } 44 | } 45 | 46 | #dropdowns { 47 | width: 100%; 48 | } 49 | 50 | .dropdownRow { 51 | display: flex; 52 | flex-direction: row; 53 | margin: 5px 0 5px 0; 54 | } 55 | 56 | .dropdownIcon { 57 | padding: 0 5px 0 5px; 58 | } -------------------------------------------------------------------------------- /dist/components/phone/Phone.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Session, UserAgent } from 'sip.js'; 3 | import { PhoneConfig } from '../../models'; 4 | interface Props { 5 | session: Session; 6 | userAgent: UserAgent; 7 | endCall: Function; 8 | setAppConfigStarted: Function; 9 | phoneConfig: PhoneConfig; 10 | deviceId: string; 11 | strictMode: string; 12 | appSize: string; 13 | } 14 | declare class Phone extends React.Component { 15 | state: { 16 | dialpadOpen: boolean; 17 | transferMenu: boolean; 18 | ended: boolean; 19 | transferDialString: string; 20 | attendedTransferStarted: boolean; 21 | duration: number; 22 | counterStarted: boolean; 23 | }; 24 | constructor(props: any); 25 | componentDidMount(): void; 26 | componentDidUpdate(newProps: Props): void; 27 | endCall(): void; 28 | attendedProcess(bool: boolean): void; 29 | handleCounter(): void; 30 | render(): JSX.Element; 31 | } 32 | declare const _default: import("react-redux").ConnectedComponent & Props, "phoneConfig" | "ref" | "key" | "session">>; 33 | export default _default; 34 | -------------------------------------------------------------------------------- /src/reducers/device.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './models' 2 | import { 3 | AUDIO_INPUT_DEVICES_DETECTED, 4 | AUDIO_OUTPUT_DEVICES_DETECTED, 5 | SET_PRIMARY_INPUT, 6 | SET_PRIMARY_OUTPUT, 7 | AUDIO_SINKID_NOT_ALLOWED 8 | } from '../actions/device' 9 | 10 | const device = ( 11 | state = { 12 | audioInput: [], 13 | audioOutput: [], 14 | primaryAudioOutput: 'default', 15 | primaryAudioInput: 'default', 16 | sinkId: true 17 | }, 18 | action: Action 19 | ) => { 20 | const { type, payload } = action 21 | switch (type) { 22 | case AUDIO_INPUT_DEVICES_DETECTED: 23 | return { 24 | ...state, 25 | audioInput: payload 26 | } 27 | case AUDIO_OUTPUT_DEVICES_DETECTED: 28 | return { 29 | ...state, 30 | audioOutput: payload 31 | } 32 | case SET_PRIMARY_OUTPUT: 33 | return { 34 | ...state, 35 | primaryAudioOutput: payload 36 | } 37 | case SET_PRIMARY_INPUT: 38 | return { 39 | ...state, 40 | primaryAudioInput: payload 41 | } 42 | case AUDIO_SINKID_NOT_ALLOWED: 43 | return { 44 | ...state, 45 | sinkId: false 46 | } 47 | default: 48 | return state 49 | } 50 | } 51 | 52 | export default device 53 | -------------------------------------------------------------------------------- /dist/actions/config.d.ts: -------------------------------------------------------------------------------- 1 | import { PhoneConfig, AppConfig } from '../models'; 2 | export declare const SET_CREDENTIALS = "SET_CREDENTIALS"; 3 | export declare const SET_PHONE_CONFIG = "SET_PHONE_CONFIG"; 4 | export declare const SET_APP_CONFIG = "SET_APP_CONFIG"; 5 | export declare const STRICT_MODE_SHOW_CALL_BUTTON = "STRICT_MODE_SHOW_CALL_BUTTON"; 6 | export declare const STRICT_MODE_HIDE_CALL_BUTTON = "STRICT_MODE_HIDE_CALL_BUTTON"; 7 | export declare const ATTENDED_TRANSFER_LIMIT_REACHED = "ATTENDED_TRANSFER_LIMIT_REACHED"; 8 | export declare const SESSIONS_LIMIT_REACHED = "SESSIONS_LIMIT_REACHED"; 9 | export declare const setCredentials: (uri?: string, password?: string) => { 10 | type: string; 11 | payload: { 12 | uri: string; 13 | password: string; 14 | }; 15 | }; 16 | export declare const setPhoneConfig: (config: PhoneConfig) => { 17 | type: string; 18 | payload: PhoneConfig; 19 | }; 20 | export declare const setAppConfig: (config: AppConfig) => { 21 | type: string; 22 | payload: AppConfig; 23 | }; 24 | export declare const setAppConfigStarted: () => { 25 | type: string; 26 | }; 27 | export declare const setAppConfigCallEnded: () => { 28 | type: string; 29 | }; 30 | export declare const attendedTransferLimitReached: () => { 31 | type: string; 32 | }; 33 | export declare const sessionsLimitReached: () => { 34 | type: string; 35 | }; 36 | -------------------------------------------------------------------------------- /src/actions/config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import { PhoneConfig, AppConfig } from '../models' 3 | 4 | export const SET_CREDENTIALS = 'SET_CREDENTIALS' 5 | export const SET_PHONE_CONFIG = 'SET_PHONE_CONFIG' 6 | export const SET_APP_CONFIG = 'SET_APP_CONFIG' 7 | export const STRICT_MODE_SHOW_CALL_BUTTON = 'STRICT_MODE_SHOW_CALL_BUTTON' 8 | export const STRICT_MODE_HIDE_CALL_BUTTON = 'STRICT_MODE_HIDE_CALL_BUTTON' 9 | export const ATTENDED_TRANSFER_LIMIT_REACHED = 'ATTENDED_TRANSFER_LIMIT_REACHED' 10 | export const SESSIONS_LIMIT_REACHED = 'SESSIONS_LIMIT_REACHED' 11 | 12 | export const setCredentials = (uri: string = '', password: string = '') => { 13 | return { type: SET_CREDENTIALS, payload: { uri, password } } 14 | } 15 | 16 | export const setPhoneConfig = (config: PhoneConfig) => { 17 | return { type: SET_PHONE_CONFIG, payload: config } 18 | } 19 | 20 | export const setAppConfig = (config: AppConfig) => { 21 | return { type: SET_APP_CONFIG, payload: config } 22 | } 23 | 24 | export const setAppConfigStarted = () => { 25 | return { type: STRICT_MODE_SHOW_CALL_BUTTON } 26 | } 27 | 28 | export const setAppConfigCallEnded = () => { 29 | return { type: STRICT_MODE_HIDE_CALL_BUTTON } 30 | } 31 | 32 | export const attendedTransferLimitReached = () => { 33 | return { type: ATTENDED_TRANSFER_LIMIT_REACHED } 34 | } 35 | 36 | export const sessionsLimitReached = () => { 37 | return { type: SESSIONS_LIMIT_REACHED } 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-sip-phone 2 | 3 | > SIP phone component for use in react projects 4 | 5 | [![NPM](https://img.shields.io/npm/v/react-sip-phone.svg)](https://www.npmjs.com/package/react-sip-phone) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save react-sip-phone 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```tsx 16 | import React, { Component } from 'react' 17 | 18 | import { ReactSipPhone } from 'react-sip-phone' 19 | import 'react-sip-phone/dist/index.css' 20 | 21 | class Example extends Component { 22 | render() { 23 | return 35 | } 36 | } 37 | ``` 38 | 39 | To make calls from outside of the component, import the phone's store and access the SIP Account once it is registered 40 | 41 | ```tsx 42 | import { ReactSipPhone, phoneStore } from 'react-sip-phone' 43 | class MyApp extends Component { 44 | 45 | makeCall(number) { 46 | const sipAccount = phoneStore.getState().sipAccounts.sipAccount 47 | if (sipAccount && number) { 48 | sipAccount.makeCall(number) 49 | } 50 | } 51 | 52 | render() { 53 | return 54 | } 55 | } 56 | ``` 57 | 58 | ## License 59 | 60 | MIT 61 | -------------------------------------------------------------------------------- /src/SipWrapper.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { SipConfig, SipCredentials, PhoneConfig, AppConfig } from './models' 3 | import SIPAccount from './lib/SipAccount' 4 | import { connect } from 'react-redux' 5 | import { setNewAccount } from './actions/sipAccounts' 6 | import { setPhoneConfig, setCredentials, setAppConfig } from './actions/config' 7 | 8 | // Wrapper component to do any initialization of the Sip connection. 9 | 10 | interface Props { 11 | sipCredentials: SipCredentials 12 | sipConfig: SipConfig 13 | phoneConfig: PhoneConfig 14 | appConfig: AppConfig 15 | setNewAccount: Function 16 | setPhoneConfig: Function 17 | setCredentials: Function 18 | setAppConfig: Function 19 | children: any 20 | } 21 | 22 | class SipWrapper extends React.Component { 23 | componentDidMount() { 24 | console.log('mounted') 25 | if (this.props.sipCredentials.password) { 26 | this.initializeSip() 27 | } 28 | } 29 | 30 | initializeSip() { 31 | const account = new SIPAccount( 32 | this.props.sipConfig, 33 | this.props.sipCredentials 34 | ) 35 | this.props.setNewAccount(account) 36 | this.props.setPhoneConfig(this.props.phoneConfig) 37 | this.props.setAppConfig(this.props.appConfig) 38 | } 39 | 40 | render() { 41 | return {this.props.children} 42 | } 43 | } 44 | const mapStateToProps = () => ({}) 45 | const actions = { 46 | setNewAccount, 47 | setPhoneConfig, 48 | setCredentials, 49 | setAppConfig 50 | } 51 | export default connect(mapStateToProps, actions)(SipWrapper) -------------------------------------------------------------------------------- /dist/components/phone/AttendedTransfer.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PhoneConfig } from '../../models'; 3 | import { Session, UserAgent } from 'sip.js'; 4 | import SIPAccount from '../../lib/SipAccount'; 5 | interface Props { 6 | session: Session; 7 | sipAccount: SIPAccount; 8 | userAgent: UserAgent; 9 | destination: string; 10 | started: Function; 11 | attendedTransferRequest: Function; 12 | attendedTransferCancel: Function; 13 | attendedTransferReady: Function; 14 | attendedTransferPending: Function; 15 | attendedTransferSuccess: Function; 16 | attendedTransferFail: Function; 17 | attendedTransferLimitReached: Function; 18 | holdCallRequest: Function; 19 | stateChange: Function; 20 | closeSession: Function; 21 | attendedTransfersList: Array; 22 | phoneConfig: PhoneConfig; 23 | } 24 | declare class AttendedTransfer extends React.Component { 25 | state: { 26 | attendedTransferSessionPending: null; 27 | attendedTransferSessionReady: null; 28 | }; 29 | attendedTransferCall(): void; 30 | attendedTransferClear(): void; 31 | connectAttendedTransfer(attendedTransferSession: any): void; 32 | cancelAttendedTransfer(attendedTransferSession: any): void; 33 | holdAll(): void; 34 | render(): JSX.Element; 35 | } 36 | declare const _default: import("react-redux").ConnectedComponent & Props, "ref" | "key" | "started" | "session" | "destination">>; 37 | export default _default; 38 | -------------------------------------------------------------------------------- /src/reducers/config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import { Action } from './models' 3 | import { 4 | SET_CREDENTIALS, 5 | SET_PHONE_CONFIG, 6 | SET_APP_CONFIG, 7 | STRICT_MODE_SHOW_CALL_BUTTON, 8 | STRICT_MODE_HIDE_CALL_BUTTON 9 | } from '../actions/config' 10 | 11 | const config = ( 12 | state = { 13 | uri: '', 14 | password: '', 15 | phoneConfig: {}, 16 | appConfig: { mode: '', started: false, appSize: '' } 17 | }, 18 | action: Action 19 | ) => { 20 | switch (action.type) { 21 | case SET_PHONE_CONFIG: 22 | return { 23 | ...state, 24 | phoneConfig: action.payload 25 | } 26 | case SET_CREDENTIALS: 27 | return { 28 | ...state, 29 | uri: action.payload.uri, 30 | password: action.payload.password, 31 | } 32 | case SET_APP_CONFIG: 33 | return { 34 | ...state, 35 | appConfig: action.payload 36 | } 37 | case STRICT_MODE_SHOW_CALL_BUTTON: 38 | if (state.appConfig.mode === 'strict') { 39 | return { 40 | ...state, 41 | appConfig:{ 42 | ...state.appConfig, 43 | mode: 'strict', 44 | started: true 45 | } 46 | } 47 | } 48 | return state 49 | case STRICT_MODE_HIDE_CALL_BUTTON: 50 | if (state.appConfig.mode === 'strict'){ 51 | return { 52 | ...state, 53 | appConfig:{ 54 | ...state.appConfig, 55 | mode: 'strict', 56 | started: false 57 | } 58 | } 59 | } 60 | return state 61 | default: 62 | return state 63 | } 64 | } 65 | 66 | export default config 67 | -------------------------------------------------------------------------------- /src/components/phone/Hold.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { connect } from 'react-redux' 3 | import styles from './Phone.scss' 4 | // eslint-disable-next-line no-unused-vars 5 | import { Session, UserAgent } from 'sip.js' 6 | import { holdCallRequest, unHoldCallRequest } from '../../actions/sipSessions' 7 | 8 | const holdIcon = require('./assets/phone_paused-24px.svg') 9 | 10 | interface Props { 11 | session: Session 12 | userAgent: UserAgent 13 | holdCallRequest: Function 14 | unHoldCallRequest: Function 15 | onHold: Array 16 | sessions: Array 17 | } 18 | 19 | class Hold extends React.Component { 20 | hold() { 21 | if (this.checkHoldState()) { 22 | this.props.unHoldCallRequest( 23 | this.props.session, 24 | this.props.onHold, 25 | this.props.sessions 26 | ) 27 | } else { 28 | this.props.holdCallRequest(this.props.session) 29 | } 30 | } 31 | 32 | checkHoldState() { 33 | return this.props.onHold.includes(this.props.session.id) 34 | } 35 | 36 | render() { 37 | return ( 38 | 45 | ) 46 | } 47 | } 48 | 49 | const mapStateToProps = (state: any) => ({ 50 | stateChanged: state.sipSessions.stateChanged, 51 | sessions: state.sipSessions.sessions, 52 | userAgent: state.sipAccounts.userAgent, 53 | onHold: state.sipSessions.onHold 54 | }) 55 | const actions = { 56 | holdCallRequest, 57 | unHoldCallRequest 58 | } 59 | 60 | export default connect(mapStateToProps, actions)(Hold) 61 | -------------------------------------------------------------------------------- /src/util/incomingSession.ts: -------------------------------------------------------------------------------- 1 | import { phoneStore } from '../index' 2 | import { SessionState, Session } from 'sip.js' 3 | import { SIPSESSION_STATECHANGE, CLOSE_SESSION } from '../actions/sipSessions' 4 | import { holdAll } from '../util/hold' 5 | import { setLocalAudio, setRemoteAudio, cleanupMedia } from './audio' 6 | 7 | export class IncomingSessionStateHandler { 8 | private incomingSession: Session 9 | constructor(incomingSession: Session) { 10 | this.incomingSession = incomingSession 11 | } 12 | 13 | public stateChange = (newState: SessionState) => { 14 | switch (newState) { 15 | case SessionState.Establishing: 16 | phoneStore.dispatch({ 17 | type: SIPSESSION_STATECHANGE 18 | }) 19 | break 20 | case SessionState.Established: 21 | phoneStore.dispatch({ 22 | type: SIPSESSION_STATECHANGE 23 | }) 24 | holdAll(this.incomingSession.id) 25 | setLocalAudio(this.incomingSession) 26 | setRemoteAudio(this.incomingSession) 27 | break 28 | case SessionState.Terminating: 29 | phoneStore.dispatch({ 30 | type: SIPSESSION_STATECHANGE 31 | }) 32 | cleanupMedia(this.incomingSession.id) 33 | break 34 | case SessionState.Terminated: 35 | phoneStore.dispatch({ 36 | type: SIPSESSION_STATECHANGE 37 | }) 38 | setTimeout(() => { 39 | phoneStore.dispatch({ 40 | type: CLOSE_SESSION, 41 | payload: this.incomingSession.id 42 | }) 43 | }, 5000) 44 | break 45 | default: 46 | console.log(`Unknown session state change: ${newState}`) 47 | break 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /dist/actions/device.d.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux'; 2 | export declare const AUDIO_INPUT_DEVICES_DETECTED = "AUDIO_INPUT_DEVICES_DETECTED"; 3 | export declare const AUDIO_OUTPUT_DEVICES_DETECTED = "AUDIO_OUTPUT_DEVICES_DETECTED"; 4 | export declare const REMOTE_AUDIO_CONNECTED = "REMOTE_AUDIO_CONNECTED"; 5 | export declare const REMOTE_AUDIO_FAIL = "REMOTE_AUDIO_FAIL"; 6 | export declare const LOCAL_AUDIO_CONNECTED = "LOCAL_AUDIO_CONNECTED"; 7 | export declare const LOCAL_AUDIO_FAIL = "REMOTE_AUDIO_FAIL"; 8 | export declare const SET_PRIMARY_OUTPUT = "SET_PRIMARY_OUTPUT"; 9 | export declare const SET_PRIMARY_INPUT = "SET_PRIMARY_INPUT"; 10 | export declare const SET_LOCAL_AUDIO_SESSIONS_PENDING = "SET_LOCAL_AUDIO_SESSIONS_PENDING"; 11 | export declare const SET_LOCAL_AUDIO_SESSION_SUCCESS = "SET_LOCAL_AUDIO_SESSION_SUCCESS"; 12 | export declare const SET_LOCAL_AUDIO_SESSION_FAIL = "SET_LOCAL_AUDIO_SESSION_FAIL"; 13 | export declare const SET_REMOTE_AUDIO_SESSIONS_PENDING = "SET_REMOTE_AUDIO_SESSIONS_PENDING"; 14 | export declare const SET_REMOTE_AUDIO_SESSION_SUCCESS = "SET_REMOTE_AUDIO_SESSION_SUCCESS"; 15 | export declare const SET_REMOTE_AUDIO_SESSION_FAIL = "SET_REMOTE_AUDIO_SESSION_FAIL"; 16 | export declare const AUDIO_SINKID_NOT_ALLOWED = "AUDIO_SINKID_NOT_ALLOWED"; 17 | export declare const getInputAudioDevices: () => { 18 | type: string; 19 | payload: Object[]; 20 | }; 21 | export declare const getOutputAudioDevices: () => { 22 | type: string; 23 | payload: Object[]; 24 | }; 25 | export declare const setPrimaryOutput: (deviceId: string, sessions: any) => (dispatch: Dispatch) => void; 26 | export declare const setPrimaryInput: (deviceId: string, sessions: any, sinkIdAllowed: boolean) => (dispatch: Dispatch) => void; 27 | export declare const sinkIdAllowed: () => (dispatch: Dispatch) => void; 28 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | 17 | 18 | 27 | react-sip-phone 28 | 29 | 30 | 31 | 34 | 35 |
36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/components/PhoneSessions.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { connect } from 'react-redux' 3 | import Phone from './phone/Phone' 4 | import Incoming from './phone/Incoming' 5 | import { PhoneConfig } from '../models' 6 | 7 | const getSessions = ( 8 | sessions: any, 9 | phoneConfig: PhoneConfig, 10 | attendedTransfers: Array, 11 | incomingCalls: Array 12 | ) => { 13 | const elements = [] 14 | for (const session in sessions) { 15 | if (attendedTransfers.includes(session)) continue 16 | if (incomingCalls.includes(session)) { 17 | if (Object.keys(sessions).length >= phoneConfig.sessionsLimit + incomingCalls.length){ 18 | console.log('Unable to create more sessions...') 19 | console.log('Check your phoneConfig.sessionsLimit option!') 20 | } else { 21 | elements.push() 22 | } 23 | } else { 24 | elements.push( 25 | 30 | ) 31 | } 32 | } 33 | return elements 34 | } 35 | 36 | interface Props { 37 | sessions: Object 38 | incomingCalls: Array 39 | phoneConfig: PhoneConfig 40 | attendedTransfers: Array 41 | } 42 | 43 | class PhoneSessions extends React.Component { 44 | render() { 45 | return ( 46 | 47 | {getSessions( 48 | this.props.sessions, 49 | this.props.phoneConfig, 50 | this.props.attendedTransfers, 51 | this.props.incomingCalls 52 | )} 53 | 54 | )} 55 | } 56 | const mapStateToProps = (state: any) => ({ 57 | sessions: state.sipSessions.sessions, 58 | incomingCalls: state.sipSessions.incomingCalls, 59 | attendedTransfers: state.sipSessions.attendedTransfers 60 | }) 61 | const PS = connect(mapStateToProps)(PhoneSessions) 62 | export default PS 63 | -------------------------------------------------------------------------------- /src/components/phone/BlindTransfer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import styles from './Phone.scss' 5 | 6 | import { Session, UserAgent } from 'sip.js' 7 | import { 8 | blindTransferRequest, 9 | blindTransferSuccess, 10 | blindTransferFail 11 | } from '../../actions/sipSessions' 12 | import { getFullNumber } from '../../util/sessions' 13 | import SIPAccount from '../../lib/SipAccount' 14 | 15 | const blindIcon = require('./assets/arrow_forward-24px.svg') 16 | 17 | interface Props { 18 | session: Session 19 | sipAccount: SIPAccount 20 | userAgent: UserAgent 21 | destination: string 22 | blindTransferRequest: Function 23 | blindTransferSuccess: Function 24 | blindTransferFail: Function 25 | } 26 | 27 | class BlindTransfer extends React.Component { 28 | blindTransferCall() { 29 | this.props.blindTransferRequest() 30 | const target = UserAgent.makeURI( 31 | `sip:${getFullNumber(this.props.destination)}@${this.props.sipAccount._credentials.sipuri.split('@')[1]};user=phone` 32 | ) 33 | if (target) { 34 | try { 35 | this.props.session.refer(target) 36 | this.props.blindTransferSuccess() 37 | } catch (err) { 38 | console.log(err) 39 | } 40 | } else { 41 | this.props.blindTransferFail() 42 | } 43 | } 44 | 45 | render() { 46 | return ( 47 | 48 | 54 | 55 | ) 56 | } 57 | } 58 | 59 | const mapStateToProps = (state: any) => ({ 60 | sipAccount: state.sipAccounts.sipAccount, 61 | stateChanged: state.sipSessions.stateChanged, 62 | sessions: state.sipSessions.sessions, 63 | userAgent: state.sipAccounts.userAgent 64 | }) 65 | const actions = { 66 | blindTransferRequest, 67 | blindTransferSuccess, 68 | blindTransferFail 69 | } 70 | 71 | export default connect(mapStateToProps, actions)(BlindTransfer) 72 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Provider } from 'react-redux' 3 | import { PersistGate } from 'redux-persist/integration/react' 4 | import styles from './styles.scss' 5 | import SipWrapper from './SipWrapper' 6 | import Status from './components/Status' 7 | import PhoneSessions from './components/PhoneSessions' 8 | import Dialstring from './components/Dialstring' 9 | import { SipConfig, SipCredentials, PhoneConfig, AppConfig } from './models' 10 | 11 | import { defaultStore, persistor } from './store/configureStore' 12 | 13 | interface Props { 14 | width: number 15 | height: number 16 | name: string 17 | phoneConfig: PhoneConfig 18 | sipCredentials: SipCredentials 19 | sipConfig: SipConfig 20 | appConfig: AppConfig 21 | containerStyle: any 22 | } 23 | 24 | export const phoneStore = defaultStore 25 | 26 | export const ReactSipPhone = ({ 27 | name, 28 | phoneConfig, 29 | sipConfig, 30 | appConfig, 31 | sipCredentials, 32 | containerStyle = {} 33 | }: Props) => { 34 | // If no store is passed into component, default store is used 35 | return ( 36 | 37 | 38 | 44 |
49 | 54 | {phoneConfig.disabledFeatures.includes('dialstring') ? null : ( 55 | 60 | )} 61 | 62 | 63 |
65 |
66 |
67 |
68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /src/components/phone/Phone.scss: -------------------------------------------------------------------------------- 1 | @import '../../variables'; 2 | @import '../../styles.scss'; 3 | 4 | @mixin phone-buttons { 5 | margin: 10px; 6 | } 7 | 8 | @mixin action-buttons { 9 | background-color: lightgray; 10 | margin: 10px; 11 | } 12 | 13 | @mixin flex-container { 14 | display: flex; 15 | flex-direction: row; 16 | flex-wrap: wrap; 17 | justify-content: center; 18 | align-items: center; 19 | } 20 | 21 | #incoming { 22 | @include flex-container; 23 | } 24 | 25 | #dialpad { 26 | display: flex; 27 | flex-direction: column; 28 | width: 100%; 29 | align-items: center; 30 | transition: all 0.3s ease, opacity 0.2s ease; 31 | height: 320px; 32 | &.closed { 33 | visibility: hidden; 34 | opacity: 0; 35 | height: 0; 36 | width: 0; 37 | } 38 | } 39 | 40 | .statusLarge{ 41 | padding: 2px; 42 | font-size: 25px; 43 | } 44 | 45 | .dialpadButton { 46 | @include common-button; 47 | @include phone-buttons; 48 | font-size: 1.5em; 49 | } 50 | 51 | .dialpadButtonLetters { 52 | font-size: 0.5em; 53 | color: $almost-black; 54 | } 55 | 56 | .dialpadRow { 57 | display: flex; 58 | flex-direction: row; 59 | } 60 | 61 | #actionButton { 62 | @include common-button; 63 | @include action-buttons; 64 | &.on { 65 | background-color: gray; 66 | } 67 | } 68 | 69 | .endCallButton { 70 | @include common-button; 71 | @include action-buttons; 72 | background-color: #ff6961; 73 | } 74 | 75 | .startCallButton { 76 | @include common-button; 77 | @include action-buttons; 78 | background-color: #15c73b; 79 | } 80 | 81 | .actionsContainer { 82 | @include flex-container; 83 | } 84 | 85 | #transferMenu { 86 | @include flex-container; 87 | transition: all 0.3s ease; 88 | &.closed { 89 | visibility: hidden; 90 | opacity: 0; 91 | height: 0; 92 | width: 0; 93 | } 94 | } 95 | 96 | #transferInput { 97 | @include common-input; 98 | border: 2px solid $almost-black; 99 | border-radius: 100px; 100 | width: 80%; 101 | padding: 5px 10px 5px 10px; 102 | } 103 | 104 | .transferButtons { 105 | @include common-button; 106 | @include phone-buttons; 107 | } -------------------------------------------------------------------------------- /src/components/phone/Dialpad.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import styles from './Phone.scss' 5 | import DialButton from './DialButton' 6 | import { Session, SessionState } from 'sip.js' 7 | import { getButtonLetters } from '../../util/buttons' 8 | import { playDTMF } from '../../util/TonePlayer' 9 | 10 | interface Props { 11 | open: boolean 12 | session: Session 13 | deviceId: string 14 | } 15 | 16 | class Dialpad extends React.Component { 17 | topRow: any = [] 18 | middleRow: any = [] 19 | bottomRow: any = [] 20 | 21 | constructor(props: Props) { 22 | super(props) 23 | for (let x = 1; x < 4; x++) { 24 | this.topRow.push(this.getButton(x.toString())) 25 | } 26 | for (let x = 4; x < 7; x++) { 27 | this.middleRow.push(this.getButton(x.toString())) 28 | } 29 | for (let x = 7; x < 10; x++) { 30 | this.bottomRow.push(this.getButton(x.toString())) 31 | } 32 | } 33 | 34 | getButton(value: string) { 35 | return ( 36 | this.handleClick(value)} 41 | /> 42 | ) 43 | } 44 | 45 | handleClick(value: string) { 46 | if (this.props.session.state === SessionState.Established) { 47 | this.sendDTMF(value) 48 | playDTMF(value, this.props.deviceId) 49 | } 50 | } 51 | 52 | sendDTMF(value: string) { 53 | const options = { 54 | requestOptions: { 55 | body: { 56 | contentDisposition: 'render', 57 | contentType: 'application/dtmf-relay', 58 | content: `Signal=${value}\r\nDuration=1000` 59 | } 60 | } 61 | } 62 | this.props.session.info(options) 63 | } 64 | 65 | render() { 66 | return ( 67 |
68 |
{this.topRow}
69 |
{this.middleRow}
70 |
{this.bottomRow}
71 |
72 | {this.getButton('*')} 73 | {this.getButton('0')} 74 | {this.getButton('#')} 75 |
76 |
77 | ) 78 | } 79 | } 80 | 81 | const mapStateToProps = (state: any) => ({ 82 | deviceId: state.device.primaryAudioOutput 83 | }) 84 | const actions = {} 85 | export default connect(mapStateToProps, actions)(Dialpad) 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sip-phone", 3 | "version": "1.0.0", 4 | "description": "SIP phone component for use in react projects", 5 | "author": "JaonL", 6 | "license": "MIT", 7 | "repository": "OpenTelecom/react-sip-phone", 8 | "main": "dist/index.js", 9 | "module": "dist/index.modern.js", 10 | "source": "src/index.tsx", 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "build": "microbundle-crl --no-compress --format modern,cjs", 16 | "start": "microbundle-crl watch --no-compress --format modern,cjs", 17 | "prepublish": "run-s build", 18 | "test": "run-s test:unit test:lint test:build", 19 | "test:build": "run-s build", 20 | "test:lint": "eslint .", 21 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom", 22 | "test:watch": "react-scripts test --env=jsdom", 23 | "predeploy": "cd example && npm install && npm run build", 24 | "deploy": "gh-pages -d example/build" 25 | }, 26 | "peerDependencies": { 27 | "react": "^16.0.0" 28 | }, 29 | "devDependencies": { 30 | "@types/events": "^3.0.0", 31 | "@types/jest": "^25.1.4", 32 | "@types/node-sass": "^4.11.1", 33 | "@types/react": "^16.9.27", 34 | "@types/react-redux": "^7.1.9", 35 | "@types/sip.js": "^0.12.0", 36 | "@typescript-eslint/eslint-plugin": "^2.26.0", 37 | "@typescript-eslint/parser": "^2.26.0", 38 | "babel-eslint": "^10.0.3", 39 | "cross-env": "^7.0.2", 40 | "eslint": "^6.8.0", 41 | "eslint-config-prettier": "^6.7.0", 42 | "eslint-config-standard": "^14.1.0", 43 | "eslint-config-standard-react": "^9.2.0", 44 | "eslint-plugin-import": "^2.18.2", 45 | "eslint-plugin-node": "^11.0.0", 46 | "eslint-plugin-prettier": "^3.1.1", 47 | "eslint-plugin-promise": "^4.2.1", 48 | "eslint-plugin-react": "^7.17.0", 49 | "eslint-plugin-standard": "^4.0.1", 50 | "gh-pages": "^2.2.0", 51 | "microbundle-crl": "github:JaonL/microbundle#master", 52 | "node-sass": "^4.14.1", 53 | "npm-run-all": "^4.1.5", 54 | "prettier": "^2.0.4", 55 | "react": "^16.13.1", 56 | "react-dom": "^16.13.1", 57 | "react-scripts": "^3.4.1" 58 | }, 59 | "files": [ 60 | "dist" 61 | ], 62 | "dependencies": { 63 | "@types/react-select": "^3.0.13", 64 | "react-redux": "^7.2.0", 65 | "react-select": "^3.1.0", 66 | "redux": "^4.0.5", 67 | "redux-devtools-extension": "^2.13.8", 68 | "redux-persist": "^6.0.0", 69 | "redux-thunk": "^2.3.0", 70 | "sip.js": "^0.16.1", 71 | "tone": "^13.8.25" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/util/audio.ts: -------------------------------------------------------------------------------- 1 | import { phoneStore } from '../index' 2 | import { Session } from 'sip.js' 3 | 4 | import { 5 | REMOTE_AUDIO_CONNECTED, 6 | REMOTE_AUDIO_FAIL, 7 | LOCAL_AUDIO_CONNECTED, 8 | AUDIO_SINKID_NOT_ALLOWED 9 | } from '../actions/device' 10 | 11 | // adds track from getReceiver stream to