diff --git a/README.md b/README.md index 2b26e10e..11807a61 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ A [PJSIP](http://www.pjsip.org/) module for React Native. ## Installation -- [iOS](https://github.com/datso/react-native-pjsip/blob/master/docs/installation_ios.md) -- [Android](https://github.com/datso/react-native-pjsip/blob/master/docs/installation_android.md) +- [iOS](/wiki/installation_ios.md) +- [Android](/wiki/installation_android.md) ## Usage @@ -92,10 +92,10 @@ endpoint.addListener("call_terminated", (newCall) => { ## API -1. [Startup](https://github.com/datso/react-native-pjsip/blob/master/docs/startup.md) -2. [Accounts](https://github.com/datso/react-native-pjsip/blob/master/docs/accounts.md) -3. [Calls](https://github.com/datso/react-native-pjsip/blob/master/docs/calls.md) -4. [Settings](https://github.com/datso/react-native-pjsip/blob/master/docs/settings.md) +1. [Startup](/wiki/startup.md) +2. [Accounts](/wiki/accounts.md) +3. [Calls](/wiki/calls.md) +4. [Settings](/wiki/settings.md) ## Demo The demo project is https://github.com/datso/react-native-pjsip-app. And you will need a SIP server. diff --git a/wiki/accounts.md b/wiki/accounts.md new file mode 100644 index 00000000..d50b484f --- /dev/null +++ b/wiki/accounts.md @@ -0,0 +1,80 @@ + +# Events + +All interaction from javascript to pjsip module is asynchromius. +So for each action, promise will be returned. + +# Create account + +``` +let configuration = { + "name": "John", + "username": "sip_username", + "domain": "pbx.carusto.com", + "password": "****", + "proxy": null, + "transport": null, // Default TCP + "regServer": null, // Default wildcard + "regTimeout": null // Default 3600 + "regHeaders": { + "X-Custom-Header": "Value" + }, + "regContactParams": ";unique-device-token-id=XXXXXXXXX", + "regOnAdd": false, // Default true, use false for manual REGISTRATION +}; + +let endpoint = new Endpoint(); +let state = await endpoint.start(); +let account = await endpoint.createAccont(configuration); + +// Do smth with account. For example wait until registration complete and make a call. +``` + +* There is no change account method. But this functionality is easy to implement by calling delete and create account methods. + + +# Remove account + +TODO: Description + +``` +let account = ...; +await endpoint.deleteAccont(account); + +await endpoint.deleteAccont(account); // There should be exception, bcs account already removed. +``` + + +# Events + + +## registration_changed + +TODO: Answer how much times it will be executed during lifetime, with examples. + +``` + +``` + + +Example: Forbidden + +Example: Invalid host + + + +.then((account) => { + console.log("Account: ", account); + + setTimeout(() => { + endpoint.registerAccount(account, true); + }, 10000); + + setTimeout(() => { + endpoint.registerAccount(account, false); + }, 20000); + }); + + + + diff --git a/wiki/android_notification_example.png b/wiki/android_notification_example.png new file mode 100644 index 00000000..77a3e29f Binary files /dev/null and b/wiki/android_notification_example.png differ diff --git a/wiki/android_sip_background.md b/wiki/android_sip_background.md new file mode 100644 index 00000000..b3fa4f03 --- /dev/null +++ b/wiki/android_sip_background.md @@ -0,0 +1,79 @@ +# Android background service + +In order to accept incoming calls while applicaiton in background you should set `notifications` property to `true` (true by default). +This will make PJSIP service run in the *foreground*, supplying the ongoing notification to be shown to the user while in this state. +Without foreground notification, Android could kill PJSIP service to reclaim more memory. + +![Android Pending Intent PjSip](android_notification_example.png) + +```javascript +let configuration = { + service: { + ua: Platform.select({ios: "Reachify iOS", android: "Reachify Android"}), // Default: React Native PjSip (version) + notifications: true, // Creates peding notification that will allow service work while your app in background + notifications: false, // Disables pending notification + notifications: { + account: true, + call: false // Disables only call notification + }, + notifications: { + account: { + title: "My cool react native app", // Default: account name + text: "Here we go", // Default: account registration status + info: null, + ticker: null, + smallIcon: null, + largeIcon: null + }, + call: { + title: "Active call", // Default: "Call in Progress - %Account Name%" + text: "John Doe", // Default: "%Caller Name% (%Number%)" + info: null, + ticker: null, // Default: "Call in Progress" + smallIcon: "icon_call", // Default: R.drawable.stat_sys_phone_call + largeIcon: null + } + } + }, + network: { + useAnyway: false, // Default: true + useWifi: true, // Default: true + use3g: true, // Default: false + useEdge: false, // Default: false + useGprs: false, // Default: false + useInRoaming: false, // Default: false + useOtherNetworks: true // Default: false + } +}; +let endpoint = new Endpoint(); +let state = await endpoint.start(configuration); +// ... +``` + +### smallIcon & largeIcon +To use own images for nofitications, copy them into `android/app/src/main/res/mipmap-XXXX/` and set thier names into `smallIcon` and `largeIcon` without extension. +For more info: [ui_guidelines/icon_design_status_bar](https://developer.android.com/guide/practices/ui_guidelines/icon_design_status_bar.html) + +### Handle clicks to call notifications + +Typically you should contain code that will change "route" in react-native app depending on result of `endpoint.start` command +```javascript +let state = await endpoint.start(configuration); +let calls = state.calls; // A list of active calls + +if (state.hasOwnProperty("notificationCallId")) { + for (let c of calls) { + if (c.getId() == state['notificationCallId']) { + route = {name:'call', call: c}; + break; + } + } +} + +//... + +// If true you should use slider instead of buttons for incoming call, because device was in sleep when this call comes. +if (state.notificationIsFromForeground) { + //... +} +``` diff --git a/wiki/calls.md b/wiki/calls.md new file mode 100644 index 00000000..f7d54d67 --- /dev/null +++ b/wiki/calls.md @@ -0,0 +1,122 @@ +TODO: Introduction + links to other sections. + +# Events + +All interaction from javascript to pjsip module is asynchronous. +So for each action, promise will be returned. + +## call_received + +TODO: Description + +## call_changed + +TODO: Description + +## call_terminated + +TODO: Description + + +# Actions + +## Initiate a call +To be able to make a call first of all you should createAccount, and pass account instance into Endpoint.makeCall function. +This function will return a promise that will be resolved when PjSIP initializes the call. + +``` +let options = { + headers: { + "P-Assserted-Identity": "Header example", + "X-UA": "React native" + } +} + +let call = await endpoint.makeCall(account, destination, options); +call.getId() // Use this id to detect changes and make actions + +endpoint.addListener("call_changed", (newCall) => { + if (call.getId() === newCall.getId()) { + // Our call changed, do smth. + } +} +endpoint.addListener("call_terminated", (newCall) => { + if (call.getId() === newCall.getId()) { + // Our call terminated + } +} +``` + +## Answer the call + +After answer there will be event "call_changed" that reflect the changes. +If there is already active call, it will be placed on hold (so expect "call_changed" event) + +``` +let options = {}; +let call = ...; +let promise = endpoint.answerCall(call, options); +promise.then(() => { + // Answer complete, expect that "call_changed" will be fired. +})); + +promise.catch(() => { + // Answer failed, show error +}); +``` + +## Hangup +Use this function when you have active call, and Decline for unanswered incoming calls. +After successul hangup, Endpoint should fire "call_terminated" event, use it to how final call duration and status. + +``` +let options = {}; +let call = ...; +await endpoint.hangupCall(call, options); +``` + +## Decline +Use this function when you have unanswered incoming call. +After successul decline, Endpoint should fire "call_terminated" event. + +``` +let options = {}; +let call = ...; +await endpoint.declineCall(call, options); +``` + +## Hold/Unhold + +TODO: Description +After successul hold/unhold, Endpoint should fire "call_changed" event, where `isHeld` should be false or true. + +``` +let options = {}; +let call = ...; + +await endpoint.holdCall(call, options); +await endpoint.unholdCall(call, options); +``` + +## Transfer + +TODO: Description + +``` +let options = {}; +let call = ...; + +await endpoint.xferCall(call, destination, options); +``` + +## DTMF + +TODO: Description + +``` +let options = {}; +let call = ...; +let key = "3"; + +await endpoint.dtmfCall(call, key, options); +``` diff --git a/wiki/installation_android.md b/wiki/installation_android.md new file mode 100644 index 00000000..ca9e94e4 --- /dev/null +++ b/wiki/installation_android.md @@ -0,0 +1,62 @@ +# Android installation + +## Step 1 +Add permissions & service to `android/app/src/main/AndroidManifest.xml` + +```xml + + + + + + + + + + + + +``` + +```xml + + ... + + ... + +``` + +## Step 2 +```bash +react-native link +``` + +## Additional step: Ability to answer incoming call without Lock Screen + +In `android/app/src/main/java/com/xxx/MainActivity.java` + +```java +import android.view.Window; +import android.view.WindowManager; +import android.os.Bundle; +... + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Window w = getWindow(); + w.setFlags( + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + ); + } +``` + +## If Android targetSdk >= 23 + +If your Android targetSdk is 23 or above you should grant `android.permission.RECORD_AUDIO` at runtime before making/receiving an audio call. + +To check and request Android permissions, please check out [react-native-android-permissions](https://github.com/lucasferreira/react-native-android-permissions). diff --git a/wiki/installation_ios.md b/wiki/installation_ios.md new file mode 100644 index 00000000..9269e1d4 --- /dev/null +++ b/wiki/installation_ios.md @@ -0,0 +1,66 @@ +# iOS installation + +## Step 1 +Link library + +```bash +react-native link +``` + +## Step 2 +Open project in xcode. +1. In the project build settings, make sure you have enabled All settings to be visible. +2. The Build Options are the 4th section down. Select *No* for the Enable Bitcode option. + +## Step 3 +Add permissions and capabilities to use microphone and camera by adding following lines to `ios/%PROJECT_NAME%/Info.plist` + +```xml +NSCameraUsageDescription +Video calls +NSMicrophoneUsageDescription +Audio calls +UIBackgroundModes + + audio + fetch + voip + +``` + +# PushNotifications + +To be able to receive incoming call when app in background you have to use PushKit. +This is the only way to receive information and wake up application to perform some action like use CallKit to show incoming call dialog. + +## How it works +1. Your application registers for receiving VoIP notifications +2. After a successful registration your application adds *device token* to Contact header when REGISTER. +3. Your SIP server should parse those headers and when someone calling to those user, server should also send push notification for those device(s). +4. When iOS application receives this PushNotificaiton it should show incoming call dialog via callkit, and register on server. +5. Server should send INVITE to new registration from this iOS device. +6. When user press Answer button via callkit, iOS application answers those SIP call. + +## Client side +When application starts, it should send REGISTER with additional attributes of `Contact` header and use a long term *registration timeout*. +In example bellow we use one month as regTimeout to be sure that our registration will not be expired when application goes to background. + +Your configuration might looks like this +```javascript +endpoint.createAccount({ + "username":"****", + "domain":"****", + "password":"****", + "regTimeout": 2592000, // one month + "regContactParams": ";app-id=****;pn-voip-tok=XXXXXXXXX;pn-im-tok=XXXXXXXXXX" +}) +``` + +## Server side +Your SIP server should support ability to send PushNotifications and also have addtional logic that re-send's INVITE messages during calling to user when new registration is available. +For example an working module for freeswitch consider using *mod_apn*. + +# CallKit + +Ensure that the Push Notification Capability is ON +TODO: Example \ No newline at end of file diff --git a/wiki/ios_push_notifications_callkit.md b/wiki/ios_push_notifications_callkit.md new file mode 100644 index 00000000..e425a159 --- /dev/null +++ b/wiki/ios_push_notifications_callkit.md @@ -0,0 +1,55 @@ +# PushNotifications + +To be able to receive incoming call when app in background you have to use PushKit. +This is the only way to receive information and wake up application to perform some action like use CallKit to show incoming call dialog. + +PushNotifications didn't work inside emualtor. + +## How it works +1. Your application registers for receiving VoIP notifications +2. After a successful registration your application adds *device token* to Contact header when REGISTER. +3. Your SIP server should parse those headers and when someone calling to those user, server should also send push notification for those device(s). +4. When iOS application receives this PushNotificaiton it should show incoming call dialog via callkit, and register on server. +5. Server should send INVITE to new registration from this iOS device. +6. When user press Answer button via callkit, iOS application answers those SIP call. + +## Client side +When application starts, it should send REGISTER with additional attributes of `Contact` header. + +Your configuration might looks like this +```javascript +endpoint.createAccount({ + "username":"****", + "domain":"****", + "password":"****", + "regContactParams": ";app-id=****;pn-voip-tok=XXXXXXXXX;pn-im-tok=XXXXXXXXXX" +}) +``` + +By using react-native-voip-nitifications +1. Register for *VoIP* notifications. +2. Obtain `Device token` +2. Send device token in `Contact` header options by using `contactUriParams` property of account configuration. + +Ensure that the Push Notification Capability is ON + +## Server side +Your SIP server should support ability to send PushNotifications and also have addtional logic that re-send's INVITE messages during calling to user when new registration is available. +For example an working module for freeswitch consider using *mod_apn*. + +## Background mode + +When your application goes to background mode it should send UNREGISTER to ensure that PJSIP will send NEW REGISTRATION when VoIP notification will be received from server. +When new registration + +# CallKit + + + + +CallKit app receives an incoming call while it is in the background, the system's native incoming call UI will be shown. + + + + +TODO: Example \ No newline at end of file diff --git a/wiki/settings.md b/wiki/settings.md new file mode 100644 index 00000000..6b2fb2e3 --- /dev/null +++ b/wiki/settings.md @@ -0,0 +1,32 @@ +# Settings + + +# Connectivity settings +TODO + +# Network settings +TODO + + +# Codecs settings +Print codec settings +```javascript +let endpoint = new Endpoint(); +let state = await endpoint.start(); +let {accounts, calls, settings, connectivity} = state; + +console.log("codecs", settings.codecs); // Shows a list of available codecs with priority +``` + +Change codec configuration +```javascript +// Not listed codecs are automatically will have zero priority +endpoint.changeCodecSettings({ + "PCMA/8000/1": 0, + "G722/16000/1": 0, // Zero means to disable the codec. + "iLBC/8000/1": 210, + "speex/8000/1": 0, + "speex/16000/1": 0, + "speex/32000/1": 0 +}) +``` \ No newline at end of file diff --git a/wiki/startup.md b/wiki/startup.md new file mode 100644 index 00000000..39c3b240 --- /dev/null +++ b/wiki/startup.md @@ -0,0 +1,19 @@ + +# Initialization + +First of all you have to initialize module to be able to work with it. + +There are some interesting moment in initialization. +When application goes to background, PJSIP module is still working and able to receive calls, but your javascipt is totally suspended. +When User open your application, javascript start to work and now your js application need to know what status have your account or may be you have pending incoming call. + +So thats why first step should call start method for pjsip module. + +``` +let endpoint = new Endpoint(); +let state = await endpoint.start(); +let {accounts, calls} = state; +``` + +It works in background because in Android where is a service PjSip service, that you included in AndroidManifest.xml. +TODO: Describe how it works on iOS.