├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENCE ├── README.md ├── example ├── .gitignore ├── .postinstall.js ├── config.xml ├── package.json └── www │ ├── css │ └── index.css │ ├── img │ └── logo.png │ ├── index.html │ └── js │ └── index.js ├── package.json ├── plugin.xml ├── src ├── android │ └── OAuthPlugin.java └── ios │ └── OAuthPlugin.swift ├── tests ├── android │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ ├── androidTest │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── ayogo │ │ │ └── cordova │ │ │ └── oauth │ │ │ └── OAuthPluginTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── ayogo │ │ │ └── cordova │ │ │ └── oauth │ │ │ └── tests │ │ │ └── MainActivity.java │ │ └── test │ │ └── java │ │ └── com │ │ └── ayogo │ │ └── cordova │ │ └── oauth │ │ └── OAuthPluginUnitTest.java └── ios │ ├── .gitignore │ ├── App │ ├── AppDelegate.swift │ └── config.xml │ ├── OAuthPluginTests.xcodeproj │ ├── DeviceTests.xctestplan │ ├── UnitTests.xctestplan │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── OAuthPluginTests.xcscheme │ ├── Tests │ ├── Info.plist │ └── Tests.swift │ └── UITests │ └── UITests.swift └── www └── oauth.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Ayogo Health Inc 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | indent_style = space 7 | insert_final_newline = true 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | indent_size = 2 11 | 12 | [*.java] 13 | indent_size = 4 14 | 15 | [*.swift] 16 | indent_size = 4 17 | 18 | [*.md] 19 | indent_size = 4 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | test-ios: 13 | name: "iOS Tests" 14 | runs-on: macos-15 15 | env: 16 | DEVELOPER_DIR: /Applications/Xcode_16.3.app/Contents/Developer 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Use Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | 26 | - name: Set up example app 27 | run: | 28 | npm i 29 | npx cordova prepare ios 30 | working-directory: ./example 31 | 32 | - name: Run iOS Unit Tests 33 | run: | 34 | xcodebuild test -quiet \ 35 | -project OAuthPluginTests.xcodeproj \ 36 | -scheme OAuthPluginTests \ 37 | -testPlan UnitTests \ 38 | -destination "platform=iOS Simulator,name=iPhone 16" 39 | working-directory: ./tests/ios 40 | 41 | - name: Run iOS UI Tests 42 | run: | 43 | xcodebuild test \ 44 | -project OAuthPluginTests.xcodeproj \ 45 | -scheme OAuthPluginTests \ 46 | -testPlan DeviceTests \ 47 | -destination "platform=iOS Simulator,name=iPhone 16" \ 48 | -destination-timeout 300 49 | working-directory: ./tests/ios 50 | 51 | 52 | test-android: 53 | name: "Android Tests" 54 | runs-on: ubuntu-latest 55 | 56 | steps: 57 | - name: Enable KVM group perms 58 | run: | 59 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 60 | sudo udevadm control --reload-rules 61 | sudo udevadm trigger --name-match=kvm 62 | 63 | - uses: actions/checkout@v4 64 | 65 | - name: Use Node.js 66 | uses: actions/setup-node@v4 67 | with: 68 | node-version: 20 69 | 70 | - name: Use Java 71 | uses: actions/setup-java@v4 72 | with: 73 | distribution: temurin 74 | java-version: 17 75 | 76 | - name: Set up example app 77 | run: | 78 | npm i 79 | npx cordova prepare android 80 | working-directory: ./example 81 | 82 | - name: Set up gradle 83 | uses: gradle/actions/setup-gradle@v3 84 | with: 85 | gradle-version: 8.13 86 | 87 | - name: Run Android Unit Tests 88 | run: | 89 | gradle test 90 | working-directory: ./tests/android 91 | 92 | - name: Run Android UI Tests (29) 93 | uses: reactivecircus/android-emulator-runner@v2 94 | with: 95 | api-level: 29 96 | script: gradle connectedCheck 97 | working-directory: ./tests/android 98 | 99 | - name: Run Android UI Tests (34) 100 | uses: reactivecircus/android-emulator-runner@v2 101 | with: 102 | api-level: 34 103 | target: google_apis 104 | arch: x86_64 105 | script: adb shell settings put global development_settings_enabled 1 && gradle connectedCheck 106 | working-directory: ./tests/android 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.csproj.user 2 | *.suo 3 | *.cache 4 | Thumbs.db 5 | *.DS_Store 6 | 7 | *.bak 8 | *.cache 9 | *.log 10 | *.swp 11 | *.user -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@ayogo.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | 78 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | cordova-plugin-oauth 18 | ==================== 19 | 20 | This plugin provides a mechanism for showing an OAuth flow in a secured 21 | system-provided browser view and receiving the result via a Message event. 22 | 23 | On iOS, this uses ASWebAuthenticationSession (iOS >=12), 24 | SFAuthenticationSession (iOS 11), SFSafariViewController (iOS >=8, <=10), 25 | falling back to Safari. 26 | 27 | On Android, this uses Custom Tabs in the user's preferred web application, 28 | falling back to their browser. 29 | 30 | **Note:** This plugin might conflict with the Cordova In-App Browser plugin! 31 | 32 | > ℹ️ **This plugin uses AndroidX!** 33 | > 34 | > Use version 3.x if you are building without AndroidX enabled. 35 | 36 | 37 | Installation 38 | ------------ 39 | 40 | ``` 41 | cordova plugin add cordova-plugin-oauth [--variable URL_SCHEME=mycoolapp] 42 | ``` 43 | 44 | By default, the plugin registers the app ID as a scheme to be used as the 45 | OAuth callback URL, and expects a host of `oauth_callback` (i.e., if your 46 | app's ID is `com.example.foo`, your OAuth redirect URL should be 47 | `com.example.foo://oauth_callback`). 48 | 49 | The scheme for the OAuth callback URL can be changed by providing a 50 | `URL_SCHEME` variable when installing. If your `URL_SCHEME` is `mycoolapp`, 51 | then your OAuth redirect URL should be `mycoolapp://oauth_callback`. 52 | 53 | 54 | Supported Platforms 55 | ------------------- 56 | 57 | * **iOS** (cordova-ios >= 6.1.0) 58 | * **Android** (cordova-android >= 9.0.0) 59 | 60 | 61 | Usage 62 | ----- 63 | 64 | 1. Trigger the plugin by opening the OAuth login page in a new window, with a 65 | window name that includes `"oauth:"`: 66 | 67 | ```javascript 68 | var endpoint = 'https://accounts.google.com/o/oauth2/v2/auth?....'; 69 | window.open(endpoint, 'oauth:google', ''); 70 | ``` 71 | 72 | Alternatively, you can include `oauth=yes` in the window features string: 73 | 74 | ```javascript 75 | var endpoint = 'https://accounts.google.com/o/oauth2/v2/auth?....'; 76 | window.open(endpoint, '_self', 'oauth=yes'); 77 | ``` 78 | 79 | 2. The plugin will open the OAuth login page in a new browser window. 80 | 81 | 3. When the OAuth process is complete and it redirects to your app scheme, the 82 | plugin will send a message to the Cordova app. 83 | 84 | The message begins with `oauth::` and is followed by a JSON object 85 | containing all of the key/value pairs from the OAuth redirect query string 86 | or fragment parameters. The complete OAuth callback URL is available in a 87 | `oauth_callback_url` property. 88 | 89 | ```javascript 90 | // Called from a callback URL like 91 | // com.example.foo://oauth_callback?code=b10a8db164e0754105b7a99be72e3fe5 92 | // it would be received in JavaScript like this: 93 | 94 | window.addEventListener('message', function(event) { 95 | if (event.data.match(/^oauth::/)) { 96 | var data = JSON.parse(event.data.substring(7)); 97 | 98 | // Use data.code 99 | } 100 | } 101 | ``` 102 | 103 | The advantage of this implementation is that it can be made to work with 104 | Universal Links and App Links so that the same flow can be used in both 105 | mobile apps and web. 106 | 107 | 108 | Contributing 109 | ------------ 110 | 111 | Contributions of bug reports, feature requests, and pull requests are greatly 112 | appreciated! 113 | 114 | Please note that this project is released with a [Contributor Code of 115 | Conduct][coc]. By participating in this project you agree to abide by its 116 | terms. 117 | 118 | 119 | Licence 120 | ------- 121 | 122 | Released under the Apache 2.0 Licence. 123 | Copyright © 2020-2022 Ayogo Health Inc. 124 | 125 | This project includes the [EventTarget polyfill][etpoly], copyright © 2018, 126 | Andrea Giammarchi under the [ISC Licence][isc]. 127 | 128 | [coc]: https://github.com/AyogoHealth/cordova-plugin-oauth/blob/main/CODE_OF_CONDUCT.md 129 | [etpoly]: https://github.com/ungap/event-target 130 | [isc]: https://github.com/ungap/event-target/blob/master/LICENSE 131 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by package manager 2 | node_modules/ 3 | package-lock.json 4 | 5 | # Generated by Cordova 6 | /plugins/ 7 | /platforms/ 8 | -------------------------------------------------------------------------------- /example/.postinstall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Darryl Pogue 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const fs = require('node:fs'); 18 | 19 | fs.cpSync('../www/', 'node_modules/cordova-plugin-oauth/www/', { recursive: true, force: true }); 20 | fs.cpSync('../src/', 'node_modules/cordova-plugin-oauth/src/', { recursive: true, force: true }); 21 | fs.cpSync('../plugin.xml', 'node_modules/cordova-plugin-oauth/plugin.xml', { force: true }); 22 | -------------------------------------------------------------------------------- /example/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | OAuthExample 19 | Ayogo Health Inc. 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.ayogo.cordova.oauth.example", 3 | "displayName": "OAuthExample", 4 | "version": "1.0.0", 5 | "author": "Ayogo Health Inc. ", 6 | "description": "A sample Apache Cordova application that uses OAuth login.", 7 | "license": "Apache-2.0", 8 | "cordova": { 9 | "platforms": [ 10 | "ios", 11 | "android" 12 | ], 13 | "plugins": { 14 | "cordova-plugin-oauth": { 15 | "URL_SCHEME": "oauthtest" 16 | } 17 | } 18 | }, 19 | "devDependencies": { 20 | "cordova": ">= 10.0.0", 21 | "cordova-ios": ">= 6.1.0", 22 | "cordova-android": ">= 9.0.0", 23 | "cordova-plugin-oauth": ">= 4.0.0" 24 | }, 25 | "scripts": { 26 | "build": "cordova prepare && cordova build --debug --emulator", 27 | "postinstall": "node .postinstall.js" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/www/css/index.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | * { 18 | -webkit-tap-highlight-color: rgba(0,0,0,0); 19 | } 20 | 21 | :root { 22 | overscroll-behavior: none; 23 | } 24 | 25 | body { 26 | -webkit-touch-callout: none; 27 | -webkit-text-size-adjust: none; 28 | -webkit-user-select: none; 29 | box-sizing: border-box; 30 | background-color: #E4E4E4; 31 | background-image: linear-gradient(to bottom, #A7A7A7 0%, #E4E4E4 51%); 32 | font-family: system-ui, -apple-system, -apple-system-font, 'Segoe UI', 'Roboto', sans-serif; 33 | font-size: 12px; 34 | height: 100vh; 35 | margin: 0px; 36 | padding: 0px; 37 | padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px); 38 | text-transform: uppercase; 39 | width: 100%; 40 | } 41 | 42 | @media screen and (prefers-color-scheme: dark) { 43 | body { 44 | background-image:linear-gradient(to bottom, #585858 0%, #1B1B1B 51%); 45 | } 46 | } 47 | 48 | .app { 49 | background: url(../img/logo.png) no-repeat center top; 50 | position: absolute; 51 | left: 50%; 52 | top: 50%; 53 | height: 50px; 54 | width: 225px; 55 | text-align: center; 56 | padding: 180px 0px 0px 0px; 57 | margin: -115px 0px 0px -112px; 58 | } 59 | 60 | @media screen and (min-aspect-ratio: 1/1) and (min-width:400px) { 61 | .app { 62 | background-position:left center; 63 | padding:75px 0px 75px 170px; 64 | margin:-90px 0px 0px -198px; 65 | } 66 | } 67 | 68 | h1 { 69 | font-size:24px; 70 | font-weight:normal; 71 | margin:0px; 72 | overflow:visible; 73 | padding:0px; 74 | text-align:center; 75 | } 76 | 77 | .login { 78 | margin-top: 1em; 79 | color: white; 80 | padding: 0.5em 1em; 81 | background: mediumblue linear-gradient(to bottom, blue, mediumblue); 82 | font-size: 16px; 83 | font-weight: 500; 84 | border: 0; 85 | border-radius: 4px; 86 | cursor: pointer; 87 | } 88 | 89 | .logout { 90 | margin-top: 1em; 91 | padding: 0.5em 1em; 92 | background: transparent; 93 | font-size: 14px; 94 | border: 0; 95 | text-decoration: underline; 96 | cursor: pointer; 97 | color: LinkText; 98 | } 99 | -------------------------------------------------------------------------------- /example/www/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyogoHealth/cordova-plugin-oauth/41b4f5f987aadd4251fa3170bd62de4d0f497f9d/example/www/img/logo.png -------------------------------------------------------------------------------- /example/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | OAuth Example 27 | 28 | 29 | 30 |
31 |

