diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..4143e75
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["env"],
+ "plugins": ["transform-async-to-generator", "transform-object-rest-spread"]
+}
diff --git a/.gitignore b/.gitignore
index 82a7772..0a45abc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,7 @@ npm-debug.log
project.xcworkspace/
xcuserdata/
+.idea
+.vscode
+javac-services.0.log*
+dist/
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..7e97da6
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,14 @@
+*.swp
+*~
+*.iml
+.*.haste_cache.*
+.DS_Store
+.idea
+.babelrc
+.eslintrc
+npm-debug.log
+src/
+examples/
+public/
+scripts/
+test/
\ No newline at end of file
diff --git a/README.md b/README.md
index 2c371a0..30427a1 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,20 @@
## react-native-oauth
-The `react-native-oauth` library provides an interface to OAuth 1.0 and OAuth 2.0 providers, such as [Twitter](http://twitter.com) and [Facebook](http://facebook.com) to React native.
+The `react-native-oauth` library provides an interface to OAuth 1.0 and OAuth 2.0 providers with support for the following providers for React Native apps:
+
+* Twitter
+* Facebook
+* Google
+* Github
+* Slack
## TL;DR;
This library cuts out the muck of dealing with the [OAuth 1.0](https://tools.ietf.org/html/rfc5849) and [OAuth 2.0](http://oauth.net/2/) protocols in react-native apps. The API is incredibly simple and straight-forward and is intended on getting you up and running quickly with OAuth providers (such as Facebook, Github, Twitter, etc).
```javascript
+import OAuthManager from 'react-native-oauth';
+
const manager = new OAuthManager('firestackexample')
manager.configure({
twitter: {
@@ -26,11 +34,18 @@ manager.authorize('google', {scopes: 'profile email'})
.catch(err => console.log('There was an error'));
```
+### Help
+
+Due to other time contraints, I cannot continue to work on react-native-oauth for the time it deserves. If you're interested in supporting this library, please help! It's a widely used library and I'd love to continue supporting it. Looking for maintainers!
+
## Features
* Isolates the OAuth experience to a few simple methods.
* Atomatically stores the tokens for later retrieval
-* Works with many providers and relatively simple to add a provider
+* Works with many providers and simple to add new providers
+* Works on both Android and iOS
+* Makes calling API methods a snap
+* Integrates seamlessly with Firestack (but can be used without it)
## Installation
@@ -44,40 +59,52 @@ As we are integrating with react-native, we have a little more setup to integrat
### iOS setup
-#### Automatically with [rnpm](https://github.com/rnpm/rnpm)
+**Important**: This will _not_ work if you do not complete all the steps:
-To automatically link our `react-native-oauth` client to our application, use the `rnpm` tool. [rnpm](https://github.com/rnpm/rnpm) is a React Native package manager which can help to automate the process of linking package environments.
+- [ ] Link the `RCTLinkingManager` project
+- [ ] Update your `AppDelegate.h` file
+- [ ] Add KeychainSharing in your app
+- [ ] Link the `react-native-oauth` project with your application (`react-native link`)
+- [ ] Register a URL type of your application (Info tab -- see below)
-```bash
-rnpm link
-```
+#### RCTLinkingManager
+
+Since `react-native-oauth` depends upon the `RCTLinkingManager` (from react-native core), we'll need to make sure we link this in our app.
-#### Manually
+In your app, add the following line to your `HEADER SEARCH PATHS`:
+
+```
+$(SRCROOT)/../node_modules/react-native-oauth/ios/OAuthManager
+$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS
+```
-If you prefer not to use `rnpm`, we can manually link the package together with the following steps, after `npm install`:
+
-1. In XCode, right click on `Libraries` and find the `Add Files to [project name]`.
+Next, navigate to the neighboring "Build Phases" section of project settings, find the "Link Binary with Library" drop down, expand it, and click the "+" to add _libOAuthManager.a_ to the list.
-
+Make sure to Update your `AppDelegate.m` as below, otherwise it will _not_ work.
-2. Add the `node_modules/react-native-oauth/ios/OAuthManager.xcodeproj`
+#### Automatically with [rnpm](https://github.com/rnpm/rnpm)
-
+To automatically link our `react-native-oauth` client to our application, use the `rnpm` tool. [rnpm](https://github.com/rnpm/rnpm) is a React Native package manager which can help to automate the process of linking package environments.
-3. In the project's "Build Settings" tab in your app's target, add `libOAuthManager.a` to the list of `Link Binary with Libraries`
+```bash
+react-native link react-native-oauth
+```
-
+Note: due to some restrictions on iOS, this module requires you to install cocoapods. The process has been semi-automated through using the above `react-native link` command.
-4. Ensure that the `Build Settings` of the `OAuthManager.xcodeproj` project is ticked to _All_ and it's `Header Search Paths` include both of the following paths _and_ are set to _recursive_:
+Once you have linked this library, run the following command in the root directory:
- 1. `$(SRCROOT)/../../react-native/React`
- 2. `$(SRCROOT)/../node_modules/react-native/React`
+```
+(cd ios && pod install)
+```
-
+Open in xcode the created `.xcworkspace` in the `ios/` directory (**NOT THE `.xproj` file**) when it's complete.
-### Android setup
+When working on iOS 10, we'll need to enable _Keychain Sharing Entitlement_ in _Capabilities_ of the build target of `io.fullstack.oauth.AUTH_MANAGER`.
-Coming soon (looking for contributors).
+
## Handle deep linking loading
@@ -90,14 +117,95 @@ We'll need to handle app loading from a url with our app in order to handle auth
We need to add a callback method in our `ios/AppDelegate.m` file and then call our OAuthManager helper method. Let's load the `ios/AppDelegate.m` file and add the following all the way at the bottom (but before the `@end`):
```objectivec
-- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
- sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
+// Add the import at the top:
+#import "OAuthManager.h"
+// ...
+@implementation AppDelegate
+// ...
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
+ return [OAuthManager handleOpenUrl:application
+ openURL:url
+ sourceApplication:sourceApplication
+ annotation:annotation];
+}
+```
+
+In addition, we'll need to set up the handlers within the iOS app. Add the following line somewhere in your `application:didFinishLaunchingWithOptions:` method, like so:
+
+```objectivec
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
- return [OAuthManager handleOpenUrl:application openURL:url sourceApplication:sourceApplication annotation:annotation];
+ NSURL *jsCodeLocation;
+
+ jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
+
+ // other existing setup here
+
+ // ADD THIS LINE SOMEWHERE IN THIS FUNCTION
+ [OAuthManager setupOAuthHandler:application];
+ // ...
+
+ [self.window makeKeyAndVisible];
+ return YES;
}
```
-When our app loads up with a request that is coming back from OAuthManager _and_ matches the pattern of `[app-name]://oauth-callback/{providerName}`, the OAuthManager will take over and handle the rest and storing the credentials for later use.
+When our app loads up with a request that is coming back from OAuthManager _and_ matches the url pattern, OAuthManager will take over and handle the rest and storing the credentials for later use.
+
+### Adding URL schemes
+
+In order for our app to load through these callbacks, we need to tell our iOS app that we want to load them. In order to do that, we'll have to create some URL schemes to register our app. Some providers require specific schemes (mentioned later).
+
+These URL schemes can be added by navigating to to the `info` panel of our app in Xcode (see screenshot).
+
+
+
+Let's add the appropriate one for our provider. For instance, to set up twitter, add the app name as a URL scheme in the URL scheme box.
+
+
+
+### Android setup
+
+After we link `react-native-oauth` to our application, we're ready to go. Android integration is much simpler, thanks to the in-app browser ability for our apps. `react-native-oauth` handles this for you.
+
+One note, *all* of the callback urls follow the scheme: `http://localhost/[provider_name]`. Make sure this is set as a configuration for each provider below (documented in the provider setup sections).
+
+Make sure you add the following to your `android/build.gradle` file:
+
+```
+maven { url "https://jitpack.io/" }
+```
+
+For instance, an example `android/build.gradle` file would look like this:
+
+```
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ // ...
+}
+
+allprojects {
+ repositories {
+ mavenLocal()
+ jcenter()
+ maven { url "https://jitpack.io/" } // <~ ADD THIS LINE
+ maven {
+ url "$rootDir/../node_modules/react-native/android"
+ }
+ }
+}
+```
+
+## Creating the manager
+
+In our JS, we can create the manager by instantiating a new instance of it using the `new` method and passing it the name of our app:
+
+```javascript
+const manager = new OAuthManager('firestackexample')
+```
+
+We need to pass the name of our app as the oauth manager uses this to create callback keys. This _must_ match the URL route created in your iOS app. For instance, above we created a URL scheme for Twitter. Pass this as the string in the `OAuthManager` constructor.
## Configuring our providers
@@ -115,9 +223,16 @@ const config = {
twitter: {
consumer_key: 'SOME_CONSUMER_KEY',
consumer_secret: 'SOME_CONSUMER_SECRET'
+ },
+ facebook: {
+ client_id: 'YOUR_CLIENT_ID',
+ client_secret: 'YOUR_CLIENT_SECRET'
}
}
-authManager.configureProvider("twitter", config.twitter);
+// Create the manager
+const manager = new OAuthManager('firestackexample')
+// configure the manager
+manager.configure(config);
```
The `consumer_key` and `consumer_secret` values are _generally_ provided by the provider development program. In the case of [twitter](https://apps.twitter.com), we can create an app and generate these values through their [development dashboard](https://apps.twitter.com).
@@ -126,55 +241,289 @@ The `consumer_key` and `consumer_secret` values are _generally_ provided by the
The following list are the providers we've implemented thus far in `react-native-oauth` and the _required_ keys to pass when configuring the provider:
-* Twitter
- * consumer_key
- * consumer_secret
-* Facebook (not fully implemented)
- * consumer_key
- * consumer_secret
+#### Twitter (iOS/Android)
+
+To authenticate against twitter, we need to register a Twitter application. Register your twitter application (or create a new one at [apps.twitter.com](https://apps.twitter.com)).
+
+
+
+Once you have created one, navigate to the application and find the `Keys and Access Tokens`. Take note of the consumer key and secret:
+
+
+
+For the authentication to work properly, you need to set the Callback URL. It doesn't matter what you choose as long as its a valid url.
+
+
+
+Twitter's URL scheme needs to be the app name (that we pass into the constructor method). Make sure we have one registered in Xcode as the same name:
+
+
+
+Add these values to the authorization configuration to pass to the `configure()` method as:
+
+```javascript
+const config = {
+ twitter: {
+ consumer_key: 'SOME_CONSUMER_KEY',
+ consumer_secret: 'SOME_CONSUMER_SECRET'
+ }
+}
+```
+
+#### Facebook (iOS/Android)
+
+To add facebook authentication, we'll need to have a Facebook app. To create one (or use an existing one), navigate to [developers.facebook.com/](https://developers.facebook.com/).
+
+
+
+Find or create an application and find the app id. Take note of this app id. Next, navigate to the `Settings` panel and find your client_secret.
+
+
+
+Before we leave the Facebook settings, we need to tell Facebook we have a new redirect url to register. Navigate to the bottom of the page and add the following into the `bundle ID` field:
+
+`fb{YOUR_APP_ID}`
+
+For instance, my app ID in this example is: `1745641015707619`. In the `Bundle ID` field, I have added `fb1745641015707619`.
+
+
+
+For Android, you will also need to set the redirect url to `http://localhost/facebook` in the Facebook Login settings.
+
+
+
+We'll need to create a new URL scheme for Facebook and (this is a weird bug on the Facebook side) the facebook redirect URL scheme _must be the first one_ in the list. The URL scheme needs to be the same id as the `Bundle ID` copied from above:
+
+
+
+Back in our application, add the App ID and the secret as:
+
+```javascript
+const config = {
+ facebook: {
+ client_id: 'YOUR_APP_ID',
+ client_secret: 'YOUR_APP_SECRET'
+ }
+}
+```
+
+#### Google (iOS)
+
+To add Google auth to our application, first we'll need to create a google application. Create or use an existing one by heading to the [developers.google.com/](https://developers.google.com/) page (or the console directly at [https://console.developers.google.com](https://console.developers.google.com)).
+
+
+
+We need to enable the `Identity Toolkit API` API. Click on `Enable API` and add this api to your app. Once it's enabled, we'll need to collect our credentials.
+
+Navigate to the `Credentials` tab and create a new credential. Create an **iOS API credential**. Take note of the `client_id` and the `iOS URL scheme`. In addition, make sure to set the bundle ID as the bundle id in our application in Xcode:
+
+
+
+Take note of the `iOS URL Scheme`. We'll need to add this as a URL scheme in our app. In the `Info` panel of our app target (in Xcode), add the URL scheme:
+
+
+
+Finally, add the `client_id` credential as the id from the url page as well as the ios scheme (with any path) in our app configuration:
+
+```javascript
+const config = {
+ google: {
+ callback_url: `[IOS SCHEME]:/google`,
+ client_id: 'YOUR_CLIENT_ID'
+ }
+}
+```
+
+#### Google (Android)
+
+To set up Google on Android, follow the same steps as before, except this time instead of creating an iOS API, create a **web api credential**. Make sure to add the **redirect url** at the bottom (it defaults to `http://localhost/google`):
+
+
+
+When creating an Android-specific configuration, create a file called `config/development.android.js`. React Native will load it instead of the `config/development.js` file automatically on Android.
+
+#### Github (iOS/Android)
+
+Adding Github auth to our application is pretty simple as well. We'll need to create a web application on the github apps page, which can be found at [https://github.com/settings/developers](https://github.com/settings/developers). Create one, making sure to add _two_ apps (one for iOS and one for Android) with the callback urls as:
+
+* ios: [app_name]:// oauth (for example: `firestackexample://oauth`)
+* android: http://localhost/github
+
+Take note of the `client_id` and `client_secret`
+
+
+
+The `iOS URL Scheme` is the same as the twitter version, which means we'll just add the app name as a URL scheme (i.e. `firestackexample`).
+
+Add the `client_id` and `client_secret` credentials to your configuration object:
+
+```javascript
+const config = {
+ github: {
+ client_id: 'YOUR_CLIENT_ID',
+ client_secret: 'YOUR_CLIENT_SECRET'
+ }
+}
+```
+
+## Slack
+
+We'll need to create an app first. Head to the slack developer docs at [https://slack.com/developers](https://slack.com/developers).
+
+
+
+Click on the Getting Started button:
+
+
+
+ From here, find the `create an app` link:
+
+
+
+ Take note of the `client_id` and the `client_secret`. We'll place these in our configuration object just like so:
+
+```javascript
+const config = {
+ slack: {
+ client_id: 'YOUR_CLIENT_ID',
+ client_secret: 'YOUR_CLIENT_SECRET'
+ }
+}
+```
+
+Lastly, Slack requires us to add a redirect_url.
+
+For **iOS**: the callback_url pattern is `${app_name}://oauth`, so make sure to add your redirect_url where it asks for them before starting to work with the API.
+
+for **Android**: the `callback_url` pattern is `http://localhost/slack`. Be sure to add this to your list of redirect urls.
+
+
## Authenticating against our providers
-In order to make any authenticated calls against a provider, we need to authenticate against it. The `react-native-oauth` library passes through an easy method for dealing with authentication with the `authorizeWithCallbackURL()` method.
+We can use the manager in our app using the `authorize()` method on the manager.
+
+The `authorize` method takes two arguments (the first one is required):
-Using the app uri we previous setup, we can call the `authorizeWithCallbackURL()` method to ask iOS to redirect our user to a browser where they can log in to our app in the usual flow. When the user authorizes the login request, the promise returned by the `authorizeWithCallbackURL()` is resolved. If they reject the login request for any reason, the promise is rejected along with an error, if there are any.
+* The provider we wish to authenticate against (i.e. twitter, facebook)
+* The list of options on a per-provider basis (optional)
+
+For example:
```javascript
-authManager.authorizeWithCallbackURL('twitter', 'firebase-example://oauth-callback/twitter')
-.then((oauthResponse) => {
- // the oauthResponse object is the response returned by the request
- // which is later stored by react-native-oauth using AsyncStorage
-})
-.catch((err) => {
- // err is an error object that contains the reason the user
- // error rejected authentication.
-})
+manager.authorize('twitter')
+ .then(resp => console.log(resp))
+ .catch(err => console.log(err));
+```
+
+This method returns a promise that is resolved once the authentication has been completed. You'll get access to the authentication keys in the `resp` object.
+
+The `resp` object is set as follows:
+
+```javascript
+{
+ status: "ok",
+ response: {
+ authorized: true, (boolean)
+ uuid: "UUID", (user UUID)
+ credentials: {
+ access_token: "access token",
+ refresh_token: "refresh token",
+ type: 1
+ }
+ }
+}
```
-When the response is returned, `react-native-oauth` will store the resulting credentials using the `AsyncStorage` object provided natively by React Native. All of this happens behinds the scenes _automatically_. When the credentials are successfully rerequested, `AsyncStorage` is updated behind the scenes automatically. All you have to do is take care of authenticating the user using the `authorizeWithCallbackURL()` method.
+The second argument accepts an object where we can ask for additional scopes, override default values, etc.
+
+```javascript
+manager.authorize('google', {scopes: 'email,profile'})
+ .then(resp => console.log(resp))
+ .catch(err => console.log(err));
+```
+
+* Scopes are a list of scopes _comma separated_ as a string.
## Calling a provider's API
-Lastly, we can use our new oauth token to make requests to the api to make authenticated, signed requests. For instance, to get a list of the mentions on twitter, we can make a request at the endpoint: `'https://api.twitter.com/1.1/statuses/user_timeline.json'`. Provided our user has been authorized (or previously authorized), we can make a request using these credentials using the `makeRequest()` method. The `makeRequest()` method accepts between three and five parameters and returns a promise:
+We can use OAuthManager to make requests to endpoints from our providers as well. For instance, let's say we want to get a user's time line from twitter. We would make the request to the url [https://api.twitter.com/1.1/statuses/user_timeline.json](https://api.twitter.com/1.1/statuses/user_timeline.json)
+
+If our user has been authorized for thi request, we can execute the request using the credentials stored by the OAuthManager.
+
+The `makeRequest()` method accepts 3 parameters:
+
+1. The provider we're making a request to
+2. The url (or path) we want to make the request
+3. Any additional options
+
+We can pass a list of options for our request with the last argument. The keys OAuthManager recognizes are:
+
+1. `params` - The query parameters
+2. `method` - The http method to make the request with.
+
+Available HTTP methods:
+ * get
+ * post
+ * put
+ * delete
+ * head
+ * options
+ * trace
-1. The provider our user is making a request (twitter, facebook, etc)
-2. The HTTP method to use to make the request, for instance `get` or `post`
-3. The URL to make the request
-4. (optional) parameters to pass through directly to the request
-5. (optional) headers are any headers associated with the request
```javascript
const userTimelineUrl = 'https://api.twitter.com/1.1/statuses/user_timeline.json';
-authManager.makeRequest('twitter', 'get', userTimelineUrl)
+manager
+ .makeRequest('twitter', userTimelineUrl)
+ .then(resp => {
+ console.log('Data ->', resp.data);
+ });
+```
+
+"me" represents the authenticated user, in any call to the Google+ API
+
+```javascript
+const googleUrl = 'https://www.googleapis.com/plus/v1/people/me';
+manager
+ .makeRequest('google', googleUrl)
+ .then(resp => {
+ console.log('Data -> ', resp.data);
+ });
+
+```
+
+It's possible to use just the path as well. For instance, making a request with Facebook at the `/me` endpoint can be:
+
+```javascript
+manager
+ .makeRequest('facebook', '/me')
.then(resp => {
- // resp is an object that includes both a `response` object containing
- // details of the returned response as well as a `data` object which is
- // a STRING of the returned data. OAuthManager makes zero assumptions of
- // the data type when returned and instead passes through the string response
+ console.log('Data ->', resp.data);
+ });
+```
+
+To add more data to our requests, we can pass a third argument:
+
+```javascript
+manager
+ .makeRequest('facebook', '/me', {
+ headers: { 'Content-Type': 'application/java' },
+ params: { email: 'me+rocks@ari.io' }
})
- .catch(err => {
- // err is an object that contains the error called when the promise
- // is rejected
+ .then(resp => {
+ console.log('Data ->', resp.data);
+ });
+```
+
+## Getting authorized accounts
+
+Since OAuthManager handles storing user accounts, we can query it to see which accounts have already been authorized or not using `savedAccounts()`:
+
+```javascript
+manager.savedAccounts()
+ .then(resp => {
+ console.log('account list: ', resp.accounts);
})
```
@@ -185,7 +534,22 @@ We can `deauthorize()` our user's from using the provider by calling the `deauth
1. The `provider` we want to remove from our user credentials.
```javascript
-authManager.deauthorize('twitter');
+manager.deauthorize('twitter');
+```
+
+## Adding your own providers
+
+To add your own providers you can use the `addProvider()` method and fill in your provider details:
+
+```javascript
+manager.addProvider({
+ 'name_of_provider': {
+ auth_version: '2.0',
+ authorize_url: 'https://provider.dev/oauth',
+ access_token_url: 'https://provider.dev/oauth/token',
+ callback_url: ({app_name}) => `${app_name}://oauth`,
+ }
+});
```
## Contributing
@@ -201,9 +565,8 @@ ___
## TODOS:
-[] Handle rerequesting tokens (automatically?)
-[] Simplify method of adding providers
- [] Complete [facebook](https://developers.facebook.com/docs/facebook-login) support
- [] Add [github](https://developer.github.com/v3/oauth/) support
- [] Add [Google]() support
-[] Add Android support
+* [x] Simplify method of adding providers
+* [x] Add github(https://developer.github.com/v3/oauth/) support
+* [x] Add Google support
+* [x] Add Facebook support
+* [x] Add Android support
diff --git a/android/android.iml b/android/android.iml
new file mode 100644
index 0000000..0ee1481
--- /dev/null
+++ b/android/android.iml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..b9ff010
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,50 @@
+buildscript {
+ repositories {
+ jcenter()
+ maven { url "https://jitpack.io/" }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.3'
+ }
+}
+// END
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ multiDexEnabled true
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+
+ android.packagingOptions {
+ exclude 'META-INF/LICENSE'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven { url "https://jitpack.io/" }
+ }
+}
+// END
+
+dependencies {
+ compile 'com.facebook.react:react-native:+'
+ compile 'com.github.scribejava:scribejava-apis:3.4.1'
+ compile 'com.github.delight-im:Android-AdvancedWebView:v3.0.0'
+ compile 'com.google.code.gson:gson:+'
+}
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c17c955
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Sep 02 21:23:30 PDT 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/android/gradlew b/android/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/android/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/android/gradlew.bat b/android/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/local.properties b/android/local.properties
new file mode 100644
index 0000000..ffb1ddd
--- /dev/null
+++ b/android/local.properties
@@ -0,0 +1,11 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Tue Apr 11 11:36:49 IST 2017
+sdk.dir=/Users/divyanshunegi/Downloads/adt-bundle-mac-x86_64-20140321/sdk
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..84af192
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/android/src/main/java/io/fullstack/oauth/OAuthManagerConstants.java b/android/src/main/java/io/fullstack/oauth/OAuthManagerConstants.java
new file mode 100644
index 0000000..281a035
--- /dev/null
+++ b/android/src/main/java/io/fullstack/oauth/OAuthManagerConstants.java
@@ -0,0 +1,5 @@
+package io.fullstack.oauth;
+
+public interface OAuthManagerConstants {
+ String CREDENTIALS_STORE_PREF_FILE = "oauth_manager";
+}
\ No newline at end of file
diff --git a/android/src/main/java/io/fullstack/oauth/OAuthManagerDialogFragment.java b/android/src/main/java/io/fullstack/oauth/OAuthManagerDialogFragment.java
new file mode 100644
index 0000000..12202bb
--- /dev/null
+++ b/android/src/main/java/io/fullstack/oauth/OAuthManagerDialogFragment.java
@@ -0,0 +1,340 @@
+package io.fullstack.oauth;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+
+import com.facebook.react.bridge.ReactContext;
+import com.github.scribejava.core.model.OAuth1AccessToken;
+
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import im.delight.android.webview.AdvancedWebView;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class OAuthManagerDialogFragment extends DialogFragment implements AdvancedWebView.Listener {
+
+ private static final int WEBVIEW_TAG = 100001;
+ private static final int WIDGET_TAG = 100002;
+
+ private static final String TAG = "OauthFragment";
+ private OAuthManagerFragmentController mController;
+
+ private ReactContext mReactContext;
+ private AdvancedWebView mWebView;
+ private ProgressBar mProgressBar;
+
+ public static final OAuthManagerDialogFragment newInstance(
+ final ReactContext reactContext,
+ OAuthManagerFragmentController controller
+ ) {
+ Bundle args = new Bundle();
+ OAuthManagerDialogFragment frag =
+ new OAuthManagerDialogFragment(reactContext, controller);
+ return frag;
+ }
+
+ public OAuthManagerDialogFragment(
+ final ReactContext reactContext,
+ OAuthManagerFragmentController controller
+ ) {
+ this.mController = controller;
+ this.mReactContext = reactContext;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Dialog dialog = getDialog();
+ if (dialog != null) {
+ dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final Context context = mReactContext;
+ LayoutParams rootViewLayoutParams = this.getFullscreenLayoutParams(context);
+
+ RelativeLayout rootView = new RelativeLayout(context);
+
+ mProgressBar = new ProgressBar(context);
+ RelativeLayout.LayoutParams progressParams = new RelativeLayout.LayoutParams(convertDpToPixel(50f,context),convertDpToPixel(50f,context));
+ progressParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+ mProgressBar.setLayoutParams(progressParams);
+ mProgressBar.setIndeterminate(true);
+
+ getDialog().setCanceledOnTouchOutside(true);
+ rootView.setLayoutParams(rootViewLayoutParams);
+
+ // mWebView = (AdvancedWebView) rootView.findViewById(R.id.webview);
+ Log.d(TAG, "Creating webview");
+ mWebView = new AdvancedWebView(context);
+// mWebView.setId(WEBVIEW_TAG);
+ mWebView.setListener(this, this);
+ mWebView.setVisibility(View.VISIBLE);
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.getSettings().setDomStorageEnabled(true);
+
+
+ LayoutParams layoutParams = this.getFullscreenLayoutParams(context);
+ //new LayoutParams(
+ // LayoutParams.FILL_PARENT,
+ // DIALOG_HEIGHT
+ // );
+ // mWebView.setLayoutParams(layoutParams);
+
+ rootView.addView(mWebView, layoutParams);
+ rootView.addView(mProgressBar,progressParams);
+
+ // LinearLayout pframe = new LinearLayout(context);
+ // pframe.setId(WIDGET_TAG);
+ // pframe.setOrientation(LinearLayout.VERTICAL);
+ // pframe.setVisibility(View.GONE);
+ // pframe.setGravity(Gravity.CENTER);
+ // pframe.setLayoutParams(layoutParams);
+
+ // rootView.addView(pframe, layoutParams);
+
+ this.setupWebView(mWebView);
+ mController.getRequestTokenUrlAndLoad(mWebView);
+
+ Log.d(TAG, "Loading view...");
+ return rootView;
+ }
+
+ private LayoutParams getFullscreenLayoutParams(Context context) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ // DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ Display display = wm.getDefaultDisplay();
+ int realWidth;
+ int realHeight;
+
+ if (Build.VERSION.SDK_INT >= 17){
+ //new pleasant way to get real metrics
+ DisplayMetrics realMetrics = new DisplayMetrics();
+ display.getRealMetrics(realMetrics);
+ realWidth = realMetrics.widthPixels;
+ realHeight = realMetrics.heightPixels;
+
+ } else if (Build.VERSION.SDK_INT >= 14) {
+ //reflection for this weird in-between time
+ try {
+ Method mGetRawH = Display.class.getMethod("getRawHeight");
+ Method mGetRawW = Display.class.getMethod("getRawWidth");
+ realWidth = (Integer) mGetRawW.invoke(display);
+ realHeight = (Integer) mGetRawH.invoke(display);
+ } catch (Exception e) {
+ //this may not be 100% accurate, but it's all we've got
+ realWidth = display.getWidth();
+ realHeight = display.getHeight();
+ Log.e("Display Info", "Couldn't use reflection to get the real display metrics.");
+ }
+
+ } else {
+ //This should be close, as lower API devices should not have window navigation bars
+ realWidth = display.getWidth();
+ realHeight = display.getHeight();
+ }
+
+ return new LayoutParams(realWidth, realHeight);
+ }
+
+
+ private void setupWebView(AdvancedWebView webView) {
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ return interceptUrl(view, url, true);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ mProgressBar.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onReceivedError(WebView view, int code, String desc, String failingUrl) {
+ Log.i(TAG, "onReceivedError: " + failingUrl);
+ super.onReceivedError(view, code, desc, failingUrl);
+ onError(desc);
+ }
+
+ private boolean interceptUrl(WebView view, String url, boolean loadUrl) {
+ Log.i(TAG, "interceptUrl called with url: " + url);
+
+ // url would be http://localhost/twitter?denied=xxx when it's canceled
+ Pattern p = Pattern.compile("\\S*denied\\S*");
+ Matcher m = p.matcher(url);
+ if(m.matches()){
+ Log.i(TAG, "authentication is canceled");
+ return false;
+ }
+
+ if (isCallbackUri(url, mController.getCallbackUrl())) {
+ mController.getAccessToken(mWebView, url);
+ return true;
+ }
+
+ if (loadUrl) {
+ view.loadUrl(url);
+ }
+
+ return false;
+ }
+ });
+ }
+
+ public void setComplete(final OAuth1AccessToken accessToken) {
+ Log.d(TAG, "Completed: " + accessToken);
+ }
+
+
+// @Override
+// public void onDismiss(final DialogInterface dialog) {
+// super.onDismiss(dialog);
+// Log.d(TAG, "Dismissing dialog");
+// }
+
+
+ // @Override
+ // void onCancel(DialogInterface dialog) {
+ // Log.d(TAG, "onCancel called for dialog");
+ // onError("Cancelled");
+ // }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onResume() {
+ super.onResume();
+ mWebView.onResume();
+ Log.d(TAG, "onResume called");
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onPause() {
+ Log.d(TAG, "onPause called");
+ mWebView.onPause();
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy() {
+ mWebView.onDestroy();
+ this.mController = null;
+ // ...
+ super.onDestroy();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ mWebView.onActivityResult(requestCode, resultCode, intent);
+
+ Log.d(TAG, "onActivityResult: " + requestCode);
+ // ...
+ }
+
+ @Override
+ public void onPageStarted(String url, Bitmap favicon) {
+ Log.d(TAG, "onPageStarted " + url);
+ }
+
+ @Override
+ public void onPageFinished(String url) {
+ Log.d(TAG, "onPageFinished: " + url);
+ // mController.onComplete(url);
+ }
+
+ @Override
+ public void onPageError(int errorCode, String description, String failingUrl) {
+ Log.e(TAG, "onPageError: " + failingUrl);
+ mController.onError(errorCode, description, failingUrl);
+ }
+
+ @Override
+ public void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent) { }
+
+ @Override
+ public void onExternalPageRequest(String url) {
+ Log.d(TAG, "onExternalPageRequest: " + url);
+ }
+
+ private void onError(String msg) {
+ Log.e(TAG, "Error: " + msg);
+ }
+
+ static boolean isCallbackUri(String uri, String callbackUrl) {
+ Uri u = null;
+ Uri r = null;
+ try {
+ u = Uri.parse(uri);
+ r = Uri.parse(callbackUrl);
+ } catch (NullPointerException e) {
+ return false;
+ }
+
+ if (u == null || r == null) return false;
+
+ boolean rOpaque = r.isOpaque();
+ boolean uOpaque = u.isOpaque();
+ if (uOpaque != rOpaque) return false;
+
+ if (rOpaque) return TextUtils.equals(uri, callbackUrl);
+ if (!TextUtils.equals(r.getScheme(), u.getScheme())) return false;
+ if (u.getPort() != r.getPort()) return false;
+ if (!TextUtils.isEmpty(r.getPath()) && !TextUtils.equals(r.getPath(), u.getPath())) return false;
+
+ Set paramKeys = r.getQueryParameterNames();
+ for (String key : paramKeys) {
+ if (!TextUtils.equals(r.getQueryParameter(key), u.getQueryParameter(key))) return false;
+ }
+
+ String frag = r.getFragment();
+ if (!TextUtils.isEmpty(frag) && !TextUtils.equals(frag, u.getFragment())) return false;
+ return true;
+ }
+
+ public static int convertDpToPixel(float dp, Context context){
+ Resources resources = context.getResources();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+ float px = dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
+ return (int)px;
+ }
+}
diff --git a/android/src/main/java/io/fullstack/oauth/OAuthManagerFragmentController.java b/android/src/main/java/io/fullstack/oauth/OAuthManagerFragmentController.java
new file mode 100644
index 0000000..5281699
--- /dev/null
+++ b/android/src/main/java/io/fullstack/oauth/OAuthManagerFragmentController.java
@@ -0,0 +1,395 @@
+package io.fullstack.oauth;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.facebook.react.bridge.ReactContext;
+import com.github.scribejava.core.exceptions.OAuthConnectionException;
+import com.github.scribejava.core.model.OAuth1AccessToken;
+import com.github.scribejava.core.model.OAuth1RequestToken;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth.OAuth10aService;
+import com.github.scribejava.core.oauth.OAuth20Service;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import im.delight.android.webview.AdvancedWebView;
+
+// Credit where credit is due:
+// Mostly taken from
+// https://github.com/wuman/android-oauth-client/blob/6e01b81b7319a6954a1156e8b93c0b5cbeb61446/library/src/main/java/com/wuman/android/auth/DialogFragmentController.java
+
+public class OAuthManagerFragmentController {
+ private static final String TAG = "OAuthManager";
+
+ private final android.app.FragmentManager fragmentManager;
+ private final Handler uiHandler;
+
+ private ReactContext context;
+ private String providerName;
+ private String authVersion;
+ private OAuth10aService oauth10aService;
+ private OAuth20Service oauth20Service;
+ private String callbackUrl;
+ private OAuth1RequestToken oauth1RequestToken;
+ private HashMap mCfg;
+ private AdvancedWebView mWebView;
+
+ private Runnable onAccessToken;
+ private OAuthManagerOnAccessTokenListener mListener;
+
+ private void runOnMainThread(Runnable runnable) {
+ uiHandler.post(runnable);
+ }
+
+ public OAuthManagerFragmentController(
+ final ReactContext mReactContext,
+ android.app.FragmentManager fragmentManager,
+ final String providerName,
+ OAuth10aService oauthService,
+ final String callbackUrl
+ ) {
+ this.uiHandler = new Handler(Looper.getMainLooper());
+ this.fragmentManager = fragmentManager;
+
+ this.context = mReactContext;
+ this.providerName = providerName;
+ this.authVersion = "1.0";
+ this.oauth10aService = oauthService;
+ this.callbackUrl = callbackUrl;
+ }
+
+ public OAuthManagerFragmentController(
+ final ReactContext mReactContext,
+ android.app.FragmentManager fragmentManager,
+ final String providerName,
+ OAuth20Service oauthService,
+ final String callbackUrl
+ ) {
+ this.uiHandler = new Handler(Looper.getMainLooper());
+ this.fragmentManager = fragmentManager;
+
+ this.context = mReactContext;
+ this.providerName = providerName;
+ this.authVersion = "2.0";
+ this.oauth20Service = oauthService;
+ this.callbackUrl = callbackUrl;
+ }
+
+
+ public void requestAuth(HashMap cfg, OAuthManagerOnAccessTokenListener listener) {
+ mListener = listener;
+ mCfg = cfg;
+
+ runOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "fragment manager checking...");
+ if (fragmentManager.isDestroyed()) {
+ return;
+ }
+
+ FragmentTransaction ft = fragmentManager.beginTransaction();
+ Fragment prevDialog =
+ fragmentManager.findFragmentByTag(TAG);
+
+ Log.d(TAG, "previous() Dialog?");
+
+ if (prevDialog != null) {
+ ft.remove(prevDialog);
+ }
+
+ Log.d(TAG, "Creating new Fragment");
+ OAuthManagerDialogFragment frag =
+ OAuthManagerDialogFragment.newInstance(context, OAuthManagerFragmentController.this);
+
+ ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+ ft.add(frag, TAG);
+ Log.d(TAG, "Committing with State Loss");
+ // ft.commit();
+ ft.commitAllowingStateLoss();
+ }
+ });
+ }
+
+ private void dismissDialog() {
+ runOnMainThread(new Runnable() {
+ public void run() {
+ OAuthManagerDialogFragment frag =
+ (OAuthManagerDialogFragment) fragmentManager.findFragmentByTag(TAG);
+
+ if (frag != null) {
+ frag.dismissAllowingStateLoss();
+ }
+ }
+ });
+ }
+
+ public void setRequestToken(
+ final OAuth1RequestToken requestToken
+ ) {
+ this.oauth1RequestToken = requestToken;
+ }
+
+ public void loaded10aAccessToken(final OAuth1AccessToken accessToken) {
+ Log.d(TAG, "Loaded access token in OAuthManagerFragmentController");
+ Log.d(TAG, "AccessToken: " + accessToken + " (raw: " + accessToken.getRawResponse() + ")");
+
+ mWebView = null;
+ this.dismissDialog();
+ mListener.onOAuth1AccessToken(accessToken);
+ }
+
+ public void loaded20AccessToken(final OAuth2AccessToken accessToken) {
+ mWebView = null;
+ this.dismissDialog();
+ mListener.onOAuth2AccessToken(accessToken);
+ }
+
+ public void onComplete(String url) {
+ Log.d(TAG, "onComplete called in fragment controller " + url);
+ // if (mWebView != null) {
+ // this.getAccessToken(mWebView, url);
+ // } else {
+ // this.dismissDialog();
+ // }
+ }
+
+ public void onError(int errorCode, String description, String failingUrl) {
+ Log.e(TAG, "Error in OAuthManagerFragmentController: " + description);
+ this.dismissDialog();
+ mListener.onRequestTokenError(new Exception(description));
+ }
+
+ public void getRequestTokenUrlAndLoad(AdvancedWebView webView) {
+ mWebView = webView;
+ LoadRequestTokenTask task = new LoadRequestTokenTask(this, webView);
+ task.execute();
+ }
+
+ public void getAccessToken(
+ final AdvancedWebView webView,
+ final String url
+ ) {
+ Uri responseUri = Uri.parse(url);
+ if (authVersion.equals("1.0")) {
+ String oauthToken = responseUri.getQueryParameter("oauth_token");
+ String oauthVerifier = responseUri.getQueryParameter("oauth_verifier");
+ Load1AccessTokenTask task = new Load1AccessTokenTask(
+ this, webView, oauth1RequestToken, oauthVerifier);
+ task.execute();
+ } else if (authVersion.equals("2.0")) {
+ String code = responseUri.getQueryParameter("code");
+ Log.d(TAG, "Called getAccessToken with code: " + code + " at " + url);
+ if (code != null) {
+ Load2AccessTokenTask task = new Load2AccessTokenTask(
+ this, webView, code);
+ task.execute();
+ } else {
+ this.dismissDialog();
+ mListener.onRequestTokenError(new Exception("No token found"));
+ }
+ }
+ }
+
+ ////// TASKS
+
+ private abstract class OAuthTokenTask
+ extends AsyncTask {
+ protected AdvancedWebView mWebView;
+ protected OAuthManagerFragmentController mCtrl;
+
+ public OAuthTokenTask(
+ OAuthManagerFragmentController ctrl,
+ AdvancedWebView webView
+ ) {
+ this.mCtrl = ctrl;
+ this.mWebView = webView;
+ }
+
+ @Override
+ protected Result doInBackground(Void... params) {
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(final Result result) {}
+ }
+
+ private class LoadRequestTokenTask extends OAuthTokenTask {
+ private OAuth1RequestToken oauth1RequestToken;
+
+ public LoadRequestTokenTask(
+ OAuthManagerFragmentController ctrl,
+ AdvancedWebView view
+ ) {
+ super(ctrl, view);
+ }
+
+ @Override
+ protected String doInBackground(Void... params) {
+ try {
+ if (authVersion.equals("1.0")) {
+ oauth1RequestToken = oauth10aService.getRequestToken();
+
+ final String requestTokenUrl =
+ oauth10aService.getAuthorizationUrl(oauth1RequestToken);
+ return requestTokenUrl;
+ } else if (authVersion.equals("2.0")) {
+
+ String authorizationUrl;
+
+ if (mCfg.containsKey("authorization_url_params")) {
+ final HashMap additionalParams = new HashMap();
+ additionalParams.put("access_type", "offline");
+ additionalParams.put("prompt", "consent");
+
+ Map authUrlMap = (Map) mCfg.get("authorization_url_params");
+ if (authUrlMap != null) {
+ if (authUrlMap.containsKey("access_type")) {
+ additionalParams.put("access_type", (String) authUrlMap.get("access_type"));
+ }
+ if (authUrlMap.containsKey("prompt")) {
+ additionalParams.put("prompt", (String) authUrlMap.get("prompt"));
+ }
+ }
+ authorizationUrl = oauth20Service.getAuthorizationUrl(additionalParams);
+ } else {
+ authorizationUrl = oauth20Service.getAuthorizationUrl();
+ }
+
+ return authorizationUrl;
+ } else {
+ return null;
+ }
+ } catch (OAuthConnectionException ex) {
+ Log.e(TAG, "OAuth connection exception: " + ex.getMessage());
+ ex.printStackTrace();
+ return null;
+ } catch (IOException ex) {
+ Log.e(TAG, "IOException occurred: "+ ex.getMessage());
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final String url) {
+ runOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ if (url == null) {
+ mCtrl.onError(-1, "No url", "");
+ return;
+ }
+ if (authVersion.equals("1.0")) {
+ mCtrl.setRequestToken(oauth1RequestToken);
+ mWebView.loadUrl(url);
+ } else if (authVersion.equals("2.0")) {
+ mWebView.loadUrl(url);
+ }
+ }
+ });
+ }
+ }
+
+ private class Load1AccessTokenTask extends OAuthTokenTask {
+ private String oauthVerifier;
+
+ public Load1AccessTokenTask(
+ OAuthManagerFragmentController ctrl,
+ AdvancedWebView view,
+ OAuth1RequestToken requestToken,
+ String oauthVerifier
+ ) {
+ super(ctrl, view);
+ this.oauthVerifier = oauthVerifier;
+ }
+
+ @Override
+ protected OAuth1AccessToken doInBackground(Void... params) {
+ try {
+ final OAuth1AccessToken accessToken =
+ (OAuth1AccessToken) oauth10aService.getAccessToken(oauth1RequestToken, oauthVerifier);
+ return accessToken;
+ } catch (OAuthConnectionException ex) {
+ Log.e(TAG, "OAuth connection exception: " + ex.getMessage());
+ ex.printStackTrace();
+ return null;
+ } catch (IOException ex) {
+ Log.e(TAG, "An exception occurred getRequestToken: " + ex.getMessage());
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final OAuth1AccessToken accessToken) {
+ runOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ if (accessToken == null) {
+ mCtrl.onError(-1, "No accessToken found", "");
+ return;
+ }
+ mCtrl.loaded10aAccessToken(accessToken);
+ }
+ });
+ }
+ }
+
+ private class Load2AccessTokenTask extends OAuthTokenTask {
+ private String authorizationCode;
+
+ public Load2AccessTokenTask(
+ OAuthManagerFragmentController ctrl,
+ AdvancedWebView view,
+ String authorizationCode
+ ) {
+ super(ctrl, view);
+ this.authorizationCode = authorizationCode;
+ }
+
+ @Override
+ protected OAuth2AccessToken doInBackground(Void... params) {
+ try {
+ final OAuth2AccessToken accessToken =
+ (OAuth2AccessToken) oauth20Service.getAccessToken(authorizationCode);
+ return accessToken;
+ } catch (OAuthConnectionException ex) {
+ Log.e(TAG, "OAuth connection exception: " + ex.getMessage());
+ ex.printStackTrace();
+ return null;
+ } catch (IOException ex) {
+ Log.e(TAG, "An exception occurred getRequestToken: " + ex.getMessage());
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final OAuth2AccessToken accessToken) {
+ runOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ if (accessToken == null) {
+ mCtrl.onError(-1, "No accessToken found", "");
+ return;
+ }
+ mCtrl.loaded20AccessToken(accessToken);
+ }
+ });
+ }
+ }
+
+ public String getCallbackUrl() {
+ return this.callbackUrl;
+ }
+}
diff --git a/android/src/main/java/io/fullstack/oauth/OAuthManagerModule.java b/android/src/main/java/io/fullstack/oauth/OAuthManagerModule.java
new file mode 100644
index 0000000..4ac136f
--- /dev/null
+++ b/android/src/main/java/io/fullstack/oauth/OAuthManagerModule.java
@@ -0,0 +1,577 @@
+package io.fullstack.oauth;
+
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
+import com.facebook.react.bridge.ReadableType;
+import com.facebook.react.bridge.WritableMap;
+import com.github.scribejava.core.model.OAuth1AccessToken;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth10aService;
+import com.github.scribejava.core.oauth.OAuth20Service;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class ProviderNotConfiguredException extends Exception {
+ public ProviderNotConfiguredException(String message) {
+ super(message);
+ }
+}
+
+@SuppressWarnings("WeakerAccess")
+class OAuthManagerModule extends ReactContextBaseJavaModule {
+ private static final String TAG = "OAuthManager";
+
+ private Context context;
+ private ReactContext mReactContext;
+
+ private HashMap _configuration = new HashMap>();
+ private ArrayList _callbackUrls = new ArrayList();
+ private OAuthManagerStore _credentialsStore;
+
+ public OAuthManagerModule(ReactApplicationContext reactContext) {
+ super(reactContext);
+ mReactContext = reactContext;
+ _credentialsStore = OAuthManagerStore.getOAuthManagerStore(mReactContext, TAG, Context.MODE_PRIVATE);
+ Log.d(TAG, "New instance");
+ }
+
+ @Override
+ public String getName() {
+ return TAG;
+ }
+
+ @ReactMethod
+ public void configureProvider(
+ final String providerName,
+ final ReadableMap params,
+ @Nullable final Callback onComplete
+ ) {
+ Log.i(TAG, "configureProvider for " + providerName);
+
+ // Save callback url for later
+ String callbackUrlStr = params.getString("callback_url");
+ _callbackUrls.add(callbackUrlStr);
+
+ Log.d(TAG, "Added callback url " + callbackUrlStr + " for providler " + providerName);
+
+ // Keep configuration map
+ HashMap cfg = new HashMap();
+
+ ReadableMapKeySetIterator iterator = params.keySetIterator();
+ while (iterator.hasNextKey()) {
+ String key = iterator.nextKey();
+ ReadableType readableType = params.getType(key);
+ switch(readableType) {
+ case String:
+ String val = params.getString(key);
+ // String escapedVal = Uri.encode(val);
+ cfg.put(key, val);
+ break;
+ default:
+ throw new IllegalArgumentException("Could not read object with key: " + key);
+ }
+ }
+
+ _configuration.put(providerName, cfg);
+
+ onComplete.invoke(null, true);
+ }
+
+ @ReactMethod
+ public void authorize(
+ final String providerName,
+ @Nullable final ReadableMap params,
+ final Callback callback)
+ {
+ try {
+ final OAuthManagerModule self = this;
+ final HashMap cfg = this.getConfiguration(providerName);
+ final String authVersion = (String) cfg.get("auth_version");
+ Activity activity = this.getCurrentActivity();
+ FragmentManager fragmentManager = activity.getFragmentManager();
+ String callbackUrl = "http://localhost/" + providerName;
+
+ OAuthManagerOnAccessTokenListener listener = new OAuthManagerOnAccessTokenListener() {
+ public void onRequestTokenError(final Exception ex) {
+ Log.e(TAG, "Exception with request token: " + ex.getMessage());
+ _credentialsStore.delete(providerName);
+ _credentialsStore.commit();
+ }
+ public void onOAuth1AccessToken(final OAuth1AccessToken accessToken) {
+ _credentialsStore.store(providerName, accessToken);
+ _credentialsStore.commit();
+
+ WritableMap resp = self.accessTokenResponse(providerName, cfg, accessToken, authVersion);
+ callback.invoke(null, resp);
+ }
+ public void onOAuth2AccessToken(final OAuth2AccessToken accessToken) {
+ _credentialsStore.store(providerName, accessToken);
+ _credentialsStore.commit();
+
+ WritableMap resp = self.accessTokenResponse(providerName, cfg, accessToken, authVersion);
+ callback.invoke(null, resp);
+ }
+ };
+
+ if (authVersion.equals("1.0")) {
+ final OAuth10aService service =
+ OAuthManagerProviders.getApiFor10aProvider(providerName, cfg, params, callbackUrl);
+
+ OAuthManagerFragmentController ctrl =
+ new OAuthManagerFragmentController(mReactContext, fragmentManager, providerName, service, callbackUrl);
+
+ ctrl.requestAuth(cfg, listener);
+ } else if (authVersion.equals("2.0")) {
+ final OAuth20Service service =
+ OAuthManagerProviders.getApiFor20Provider(providerName, cfg, params, callbackUrl);
+
+ OAuthManagerFragmentController ctrl =
+ new OAuthManagerFragmentController(mReactContext, fragmentManager, providerName, service, callbackUrl);
+
+ ctrl.requestAuth(cfg, listener);
+ } else {
+ Log.d(TAG, "Auth version unknown: " + (String) cfg.get("auth_version"));
+ }
+ } catch (Exception ex) {
+ Log.d(TAG, "Exception in callback " + ex.getMessage());
+ exceptionCallback(ex, callback);
+ }
+ }
+
+ @ReactMethod
+ public void makeRequest(
+ final String providerName,
+ final String urlString,
+ final ReadableMap params,
+ final Callback onComplete) {
+
+ Log.i(TAG, "makeRequest called for " + providerName + " to " + urlString);
+ try {
+ HashMap cfg = this.getConfiguration(providerName);
+ final String authVersion = (String) cfg.get("auth_version");
+
+ URL url;
+ try {
+ if (urlString.contains("http")) {
+ url = new URL(urlString);
+ } else {
+ String apiHost = (String) cfg.get("api_url");
+ url = new URL(apiHost + urlString);
+ }
+ } catch (MalformedURLException ex) {
+ Log.e(TAG, "Bad url. Check request and try again: " + ex.getMessage());
+ exceptionCallback(ex, onComplete);
+ return;
+ }
+
+ String httpMethod;
+ if (params.hasKey("method")) {
+ httpMethod = params.getString("method");
+ } else {
+ httpMethod = "GET";
+ }
+
+ Verb httpVerb;
+ if (httpMethod.equalsIgnoreCase("GET")) {
+ httpVerb = Verb.GET;
+ } else if (httpMethod.equalsIgnoreCase("POST")) {
+ httpVerb = Verb.POST;
+ } else if (httpMethod.equalsIgnoreCase("PUT")) {
+ httpVerb = Verb.PUT;
+ } else if (httpMethod.equalsIgnoreCase("DELETE")) {
+ httpVerb = Verb.DELETE;
+ } else if (httpMethod.equalsIgnoreCase("OPTIONS")) {
+ httpVerb = Verb.OPTIONS;
+ } else if (httpMethod.equalsIgnoreCase("HEAD")) {
+ httpVerb = Verb.HEAD;
+ } else if (httpMethod.equalsIgnoreCase("PATCH")) {
+ httpVerb = Verb.PATCH;
+ } else if (httpMethod.equalsIgnoreCase("TRACE")) {
+ httpVerb = Verb.TRACE;
+ } else {
+ httpVerb = Verb.GET;
+ }
+
+ ReadableMap requestParams = null;
+ if (params != null && params.hasKey("params")) {
+ requestParams = params.getMap("params");
+ }
+ OAuthRequest request = oauthRequestWithParams(providerName, cfg, authVersion, httpVerb, url, requestParams);
+
+ if (authVersion.equals("1.0")) {
+ final OAuth10aService service =
+ OAuthManagerProviders.getApiFor10aProvider(providerName, cfg, requestParams, null);
+ OAuth1AccessToken token = _credentialsStore.get(providerName, OAuth1AccessToken.class);
+
+ service.signRequest(token, request);
+ } else if (authVersion.equals("2.0")) {
+ final OAuth20Service service =
+ OAuthManagerProviders.getApiFor20Provider(providerName, cfg, requestParams, null);
+ OAuth2AccessToken token = _credentialsStore.get(providerName, OAuth2AccessToken.class);
+
+ service.signRequest(token, request);
+ } else {
+ // Some kind of error here
+ Log.e(TAG, "An error occurred");
+ WritableMap err = Arguments.createMap();
+ err.putString("status", "error");
+ err.putString("msg", "A weird error occurred");
+ onComplete.invoke(err);
+ return;
+ }
+
+ final Response response = request.send();
+ final String rawBody = response.getBody();
+
+ Log.d(TAG, "rawBody: " + rawBody);
+ // final Object response = new Gson().fromJson(rawBody, Object.class);
+
+ WritableMap resp = Arguments.createMap();
+ resp.putInt("status", response.getCode());
+ resp.putString("data", rawBody);
+ onComplete.invoke(null, resp);
+
+ } catch (IOException ex) {
+ Log.e(TAG, "IOException when making request: " + ex.getMessage());
+ ex.printStackTrace();
+ exceptionCallback(ex, onComplete);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception when making request: " + ex.getMessage());
+ exceptionCallback(ex, onComplete);
+ }
+ }
+
+ private OAuthRequest oauthRequestWithParams(
+ final String providerName,
+ final HashMap cfg,
+ final String authVersion,
+ final Verb httpVerb,
+ final URL url,
+ @Nullable final ReadableMap params
+ ) throws Exception {
+ OAuthRequest request;
+ // OAuthConfig config;
+
+ if (authVersion.equals("1.0")) {
+ // final OAuth10aService service =
+ // OAuthManagerProviders.getApiFor10aProvider(providerName, cfg, null, null);
+ OAuth1AccessToken oa1token = _credentialsStore.get(providerName, OAuth1AccessToken.class);
+ request = OAuthManagerProviders.getRequestForProvider(
+ providerName,
+ httpVerb,
+ oa1token,
+ url,
+ cfg,
+ params);
+
+ // config = service.getConfig();
+ // request = new OAuthRequest(httpVerb, url.toString(), config);
+ } else if (authVersion.equals("2.0")) {
+ // final OAuth20Service service =
+ // OAuthManagerProviders.getApiFor20Provider(providerName, cfg, null, null);
+ // oa2token = _credentialsStore.get(providerName, OAuth2AccessToken.class);
+
+ OAuth2AccessToken oa2token = _credentialsStore.get(providerName, OAuth2AccessToken.class);
+ request = OAuthManagerProviders.getRequestForProvider(
+ providerName,
+ httpVerb,
+ oa2token,
+ url,
+ cfg,
+ params);
+
+ // config = service.getConfig();
+ // request = new OAuthRequest(httpVerb, url.toString(), config);
+ } else {
+ Log.e(TAG, "Error in making request method");
+ throw new Exception("Provider not handled yet");
+ }
+
+ return request;
+ }
+
+ @ReactMethod
+ public void getSavedAccounts(final ReadableMap options, final Callback onComplete) {
+ // Log.d(TAG, "getSavedAccounts");
+ }
+
+ @ReactMethod
+ public void getSavedAccount(
+ final String providerName,
+ final ReadableMap options,
+ final Callback onComplete)
+ {
+ try {
+ HashMap cfg = this.getConfiguration(providerName);
+ final String authVersion = (String) cfg.get("auth_version");
+
+ Log.i(TAG, "getSavedAccount for " + providerName);
+
+ if (authVersion.equals("1.0")) {
+ OAuth1AccessToken token = _credentialsStore.get(providerName, OAuth1AccessToken.class);
+ Log.d(TAG, "Found token: " + token);
+ if (token == null || token.equals("")) {
+ throw new Exception("No token found");
+ }
+
+ WritableMap resp = this.accessTokenResponse(providerName, cfg, token, authVersion);
+ onComplete.invoke(null, resp);
+ } else if (authVersion.equals("2.0")) {
+ OAuth2AccessToken token = _credentialsStore.get(providerName, OAuth2AccessToken.class);
+
+ if (token == null || token.equals("")) {
+ throw new Exception("No token found");
+ }
+ WritableMap resp = this.accessTokenResponse(providerName, cfg, token, authVersion);
+ onComplete.invoke(null, resp);
+ } else {
+
+ }
+ } catch (ProviderNotConfiguredException ex) {
+ Log.e(TAG, "Provider not yet configured: " + providerName);
+ exceptionCallback(ex, onComplete);
+ } catch (Exception ex) {
+ Log.e(TAG, "An exception occurred getSavedAccount: " + ex.getMessage());
+ ex.printStackTrace();
+ exceptionCallback(ex, onComplete);
+ }
+
+ }
+
+ @ReactMethod
+ public void deauthorize(final String providerName, final Callback onComplete) {
+ try {
+ Log.i(TAG, "deauthorizing " + providerName);
+ HashMap cfg = this.getConfiguration(providerName);
+ final String authVersion = (String) cfg.get("auth_version");
+
+ _credentialsStore.delete(providerName);
+
+ WritableMap resp = Arguments.createMap();
+ resp.putString("status", "ok");
+
+ onComplete.invoke(null, resp);
+ } catch (Exception ex) {
+ exceptionCallback(ex, onComplete);
+ }
+ }
+
+
+ private HashMap getConfiguration(
+ final String providerName
+ ) throws Exception {
+ if (!_configuration.containsKey(providerName)) {
+ throw new ProviderNotConfiguredException("Provider not configured: " + providerName);
+ }
+
+ HashMap cfg = (HashMap) _configuration.get(providerName);
+ return cfg;
+ }
+
+ private WritableMap accessTokenResponse(
+ final String providerName,
+ final HashMap cfg,
+ final OAuth1AccessToken accessToken,
+ final String oauthVersion
+ ) {
+ WritableMap resp = Arguments.createMap();
+ WritableMap response = Arguments.createMap();
+
+ Log.d(TAG, "Credential raw response: " + accessToken.getRawResponse());
+
+ /* Some things return as JSON, some as x-www-form-urlencoded (querystring) */
+
+ Map accessTokenMap = null;
+ try {
+ accessTokenMap = new Gson().fromJson(accessToken.getRawResponse(), Map.class);
+ } catch (JsonSyntaxException e) {
+ /*
+ failed to parse as JSON, so turn it into a HashMap which looks like the one we'd
+ get back from the JSON parser, so the rest of the code continues unchanged.
+ */
+ Log.d(TAG, "Credential looks like a querystring; parsing as such");
+ accessTokenMap = new HashMap();
+ accessTokenMap.put("user_id", accessToken.getParameter("user_id"));
+ accessTokenMap.put("oauth_token_secret", accessToken.getParameter("oauth_token_secret"));
+ accessTokenMap.put("token_type", accessToken.getParameter("token_type"));
+ }
+
+
+ resp.putString("status", "ok");
+ resp.putBoolean("authorized", true);
+ resp.putString("provider", providerName);
+
+ String uuid = accessToken.getParameter("user_id");
+ response.putString("uuid", uuid);
+ String oauthTokenSecret = (String) accessToken.getParameter("oauth_token_secret");
+
+ String tokenType = (String) accessToken.getParameter("token_type");
+ if (tokenType == null) {
+ tokenType = "Bearer";
+ }
+
+ String consumerKey = (String) cfg.get("consumer_key");
+
+ WritableMap credentials = Arguments.createMap();
+ credentials.putString("access_token", accessToken.getToken());
+ credentials.putString("access_token_secret", oauthTokenSecret);
+ credentials.putString("type", tokenType);
+ credentials.putString("consumerKey", consumerKey);
+
+ response.putMap("credentials", credentials);
+
+ resp.putMap("response", response);
+
+ return resp;
+ }
+
+ private WritableMap accessTokenResponse(
+ final String providerName,
+ final HashMap cfg,
+ final OAuth2AccessToken accessToken,
+ final String oauthVersion
+ ) {
+ WritableMap resp = Arguments.createMap();
+ WritableMap response = Arguments.createMap();
+
+ resp.putString("status", "ok");
+ resp.putBoolean("authorized", true);
+ resp.putString("provider", providerName);
+
+ String uuid = accessToken.getParameter("user_id");
+ response.putString("uuid", uuid);
+
+ WritableMap credentials = Arguments.createMap();
+ Log.d(TAG, "Credential raw response: " + accessToken.getRawResponse());
+
+ credentials.putString("accessToken", accessToken.getAccessToken());
+ String authHeader;
+
+ String tokenType = accessToken.getTokenType();
+ if (tokenType == null) {
+ tokenType = "Bearer";
+ }
+
+ String scope = accessToken.getScope();
+ if (scope == null) {
+ scope = (String) cfg.get("scopes");
+ }
+
+ String clientID = (String) cfg.get("client_id");
+ String idToken = accessToken.getParameter("id_token");
+
+ authHeader = tokenType + " " + accessToken.getAccessToken();
+ credentials.putString("authorizationHeader", authHeader);
+ credentials.putString("type", tokenType);
+ credentials.putString("scopes", scope);
+ credentials.putString("clientID", clientID);
+ credentials.putString("idToken", idToken);
+ response.putMap("credentials", credentials);
+
+ resp.putMap("response", response);
+
+ return resp;
+ }
+
+
+ private void exceptionCallback(Exception ex, final Callback onFail) {
+ WritableMap error = Arguments.createMap();
+ error.putInt("errorCode", ex.hashCode());
+ error.putString("errorMessage", ex.getMessage());
+ error.putString("allErrorMessage", ex.toString());
+
+ onFail.invoke(error);
+ }
+
+ public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) {
+ Map deconstructedMap = new HashMap<>();
+ if (readableMap == null) {
+ return deconstructedMap;
+ }
+
+ ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
+ while (iterator.hasNextKey()) {
+ String key = iterator.nextKey();
+ ReadableType type = readableMap.getType(key);
+ switch (type) {
+ case Null:
+ deconstructedMap.put(key, null);
+ break;
+ case Boolean:
+ deconstructedMap.put(key, readableMap.getBoolean(key));
+ break;
+ case Number:
+ deconstructedMap.put(key, readableMap.getDouble(key));
+ break;
+ case String:
+ deconstructedMap.put(key, readableMap.getString(key));
+ break;
+ case Map:
+ deconstructedMap.put(key, OAuthManagerModule.recursivelyDeconstructReadableMap(readableMap.getMap(key)));
+ break;
+ case Array:
+ deconstructedMap.put(key, OAuthManagerModule.recursivelyDeconstructReadableArray(readableMap.getArray(key)));
+ break;
+ default:
+ throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
+ }
+
+ }
+ return deconstructedMap;
+ }
+
+ public static List