{"id":24992768,"url":"https://github.com/cybex-dev/twilio_voice","last_synced_at":"2025-04-05T12:04:32.686Z","repository":{"id":37817469,"uuid":"344644840","full_name":"cybex-dev/twilio_voice","owner":"cybex-dev","description":"Flutter Twilio Voice Plugin","archived":false,"fork":false,"pushed_at":"2025-03-26T07:01:32.000Z","size":1566,"stargazers_count":44,"open_issues_count":51,"forks_count":96,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-29T11:06:18.046Z","etag":null,"topics":["apns","callkit","connection-service","fcm","flutter","flutter-package","flutter-plugin","flutter-ui","flutter-web","javascript","kotlin","swift","twilio","twilio-voice","voip"],"latest_commit_sha":null,"homepage":"https://twilio-voice-web.web.app/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cybex-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-05T00:12:29.000Z","updated_at":"2025-03-26T07:01:36.000Z","dependencies_parsed_at":"2023-10-15T10:57:32.913Z","dependency_job_id":"2fbcdc73-f5be-4dc5-b334-5fbdfb998fd1","html_url":"https://github.com/cybex-dev/twilio_voice","commit_stats":null,"previous_names":["diegogarciar/twilio_voice"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybex-dev%2Ftwilio_voice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybex-dev%2Ftwilio_voice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybex-dev%2Ftwilio_voice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybex-dev%2Ftwilio_voice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cybex-dev","download_url":"https://codeload.github.com/cybex-dev/twilio_voice/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332601,"owners_count":20921853,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apns","callkit","connection-service","fcm","flutter","flutter-package","flutter-plugin","flutter-ui","flutter-web","javascript","kotlin","swift","twilio","twilio-voice","voip"],"created_at":"2025-02-04T13:57:39.271Z","updated_at":"2025-04-05T12:04:32.667Z","avatar_url":"https://github.com/cybex-dev.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# twilio_voice\n\nProvides an interface to Twilio's Programmable Voice SDK to allow voice-over-IP (VoIP) calling into\nyour Flutter applications.\n~~This plugin was taken from the original `flutter_twilio_voice` as it seems that plugin is no longer maintained, this one is.~~  Project ownership \u0026 maintenance handed over by [diegogarcia](https://github.com/diegogarciar). For the foreseeable future, I'll be actively maintaining this project.\n\n#### 🐞Bug? Issue? Something odd?\n\nReport it [here](https://github.com/cybex-dev/twilio_voice/issues/new?assignees=\u0026labels=type:Bug,status:Unconfirmed\u0026projects=\u0026template=BUG_REPORT.md\u0026title=).\n\n#### 🚀 Feature Requests?\n\nAny and all [Feature Requests](https://github.com/cybex-dev/twilio_voice/issues/new?assignees=\u0026labels=type:Enhancement\u0026projects=\u0026template=FEATURE_REQUEST.md\u0026title=) or Pull Requests are gladly welcome!\n\n#### Live Example/Samples:\n\n- [Twilio Voice Web](https://twilio-voice-web.web.app/#/)\n\n*Currently, only Web sample is provided. If demand arises for a Desktop or Mobile builds, I'll throw one up on the relevant store/app provider or make one available.*\n\n## Features\n\n- Receive and place calls from iOS devices, uses Callkit to receive calls (Twilio Voice SDK [v6.13.0](https://www.twilio.com/docs/voice/sdks/ios/changelog#6130)).\n- Receive and place calls from Android devices, uses ~~custom UI~~ native call screen to receive calls (via a `ConnectionService` impl) (Twilio Voice SDK [v6.9.0](https://www.twilio.com/docs/voice/sdks/android/3x-changelog#690)).\n- Receive and place calls from Web (FCM push notification integration not yet supported by Twilio Voice Web, see [here](https://github.com/twilio/twilio-voice.js/pull/159#issuecomment-1551553299) for discussion)\n- Receive and place calls from MacOS devices, uses custom UI to receive calls (in future \u0026 macOS\n  13.0+, we'll be using CallKit).\n- Interpret TwiML parameters to populate UI, see below [Interpreting Parameters](#interpreting-parameters)\n\n### Feature addition schedule:\n\n- Audio device selection support (select input/output audio devices, on-hold)\n- Update plugin to Flutter federated packages (step 1 of 2 with Web support merge)\n- Desktop platform support (implementation as JS wrapper/native implementation, Windows/Linux to start development)\n\n### Android Limitations\n\n~~As iOS has CallKit, an Apple provided UI for answering calls, there is no default UI for android to\nreceive calls, for this reason a default UI was made. To increase customization, the UI will use a\nsplash_icon.png registered on your res/drawable folder. I haven't found a way to customize colors,\nif you find one, please submit a pull request.~~\n\nAndroid provides a native UI by way of the `ConnectionService`. Twilio has made an attempt a [ConnectionService](https://github.com/twilio/voice-quickstart-android/tree/master/app/src/connection_service) implementation however it is fully realized in this package.\n\n### macOS Limitations\n\n1. CallKit support is found in macOS 13.0+ which there is no support for yet. In future, this will\n   be taken into consideration for feature development.\n2. Twilio Voice does not offer a native SDK for macOS, so we're using the Twilio Voice Web SDK (\n   twilio-voice.js, v2.4.1-dev) to provide the functionality. This is a temporary solution until (or\n   even if) Twilio Voice SDK for macOS is released.\n\nThis limits macOS to not support remote push notifications `.voip` and `.apns` as the web SDK does\nnot support this. Instead, it uses a web socket connection to listen for incoming calls, arguably\nmore efficient vs time but forces the app to be open at all times to receive incoming calls.\n\n## Getting Started\n\nFirst, add the package to your `pubspec.yaml` file:\n\n```yaml\ndependencies:\n  ...\n  twilio_voice: ^0.2.0+1\n```\n\nThen run `flutter pub get` in your terminal.\n\nPlease follow Twilio's quickstart setup for each platform, you don't need to write the native code\nbut it will help you understand the basic functionality of setting up your server, registering your\niOS app for VOIP, etc.\n\n### iOS Setup\n\nTo customize the icon displayed on a CallKit call, Open XCode and add a png icon named '\ncallkit_icon' to your assets.xassets folder\n\nsee [[Notes]](https://github.com/diegogarciar/twilio_voice/blob/master/NOTES.md#ios--macos) for more information\n\n### macOS Setup\n\nDrop in addition.\n\nsee [[Limitations]](https://github.com/diegogarciar/twilio_voice/blob/master/NOTES.md#macos) and [[Notes]](https://github.com/diegogarciar/twilio_voice/blob/master/NOTES.md#ios--macos) for more information.\n\n### Android Setup:\n\nFirstly, ensure you place this in your app's proguard-rules.pro file:\n```proguard\n# Twilio Programmable Voice\n-keep class com.twilio.** { *; }\n-keep class tvo.webrtc.** { *; }\n-dontwarn tvo.webrtc.**\n-keep class com.twilio.voice.** { *; }\n-keepattributes InnerClasses\n```\n\nnext, register in your `AndroidManifest.xml` the service in charge of displaying incoming call\nnotifications:\n\n```xml\n\u003cApplication\u003e\n .....\n \u003cservice\n android:name=\"com.twilio.twilio_voice.fcm.VoiceFirebaseMessagingService\"\n android:stopWithTask=\"false\"\u003e\n\u003cintent-filter\u003e \u003caction android:name=\"com.google.firebase.MESSAGING_EVENT\" /\u003e\n\u003c/intent-filter\u003e \u003c/service\u003e\n```\n\n#### Phone Account\n\nTo register a Phone Account, request access to `READ_PHONE_NUMBERS` permission first:\n\n```dart\nTwilioVoice.instance.requestReadPhoneNumbersPermission();  // Gives Android permissions to read Phone Accounts\n```\n\nthen, register the `PhoneAccount` with:\n\n```dart\nTwilioVoice.instance.registerPhoneAccount();\n```\n\n#### Enable calling account\n\nTo open the `Call Account` settings, use the following code:\n\n```dart\nTwilioVoice.instance.openPhoneAccountSettings();\n```\n\nCheck if it's enabled with:\n\n```dart\nTwilioVoice.instance.isPhoneAccountEnabled();\n```\n\n#### Calling with ConnectionService\n\nPlacing a call with Telecom app via Connection Service requires a `PhoneAccount` to be registered. See [Phone Account](#phone-account) above for more information.\n\nFinally, to grant access to place calls, run:\n\n```dart\nTwilioVoice.instance.requestCallPhonePermission();  // Gives Android permissions to place calls\n```\n\nSee [Customizing the Calling Account](#customizing-the-calling-account) for more information.\n\n#### Enabling the ConnectionService\n\nTo enable the `ConnectionService` and make/receive calls, run:\n\n```dart\nTwilioVoice.instance.requestReadPhoneStatePermission();  // Gives Android permissions to read Phone State\n```\n\nHighly recommended to review the notes for **Android**. See [[Notes]](https://github.com/diegogarciar/twilio_voice/blob/master/NOTES.md#android) for more information.\n\n#### Customizing the Calling Account\n\nTo customize the `label` and `shortDescription` of the calling account, add the following in your `res/values/strings.xml`:\n\n```xml\n\u003cstring name=\"phone_account_name\" translatable=\"false\"\u003eExample App\u003c/string\u003e\n\u003cstring name=\"phone_account_desc\" translatable=\"false\"\u003eExample app voice calls calling account\u003c/string\u003e\n```\n\nThis can be found in alternatively the Phone App's settings, `Other/Advanced Call Settings -\u003e Calling Accounts -\u003e (Example App)` (then toggle the switch)\n\n![enter image description here](https://camo.githubusercontent.com/f483d950b603c08d07f566849b06c489ef8331919b8b50b6cb5b94f92d2a29be/68747470733a2f2f692e696d6775722e636f6d2f366d686a46575a2e676966)\n\nSee [example](https://github.com/cybex-dev/twilio_voice/blob/master/example/android/app/src/main/res/values/strings.xml) for more details\n\n#### Known Issues\n\n##### Bluetooth, Telecom App Crash\n\n- Upon accepting an inbound call, at times the Telecom app/ Bluetooth service will crash and restart. This is a known bug, caused by `Class not found when unmarshalling: com.twilio.voice.CallInvite`. This is due to the Telecom service not using the same Classloader as the main Flutter app. See [here](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/telecomm/java/android/telecom/Call.java#2466) for source of error.\n- Callback action on post dialer screen may not work as expected - this is platform and manufacturer specific. PRs are welcome here.\n- Complete integration with showing missed calls. This is a work in progress.\n\n### Web Setup:\n\nThere are 4 important files for Twilio incoming/missed call notifications to work:\n\n- `notifications.js` is the main file, it handles the notifications and the service worker.\n- `twilio-sw.js` is the service worker _content_ used to work with the default `flutter_service_worker.js` (this can be found in `build/web/flutter_service_worker.js` after calling `flutter build web`). This file's contents are to be copied into the `flutter_service_worker.js` file after you've built your application.\n\nAlso, the twilio javascript SDK itself, `twilio.min.js` is needed.\n\n### To ensure proper/as intended setup:\n\n1. Copy files `example/web/notifications.js` and `example/web/twilio.min.js` into your application's `web` folder.\n2. This step should be done AFTER you've built your application, every time the `flutter_service_worker.js` changes (this includes hot reloads on your local machine unfortunately)\n   1. Copy the contents of `example/web/twilio-sw.js` into your `build/web/flutter_service_worker.js` file, **at the end of the file**. See [service-worker](#service-worker) for more information.\n\nNote, these files can be changed to suite your needs - however the core functionality should remain the same: responding to `notificationclick`, `notificationclose`, `message` events and associated sub-functions.\n\nFinally, add the following code to your `index.html` file, **at the end of body tag**:\n\n```html\n    \u003cbody\u003e\n        \u003c!--Start Twilio Voice impl--\u003e\n        \u003c!--twilio native js library--\u003e\n        \u003cscript type=\"text/javascript\" src=\"./twilio.min.js\"\u003e\u003c/script\u003e\n        \u003c!--End Twilio Voice impl--\u003e\n\n        \u003cscript\u003e\n            window.addEventListener('load', function(ev) {\n              // Download main.dart.js\n              ...          \n    \u003c/body\u003e\n```\n\n#### Web Considerations\n\n_If you need to debug the service worker, open up Chrome Devtools, go to Application tab, and select Service Workers from the left menu. There you can see the service workers and their status.\nTo review service worker `notificationclick`, `notificationclose`, `message`, etc events - do this using Chrome Devtools (Sources tab, left panel below 'site code' the service workers are listed)_\n\n##### Service Worker\n\nUnifying the service worker(s) is best done via post-compilation tools or a CI/CD pipeline (suggested).\n\nA snippet of the suggested service worker integration is as follows:\n\n```yaml\n#...\n- run: cd ./example; flutter build web --release --target=lib/main.dart --output=build/web\n\n- name: Update service worker\n  run: cat ./example/web/twilio-sw.js \u003e\u003e ./example/build/web/flutter_service_worker.js\n#...\n```\n\nA complete example could be found in the github workflows `.github/workflows/flutter.yml` file, see [here](https://github.com/cybex-dev/twilio_voice/blob/master/.github/workflows/flutter.yml). \n\n##### Web Notifications\n\n2 types of notifications are shown:\n - Incoming call notifications with 2 buttons: `Answer` and `Reject`,\n - Missed call notifications with 1 button: `Call back`.\n\nNotifications are presented as **alerts**. These notifications may not always been shown, check:\n - if the browser supports notifications,\n - if the user has granted permissions to show notifications,\n - if the notifications display method / notifications is enabled by the system (e.g. macOS notifications are disabled, or Windows notifications are disabled, etc).\n - if there are already notifications shown (https://stackoverflow.com/a/36383155/4628115)\n - if system is in 'Do Not Disturb' or 'Focus' mode.\n\n### MacOS Setup:\n\nThe plugin is essentially a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview)\nwrapper. This makes macOS integration a drop-in solution.\n\nHowever, you'll need to:\n\n1. add the following to your `Info.plist` file:\n\n   ```xml\n   \u003ckey\u003eNSMicrophoneUsageDescription\u003c/key\u003e\n   \u003cstring\u003eAllow microphone access to make calls\u003c/string\u003e\n   ```\n2. include Hardened Runtime entitlements (this is required for App Store distributed MacOS apps):\n\n   ```xml\n   \u003ckey\u003ecom.apple.security.audio-input\u003c/key\u003e\n   \u003ctrue/\u003e\n\n   \u003c!--Optionally for bluetooth support/permissions--\u003e\n   \u003ckey\u003ecom.apple.security.device.bluetooth\u003c/key\u003e\n   \u003ctrue/\u003e\n   ```\n3. Lastly and most importantly, ensure the `index.html` and `twilio.min.js` is bundled inside of `twilio_voice` package (this\n   shouldn't be a problem, but just in case). Found in `twilio_voice.../.../Classes/Resources/*`.\n\nSee [this](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution#3087727)\nfor more information on preparing for publishing your macOS app\n\n### Usage\n\nThe plugin was separated into two classes, the `TwilioVoice.instance`\nand `TwilioVoice.instance.call`, the first one is in charge of general configuration and the second\none is in charge of managing calls.\n\nRegister iOS capabilities\n\n- Add Audio and Voice over IP in background modes\n\n### TwilioVoice.instance\n\n#### Setting the tokens\n\ncall `TwilioVoice.instance.setTokens` as soon as your app starts.\n\n- `accessToken` provided from your server, you can see an example cloud\n  function [here](https://github.com/diegogarciar/twilio_voice/blob/master/functions.js).\n- `deviceToken` is automatically handled on iOS, for android you need to pass a FCM token.\n\ncall `TwilioVoice.instance.unregister` to unregister from Twilio, if no access token is passed, it\nwill use the token provided in `setTokens` at the same session.\n\n### Call Identifier\n\nAs incoming call UI is shown in background and the App can even be closed when receiving the calls,\nyou can map call identifiers such as `firebaseAuth` userIds to real names, this operation must be\ndone before actually receiving the call. So if you have a chat app, and know the members names,\nregister them so when they call, the call UI can display their names and not their userIds.\n\n#### Registering a client\n\n```\nTwilioVoice.instance.registerClient(String clientId, String clientName)\n```\n\n#### Unregistering a client\n\n```\nTwilioVoice.instance.unregisterClient(String clientId)\n```\n\n#### Default caller\n\nYou can also set a default caller, such as \"unknown number\" or \"chat friend\" in case a call comes in\nfrom an unregistered client.\n\n```\nTwilioVoice.instance.setDefaultCallerName(String callerName)\n```\n\n### Call Events\n\nuse stream `TwilioVoice.instance.callEventsListener` to receive events from the TwilioSDK such as\ncall events and logs, it is a broadcast so you can listen to it on different parts of your app. Some\nevents might be missed when the app has not launched, please check out the example project to find\nthe workarounds.\n\nThe events sent are the following\n\n- incoming // web, MacOS only\n- ringing\n- connected\n- reconnected\n- reconnecting\n- callEnded\n- unhold\n- hold\n- unmute\n- mute\n- speakerOn\n- speakerOff\n- log\n- declined (based on Twilio Error codes, or remote abort)\n- answer\n- missedCall\n- returningCall\n- permission (Android only)\n\n### Interpreting Parameters\n\nAs a convenience, the plugin will interpret the TwiML parameters and send them as a map in the `CallInvite` or provided via `extraOptions` when creating the call. This is useful for passing additional information to the call screen and are prefixed with `__TWI`.\n\n- `__TWI_CALLER_ID` - caller id\n- `__TWI_CALLER_NAME` - caller name\n- `__TWI_CALLER_URL` - caller image/thumbnail url (not implemented/supported at the moment)\n- `__TWI_RECIPIENT_ID` - recipient id\n- `__TWI_RECIPIENT_NAME` - recipient name\n- `__TWI_RECIPIENT_URL` - recipient image/thumbnail url (not implemented/supported at the moment)\n- `__TWI_SUBJECT` - subject/additional info\n\nThese parameters above are interpreted as follows.\n\n#### Name resolution\n\nCaller is usually referred to as `call.from` or `callInvite.from`. This can either be a number of a string (with the format `client:clientName`) or null.\n\nThe following rules are applied to determine the caller/recipient name, which is shown in the call screen and heads-up notification:\n\n##### Incoming Calls:\n\n`__TWI_CALLER_NAME` -\u003e `resolve(__TWI_CALLER_ID)` -\u003e (phone number) -\u003e `registered client (from)` -\u003e `defaultCaller name` -\u003e `\"Unknown Caller\"`\n\n\n##### Outgoing Calls:\n\n`__TWI_RECIPIENT_NAME` -\u003e `resolve(__TWI_RECIPIENT_ID)` -\u003e (phone number) -\u003e `registered client (to)` -\u003e `defaultCaller name` -\u003e `\"Unknown Caller\"`\n\n**Details explaination:**\n\n- if the call is an CallInvite (incoming), the plugin will interpret the string as follows or if the call is outgoing, the twilio `To` parameter field is used to:\n  - if the `__TWI_CALLER_NAME` (or `__TWI_RECIPIENT_NAME`) parameter is provided, the plugin will show the value of `__TWI_CALLER_NAME` (or `__TWI_RECIPIENT_NAME`) as is, else\n  - if the `__TWI_CALLER_ID` (or `__TWI_RECIPIENT_ID`) parameter is provided, the plugin will search for a registered client with the same id and show the client name,\n- if the caller (`from` or `to` fields) is empty/not provided, the default caller name is shown e.g. \"Unknown Caller\", else\n- else if the caller (`from` or `to` fields) is a number, the plugin will show the number as is, else\n- else the plugin will search for a registered client with the `callInvite.from` (or call.to) value and show the client name, as a last resort\n  - the default caller name is shown e.g. \"Unknown Caller\"\n\n*Please note: the same approach applies to both caller and recipient name resolution.*\n\n#### Subject\n\nUsing the provided `__TWI_SUBJECT` parameter, the plugin will show the subject as is, else (depending on the platform and manufacturer), the plugin will show:\n\n- the caller name as the subject, or\n- the app name as the subject, or\n- the default subject \"Incoming Call\"\n\n## showMissedCallNotifications\n\nBy default a local notification will be shown to the user after missing a call, clicking on the\nnotification will call back the user. To remove this feature, set `showMissedCallNotifications`\nto `false`.\n\n### Calls\n\n#### Make a Call\n\n`from` your own identifier\n`to` the id you want to call\nuse `extraOptions` to pass additional variables to your server callback function.\n\n```\n await TwilioVoice.instance.call.place(from:myId, to: clientId, extraOptions);\n\n```\n\nThese translate to the your TwiML `event` function/service as:\n\n*javascript sample*\n\n```javascript\nexports.handler = function(context, event, callback) {\n    const from = event.From;\n    const to = event.To;\n    // event contains extraOptions as a key/value map\n\n    // your TwiML code...\n}\n```\n\nSee [Setting up the Application](#setting-up-the-application) for more information.\n\n*Please note: the hardcoded `To`, `From` may change in future.*\n\n#### Receiving Calls\n\n##### iOS\n\nReceives calls via [CallKit](https://developer.apple.com/documentation/callkit) integration. Make sure to review the [iOS Setup](#ios-setup) section for more information.\n\n##### Android\n\nReceives calls via [ConnectionService](https://developer.android.com/reference/android/telecom/ConnectionService) integration. Make sure to review the [Android Setup](#android-setup) section for more information.\n\n#### Mute a Call\n\n```\n TwilioVoice.instance.call.toggleMute(isMuted: true);\n\n```\n\n#### Toggle Speaker\n\n```\n TwilioVoice.instance.call.toggleSpeaker(speakerIsOn: true);\n\n```\n\n#### Hang Up\n\n```\n TwilioVoice.instance.call.hangUp();\n\n```\n\n#### Send Digits\n\n```\n TwilioVoice.instance.call.sendDigits(String digits);\n\n```\n\n### Permissions\n\n#### Microphone\n\nTo receive and place calls you need Microphone permissions, register the microphone permission in\nyour info.plist for iOS.\n\nYou can use `TwilioVoice.instance.hasMicAccess` and `TwilioVoice.instance.requestMicAccess` to check\nand request the permission. Permissions is also automatically requested when receiving a call.\n\n#### Background calls (Android only on some devices)\n\n~~Xiaomi devices, and maybe others, need a special permission to receive background calls.\nuse `TwilioVoice.instance.requiresBackgroundPermissions` to check if your device requires a special\npermission, if it does, show a rationale explaining the user why you need the permission. Finally\ncall\n`TwilioVoice.instance.requestBackgroundPermissions` which will take the user to the App Settings\npage to enable the permission.~~\n\nDeprecated in 0.10.0, as it is no longer needed. Custom UI has been replaced with native UI.\n\n#### ConnectionService \u0026 Native Phone Account (Android only)\n\nSimilar to CallKit on iOS, Android implements their own via a [ConnectionService](https://developer.android.com/reference/android/telecom/ConnectionService) integration. To make use of this, you'll need to request `CALL_PHONE` permissions via:\n\n```dart\nTwilioVoice.instance.requestCallPhonePermission();  // Gives Android permissions to place outgoing calls\nTwilioVoice.instance.requestReadPhoneStatePermission();  // Gives Android permissions to read Phone State including receiving calls\nTwilioVoice.instance.requestReadPhoneNumbersPermission();  // Gives Android permissions to read Phone Accounts\nTwilioVoice.instance.requestManageOwnCallsPermission();  // Gives Android permissions to manage calls, this isn't necessary to request as the permission is simply required in the Manifest, but added nontheless.\n```\n\nFollowing this, to register a Phone Account (required by all applications implementing a system-managed `ConnectionService`, run:\n\n```dart\nTwilioVoice.instance.registerPhoneAccount();  // Registers the Phone Account\nTwilioVoice.instance.openPhoneAccountSettings();  // Opens the Phone Account settings\n\n// After the account is enabled, you can check if it's enabled with:\nTwilioVoice.instance.isPhoneAccountEnabled();  // Checks if the Phone Account is enabled\n```\n\nThis last step can be considered the 'final check' to make/receive calls on Android.\n\n**Permissions not granted?**\n\nFinally, a consideration for not all (`CALL_PHONE`) permissions granted on an Android device. The following feature is available on Android only:\n\n```dart\nTwilioVoice.instance.rejectCallOnNoPermissions({Bool = false}); // Rejects incoming calls if permissions are not granted\nTwilioVoice.instance.isRejectingCallOnNoPermissions(); // Checks if the plugin is rejecting calls if permissions are not granted\n```\n\nIf the `CALL_PHONE` permissions group i.e. `READ_PHONE_STATE`, `READ_PHONE_NUMBERS`, `CALL_PHONE` aren't granted nor a Phone Account is registered and enabled, the plugin will either reject the incoming call (true) or not show the incoming call UI (false).\n\n_Note: If `MANAGE_OWN_CALLS` permission is not granted, outbound calls will not work._\n\nSee [Android Setup](#android-setup) and [Android Notes](https://github.com/diegogarciar/twilio_voice/blob/master/NOTES.md#android) for more information regarding configuring the `ConnectionService` and registering a Phone Account.\n\n### Localization\n\nBecause some of the UI is in native code, you need to localize those strings natively in your\nproject. You can find in the example project localization for spanish, PRs are welcome for other\nlanguages.\n\n---\n\n## Twilio Setup/Quickstart Help\n\nTwilio makes use of cloud functions to generate access tokens and sends them to your app. Further,\nTwilio makes use of their own apps called TwiML apps to handle calling functions, etc\n\nThere are 2 major components to get Twilio Setup.\n\n1. Cloud functions (facility generating **access tokens** and then **handling call requests**)\n2. Mobile app that receives/updates tokens and performs the actual calls (see above)\n\n---\n\n### 1) Cloud Functions\n\nCloud functions can be separated or grouped together. The main 2 components are:\n\n- generate access tokens\n- `make-call` endpoint to actually place the call\n\nYou can host both in firebase, in TwiML apps or a mixture. The setup below assumes a mixture, where\nFirebase Functions hosts the `access-token` for easy integration into Flutter and TwiML hosting\nthe `make-call` function.\n\n## Cloud-Functions-Step-1: Create your TwiML app\n\nThis will allow you to actually place the call\n\nPrerequisites\n-------------\n\n* A Twilio Account. Don't have one? [Sign up](https://www.twilio.com/try-twilio) for free!\n\n## Setting up the Application\n\nGrab [this](https://github.com/twilio/voice-quickstart-server-node) project from github, the sample\nTwiML app.\n\n```bash\ncp .env.example .env\n```\n\nEdit `.env` with the three configuration parameters we gathered from above.\n\n**See configure environment below for details**\n\nNext, we need to install our dependencies from npm:\n\n```bash\nnpm install\n```\n\nTo make things easier for you, go into the `src/` folder, rename the `server.js` file to `make-call`\n. This assumes each function will have its own file which for a new project isn't a bad idea.\n\nThen add the following code:\n\n```javascript\nconst AccessToken = require('twilio').jwt.AccessToken;\nconst VoiceGrant = AccessToken.VoiceGrant;\nconst VoiceResponse = require('twilio').twiml.VoiceResponse;\n\n/**\n * Creates an endpoint that can be used in your TwiML App as the Voice Request Url.\n * \u003cbr\u003e\u003cbr\u003e\n * In order to make an outgoing call using Twilio Voice SDK, you need to provide a\n * TwiML App SID in the Access Token. You can run your server, make it publicly\n * accessible and use `/makeCall` endpoint as the Voice Request Url in your TwiML App.\n * \u003cbr\u003e\u003cbr\u003e\n *\n * @returns {Object} - The Response Object with TwiMl, used to respond to an outgoing call\n * @param context\n * @param event\n * @param callback\n */\nexports.handler = function(context, event, callback) {\n    // The recipient of the call, a phone number or a client\n\n    console.log(event);\n    const from = event.From;\n    let to = event.to;\n    if(isEmptyOrNull(to)) {\n        to = event.To;\n        if(isEmptyOrNull(to)) {\n            console.error(\"Could not find someone to call\");\n            to = undefined;\n        }\n    }\n\n\n    const voiceResponse = new VoiceResponse();\n\n    if (!to) {\n        voiceResponse.say(\"Welcome, you made your first call.\");\n    } else if (isNumber(to)) {\n      const dial = voiceResponse.dial({callerId : callerNumber});\n      dial.number(to);\n  } else {\n        console.log(`Calling [${from}] -\u003e [${to}]`)\n\n        const dial = voiceResponse.dial({callerId: to, timeout: 30, record: \"record-from-answer-dual\", trim: \"trim-silence\"});\n        dial.client(to);\n    }\n\n    callback(null, voiceResponse);\n}\n\nconst isEmptyOrNull = (s) =\u003e {\n    return !s || s === '';\n}\n```\n\n### Setup Twilio CLI\n\nEnsure you are logged into `twilio-cli`. First, install `twilio-cli` with\n\n```javascript\nnpm i twilio-cli -g\n```\n\nAfterwards, login to twilio using: (b sure to provide Twilio account SID and auth token for login):\n\n```javascript\ntwilio login\n```\n\nWe need to generate an app, this will give us an App SID to use later in firebase functions, (\nsee [this](https://github.com/twilio/voice-quickstart-ios#3-create-a-twiml-application-for-the-access-token)\nmore info)\n\n### Create TwiML app\n\nWe need to create a TwiML app that will allow us to host a `make-call` function:\n\n```bash\ntwilio api:core:applications:create \\\n--friendly-name=my-twiml-app \\\n--voice-method=POST \\\n--voice-url=\"https://my-quickstart-dev.twil.io/make-call\"\n```\n\nThis will present you with a application SID in the format ```APxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx```,\nwe will use this later in firebase config and generating push credential keys.\n\n**Very Important!** The URL given here `https://my-quickstart-dev.twil.io/make-call` won't work for\nyou. Once you deployed your TwiML application (later), a URL is given to you (on first deploy) which\nyou need to copy and paste as your **Request URL** call. If you don't do this, calling won't work!\n\n### Configure environment\n\nensure you have a `.env` file in the root of your project in the same directory as `package.json`\n\nnext, edit the `.env` file in the format\n\n```bash\nACCOUNT_SID=(insert account SID)\nAPP_SID=(insert App SID, found on TwiML app or the APxxxxx key above)\n```\n\n`API_KEY` and `API_KEY_SECRET` aren't necessary here since we won't be using them\n\n#### Get Push Credential:\n\n**We will generate them a bit later**\n\n- Android\n  FCM: [Android instructions](https://github.com/twilio/voice-quickstart-android#7-create-a-push-credential-using-your-fcm-server-key)\n- Apple\n  APNS: [Apple instructions](https://github.com/twilio/voice-quickstart-ios#6-create-a-push-credential-with-your-voip-service-certificate)\n\nYou will get a Push Credential SID in the format: `CRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`, use this\nin `PUSH_CREDENTIAL_SID`\n\n### Deploying\n\nNow lets deploy.\n\n#### Please note:  Check you have configured your environment first\n\nNavigate to root directory, and deploy using\n\n```javascript\ntwilio serverless:deploy\n```\n\n**Very Important!**: once complete (if you haven't done so), make sure to add the `make-call`\nendpoint your Twilio app's `Request URL` in the main Twilio page. This URL will be shown as part of\nthe deployment text. If this isn't done, calling won't work!\n\n### Cloud-Functions-Step-2: Setup Firebase \u0026 Configuration\n\nTwilio's configurations are stored in `.runtimeconfig.json` which contains:\n\n\"auth_token\": \"\",\n\"account_sid\": \"\",\n\"app_sid\": \"\",\n\"phone\": \"\",\n\"api_key\": \"\",\n\"api_key_secret\": \"\",\n\"android_push_credential\": \"\",\n\"apple_push_credential_debug\": \"\",\n\"apple_push_credential_release\": \"\"\n_**Note:** this is used for local emulator testing, but you need to deploy these to your firebase\nfunction application once you are ready to go live. If you don't, this won't work!_\n\n**Push Credentials** are created once (for iOS, Android) and used to generate `access-token`s, a\ncallback function for all Twilio apps to use for their communication.\n\n---\n\nBelow are the 3 operations you need to run to generate push credentials that should be added into\nthe `.runtimeconfig.json` above\n\n##### Android\n\nTo generate Android push credentials, get the Cloud Messaging server key from Firebase FCM, and add\nit to the following:\n\n```\ntwilio api:chat:v2:credentials:create \\\n--type=fcm \\\n--friendly-name=\"voice-push-credential-fcm\" \\\n--secret=SERVER_KEY_VALUE\n```\nand then place into the field: `android_push_credential` above\n\nThis generated a push credential SID in the format `CRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` which must\nbe used to generate access tokens for android devices.\n\nsee for more\ninfo: https://github.com/twilio/voice-quickstart-android#7-create-a-push-credential-using-your-fcm-server-key\n\n##### iOS\n\nSimilar to Android, but more steps including using .p12 certificates. To get these certificates,\nlogin into [Apple's developer site](https://developer.apple.com/) and go to\nthe [certificates page](https://developer.apple.com/account/resources/certificates/list). You need\nto generate a VoIP Services certificate as shown below.\n\n![voip_services.png](images/voip_services.png)\n\n**Please note:** there are 2 different modes: sandbox and production.\n\n**- SandBox Mode**\n\nUsing sandbox VoIP certificate:\n\n\u003e Export your VoIP Service Certificate as a .p12 file from Keychain Access and extract the\n\u003e certificate and private key from the .p12 file using the openssl command.\n\n```\n$ openssl pkcs12 -in PATH_TO_YOUR_SANDBOX_P12 -nokeys -out sandbox_cert.pem -nodes\n$ openssl pkcs12 -in PATH_TO_YOUR_SANDBOX_P12 -nocerts -out sandbox_key.pem -nodes\n$ openssl rsa -in sandbox_key.pem -out sandbox_key.pem\n```\nUsing sandbox certificates, generate credential:\n\n```\ntwilio api:chat:v2:credentials:create \\\n--type=apn \\\n--sandbox \\\n--friendly-name=\"voice-push-credential (sandbox)\" \\\n--certificate=\"$(cat PATH_TO_SANDBOX_CERT_PEM)\" \\\n--private-key=\"$(cat PATH_TO_SANDBOX_KEY_PEM)\"\n```\nthen place it into the field `apple_push_credential_debug`\n\n**- Production Mode**\n\nUsing production VoIP certificate:\n\n\u003e Export your VoIP Service Certificate as a .p12 file from Keychain Access and extract the\n\u003e certificate and private key from the .p12 file using the openssl command.\n\n```\n$ openssl pkcs12 -in PATH_TO_YOUR_P12 -nokeys -out prod_cert.pem -nodes\n$ openssl pkcs12 -in PATH_TO_YOUR_P12 -nocerts -out prod_key.pem -nodes\n$ openssl rsa -in prod_key.pem -out prod_key.pem\n```\nUsing production certificates, generate credential:\n\n```\ntwilio api:chat:v2:credentials:create \\\n--type=apn \\\n--friendly-name=\"voice-push-credential (production)\" \\\n--certificate=\"$(cat PATH_TO_PROD_CERT_PEM)\" \\\n--private-key=\"$(cat PATH_TO_PROD_KEY_PEM)\"\n```\nthen place it into the field `apple_push_credential_release`\n\nsee for more\ninfo: https://github.com/twilio/voice-quickstart-ios#6-create-a-push-credential-with-your-voip-service-certificate\n\n---\n\n## Cloud-Functions-Step-3: Generate access tokens via cloud function\n\n`HTTP/GET api-voice-accessToken`\n\nTo generate **access-tokens**, the following firebase function is used:\n\n_**Please Note** the default time is 1 hour token validity._\n\nSee for more\ninfo: https://github.com/twilio/voice-quickstart-android/blob/master/Docs/access-token.md\n\n**Firebase Cloud Function: access-token**\n\n```javascript\nconst { AccessToken } = require('twilio').jwt;\nconst functions = require('firebase-functions');\n\nconst { VoiceGrant } = AccessToken;\n\n/**\n * Creates an access token with VoiceGrant using your Twilio credentials.\n *\n * @param {Object} request - POST or GET request that provides the recipient of the call, a phone number or a client\n * @param {Object} response - The Response Object for the http request\n * @returns {string} - The Access Token string and expiry date in milliseconds\n */\nexports.accessToken = functions.https.onCall((payload, context) =\u003e {\n    // Check user authenticated\n    if (typeof (context.auth) === 'undefined') {\n        throw new functions.https.HttpsError('unauthenticated', 'The function must be called while authenticated');\n    }\n    let userId = context.auth.uid;\n\n    console.log('creating access token for ', userId);\n\n    //configuration using firebase environment variables\n    const twilioConfig = functions.config().twilio;\n    const accountSid = twilioConfig.account_sid;\n    const apiKey = twilioConfig.api_key;\n    const apiSecret = twilioConfig.api_key_secret;\n    const outgoingApplicationSid = twilioConfig.app_sid;\n\n    // Used specifically for creating Voice tokens, we need to use seperate push credentials for each platform.\n    // iOS has different APNs environments, so we need to distinguish between sandbox \u0026 production as the one won't work in the other.\n    let pushCredSid;\n    if (payload.isIOS === true) {\n        console.log('creating access token for iOS');\n        pushCredSid = payload.production ? twilioConfig.apple_push_credential_release\n            : (twilioConfig.apple_push_credential_debug || twilioConfig.apple_push_credential_release);\n    } else if (payload.isAndroid === true) {\n        console.log('creating access token for Android');\n        pushCredSid = twilioConfig.android_push_credential;\n    } else {\n        throw new functions.https.HttpsError('unknown_platform', 'No platform specified');\n    }\n\n    // generate token valid for 24 hours - minimum is 3min, max is 24 hours, default is 1 hour\n    const dateTime = new Date();\n    dateTime.setDate(dateTime.getDate()+1);\n    // Create an access token which we will sign and return to the client,\n    // containing the grant we just created\n    const voiceGrant = new VoiceGrant({\n        outgoingApplicationSid,\n        pushCredentialSid: pushCredSid,\n    });\n\n    // Create an access token which we will sign and return to the client,\n    // containing the grant we just created\n    const token = new AccessToken(accountSid, apiKey, apiSecret);\n    token.addGrant(voiceGrant);\n\n    // use firebase ID for identity\n    token.identity = userId;\n    console.log(`Token:${token.toJwt()}`);\n\n    // return json object\n    return {\n        \"token\": token.toJwt(),\n        \"identity\": userId,\n        \"expiry_date\": dateTime.getTime()\n    };\n});\n```\nAdd the function above to your Firebase Functions application,\nsee [this](https://firebase.google.com/docs/functions/get-started) for more help on creating a\nfirebase functions project\n\nAfter you are done, deploy your `.runtimeconfig.json`,\nsee [this](https://firebase.google.com/docs/functions/config-env) for more help.\n\nOnce done with everything above, deploy your firebase function with this:\n\n```bash\nfirebase deploy --only functions\n```\n##### Done!\n\nCalling should work naturally - just make sure to fetch the token from the endpoint and you can call\n\nSee [example](https://github.com/diegogarciar/twilio_voice/blob/master/example/lib/main.dart#L51)\ncode, make sure to change the `voice-accessToken` to your function name, given to you by firebase\nwhen deploying (as part of the deploy text)\n\n## Future Work\n\n- Move package to `federated plugin` structure (see [here](https://flutter.dev/go/federated-plugins) for more info), see reduced overhead advantages covered as motivation (see [here](https://medium.com/flutter/how-to-write-a-flutter-web-plugin-part-2-afdddb69ece6) for more info))\n\n## Updating Twilio Voice JS SDK\n\n`twilio.js` is no longer hosted via CDNs (see [reference](https://github.com/twilio/twilio-voice.js/blob/master/README.md#cdn)), instead it is hosted via npm / github. See instructions found [here](https://github.com/twilio/twilio-voice.js/blob/master/README.md#github)\nThis is automatically added to your `web/index.html` file, as a `\u003cscript/\u003e` tag during runtime. See [here](./lib/_internal/twilio_loader.dart) for more info.);\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcybex-dev%2Ftwilio_voice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcybex-dev%2Ftwilio_voice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcybex-dev%2Ftwilio_voice/lists"}