Welcome!

32 | 33 | 34 |
35 | 36 |
37 |

Login Cancelled!

38 | 39 | 40 |
41 | 42 |
43 |

Logged In

44 | 45 | 46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/www/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** Set of authentication elements we need to re-evaluate on login. */ 18 | const authElements = new Set(); 19 | 20 | 21 | /** 22 | * Click handler for the login button to start the OAuth flow. 23 | */ 24 | document.querySelectorAll('.login').forEach((btn) => { 25 | btn.addEventListener('click', () => { 26 | // Our fake OAuth redirecting page 27 | const url = 'https://ayogohealth.github.io/cordova-plugin-oauth/example/oauth_access_token.html'; 28 | 29 | // Open a window with the "oauth:" prefix to trigger the plugin 30 | const hwnd = window.open(url, '_blank', 'oauth=yes'); 31 | 32 | hwnd.addEventListener('close', (evt) => { 33 | if (!localStorage.getItem('access_token')) { 34 | localStorage.setItem('login_cancelled', 'true'); 35 | 36 | // Re-evaluate the authentication elements 37 | authElements.forEach((el) => el.evaluateState()); 38 | } 39 | }); 40 | }) 41 | }); 42 | 43 | 44 | /** 45 | * Receiver for OAuth login postMessage data from the plugin. 46 | */ 47 | window.addEventListener('message', (evt) => { 48 | // Ensure this message is from the plugin, then parse the data 49 | if (evt.data.match(/^oauth::/)) { 50 | const data = JSON.parse(evt.data.substring(7)); 51 | 52 | // Store the access token from the OAuth message data 53 | localStorage.setItem('access_token', data.access_token); 54 | 55 | // Re-evaluate the authentication elements 56 | authElements.forEach((el) => el.evaluateState()); 57 | } 58 | }); 59 | 60 | 61 | /** 62 | * Click handler for the logout button. 63 | */ 64 | document.getElementById('logout-button').addEventListener('click', () => { 65 | // Erase the access token in localStorage 66 | localStorage.clear(); 67 | 68 | // Re-evaluate the authentication elements 69 | authElements.forEach((el) => el.evaluateState()); 70 | }); 71 | 72 | 73 | /** 74 | * Custom Element to conditionally show a slot based on authentication status. 75 | */ 76 | class AuthRouterElement extends HTMLElement { 77 | constructor() { 78 | super(); 79 | this.attachShadow({ mode: 'open' }); 80 | } 81 | 82 | connectedCallback() { 83 | authElements.add(this); 84 | 85 | this.evaluateState(); 86 | } 87 | 88 | disconnectedCallback() { 89 | authElements.delete(this); 90 | } 91 | 92 | evaluateState() { 93 | const access_token = localStorage.getItem('access_token'); 94 | const login_cancelled = localStorage.getItem('login_cancelled'); 95 | 96 | if (access_token && access_token != '') { 97 | this.shadowRoot.innerHTML = ''; 98 | } else if (login_cancelled && login_cancelled != '') { 99 | this.shadowRoot.innerHTML = ''; 100 | } else { 101 | this.shadowRoot.innerHTML = ''; 102 | } 103 | } 104 | } 105 | window.customElements.define('auth-router', AuthRouterElement); 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-oauth", 3 | "version": "4.0.1", 4 | "author": "Ayogo Health Inc. ", 5 | "contributors": [ 6 | "Darryl Pogue ", 7 | "Charalampos Pournaris ", 8 | "Harel Mazor" 9 | ], 10 | "description": "Cordova plugin for performing OAuth login flows.", 11 | "license": "Apache-2.0", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/AyogoHealth/cordova-plugin-oauth.git" 15 | }, 16 | "keywords": [ 17 | "cordova", 18 | "ecosystem:cordova", 19 | "cordova-android", 20 | "cordova-ios" 21 | ], 22 | "files": [ 23 | "src", 24 | "www", 25 | "plugin.xml" 26 | ], 27 | "cordova": { 28 | "platforms": [ 29 | "ios", 30 | "android" 31 | ] 32 | }, 33 | "engines": [ 34 | { 35 | "name": "cordova-android", 36 | "version": ">= 9.0.0" 37 | }, 38 | { 39 | "name": "cordova-ios", 40 | "version": ">= 6.1.0" 41 | } 42 | ], 43 | "dependencies": {} 44 | } 45 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | cordova-plugin-oauth 19 | Cordova plugin for performing OAuth login flows. 20 | cordova,ios,android,oauth 21 | Apache 2.0 22 | https://github.com/AyogoHealth/cordova-plugin-oauth.git 23 | https://github.com/AyogoHealth/cordova-plugin-oauth/issues 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | CFBundleURLSchemes 54 | 55 | $URL_SCHEME 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/android/OAuthPlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 - 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ayogo.cordova.oauth; 18 | 19 | import android.content.Intent; 20 | import android.net.Uri; 21 | import androidx.browser.customtabs.CustomTabsIntent; 22 | import android.text.TextUtils; 23 | import java.net.URLDecoder; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import org.apache.cordova.CallbackContext; 29 | import org.apache.cordova.CordovaArgs; 30 | import org.apache.cordova.CordovaInterface; 31 | import org.apache.cordova.CordovaPlugin; 32 | import org.apache.cordova.CordovaWebView; 33 | import org.apache.cordova.CordovaWebViewEngine; 34 | import org.apache.cordova.LOG; 35 | import org.apache.cordova.PluginResult; 36 | 37 | import org.json.JSONException; 38 | import org.json.JSONObject; 39 | 40 | 41 | public class OAuthPlugin extends CordovaPlugin { 42 | private final String TAG = "OAuthPlugin"; 43 | private CallbackContext oauthCallback = null; 44 | private boolean didFinishLoading = false; 45 | private String lastOAuthResult = null; 46 | 47 | /** 48 | * Executes the request. 49 | * 50 | * This method is called from the WebView thread. To do a non-trivial amount of work, use: 51 | * cordova.getThreadPool().execute(runnable); 52 | * 53 | * To run on the UI thread, use: 54 | * cordova.getActivity().runOnUiThread(runnable); 55 | * 56 | * @param action The action to execute. 57 | * @param rawArgs The exec() arguments in JSON form. 58 | * @param callbackContext The callback context used when calling back into JavaScript. 59 | * @return Whether the action was valid. 60 | */ 61 | @Override 62 | public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) { 63 | if ("startOAuth".equals(action)) { 64 | try { 65 | String authEndpoint = args.getString(0); 66 | oauthCallback = callbackContext; 67 | 68 | this.startOAuth(authEndpoint); 69 | 70 | return true; 71 | } catch (JSONException e) { 72 | callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR)); 73 | return false; 74 | } 75 | } 76 | 77 | callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION)); 78 | return false; 79 | } 80 | 81 | 82 | /** 83 | * Called when the activity is becoming visible to the user. 84 | */ 85 | @Override 86 | public void onStart() { 87 | onNewIntent(cordova.getActivity().getIntent()); 88 | } 89 | 90 | 91 | /** 92 | * Called when the activity receives a new intent. 93 | * 94 | * We use this method to intercept the OAuth callback URI and dispatch a 95 | * message to the WebView. 96 | */ 97 | @Override 98 | public void onNewIntent(Intent intent) { 99 | if (intent == null || !intent.getAction().equals(Intent.ACTION_VIEW)) { 100 | return; 101 | } 102 | 103 | final Uri uri = intent.getData(); 104 | String callbackHost = preferences.getString("oauthhostname", "oauth_callback"); 105 | 106 | if (uri.getHost().equals(callbackHost)) { 107 | LOG.i(TAG, "OAuth called back with parameters."); 108 | 109 | try { 110 | JSONObject jsobj = new JSONObject(); 111 | jsobj.put("oauth_callback_url", uri.toString()); 112 | 113 | // Parse fragment parameters 114 | if (uri.getFragment() != null) { 115 | String fragment = uri.getFragment(); 116 | String[] pairs = fragment.split("&"); 117 | for (String pair : pairs) { 118 | String[] keyValue = pair.split("="); 119 | if (keyValue.length == 2) { 120 | // Decode the fragment parameter before adding it to the JSONObject 121 | String key = keyValue[0]; 122 | String value = keyValue[1]; 123 | jsobj.put(key, value); 124 | } 125 | } 126 | } 127 | 128 | // Parse query parameters 129 | for (String queryKey : uri.getQueryParameterNames()) { 130 | jsobj.put(queryKey, uri.getQueryParameter(queryKey)); 131 | } 132 | 133 | if (this.didFinishLoading) { 134 | dispatchOAuthMessage(jsobj.toString()); 135 | } else { 136 | this.lastOAuthResult = jsobj.toString(); 137 | } 138 | } catch (JSONException e) { 139 | LOG.e(TAG, "JSON Serialization failed"); 140 | e.printStackTrace(); 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * Called when the activity will start interacting with the user. 147 | * 148 | * We use this method to indicate to the JavaScript side that the OAuth 149 | * window has closed (regardless of login status). 150 | */ 151 | @Override 152 | public void onResume(boolean multitasking) { 153 | super.onResume(multitasking); 154 | 155 | if (oauthCallback != null) { 156 | oauthCallback.sendPluginResult(new PluginResult(PluginResult.Status.OK)); 157 | oauthCallback = null; 158 | } 159 | } 160 | 161 | 162 | /** 163 | * Called when a message is sent to plugin. 164 | * 165 | * @param id The message id 166 | * @param data The message data 167 | * @return Object to stop propagation or null 168 | */ 169 | @Override 170 | public Object onMessage(String id, Object data) { 171 | if (id.equals("onPageFinished")) { 172 | this.didFinishLoading = true; 173 | 174 | if (this.lastOAuthResult != null) { 175 | this.dispatchOAuthMessage(this.lastOAuthResult); 176 | this.lastOAuthResult = null; 177 | } 178 | } 179 | return null; 180 | } 181 | 182 | 183 | /** 184 | * Launches the custom tab with the OAuth endpoint URL. 185 | * 186 | * @param url The URL of the OAuth endpoint. 187 | */ 188 | private void startOAuth(String url) { 189 | CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); 190 | 191 | CustomTabsIntent customTabsIntent = builder.build(); 192 | customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); 193 | customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 194 | customTabsIntent.intent.putExtra("android.support.customtabs.extra.ENABLE_URLBAR_HIDING", true); 195 | customTabsIntent.intent.putExtra("android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS", false); 196 | customTabsIntent.intent.putExtra("android.support.customtabs.extra.SEND_TO_EXTERNAL_HANDLER", false); 197 | customTabsIntent.intent.putExtra("androidx.browser.customtabs.extra.SHARE_STATE", 2); 198 | customTabsIntent.intent.putExtra("androidx.browser.customtabs.extra.DISABLE_BACKGROUND_INTERACTION", false); 199 | customTabsIntent.intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_DOWNLOAD_BUTTON", true); 200 | customTabsIntent.intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_STAR_BUTTON", true); 201 | 202 | customTabsIntent.launchUrl(this.cordova.getActivity(), Uri.parse(url)); 203 | } 204 | 205 | @SuppressWarnings("deprecation") 206 | private void dispatchOAuthMessage(final String msg) { 207 | final String msgData = msg.replace("'", "\\'").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t"); 208 | final String jsCode = "window.dispatchEvent(new MessageEvent('message', { data: 'oauth::" + msgData + "' }));"; 209 | 210 | CordovaWebViewEngine engine = this.webView.getEngine(); 211 | if (engine != null) { 212 | engine.evaluateJavascript(jsCode, null); 213 | } else { 214 | this.webView.sendJavascript(jsCode); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/ios/OAuthPlugin.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 - 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #if canImport(Cordova) 18 | import Cordova 19 | #endif 20 | 21 | import os.log 22 | import Foundation 23 | import AuthenticationServices 24 | import SafariServices 25 | 26 | extension NSNotification.Name { 27 | static let CDVPluginOAuthCancelled = NSNotification.Name("CDVPluginOAuthCancelledNotification"); 28 | } 29 | 30 | @objc protocol OAuthSessionProvider { 31 | init(_ endpoint : URL, callbackScheme : String) 32 | func start() -> Void 33 | func cancel() -> Void 34 | } 35 | 36 | @available(iOS 12.0, *) 37 | class ASWebAuthenticationSessionOAuthSessionProvider : OAuthSessionProvider { 38 | private var aswas : ASWebAuthenticationSession 39 | 40 | var delegate : AnyObject? 41 | 42 | required init(_ endpoint : URL, callbackScheme : String) { 43 | let url: URL = URL(string: callbackScheme)! 44 | let callbackURLScheme: String = url.scheme ?? callbackScheme 45 | self.aswas = ASWebAuthenticationSession(url: endpoint, callbackURLScheme: callbackURLScheme, completionHandler: { (callBack:URL?, error:Error?) in 46 | if let incomingUrl = callBack { 47 | NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: incomingUrl) 48 | } else { 49 | NotificationCenter.default.post(name: NSNotification.Name.CDVPluginOAuthCancelled, object: nil) 50 | } 51 | }) 52 | } 53 | 54 | func start() { 55 | if #available(iOS 13.0, *) { 56 | if let provider = self.delegate as? ASWebAuthenticationPresentationContextProviding { 57 | self.aswas.presentationContextProvider = provider 58 | } 59 | } 60 | 61 | self.aswas.start() 62 | } 63 | 64 | func cancel() { 65 | self.aswas.cancel() 66 | } 67 | } 68 | 69 | @available(iOS 11.0, *) 70 | class SFAuthenticationSessionOAuthSessionProvider : OAuthSessionProvider { 71 | private var sfas : SFAuthenticationSession 72 | 73 | required init(_ endpoint : URL, callbackScheme : String) { 74 | self.sfas = SFAuthenticationSession(url: endpoint, callbackURLScheme: callbackScheme, completionHandler: { (callBack:URL?, error:Error?) in 75 | if let incomingUrl = callBack { 76 | NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: incomingUrl) 77 | } else { 78 | NotificationCenter.default.post(name: NSNotification.Name.CDVPluginOAuthCancelled, object: nil) 79 | } 80 | }) 81 | } 82 | 83 | func start() { 84 | self.sfas.start() 85 | } 86 | 87 | func cancel() { 88 | self.sfas.cancel() 89 | } 90 | } 91 | 92 | @available(iOS 9.0, *) 93 | class SFSafariViewControllerOAuthSessionProvider : OAuthSessionProvider { 94 | private var sfvc : SFSafariViewController 95 | 96 | var viewController : UIViewController? 97 | var delegate : SFSafariViewControllerDelegate? 98 | 99 | required init(_ endpoint : URL, callbackScheme : String) { 100 | self.sfvc = SFSafariViewController(url: endpoint) 101 | if #available(iOS 11.0, *) { 102 | self.sfvc.dismissButtonStyle = .cancel 103 | } 104 | } 105 | 106 | func start() { 107 | if self.delegate != nil { 108 | self.sfvc.delegate = self.delegate 109 | } 110 | 111 | self.viewController?.present(self.sfvc, animated: true, completion: nil) 112 | } 113 | 114 | func cancel() { 115 | self.sfvc.dismiss(animated: true, completion:nil) 116 | } 117 | } 118 | 119 | class SafariAppOAuthSessionProvider : OAuthSessionProvider { 120 | var url : URL; 121 | 122 | required init(_ endpoint : URL, callbackScheme : String) { 123 | self.url = endpoint 124 | } 125 | 126 | func start() { 127 | UIApplication.shared.openURL(url) 128 | } 129 | 130 | // We can't do anything here 131 | func cancel() { } 132 | } 133 | 134 | 135 | @objc(CDVOAuthPlugin) 136 | class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticationPresentationContextProviding { 137 | /** This exists for testing purposes */ 138 | static var forcedVersion : UInt32 = UInt32.max 139 | 140 | var authSystem : OAuthSessionProvider? 141 | var closeCallbackId : String? 142 | var callbackScheme : String? 143 | var logger : OSLog? 144 | 145 | override func pluginInitialize() { 146 | let urlScheme = self.commandDelegate.settings["oauthscheme"] as! String 147 | let urlHostname = self.commandDelegate.settings["oauthhostname"] as? String ?? "oauth_callback"; 148 | 149 | self.callbackScheme = "\(urlScheme)://\(urlHostname)" 150 | if #available(iOS 10.0, *) { 151 | self.logger = OSLog(subsystem: urlScheme, category: "Cordova") 152 | } 153 | 154 | NotificationCenter.default.addObserver(self, 155 | selector: #selector(OAuthPlugin._handleOpenURL(_:)), 156 | name: NSNotification.Name.CDVPluginHandleOpenURL, 157 | object: nil) 158 | 159 | NotificationCenter.default.addObserver(self, 160 | selector: #selector(OAuthPlugin._handleCancel(_:)), 161 | name: NSNotification.Name.CDVPluginOAuthCancelled, 162 | object: nil) 163 | } 164 | 165 | 166 | @objc func startOAuth(_ command : CDVInvokedUrlCommand) { 167 | guard let authEndpoint = command.argument(at: 0) as? String else { 168 | self.commandDelegate.send(CDVPluginResult(status: .error), callbackId: command.callbackId) 169 | return 170 | } 171 | 172 | guard let url = URL(string: authEndpoint) else { 173 | self.commandDelegate.send(CDVPluginResult(status: .error), callbackId: command.callbackId) 174 | return 175 | } 176 | 177 | self.closeCallbackId = command.callbackId 178 | 179 | if OAuthPlugin.forcedVersion >= 12, #available(iOS 12.0, *) { 180 | self.authSystem = ASWebAuthenticationSessionOAuthSessionProvider(url, callbackScheme:self.callbackScheme!) 181 | 182 | if #available(iOS 13.0, *) { 183 | if let aswas = self.authSystem as? ASWebAuthenticationSessionOAuthSessionProvider { 184 | aswas.delegate = self 185 | } 186 | } 187 | } else if OAuthPlugin.forcedVersion >= 11, #available(iOS 11.0, *) { 188 | self.authSystem = SFAuthenticationSessionOAuthSessionProvider(url, callbackScheme:self.callbackScheme!) 189 | } else if OAuthPlugin.forcedVersion >= 9, #available(iOS 9.0, *) { 190 | self.authSystem = SFSafariViewControllerOAuthSessionProvider(url, callbackScheme:self.callbackScheme!) 191 | 192 | if let sfvc = self.authSystem as? SFSafariViewControllerOAuthSessionProvider { 193 | sfvc.delegate = self 194 | sfvc.viewController = self.viewController 195 | } 196 | } else { 197 | self.authSystem = SafariAppOAuthSessionProvider(url, callbackScheme:self.callbackScheme!) 198 | } 199 | 200 | self.authSystem?.start() 201 | 202 | return 203 | } 204 | 205 | 206 | internal func parseToken(from url: URL) { 207 | self.authSystem?.cancel() 208 | self.authSystem = nil 209 | 210 | var jsobj : [String : String] = ["oauth_callback_url": url.absoluteString] 211 | let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems 212 | let fragment = url.fragment 213 | 214 | // Parse fragment parameters 215 | if let fragment = fragment { 216 | let pairs = fragment.split(separator: "&") 217 | for pair in pairs { 218 | let keyValue = pair.split(separator: "=") 219 | if keyValue.count == 2, 220 | let key = keyValue[0].removingPercentEncoding, 221 | let value = keyValue[1].removingPercentEncoding { 222 | jsobj[String(key)] = String(value) 223 | } 224 | } 225 | } 226 | 227 | queryItems?.forEach { 228 | jsobj[$0.name] = $0.value 229 | } 230 | 231 | if #available(iOS 10.0, *) { 232 | os_log("OAuth called back with parameters.", log: self.logger!, type: .info) 233 | } else { 234 | NSLog("OAuth called back with parameters.") 235 | } 236 | 237 | do { 238 | let data = try JSONSerialization.data(withJSONObject: jsobj) 239 | let msg = String(data: data, encoding: .utf8)! 240 | .replacingOccurrences(of: "'", with: "\\'") 241 | .replacingOccurrences(of: "\n", with: "\\n") 242 | .replacingOccurrences(of: "\r", with: "\\r") 243 | .replacingOccurrences(of: "\t", with: "\\t") 244 | 245 | if let msg = String(data: data, encoding: .utf8) { 246 | self.webViewEngine.evaluateJavaScript("window.dispatchEvent(new MessageEvent('message', { data: 'oauth::\(msg)' }));", completionHandler: nil) 247 | } 248 | } catch { 249 | let errStr = "JSON Serialization failed: \(error)" 250 | if #available(iOS 10.0, *) { 251 | os_log("%@", log: self.logger!, type: .error, errStr) 252 | } else { 253 | NSLog(errStr) 254 | } 255 | } 256 | } 257 | 258 | 259 | @objc internal func _handleOpenURL(_ notification : NSNotification) { 260 | guard let url = notification.object as? URL else { 261 | return 262 | } 263 | 264 | if !url.absoluteString.hasPrefix(self.callbackScheme!) { 265 | return 266 | } 267 | 268 | self.parseToken(from: url) 269 | 270 | if let cb = self.closeCallbackId { 271 | self.commandDelegate.send(CDVPluginResult(status: .ok), callbackId: cb) 272 | } 273 | self.closeCallbackId = nil 274 | } 275 | 276 | 277 | @objc internal func _handleCancel(_ notification : NSNotification) { 278 | if let cb = self.closeCallbackId { 279 | self.commandDelegate.send(CDVPluginResult(status: .ok), callbackId: cb) 280 | } 281 | self.closeCallbackId = nil 282 | } 283 | 284 | 285 | @available(iOS 9.0, *) 286 | func safariViewControllerDidFinish(_ controller: SFSafariViewController) { 287 | self.authSystem?.cancel() 288 | self.authSystem = nil 289 | 290 | NotificationCenter.default.post(name: NSNotification.Name.CDVPluginOAuthCancelled, object: nil) 291 | } 292 | 293 | @available(iOS 13.0, *) 294 | func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { 295 | return self.viewController.view.window ?? ASPresentationAnchor() 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /tests/android/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle/ 3 | src/main/java/com/ayogo/cordova/oauth/OAuthPlugin.java 4 | src/main/assets/ 5 | src/main/res/ 6 | -------------------------------------------------------------------------------- /tests/android/build.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | buildscript { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | classpath 'com.android.tools.build:gradle:8.7.3' 25 | } 26 | } 27 | 28 | apply plugin: 'com.android.application' 29 | 30 | android { 31 | compileSdkVersion = 35 32 | 33 | defaultConfig { 34 | namespace = 'com.ayogo.cordova.oauth.tests' 35 | applicationId = 'com.ayogo.cordova.oauth.tests' 36 | minSdkVersion = 24 37 | targetSdkVersion = 35 38 | versionCode = 1 39 | versionName = '1.0' 40 | testInstrumentationRunner = 'androidx.test.runner.AndroidJUnitRunner' 41 | } 42 | 43 | compileOptions { 44 | sourceCompatibility JavaLanguageVersion.of(11) 45 | targetCompatibility JavaLanguageVersion.of(11) 46 | } 47 | 48 | buildTypes { 49 | debug { 50 | testCoverageEnabled = true 51 | } 52 | } 53 | } 54 | 55 | repositories { 56 | google() 57 | mavenCentral() 58 | } 59 | 60 | dependencies { 61 | implementation 'org.apache.cordova:framework:[11.0.0,)' 62 | implementation 'androidx.browser:browser:[1.3.0,1.9.0)' 63 | 64 | testImplementation 'junit:junit:4.13.1' 65 | testImplementation 'org.mockito:mockito-inline:4.6.1' 66 | testImplementation 'org.json:json:[20220924,)' 67 | 68 | androidTestImplementation 'androidx.test:core:1.5.0' 69 | androidTestImplementation 'androidx.test:runner:1.5.2' 70 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 71 | androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' 72 | 73 | constraints { 74 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { 75 | because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") 76 | } 77 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") { 78 | because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") 79 | } 80 | } 81 | } 82 | 83 | task copyPluginSource(type: Copy) { 84 | from '../../src/android' 85 | into 'src/main/java/com/ayogo/cordova/oauth' 86 | } 87 | 88 | task copyExampleAssets(type: Copy) { 89 | from '../../example/platforms/android/app/src/main/assets' 90 | into 'src/main/assets' 91 | } 92 | 93 | task copyExampleResources(type: Copy) { 94 | from '../../example/platforms/android/app/src/main/res' 95 | into 'src/main/res' 96 | } 97 | 98 | preBuild.dependsOn(copyPluginSource) 99 | preBuild.dependsOn(copyExampleAssets) 100 | preBuild.dependsOn(copyExampleResources) 101 | -------------------------------------------------------------------------------- /tests/android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX = true 2 | -------------------------------------------------------------------------------- /tests/android/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /tests/android/src/androidTest/java/com/ayogo/cordova/oauth/OAuthPluginTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ayogo.cordova.oauth; 18 | 19 | import static org.junit.Assert.assertNotNull; 20 | 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.pm.PackageManager; 24 | import android.content.pm.ResolveInfo; 25 | import android.webkit.WebView; 26 | import androidx.test.core.app.ApplicationProvider; 27 | import androidx.test.ext.junit.runners.AndroidJUnit4; 28 | import androidx.test.platform.app.InstrumentationRegistry; 29 | import androidx.test.uiautomator.By; 30 | import androidx.test.uiautomator.UiDevice; 31 | import androidx.test.uiautomator.UiObjectNotFoundException; 32 | import androidx.test.uiautomator.UiObject2; 33 | import androidx.test.uiautomator.Until; 34 | import org.junit.After; 35 | import org.junit.Before; 36 | import org.junit.Test; 37 | import org.junit.runner.RunWith; 38 | 39 | @RunWith(AndroidJUnit4.class) 40 | public class OAuthPluginTest { 41 | static final long TIMEOUT = 5000; 42 | static final String TEST_PACKAGE = "com.ayogo.cordova.oauth.tests"; 43 | 44 | private UiDevice device; 45 | 46 | @Before 47 | public void waitForAppLaunch() { 48 | device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 49 | 50 | device.pressHome(); 51 | 52 | final String launcherPackage = getLauncherPackageName(); 53 | assertNotNull(launcherPackage); 54 | device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT); 55 | 56 | Context context = ApplicationProvider.getApplicationContext(); 57 | final Intent intent = context.getPackageManager().getLaunchIntentForPackage(TEST_PACKAGE); 58 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 59 | context.startActivity(intent); 60 | 61 | device.wait(Until.hasObject(By.pkg(TEST_PACKAGE).depth(0)), TIMEOUT); 62 | } 63 | 64 | @After 65 | public void ensureLogout() { 66 | UiObject2 logoutBtn = device.wait(Until.findObject(By.text("Logout")), TIMEOUT); 67 | if (logoutBtn != null) { 68 | logoutBtn.click(); 69 | 70 | device.waitForIdle(); 71 | } 72 | } 73 | 74 | @Test 75 | public void testOAuthFlow() { 76 | assertNotNull(device); 77 | 78 | device.wait(Until.findObject(By.clazz(WebView.class)), TIMEOUT); 79 | 80 | UiObject2 loginBtn = device.wait(Until.findObject(By.text("Sign in with OAuth")), TIMEOUT); 81 | assertNotNull(loginBtn); 82 | loginBtn.click(); 83 | 84 | // Now we have to interact with the Chrome Custom Tab 85 | // First see if we need to skip the account nag screen 86 | UiObject2 skipBtn = device.wait(Until.findObject(By.text("Use without an account")), TIMEOUT); 87 | if (skipBtn != null) { 88 | skipBtn.click(); 89 | } 90 | 91 | // Now we have to click the login button in the Chrome Custom Tab 92 | UiObject2 oauthBtn = device.wait(Until.findObject(By.text("Click Here to Login")), TIMEOUT); 93 | assertNotNull(oauthBtn); 94 | oauthBtn.click(); 95 | 96 | // Should be back in the app now 97 | device.wait(Until.findObject(By.clazz(WebView.class)), TIMEOUT); 98 | 99 | UiObject2 loginText = device.wait(Until.findObject(By.text("LOGGED IN")), TIMEOUT); 100 | assertNotNull(loginText); 101 | } 102 | 103 | @Test 104 | public void testOAuthCancellation() { 105 | assertNotNull(device); 106 | 107 | device.wait(Until.findObject(By.clazz(WebView.class)), TIMEOUT); 108 | 109 | UiObject2 loginBtn = device.wait(Until.findObject(By.text("Sign in with OAuth")), TIMEOUT); 110 | assertNotNull(loginBtn); 111 | loginBtn.click(); 112 | 113 | // Now we have to interact with the Chrome Custom Tab 114 | // First see if we need to skip the account nag screen 115 | UiObject2 skipBtn = device.wait(Until.findObject(By.text("Use without an account")), TIMEOUT); 116 | if (skipBtn != null) { 117 | skipBtn.click(); 118 | } 119 | 120 | // Now we have to close the Chrome Custom Tab without logging in 121 | UiObject2 oauthBtn = device.wait(Until.findObject(By.text("Click Here to Login")), TIMEOUT); 122 | assertNotNull(oauthBtn); 123 | 124 | device.pressBack(); 125 | 126 | // Should be back in the app now 127 | device.wait(Until.findObject(By.clazz(WebView.class)), TIMEOUT); 128 | 129 | UiObject2 cancelText = device.wait(Until.findObject(By.text("LOGIN CANCELLED!")), TIMEOUT); 130 | assertNotNull(cancelText); 131 | } 132 | 133 | /** 134 | * Uses package manager to find the package name of the device launcher. Usually this package 135 | * is "com.android.launcher" but can be different at times. This is a generic solution which 136 | * works on all platforms.` 137 | */ 138 | @SuppressWarnings("deprecation") 139 | private String getLauncherPackageName() { 140 | // Create launcher Intent 141 | final Intent intent = new Intent(Intent.ACTION_MAIN); 142 | intent.addCategory(Intent.CATEGORY_HOME); 143 | 144 | // Use PackageManager to get the launcher package name 145 | PackageManager pm = ApplicationProvider.getApplicationContext().getPackageManager(); 146 | ResolveInfo resolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); 147 | return resolveInfo.activityInfo.packageName; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tests/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 26 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/android/src/main/java/com/ayogo/cordova/oauth/tests/MainActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ayogo.cordova.oauth.tests; 18 | 19 | import android.os.Bundle; 20 | import org.apache.cordova.CordovaActivity; 21 | 22 | public class MainActivity extends CordovaActivity { 23 | @Override 24 | public void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | loadUrl(launchUrl); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/android/src/test/java/com/ayogo/cordova/oauth/OAuthPluginUnitTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ayogo.cordova.oauth; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.mockito.Mockito.mock; 21 | import static org.mockito.Mockito.verify; 22 | 23 | import org.apache.cordova.CallbackContext; 24 | import org.apache.cordova.CordovaArgs; 25 | import org.apache.cordova.PluginResult; 26 | import org.json.JSONException; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | import org.mockito.ArgumentCaptor; 30 | import org.mockito.junit.MockitoJUnitRunner; 31 | 32 | @RunWith(MockitoJUnitRunner.class) 33 | public class OAuthPluginUnitTest { 34 | @Test 35 | public void executeWithUnknownAction() throws JSONException { 36 | OAuthPlugin plugin = new OAuthPlugin(); 37 | 38 | CallbackContext context = mock(CallbackContext.class); 39 | boolean retVal = plugin.execute("unknownAction", "[]", context); 40 | assertEquals(false, retVal); 41 | 42 | ArgumentCaptor result = ArgumentCaptor.forClass(PluginResult.class); 43 | verify(context).sendPluginResult(result.capture()); 44 | assertEquals(PluginResult.Status.INVALID_ACTION.ordinal(), result.getValue().getStatus()); 45 | } 46 | 47 | @Test 48 | public void executeOAuthWithMissingArgument() throws JSONException { 49 | OAuthPlugin plugin = new OAuthPlugin(); 50 | 51 | CallbackContext context = mock(CallbackContext.class); 52 | boolean retVal = plugin.execute("startOAuth", "[]", context); 53 | assertEquals(false, retVal); 54 | 55 | ArgumentCaptor result = ArgumentCaptor.forClass(PluginResult.class); 56 | verify(context).sendPluginResult(result.capture()); 57 | assertEquals(PluginResult.Status.ERROR.ordinal(), result.getValue().getStatus()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/ios/.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | Package.resolved 3 | App/www 4 | -------------------------------------------------------------------------------- /tests/ios/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import UIKit 18 | import Cordova 19 | 20 | @UIApplicationMain 21 | class AppDelegate: CDVAppDelegate { 22 | var _window : UIWindow?; 23 | 24 | override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 25 | let bounds = UIScreen.main.bounds; 26 | 27 | let window = UIWindow(frame: bounds); 28 | window.autoresizesSubviews = true; 29 | window.rootViewController = CDVViewController(); 30 | window.makeKeyAndVisible(); 31 | 32 | self._window = window; 33 | 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/ios/App/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | OAuthPluginTests 19 | Ayogo Health Inc. 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /tests/ios/OAuthPluginTests.xcodeproj/DeviceTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "499B7A65-D2B7-4953-8541-6A9A89F54ADE", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : false, 13 | "repeatInNewRunnerProcess" : true, 14 | "targetForVariableExpansion" : { 15 | "containerPath" : "container:OAuthPluginTests.xcodeproj", 16 | "identifier" : "905131B827F1631300AC00FC", 17 | "name" : "OAuthPluginTest" 18 | }, 19 | "testRepetitionMode" : "retryOnFailure" 20 | }, 21 | "testTargets" : [ 22 | { 23 | "target" : { 24 | "containerPath" : "container:OAuthPluginTests.xcodeproj", 25 | "identifier" : "905131D827F1631600AC00FC", 26 | "name" : "UITests" 27 | } 28 | } 29 | ], 30 | "version" : 1 31 | } 32 | -------------------------------------------------------------------------------- /tests/ios/OAuthPluginTests.xcodeproj/UnitTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "AE69ADA5-1D54-4DAE-AD6A-E00C0A1C9CD0", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "codeCoverage" : true, 13 | "repeatInNewRunnerProcess" : true, 14 | "targetForVariableExpansion" : { 15 | "containerPath" : "container:OAuthPluginTests.xcodeproj", 16 | "identifier" : "905131B827F1631300AC00FC", 17 | "name" : "OAuthPluginTest" 18 | }, 19 | "testRepetitionMode" : "retryOnFailure" 20 | }, 21 | "testTargets" : [ 22 | { 23 | "parallelizable" : true, 24 | "target" : { 25 | "containerPath" : "container:OAuthPluginTests.xcodeproj", 26 | "identifier" : "905131CE27F1631600AC00FC", 27 | "name" : "Tests" 28 | } 29 | } 30 | ], 31 | "version" : 1 32 | } 33 | -------------------------------------------------------------------------------- /tests/ios/OAuthPluginTests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 53; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 905131BD27F1631300AC00FC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905131BC27F1631300AC00FC /* AppDelegate.swift */; }; 11 | 905131D427F1631600AC00FC /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905131D327F1631600AC00FC /* Tests.swift */; }; 12 | 905131DE27F1631600AC00FC /* UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905131DD27F1631600AC00FC /* UITests.swift */; }; 13 | 905131EE27F1648A00AC00FC /* Cordova in Frameworks */ = {isa = PBXBuildFile; productRef = 905131ED27F1648A00AC00FC /* Cordova */; }; 14 | 905131F127F1670900AC00FC /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 905131F027F1670900AC00FC /* config.xml */; }; 15 | 905131F827F168FC00AC00FC /* OAuthPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905131F727F168FC00AC00FC /* OAuthPlugin.swift */; }; 16 | 907328F328D04C0C006F6BFA /* www in Resources */ = {isa = PBXBuildFile; fileRef = 907328F228D04C0C006F6BFA /* www */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 905131D027F1631600AC00FC /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 905131B127F1631300AC00FC /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 905131B827F1631300AC00FC; 25 | remoteInfo = Tests; 26 | }; 27 | 905131DA27F1631600AC00FC /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 905131B127F1631300AC00FC /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 905131B827F1631300AC00FC; 32 | remoteInfo = Tests; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 905131B927F1631300AC00FC /* OAuthPluginTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OAuthPluginTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 905131BC27F1631300AC00FC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 905131CF27F1631600AC00FC /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 905131D327F1631600AC00FC /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 41 | 905131D927F1631600AC00FC /* UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 905131DD27F1631600AC00FC /* UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITests.swift; sourceTree = ""; }; 43 | 905131F027F1670900AC00FC /* config.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; 44 | 905131F727F168FC00AC00FC /* OAuthPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OAuthPlugin.swift; path = ../../../src/ios/OAuthPlugin.swift; sourceTree = ""; }; 45 | 9056A938285BDDC00086CDF8 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = UnitTests.xctestplan; path = OAuthPluginTests.xcodeproj/UnitTests.xctestplan; sourceTree = ""; }; 46 | 9056A939285BDE4E0086CDF8 /* DeviceTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = DeviceTests.xctestplan; path = OAuthPluginTests.xcodeproj/DeviceTests.xctestplan; sourceTree = ""; }; 47 | 907328F228D04C0C006F6BFA /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; name = www; path = ../../../example/platforms/ios/www; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 905131B627F1631300AC00FC /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | 905131EE27F1648A00AC00FC /* Cordova in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | 905131CC27F1631600AC00FC /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | 905131D627F1631600AC00FC /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 905131B027F1631300AC00FC = { 77 | isa = PBXGroup; 78 | children = ( 79 | 9056A939285BDE4E0086CDF8 /* DeviceTests.xctestplan */, 80 | 9056A938285BDDC00086CDF8 /* UnitTests.xctestplan */, 81 | 905131BB27F1631300AC00FC /* App */, 82 | 905131D227F1631600AC00FC /* Tests */, 83 | 905131DC27F1631600AC00FC /* UITests */, 84 | 905131BA27F1631300AC00FC /* Products */, 85 | 909D55A627F176880050F168 /* Frameworks */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | 905131BA27F1631300AC00FC /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 905131B927F1631300AC00FC /* OAuthPluginTest.app */, 93 | 905131CF27F1631600AC00FC /* Tests.xctest */, 94 | 905131D927F1631600AC00FC /* UITests.xctest */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | 905131BB27F1631300AC00FC /* App */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 907328F228D04C0C006F6BFA /* www */, 103 | 905131F727F168FC00AC00FC /* OAuthPlugin.swift */, 104 | 905131F027F1670900AC00FC /* config.xml */, 105 | 905131BC27F1631300AC00FC /* AppDelegate.swift */, 106 | ); 107 | path = App; 108 | sourceTree = ""; 109 | }; 110 | 905131D227F1631600AC00FC /* Tests */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 905131D327F1631600AC00FC /* Tests.swift */, 114 | ); 115 | path = Tests; 116 | sourceTree = ""; 117 | }; 118 | 905131DC27F1631600AC00FC /* UITests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 905131DD27F1631600AC00FC /* UITests.swift */, 122 | ); 123 | path = UITests; 124 | sourceTree = ""; 125 | }; 126 | 909D55A627F176880050F168 /* Frameworks */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | ); 130 | name = Frameworks; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXNativeTarget section */ 136 | 905131B827F1631300AC00FC /* OAuthPluginTest */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = 905131E327F1631600AC00FC /* Build configuration list for PBXNativeTarget "OAuthPluginTest" */; 139 | buildPhases = ( 140 | 905131B527F1631300AC00FC /* Sources */, 141 | 905131B627F1631300AC00FC /* Frameworks */, 142 | 905131B727F1631300AC00FC /* Resources */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = OAuthPluginTest; 149 | packageProductDependencies = ( 150 | 905131ED27F1648A00AC00FC /* Cordova */, 151 | ); 152 | productName = Tests; 153 | productReference = 905131B927F1631300AC00FC /* OAuthPluginTest.app */; 154 | productType = "com.apple.product-type.application"; 155 | }; 156 | 905131CE27F1631600AC00FC /* Tests */ = { 157 | isa = PBXNativeTarget; 158 | buildConfigurationList = 905131E627F1631600AC00FC /* Build configuration list for PBXNativeTarget "Tests" */; 159 | buildPhases = ( 160 | 905131CB27F1631600AC00FC /* Sources */, 161 | 905131CC27F1631600AC00FC /* Frameworks */, 162 | 905131CD27F1631600AC00FC /* Resources */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | 905131D127F1631600AC00FC /* PBXTargetDependency */, 168 | ); 169 | name = Tests; 170 | packageProductDependencies = ( 171 | ); 172 | productName = TestsTests; 173 | productReference = 905131CF27F1631600AC00FC /* Tests.xctest */; 174 | productType = "com.apple.product-type.bundle.unit-test"; 175 | }; 176 | 905131D827F1631600AC00FC /* UITests */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 905131E927F1631600AC00FC /* Build configuration list for PBXNativeTarget "UITests" */; 179 | buildPhases = ( 180 | 905131D527F1631600AC00FC /* Sources */, 181 | 905131D627F1631600AC00FC /* Frameworks */, 182 | 905131D727F1631600AC00FC /* Resources */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | 905131DB27F1631600AC00FC /* PBXTargetDependency */, 188 | ); 189 | name = UITests; 190 | productName = TestsUITests; 191 | productReference = 905131D927F1631600AC00FC /* UITests.xctest */; 192 | productType = "com.apple.product-type.bundle.ui-testing"; 193 | }; 194 | /* End PBXNativeTarget section */ 195 | 196 | /* Begin PBXProject section */ 197 | 905131B127F1631300AC00FC /* Project object */ = { 198 | isa = PBXProject; 199 | attributes = { 200 | BuildIndependentTargetsInParallel = 1; 201 | LastSwiftUpdateCheck = 1330; 202 | LastUpgradeCheck = 1330; 203 | TargetAttributes = { 204 | 905131B827F1631300AC00FC = { 205 | CreatedOnToolsVersion = 13.3; 206 | }; 207 | 905131CE27F1631600AC00FC = { 208 | CreatedOnToolsVersion = 13.3; 209 | TestTargetID = 905131B827F1631300AC00FC; 210 | }; 211 | 905131D827F1631600AC00FC = { 212 | CreatedOnToolsVersion = 13.3; 213 | TestTargetID = 905131B827F1631300AC00FC; 214 | }; 215 | }; 216 | }; 217 | buildConfigurationList = 905131B427F1631300AC00FC /* Build configuration list for PBXProject "OAuthPluginTests" */; 218 | compatibilityVersion = "Xcode 3.1"; 219 | developmentRegion = en; 220 | hasScannedForEncodings = 0; 221 | knownRegions = ( 222 | en, 223 | Base, 224 | ); 225 | mainGroup = 905131B027F1631300AC00FC; 226 | packageReferences = ( 227 | 905131EC27F1648A00AC00FC /* XCRemoteSwiftPackageReference "cordova-ios" */, 228 | ); 229 | productRefGroup = 905131BA27F1631300AC00FC /* Products */; 230 | projectDirPath = ""; 231 | projectRoot = ""; 232 | targets = ( 233 | 905131B827F1631300AC00FC /* OAuthPluginTest */, 234 | 905131CE27F1631600AC00FC /* Tests */, 235 | 905131D827F1631600AC00FC /* UITests */, 236 | ); 237 | }; 238 | /* End PBXProject section */ 239 | 240 | /* Begin PBXResourcesBuildPhase section */ 241 | 905131B727F1631300AC00FC /* Resources */ = { 242 | isa = PBXResourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | 907328F328D04C0C006F6BFA /* www in Resources */, 246 | 905131F127F1670900AC00FC /* config.xml in Resources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | 905131CD27F1631600AC00FC /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | 905131D727F1631600AC00FC /* Resources */ = { 258 | isa = PBXResourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXResourcesBuildPhase section */ 265 | 266 | /* Begin PBXSourcesBuildPhase section */ 267 | 905131B527F1631300AC00FC /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 905131F827F168FC00AC00FC /* OAuthPlugin.swift in Sources */, 272 | 905131BD27F1631300AC00FC /* AppDelegate.swift in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | 905131CB27F1631600AC00FC /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 905131D427F1631600AC00FC /* Tests.swift in Sources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | 905131D527F1631600AC00FC /* Sources */ = { 285 | isa = PBXSourcesBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | 905131DE27F1631600AC00FC /* UITests.swift in Sources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXSourcesBuildPhase section */ 293 | 294 | /* Begin PBXTargetDependency section */ 295 | 905131D127F1631600AC00FC /* PBXTargetDependency */ = { 296 | isa = PBXTargetDependency; 297 | target = 905131B827F1631300AC00FC /* OAuthPluginTest */; 298 | targetProxy = 905131D027F1631600AC00FC /* PBXContainerItemProxy */; 299 | }; 300 | 905131DB27F1631600AC00FC /* PBXTargetDependency */ = { 301 | isa = PBXTargetDependency; 302 | target = 905131B827F1631300AC00FC /* OAuthPluginTest */; 303 | targetProxy = 905131DA27F1631600AC00FC /* PBXContainerItemProxy */; 304 | }; 305 | /* End PBXTargetDependency section */ 306 | 307 | /* Begin XCBuildConfiguration section */ 308 | 905131E127F1631600AC00FC /* Debug */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ALWAYS_SEARCH_USER_PATHS = NO; 312 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_ENABLE_OBJC_WEAK = YES; 319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_COMMA = YES; 322 | CLANG_WARN_CONSTANT_CONVERSION = YES; 323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 326 | CLANG_WARN_EMPTY_BODY = YES; 327 | CLANG_WARN_ENUM_CONVERSION = YES; 328 | CLANG_WARN_INFINITE_RECURSION = YES; 329 | CLANG_WARN_INT_CONVERSION = YES; 330 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 332 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 333 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 334 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 335 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 336 | CLANG_WARN_STRICT_PROTOTYPES = YES; 337 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 338 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = dwarf; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | ENABLE_TESTABILITY = YES; 345 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu11; 347 | GCC_DYNAMIC_NO_PIC = NO; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_OPTIMIZATION_LEVEL = 0; 350 | GCC_PREPROCESSOR_DEFINITIONS = ( 351 | "DEBUG=1", 352 | "$(inherited)", 353 | ); 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 361 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 362 | MTL_FAST_MATH = YES; 363 | ONLY_ACTIVE_ARCH = YES; 364 | SDKROOT = iphoneos; 365 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 366 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | }; 369 | name = Debug; 370 | }; 371 | 905131E227F1631600AC00FC /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ALWAYS_SEARCH_USER_PATHS = NO; 375 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 376 | CLANG_ANALYZER_NONNULL = YES; 377 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 379 | CLANG_ENABLE_MODULES = YES; 380 | CLANG_ENABLE_OBJC_ARC = YES; 381 | CLANG_ENABLE_OBJC_WEAK = YES; 382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 383 | CLANG_WARN_BOOL_CONVERSION = YES; 384 | CLANG_WARN_COMMA = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 388 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INFINITE_RECURSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 397 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 398 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 399 | CLANG_WARN_STRICT_PROTOTYPES = YES; 400 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 401 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 402 | CLANG_WARN_UNREACHABLE_CODE = YES; 403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 404 | COPY_PHASE_STRIP = NO; 405 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 406 | ENABLE_NS_ASSERTIONS = NO; 407 | ENABLE_STRICT_OBJC_MSGSEND = YES; 408 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 409 | GCC_C_LANGUAGE_STANDARD = gnu11; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 418 | MTL_ENABLE_DEBUG_INFO = NO; 419 | MTL_FAST_MATH = YES; 420 | SDKROOT = iphoneos; 421 | SWIFT_COMPILATION_MODE = wholemodule; 422 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 423 | TARGETED_DEVICE_FAMILY = "1,2"; 424 | VALIDATE_PRODUCT = YES; 425 | }; 426 | name = Release; 427 | }; 428 | 905131E427F1631600AC00FC /* Debug */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 432 | CODE_SIGN_STYLE = Automatic; 433 | CURRENT_PROJECT_VERSION = 1; 434 | GENERATE_INFOPLIST_FILE = YES; 435 | INFOPLIST_FILE = Tests/Info.plist; 436 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 437 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 438 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 439 | LD_RUNPATH_SEARCH_PATHS = ( 440 | "$(inherited)", 441 | "@executable_path/Frameworks", 442 | ); 443 | MARKETING_VERSION = 1.0; 444 | PRODUCT_BUNDLE_IDENTIFIER = com.ayogo.cordova.oauth.tests; 445 | PRODUCT_NAME = "$(TARGET_NAME)"; 446 | SWIFT_VERSION = 5.0; 447 | }; 448 | name = Debug; 449 | }; 450 | 905131E527F1631600AC00FC /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 454 | CODE_SIGN_STYLE = Automatic; 455 | CURRENT_PROJECT_VERSION = 1; 456 | GENERATE_INFOPLIST_FILE = YES; 457 | INFOPLIST_FILE = Tests/Info.plist; 458 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 459 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 460 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 461 | LD_RUNPATH_SEARCH_PATHS = ( 462 | "$(inherited)", 463 | "@executable_path/Frameworks", 464 | ); 465 | MARKETING_VERSION = 1.0; 466 | PRODUCT_BUNDLE_IDENTIFIER = com.ayogo.cordova.oauth.tests; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | SWIFT_VERSION = 5.0; 469 | }; 470 | name = Release; 471 | }; 472 | 905131E727F1631600AC00FC /* Debug */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | BUNDLE_LOADER = "$(TEST_HOST)"; 476 | CODE_SIGN_STYLE = Automatic; 477 | CURRENT_PROJECT_VERSION = 1; 478 | GENERATE_INFOPLIST_FILE = YES; 479 | MARKETING_VERSION = 1.0; 480 | PRODUCT_BUNDLE_IDENTIFIER = com.ayogo.cordova.oauth.tests.Tests; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SWIFT_VERSION = 5.0; 483 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OAuthPluginTest.app/OAuthPluginTest"; 484 | }; 485 | name = Debug; 486 | }; 487 | 905131E827F1631600AC00FC /* Release */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | BUNDLE_LOADER = "$(TEST_HOST)"; 491 | CODE_SIGN_STYLE = Automatic; 492 | CURRENT_PROJECT_VERSION = 1; 493 | GENERATE_INFOPLIST_FILE = YES; 494 | MARKETING_VERSION = 1.0; 495 | PRODUCT_BUNDLE_IDENTIFIER = com.ayogo.cordova.oauth.tests.Tests; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | SWIFT_VERSION = 5.0; 498 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OAuthPluginTest.app/OAuthPluginTest"; 499 | }; 500 | name = Release; 501 | }; 502 | 905131EA27F1631600AC00FC /* Debug */ = { 503 | isa = XCBuildConfiguration; 504 | buildSettings = { 505 | CODE_SIGN_STYLE = Automatic; 506 | CURRENT_PROJECT_VERSION = 1; 507 | GENERATE_INFOPLIST_FILE = YES; 508 | LD_RUNPATH_SEARCH_PATHS = ( 509 | "$(inherited)", 510 | "@executable_path/Frameworks", 511 | "$(LD_RUNPATH_SEARCH_PATHS_SHALLOW_BUNDLE_$(SHALLOW_BUNDLE))", 512 | ); 513 | MARKETING_VERSION = 1.0; 514 | PRODUCT_BUNDLE_IDENTIFIER = com.ayogo.cordova.oauth.tests.UITests; 515 | PRODUCT_NAME = "$(TARGET_NAME)"; 516 | SWIFT_VERSION = 5.0; 517 | TEST_TARGET_NAME = OAuthPluginTest; 518 | }; 519 | name = Debug; 520 | }; 521 | 905131EB27F1631600AC00FC /* Release */ = { 522 | isa = XCBuildConfiguration; 523 | buildSettings = { 524 | CODE_SIGN_STYLE = Automatic; 525 | CURRENT_PROJECT_VERSION = 1; 526 | GENERATE_INFOPLIST_FILE = YES; 527 | LD_RUNPATH_SEARCH_PATHS = ( 528 | "$(inherited)", 529 | "@executable_path/Frameworks", 530 | "$(LD_RUNPATH_SEARCH_PATHS_SHALLOW_BUNDLE_$(SHALLOW_BUNDLE))", 531 | ); 532 | MARKETING_VERSION = 1.0; 533 | PRODUCT_BUNDLE_IDENTIFIER = com.ayogo.cordova.oauth.tests.UITests; 534 | PRODUCT_NAME = "$(TARGET_NAME)"; 535 | SWIFT_VERSION = 5.0; 536 | TEST_TARGET_NAME = OAuthPluginTest; 537 | }; 538 | name = Release; 539 | }; 540 | /* End XCBuildConfiguration section */ 541 | 542 | /* Begin XCConfigurationList section */ 543 | 905131B427F1631300AC00FC /* Build configuration list for PBXProject "OAuthPluginTests" */ = { 544 | isa = XCConfigurationList; 545 | buildConfigurations = ( 546 | 905131E127F1631600AC00FC /* Debug */, 547 | 905131E227F1631600AC00FC /* Release */, 548 | ); 549 | defaultConfigurationIsVisible = 0; 550 | defaultConfigurationName = Release; 551 | }; 552 | 905131E327F1631600AC00FC /* Build configuration list for PBXNativeTarget "OAuthPluginTest" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | 905131E427F1631600AC00FC /* Debug */, 556 | 905131E527F1631600AC00FC /* Release */, 557 | ); 558 | defaultConfigurationIsVisible = 0; 559 | defaultConfigurationName = Release; 560 | }; 561 | 905131E627F1631600AC00FC /* Build configuration list for PBXNativeTarget "Tests" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | 905131E727F1631600AC00FC /* Debug */, 565 | 905131E827F1631600AC00FC /* Release */, 566 | ); 567 | defaultConfigurationIsVisible = 0; 568 | defaultConfigurationName = Release; 569 | }; 570 | 905131E927F1631600AC00FC /* Build configuration list for PBXNativeTarget "UITests" */ = { 571 | isa = XCConfigurationList; 572 | buildConfigurations = ( 573 | 905131EA27F1631600AC00FC /* Debug */, 574 | 905131EB27F1631600AC00FC /* Release */, 575 | ); 576 | defaultConfigurationIsVisible = 0; 577 | defaultConfigurationName = Release; 578 | }; 579 | /* End XCConfigurationList section */ 580 | 581 | /* Begin XCRemoteSwiftPackageReference section */ 582 | 905131EC27F1648A00AC00FC /* XCRemoteSwiftPackageReference "cordova-ios" */ = { 583 | isa = XCRemoteSwiftPackageReference; 584 | repositoryURL = "https://github.com/apache/cordova-ios.git"; 585 | requirement = { 586 | branch = master; 587 | kind = branch; 588 | }; 589 | }; 590 | /* End XCRemoteSwiftPackageReference section */ 591 | 592 | /* Begin XCSwiftPackageProductDependency section */ 593 | 905131ED27F1648A00AC00FC /* Cordova */ = { 594 | isa = XCSwiftPackageProductDependency; 595 | package = 905131EC27F1648A00AC00FC /* XCRemoteSwiftPackageReference "cordova-ios" */; 596 | productName = Cordova; 597 | }; 598 | /* End XCSwiftPackageProductDependency section */ 599 | }; 600 | rootObject = 905131B127F1631300AC00FC /* Project object */; 601 | } 602 | -------------------------------------------------------------------------------- /tests/ios/OAuthPluginTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/ios/OAuthPluginTests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/ios/OAuthPluginTests.xcodeproj/xcshareddata/xcschemes/OAuthPluginTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 52 | 58 | 59 | 60 | 61 | 62 | 72 | 74 | 80 | 81 | 82 | 83 | 89 | 91 | 97 | 98 | 99 | 100 | 102 | 103 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/ios/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleURLSchemes 9 | 10 | oauthtest 11 | 12 | 13 | 14 | UILaunchScreen 15 | 16 | UIImageName 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/ios/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | import XCTest 19 | import WebKit 20 | @testable import Cordova 21 | @testable import OAuthPluginTest 22 | 23 | // MARK: Mock classes 24 | class MockCommandDelegate : NSObject, CDVCommandDelegate { 25 | var settings: CDVSettingsDictionary { 26 | get { 27 | return ["oauthscheme": "oauthtest"] 28 | } 29 | } 30 | 31 | var lastResult: CDVPluginResult! 32 | 33 | func path(forResource resourcepath: String) -> String { 34 | return ""; 35 | } 36 | 37 | func getCommandInstance(_ pluginName: String) -> CDVPlugin? { 38 | return nil; 39 | } 40 | 41 | func send(_ result: CDVPluginResult, callbackId: String) { 42 | self.lastResult = result 43 | } 44 | 45 | func evalJs(_ js: String) { } 46 | func evalJs(_ js: String, scheduledOnRunLoop: Bool) { } 47 | func run(inBackground block: (() -> Void)) { } 48 | } 49 | 50 | class MockWebViewEngine : NSObject, CDVWebViewEngineProtocol { 51 | var engineWebView: UIView 52 | 53 | func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any, Error) -> Void)? = nil) { 54 | // TODO 55 | } 56 | 57 | func url() -> URL { 58 | return URL(string: "https://example.com")! 59 | } 60 | 61 | func canLoad(_ request: URLRequest) -> Bool { 62 | return false 63 | } 64 | 65 | required init?(frame: CGRect) { 66 | self.engineWebView = WKWebView() 67 | } 68 | required init?(frame: CGRect, configuration: WKWebViewConfiguration?) { 69 | self.engineWebView = WKWebView() 70 | } 71 | func update(withInfo info: [AnyHashable : Any]) { } 72 | func load(_ request: URLRequest) -> Any { return 0 } 73 | func loadHTMLString(_ string: String, baseURL: URL?) -> Any { return 0 } 74 | } 75 | 76 | // MARK: Test Cases 77 | class OAuthPluginTests: XCTestCase { 78 | var plugin: OAuthPlugin! 79 | var cmdDlg: MockCommandDelegate! 80 | var webEngine: MockWebViewEngine! 81 | var vc: CDVViewController! 82 | 83 | override func setUpWithError() throws { 84 | // Always reset this to the default 85 | OAuthPlugin.forcedVersion = UInt32.max 86 | 87 | vc = CDVViewController() 88 | cmdDlg = MockCommandDelegate() 89 | webEngine = MockWebViewEngine(frame: CGRect(x: 0, y: 0, width: 0, height: 0)) 90 | 91 | plugin = OAuthPlugin() 92 | plugin.commandDelegate = cmdDlg 93 | plugin.viewController = vc 94 | plugin.setValue(webEngine, forKey:"webViewEngine") 95 | } 96 | 97 | override func tearDownWithError() throws { 98 | cmdDlg = nil 99 | plugin = nil 100 | webEngine = nil 101 | vc = nil 102 | } 103 | 104 | func testCallbackScheme() throws { 105 | plugin.pluginInitialize() 106 | 107 | XCTAssertEqual(plugin.callbackScheme, "oauthtest://oauth_callback", "OAuth Callback Scheme did not match") 108 | } 109 | 110 | func testOAuthCommandArgument() throws { 111 | plugin.pluginInitialize() 112 | 113 | let emptycmd = CDVInvokedUrlCommand(arguments:[], callbackId:"", className:"CDVOAuthPlugin", methodName:"startOAuth") 114 | plugin.startOAuth(emptycmd!) 115 | XCTAssertEqual(cmdDlg.lastResult.status as! UInt, CDVCommandStatus.error.rawValue) 116 | } 117 | 118 | func testOAuthCommandURL() throws { 119 | plugin.pluginInitialize() 120 | 121 | let nonURLcmd = CDVInvokedUrlCommand(arguments:[""], callbackId:"", className:"CDVOAuthPlugin", methodName:"startOAuth") 122 | plugin.startOAuth(nonURLcmd!) 123 | XCTAssertEqual(cmdDlg.lastResult.status as! UInt, CDVCommandStatus.error.rawValue) 124 | } 125 | 126 | func testSafariProvider() throws { 127 | OAuthPlugin.forcedVersion = 7 128 | plugin.pluginInitialize() 129 | 130 | let cmd = CDVInvokedUrlCommand(arguments:["https://example.com"], callbackId:"", className:"CDVOAuthPlugin", methodName:"startOAuth") 131 | plugin.startOAuth(cmd!) 132 | 133 | XCTAssertTrue(plugin.authSystem is SafariAppOAuthSessionProvider) 134 | } 135 | 136 | func testSafariViewControllerProvider() throws { 137 | guard #available(iOS 9.0, *) else { 138 | throw XCTSkip("Only for iOS 9+") 139 | } 140 | 141 | OAuthPlugin.forcedVersion = 9 142 | plugin.pluginInitialize() 143 | 144 | let cmd = CDVInvokedUrlCommand(arguments:["https://example.com"], callbackId:"", className:"CDVOAuthPlugin", methodName:"startOAuth") 145 | plugin.startOAuth(cmd!) 146 | 147 | XCTAssertTrue(plugin.authSystem is SFSafariViewControllerOAuthSessionProvider) 148 | } 149 | 150 | func testSFAuthenticationSessionProvider() throws { 151 | guard #available(iOS 11.0, *) else { 152 | throw XCTSkip("Only for iOS 11+") 153 | } 154 | 155 | OAuthPlugin.forcedVersion = 11 156 | plugin.pluginInitialize() 157 | 158 | let cmd = CDVInvokedUrlCommand(arguments:["https://example.com"], callbackId:"", className:"CDVOAuthPlugin", methodName:"startOAuth") 159 | plugin.startOAuth(cmd!) 160 | 161 | XCTAssertTrue(plugin.authSystem is SFAuthenticationSessionOAuthSessionProvider) 162 | } 163 | 164 | func testASWebAuthenticationSessionProvider() throws { 165 | guard #available(iOS 12.0, *) else { 166 | throw XCTSkip("Only for iOS 12+") 167 | } 168 | 169 | OAuthPlugin.forcedVersion = 12 170 | plugin.pluginInitialize() 171 | 172 | let cmd = CDVInvokedUrlCommand(arguments:["https://example.com"], callbackId:"", className:"CDVOAuthPlugin", methodName:"startOAuth") 173 | plugin.startOAuth(cmd!) 174 | 175 | XCTAssertTrue(plugin.authSystem is ASWebAuthenticationSessionOAuthSessionProvider) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /tests/ios/UITests/UITests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import XCTest 18 | 19 | class OAuthPluginUITests: XCTestCase { 20 | let app = XCUIApplication() 21 | 22 | override func setUpWithError() throws { 23 | continueAfterFailure = false 24 | } 25 | 26 | override func tearDownWithError() throws { 27 | let logoutBtn = app.webViews.buttons["Logout"] 28 | if logoutBtn.exists { 29 | logoutBtn.tap() 30 | sleep(1) 31 | } 32 | 33 | app.terminate() 34 | } 35 | 36 | func testOAuthFlow() throws { 37 | app.launch() 38 | app.tap() 39 | 40 | // Wait for the landing page to load 41 | let landingPage = app.staticTexts["WELCOME!"] 42 | _ = landingPage.waitForExistence(timeout: 5) 43 | 44 | // Tap the button, Kronk! 45 | let button = app.webViews.buttons["Sign in with OAuth"] 46 | XCTAssert(button.exists) 47 | XCTAssert(button.isHittable) 48 | button.tap() 49 | 50 | if #available(iOS 12.0, *) { 51 | runAuthenticationSessionFlow() 52 | } else if #available(iOS 11.0, *) { 53 | runAuthenticationSessionFlow() 54 | } else if #available(iOS 9.0, *) { 55 | runSafariViewControllerFlow() 56 | } else { 57 | runMobileSafariFlow() 58 | } 59 | 60 | // Verify the app received the OAuth token and considers us logged in 61 | let loggedIn = app.staticTexts["LOGGED IN"] 62 | _ = loggedIn.waitForExistence(timeout: 45) 63 | XCTAssert(loggedIn.exists) 64 | } 65 | 66 | func testCancelledFlow() throws { 67 | app.launch() 68 | app.tap() 69 | 70 | // Wait for the landing page to load 71 | let landingPage = app.staticTexts["WELCOME!"] 72 | _ = landingPage.waitForExistence(timeout: 5) 73 | 74 | // Tap the button, Kronk! 75 | let button = app.webViews.buttons["Sign in with OAuth"] 76 | XCTAssert(button.exists) 77 | XCTAssert(button.isHittable) 78 | button.tap() 79 | 80 | if #available(iOS 11.0, *) { 81 | let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") 82 | let continueBtn = springboard.buttons["Continue"] 83 | if continueBtn.waitForExistence(timeout: 10) { 84 | continueBtn.tap() 85 | } 86 | } 87 | 88 | // Tap the cancel button 89 | let oauthButton = app.buttons["Cancel"] 90 | _ = oauthButton.waitForExistence(timeout: 25) 91 | XCTAssert(oauthButton.exists) 92 | XCTAssert(oauthButton.isHittable) 93 | oauthButton.tap() 94 | 95 | // Verify the app received the OAuth token and considers us logged in 96 | let loggedIn = app.staticTexts["LOGIN CANCELLED!"] 97 | _ = loggedIn.waitForExistence(timeout: 45) 98 | XCTAssert(loggedIn.exists) 99 | } 100 | 101 | private func runAuthenticationSessionFlow() { 102 | let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") 103 | let continueBtn = springboard.buttons["Continue"] 104 | if continueBtn.waitForExistence(timeout: 10) { 105 | continueBtn.tap() 106 | } 107 | 108 | let oauthButton = app.webViews.buttons["Click Here to Login"] 109 | _ = oauthButton.waitForExistence(timeout: 25) 110 | XCTAssert(oauthButton.exists) 111 | XCTAssert(oauthButton.isHittable) 112 | oauthButton.tap() 113 | } 114 | 115 | private func runSafariViewControllerFlow() { 116 | let oauthButton = app.webViews.buttons["Click Here to Login"] 117 | _ = oauthButton.waitForExistence(timeout: 5) 118 | XCTAssert(oauthButton.exists) 119 | XCTAssert(oauthButton.isHittable) 120 | oauthButton.tap() 121 | } 122 | 123 | private func runMobileSafariFlow() { 124 | let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari") 125 | _ = safari.wait(for: .runningForeground, timeout: 5) 126 | 127 | let oauthButton = safari.webViews.buttons["Click Here to Login"] 128 | _ = oauthButton.waitForExistence(timeout: 5) 129 | XCTAssert(oauthButton.exists) 130 | XCTAssert(oauthButton.isHittable) 131 | oauthButton.tap() 132 | 133 | // Tell Safari to open the app 134 | let openBtn = safari.buttons["Open"] 135 | if openBtn.waitForExistence(timeout: 5) { 136 | openBtn.tap() 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /www/oauth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Ayogo Health Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var exec = require('cordova/exec'); 18 | var modulemapper = require('cordova/modulemapper'); 19 | var noop = function() { }; 20 | 21 | // https://github.com/ungap/event-target 22 | /** 23 | * Copyright (c) 2018, Andrea Giammarchi, @WebReflection 24 | * 25 | * Permission to use, copy, modify, and/or distribute this software for any 26 | * purpose with or without fee is hereby granted, provided that the above 27 | * copyright notice and this permission notice appear in all copies. 28 | * 29 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 30 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 31 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 32 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 33 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 34 | * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 35 | * PERFORMANCE OF THIS SOFTWARE. 36 | */ 37 | var EventTargetPolyfill = (function(Object, wm) { 38 | var create = Object.create; 39 | var defineProperty = Object.defineProperty; 40 | var proto = EventTarget.prototype; 41 | define(proto, 'addEventListener', function (type, listener, options) { 42 | for (var 43 | secret = wm.get(this), 44 | listeners = secret[type] || (secret[type] = []), 45 | i = 0, length = listeners.length; i < length; i++ 46 | ) { 47 | if (listeners[i].listener === listener) 48 | return; 49 | } 50 | listeners.push({target: this, listener: listener, options: options}); 51 | }); 52 | define(proto, 'dispatchEvent', function (event) { 53 | var secret = wm.get(this); 54 | var listeners = secret[event.type]; 55 | if (listeners) { 56 | define(event, 'target', this); 57 | define(event, 'currentTarget', this); 58 | listeners.slice(0).some(dispatch, event); 59 | delete event.currentTarget; 60 | delete event.target; 61 | } 62 | return true; 63 | }); 64 | define(proto, 'removeEventListener', function (type, listener) { 65 | for (var 66 | secret = wm.get(this), 67 | /* istanbul ignore next */ 68 | listeners = secret[type] || (secret[type] = []), 69 | i = 0, length = listeners.length; i < length; i++ 70 | ) { 71 | if (listeners[i].listener === listener) { 72 | listeners.splice(i, 1); 73 | return; 74 | } 75 | } 76 | }); 77 | return EventTarget; 78 | 79 | function EventTarget() {'use strict'; 80 | wm.set(this, create(null)); 81 | } 82 | function define(target, name, value) { 83 | defineProperty( 84 | target, 85 | name, 86 | { 87 | configurable: true, 88 | writable: true, 89 | value: value 90 | } 91 | ); 92 | } 93 | function dispatch(info) { 94 | var options = info.options; 95 | if (options && options.once) 96 | info.target.removeEventListener(this.type, info.listener); 97 | if (typeof info.listener === 'function') 98 | info.listener.call(info.target, this); 99 | else 100 | info.listener.handleEvent(this); 101 | return this._stopImmediatePropagationFlag; 102 | } 103 | })(Object, new WeakMap()); 104 | 105 | 106 | module.exports = function(url, name, features) { 107 | var nameMatch = name && name.match && name.match(/^oauth:/); 108 | var featureMatch = features && features.match && features.match(/^(?:.+,)?(oauth)(?:[=,].*)?$/i); 109 | 110 | if (nameMatch || featureMatch) { 111 | var wnd = null; 112 | if (window.EventTarget) { 113 | wnd = new EventTarget(); 114 | } else { 115 | wnd = new EventTargetPolyfill(); 116 | } 117 | 118 | function success() { 119 | if (wnd) { 120 | if (wnd.onclose) { 121 | wnd.onclose(); 122 | } 123 | wnd.dispatchEvent(new Event('close')); 124 | } 125 | } 126 | 127 | cordova.exec(success, noop, 'OAuth', 'startOAuth', [url]); 128 | 129 | return wnd; 130 | } else { 131 | var originalWindowOpen = modulemapper.getOriginalSymbol(window, 'open'); 132 | return originalWindowOpen.apply(window, arguments); 133 | } 134 | }; 135 | --------------------------------------------------------------------------------