@capgo/capacitor-social-login 0.0.49 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -17
- package/android/src/main/java/ee/forgr/capacitor/social/login/FacebookProvider.java +101 -55
- package/android/src/main/java/ee/forgr/capacitor/social/login/SocialLoginPlugin.java +38 -5
- package/dist/docs.json +1 -1
- package/dist/esm/definitions.d.ts +4 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/SocialLoginPlugin/AppleProvider.swift +104 -60
- package/ios/Sources/SocialLoginPlugin/FacebookProvider.swift +35 -22
- package/ios/Sources/SocialLoginPlugin/SocialLoginPlugin.swift +24 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,22 +75,6 @@ const res = await SocialLogin.login({
|
|
|
75
75
|
|
|
76
76
|
### Android configuration
|
|
77
77
|
|
|
78
|
-
In file `android/app/src/main/AndroidManifest.xml`, add the following XML elements under `<manifest><application>` :
|
|
79
|
-
|
|
80
|
-
```xml
|
|
81
|
-
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
|
|
82
|
-
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
In file `android/app/src/main/res/values/strings.xml` add the following lines :
|
|
86
|
-
|
|
87
|
-
```xml
|
|
88
|
-
<string name="facebook_app_id">[APP_ID]</string>
|
|
89
|
-
<string name="facebook_client_token">[CLIENT_TOKEN]</string>
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Don't forget to replace `[APP_ID]` and `[CLIENT_TOKEN]` by your Facebook application Id.
|
|
93
|
-
|
|
94
78
|
More information can be found here: https://developers.facebook.com/docs/android/getting-started
|
|
95
79
|
|
|
96
80
|
Then call the `initialize` method with the `facebook` provider
|
|
@@ -99,6 +83,7 @@ Then call the `initialize` method with the `facebook` provider
|
|
|
99
83
|
await SocialLogin.initialize({
|
|
100
84
|
facebook: {
|
|
101
85
|
appId: 'your-app-id',
|
|
86
|
+
clientToken: 'your-client-token',
|
|
102
87
|
},
|
|
103
88
|
});
|
|
104
89
|
const res = await SocialLogin.login({
|
|
@@ -362,7 +347,7 @@ Refresh the access token
|
|
|
362
347
|
|
|
363
348
|
| Prop | Type |
|
|
364
349
|
| -------------- | ---------------------------------------------------------------------------------------- |
|
|
365
|
-
| **`facebook`** | <code>{ appId: string; }</code>
|
|
350
|
+
| **`facebook`** | <code>{ appId: string; clientToken: string; }</code> |
|
|
366
351
|
| **`google`** | <code>{ iOSClientId?: string; iOSServerClientId?: string; webClientId?: string; }</code> |
|
|
367
352
|
| **`apple`** | <code>{ clientId?: string; redirectUrl?: string; }</code> |
|
|
368
353
|
|
|
@@ -8,12 +8,16 @@ import com.facebook.AccessToken;
|
|
|
8
8
|
import com.facebook.CallbackManager;
|
|
9
9
|
import com.facebook.FacebookCallback;
|
|
10
10
|
import com.facebook.FacebookException;
|
|
11
|
+
import com.facebook.FacebookSdk;
|
|
11
12
|
import com.facebook.GraphRequest;
|
|
12
13
|
import com.facebook.GraphResponse;
|
|
14
|
+
import com.facebook.login.LoginBehavior;
|
|
13
15
|
import com.facebook.login.LoginManager;
|
|
14
16
|
import com.facebook.login.LoginResult;
|
|
17
|
+
import com.getcapacitor.JSArray;
|
|
15
18
|
import com.getcapacitor.JSObject;
|
|
16
19
|
import com.getcapacitor.PluginCall;
|
|
20
|
+
import androidx.activity.result.ActivityResultRegistryOwner;
|
|
17
21
|
import ee.forgr.capacitor.social.login.helpers.JsonHelper;
|
|
18
22
|
import ee.forgr.capacitor.social.login.helpers.SocialProvider;
|
|
19
23
|
import java.util.Collection;
|
|
@@ -31,67 +35,92 @@ public class FacebookProvider implements SocialProvider {
|
|
|
31
35
|
this.activity = activity;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
public void initialize(JSONObject
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
public void initialize(JSONObject config) {
|
|
39
|
+
try {
|
|
40
|
+
// Set Facebook App ID
|
|
41
|
+
String facebookAppId = config.getString("appId");
|
|
42
|
+
FacebookSdk.setApplicationId(facebookAppId);
|
|
43
|
+
|
|
44
|
+
// Set Facebook Client Token
|
|
45
|
+
String facebookClientToken = config.getString("clientToken");
|
|
46
|
+
FacebookSdk.setClientToken(facebookClientToken);
|
|
47
|
+
|
|
48
|
+
// Initialize Facebook SDK
|
|
49
|
+
FacebookSdk.sdkInitialize(activity.getApplicationContext());
|
|
50
|
+
|
|
51
|
+
this.callbackManager = CallbackManager.Factory.create();
|
|
52
|
+
|
|
53
|
+
LoginManager.getInstance().registerCallback(callbackManager,
|
|
54
|
+
new FacebookCallback<LoginResult>() {
|
|
55
|
+
@Override
|
|
56
|
+
public void onSuccess(LoginResult loginResult) {
|
|
57
|
+
Log.d(LOG_TAG, "LoginManager.onSuccess");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Override
|
|
61
|
+
public void onCancel() {
|
|
62
|
+
Log.d(LOG_TAG, "LoginManager.onCancel");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Override
|
|
66
|
+
public void onError(FacebookException exception) {
|
|
67
|
+
Log.e(LOG_TAG, "LoginManager.onError", exception);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
} catch (JSONException e) {
|
|
71
|
+
Log.e(LOG_TAG, "Error initializing Facebook SDK", e);
|
|
72
|
+
throw new RuntimeException("Failed to initialize Facebook SDK: " + e.getMessage());
|
|
73
|
+
}
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
@Override
|
|
60
77
|
public void login(PluginCall call, JSONObject config) {
|
|
61
78
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
65
|
-
LoginManager.getInstance()
|
|
66
|
-
.registerCallback(
|
|
67
|
-
callbackManager,
|
|
68
|
-
new FacebookCallback<LoginResult>() {
|
|
69
|
-
@Override
|
|
70
|
-
public void onSuccess(LoginResult loginResult) {
|
|
71
|
-
Log.d(LOG_TAG, "LoginManager.onSuccess");
|
|
72
|
-
AccessToken accessToken = loginResult.getAccessToken();
|
|
73
|
-
JSObject result = new JSObject();
|
|
74
|
-
result.put("accessToken", accessToken.getToken());
|
|
75
|
-
result.put("userId", accessToken.getUserId());
|
|
76
|
-
call.resolve(result);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
@Override
|
|
80
|
-
public void onCancel() {
|
|
81
|
-
Log.d(LOG_TAG, "LoginManager.onCancel");
|
|
82
|
-
call.reject("Login cancelled");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@Override
|
|
86
|
-
public void onError(FacebookException exception) {
|
|
87
|
-
Log.e(LOG_TAG, "LoginManager.onError", exception);
|
|
88
|
-
call.reject(exception.getMessage());
|
|
89
|
-
}
|
|
90
|
-
}
|
|
79
|
+
Collection<String> permissions = JsonHelper.jsonArrayToList(
|
|
80
|
+
config.getJSONArray("permissions")
|
|
91
81
|
);
|
|
92
|
-
|
|
82
|
+
boolean limitedLogin = config.optBoolean("limitedLogin", false);
|
|
83
|
+
String nonce = config.optString("nonce", "");
|
|
84
|
+
|
|
85
|
+
LoginManager.getInstance().registerCallback(callbackManager,
|
|
86
|
+
new FacebookCallback<LoginResult>() {
|
|
87
|
+
@Override
|
|
88
|
+
public void onSuccess(LoginResult loginResult) {
|
|
89
|
+
Log.d(LOG_TAG, "LoginManager.onSuccess");
|
|
90
|
+
AccessToken accessToken = loginResult.getAccessToken();
|
|
91
|
+
JSObject result = new JSObject();
|
|
92
|
+
result.put("accessToken", createAccessTokenObject(accessToken));
|
|
93
|
+
result.put("authenticationToken", loginResult.getAuthenticationToken() != null ? loginResult.getAuthenticationToken().getToken() : null);
|
|
94
|
+
// TODO: Fetch profile information and add it to the result
|
|
95
|
+
call.resolve(result);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Override
|
|
99
|
+
public void onCancel() {
|
|
100
|
+
Log.d(LOG_TAG, "LoginManager.onCancel");
|
|
101
|
+
call.reject("Login cancelled");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@Override
|
|
105
|
+
public void onError(FacebookException exception) {
|
|
106
|
+
Log.e(LOG_TAG, "LoginManager.onError", exception);
|
|
107
|
+
call.reject(exception.getMessage());
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
LoginManager loginManager = LoginManager.getInstance();
|
|
112
|
+
if (limitedLogin) {
|
|
113
|
+
Log.w(LOG_TAG, "Limited login is not available for Android");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
loginManager.setLoginBehavior(LoginBehavior.NATIVE_WITH_FALLBACK);
|
|
117
|
+
if (!nonce.isEmpty()) {
|
|
118
|
+
loginManager.logIn((ActivityResultRegistryOwner) activity, callbackManager, permissions, nonce);
|
|
119
|
+
} else {
|
|
120
|
+
loginManager.logIn((ActivityResultRegistryOwner) activity, callbackManager, permissions);
|
|
121
|
+
}
|
|
93
122
|
} catch (JSONException e) {
|
|
94
|
-
|
|
123
|
+
call.reject("Invalid login options format");
|
|
95
124
|
}
|
|
96
125
|
}
|
|
97
126
|
|
|
@@ -170,6 +199,23 @@ public class FacebookProvider implements SocialProvider {
|
|
|
170
199
|
int resultCode,
|
|
171
200
|
Intent data
|
|
172
201
|
) {
|
|
173
|
-
|
|
202
|
+
Log.d(LOG_TAG, "FacebookProvider.handleOnActivityResult called");
|
|
203
|
+
if (callbackManager != null) {
|
|
204
|
+
return callbackManager.onActivityResult(requestCode, resultCode, data);
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private JSObject createAccessTokenObject(AccessToken accessToken) {
|
|
210
|
+
JSObject tokenObject = new JSObject();
|
|
211
|
+
tokenObject.put("applicationId", accessToken.getApplicationId());
|
|
212
|
+
tokenObject.put("declinedPermissions", new JSArray(accessToken.getDeclinedPermissions()));
|
|
213
|
+
tokenObject.put("expires", accessToken.getExpires().getTime());
|
|
214
|
+
tokenObject.put("isExpired", accessToken.isExpired());
|
|
215
|
+
tokenObject.put("lastRefresh", accessToken.getLastRefresh().getTime());
|
|
216
|
+
tokenObject.put("permissions", new JSArray(accessToken.getPermissions()));
|
|
217
|
+
tokenObject.put("token", accessToken.getToken());
|
|
218
|
+
tokenObject.put("userId", accessToken.getUserId());
|
|
219
|
+
return tokenObject;
|
|
174
220
|
}
|
|
175
221
|
}
|
|
@@ -69,11 +69,24 @@ public class SocialLoginPlugin extends Plugin {
|
|
|
69
69
|
|
|
70
70
|
JSObject facebook = call.getObject("facebook");
|
|
71
71
|
if (facebook != null) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
String facebookAppId = facebook.getString("appId");
|
|
73
|
+
String facebookClientToken = facebook.getString("clientToken");
|
|
74
|
+
if (facebookAppId == null || facebookAppId.isEmpty()) {
|
|
75
|
+
call.reject("facebook.appId is null or empty");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (facebookClientToken == null || facebookClientToken.isEmpty()) {
|
|
79
|
+
call.reject("facebook.clientToken is null or empty");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
FacebookProvider facebookProvider = new FacebookProvider(this.getActivity());
|
|
83
|
+
try {
|
|
84
|
+
facebookProvider.initialize(facebook);
|
|
85
|
+
this.socialProviderHashMap.put("facebook", facebookProvider);
|
|
86
|
+
} catch (Exception e) {
|
|
87
|
+
call.reject("Failed to initialize Facebook provider: " + e.getMessage());
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
call.resolve();
|
|
@@ -176,4 +189,24 @@ public class SocialLoginPlugin extends Plugin {
|
|
|
176
189
|
Log.e(SocialLoginPlugin.LOG_TAG, "Cannot handle apple login intent");
|
|
177
190
|
}
|
|
178
191
|
}
|
|
192
|
+
|
|
193
|
+
@Override
|
|
194
|
+
protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
|
195
|
+
super.handleOnActivityResult(requestCode, resultCode, data);
|
|
196
|
+
|
|
197
|
+
Log.d(LOG_TAG, "SocialLoginPlugin.handleOnActivityResult called");
|
|
198
|
+
|
|
199
|
+
// Handle Facebook login result
|
|
200
|
+
SocialProvider facebookProvider = socialProviderHashMap.get("facebook");
|
|
201
|
+
if (facebookProvider instanceof FacebookProvider) {
|
|
202
|
+
boolean handled = ((FacebookProvider) facebookProvider).handleOnActivityResult(requestCode, resultCode, data);
|
|
203
|
+
if (handled) {
|
|
204
|
+
Log.d(LOG_TAG, "Facebook activity result handled");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Handle other providers' activity results if needed
|
|
210
|
+
Log.d(LOG_TAG, "Activity result not handled by any provider");
|
|
211
|
+
}
|
|
179
212
|
}
|
package/dist/docs.json
CHANGED
|
@@ -4,6 +4,10 @@ export interface InitializeOptions {
|
|
|
4
4
|
* Facebook App ID, provided by Facebook for web, in mobile it's set in the native files
|
|
5
5
|
*/
|
|
6
6
|
appId: string;
|
|
7
|
+
/**
|
|
8
|
+
* Facebook Client Token, provided by Facebook for web, in mobile it's set in the native files
|
|
9
|
+
*/
|
|
10
|
+
clientToken: string;
|
|
7
11
|
};
|
|
8
12
|
google?: {
|
|
9
13
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface InitializeOptions {\n facebook?: {\n /**\n * Facebook App ID, provided by Facebook for web, in mobile it's set in the native files\n */\n appId: string;\n };\n\n google?: {\n /**\n * The app's client ID, found and created in the Google Developers Console.\n * For iOS.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSClientId?: string;\n /**\n * The app's server client ID, found and created in the Google Developers Console.\n * For iOS.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSServerClientId?: string;\n /**\n * The app's web client ID, found and created in the Google Developers Console.\n * For Android (and web in the future).\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n webClientId?: string;\n };\n apple?: {\n /**\n * Apple Client ID, provided by Apple for web and Android\n */\n clientId?: string;\n /**\n * Apple Redirect URL, should be your backend url that is configured in your apple app, only for android\n */\n redirectUrl?: string;\n };\n}\n\nexport interface FacebookLoginOptions {\n /**\n * Permissions\n * @description select permissions to login with\n */\n permissions: string[];\n /**\n * Is Limited Login\n * @description use limited login for Facebook IOS\n * @default false\n */\n limitedLogin?: boolean;\n /**\n * Nonce\n * @description A custom nonce to use for the login request\n */\n nonce?: string;\n}\n\nexport interface GoogleLoginOptions {\n /**\n * Specifies the scopes required for accessing Google APIs\n * The default is defined in the configuration.\n * @example [\"profile\", \"email\"]\n * @see [Google OAuth2 Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * Set if your application needs to refresh access tokens when the user is not present at the browser.\n * In response use `serverAuthCode` key\n *\n * @default false\n * @since 3.1.0\n * */\n grantOfflineAccess?: boolean;\n}\n\nexport interface GoogleLoginResponse {\n accessToken: AccessToken | null;\n idToken: string | null;\n profile: {\n email: string | null;\n familyName: string | null;\n givenName: string | null;\n id: string | null;\n name: string | null;\n imageUrl: string | null;\n };\n}\n\nexport interface AppleProviderOptions {\n /**\n * Scopes\n * @description select scopes to login with\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * State\n * @description state\n */\n state?: string;\n}\n\nexport interface AppleProviderResponse {\n user: string | null;\n email: string | null;\n givenName: string | null;\n familyName: string | null;\n identityToken: string | null;\n authorizationCode: string | null;\n}\n\nexport interface LoginOptions {\n /**\n * Provider\n * @description select provider to login with\n */\n provider: \"facebook\" | \"google\" | \"apple\" | \"twitter\";\n /**\n * Options\n * @description payload to login with\n */\n options: FacebookLoginOptions | GoogleLoginOptions | AppleProviderOptions;\n}\n\nexport interface LoginResult {\n /**\n * Provider\n * @description select provider to login with\n */\n provider: \"facebook\" | \"google\" | \"apple\" | \"twitter\";\n /**\n * Payload\n * @description payload to login with\n */\n result: FacebookLoginResponse | GoogleLoginResponse | AppleProviderResponse;\n}\n\nexport interface AccessToken {\n applicationId?: string;\n declinedPermissions?: string[];\n expires?: string;\n isExpired?: boolean;\n lastRefresh?: string;\n permissions?: string[];\n token: string;\n userId?: string;\n}\n\nexport interface FacebookLoginResponse {\n accessToken: AccessToken | null;\n profile: {\n userID: string;\n email: string | null;\n friendIDs: string[];\n birthday: string | null;\n ageRange: { min?: number; max?: number } | null;\n gender: string | null;\n location: { id: string; name: string } | null;\n hometown: { id: string; name: string } | null;\n profileURL: string | null;\n name: string | null;\n imageURL: string | null;\n };\n authenticationToken: string | null;\n}\n\nexport interface AuthorizationCode {\n /**\n * Jwt\n * @description A JSON web token\n */\n jwt: string;\n}\n\nexport interface AuthorizationCodeOptions {\n /**\n * Provider\n * @description Provider for the authorization code\n */\n provider: \"apple\" | \"google\" | \"facebook\";\n}\n\nexport interface isLoggedInOptions {\n /**\n * Provider\n * @description Provider for the isLoggedIn\n */\n provider: \"apple\" | \"google\" | \"facebook\";\n}\n\nexport interface SocialLoginPlugin {\n /**\n * Initialize the plugin\n * @description initialize the plugin with the required options\n */\n initialize(options: InitializeOptions): Promise<void>;\n /**\n * Login with the selected provider\n * @description login with the selected provider\n */\n login(options: LoginOptions): Promise<LoginResult>;\n /**\n * Logout\n * @description logout the user\n */\n logout(options: { provider: \"apple\" | \"google\" | \"facebook\" }): Promise<void>;\n /**\n * IsLoggedIn\n * @description logout the user\n */\n isLoggedIn(options: isLoggedInOptions): Promise<{ isLoggedIn: boolean }>;\n\n /**\n * Get the current access token\n * @description get the current access token\n */\n getAuthorizationCode(\n options: AuthorizationCodeOptions,\n ): Promise<AuthorizationCode>;\n /**\n * Refresh the access token\n * @description refresh the access token\n */\n refresh(options: LoginOptions): Promise<void>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface InitializeOptions {\n facebook?: {\n /**\n * Facebook App ID, provided by Facebook for web, in mobile it's set in the native files\n */\n appId: string;\n /**\n * Facebook Client Token, provided by Facebook for web, in mobile it's set in the native files\n */\n clientToken: string;\n };\n\n google?: {\n /**\n * The app's client ID, found and created in the Google Developers Console.\n * For iOS.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSClientId?: string;\n /**\n * The app's server client ID, found and created in the Google Developers Console.\n * For iOS.\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n iOSServerClientId?: string;\n /**\n * The app's web client ID, found and created in the Google Developers Console.\n * For Android (and web in the future).\n * @example xxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com\n * @since 3.1.0\n */\n webClientId?: string;\n };\n apple?: {\n /**\n * Apple Client ID, provided by Apple for web and Android\n */\n clientId?: string;\n /**\n * Apple Redirect URL, should be your backend url that is configured in your apple app, only for android\n */\n redirectUrl?: string;\n };\n}\n\nexport interface FacebookLoginOptions {\n /**\n * Permissions\n * @description select permissions to login with\n */\n permissions: string[];\n /**\n * Is Limited Login\n * @description use limited login for Facebook IOS\n * @default false\n */\n limitedLogin?: boolean;\n /**\n * Nonce\n * @description A custom nonce to use for the login request\n */\n nonce?: string;\n}\n\nexport interface GoogleLoginOptions {\n /**\n * Specifies the scopes required for accessing Google APIs\n * The default is defined in the configuration.\n * @example [\"profile\", \"email\"]\n * @see [Google OAuth2 Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * Set if your application needs to refresh access tokens when the user is not present at the browser.\n * In response use `serverAuthCode` key\n *\n * @default false\n * @since 3.1.0\n * */\n grantOfflineAccess?: boolean;\n}\n\nexport interface GoogleLoginResponse {\n accessToken: AccessToken | null;\n idToken: string | null;\n profile: {\n email: string | null;\n familyName: string | null;\n givenName: string | null;\n id: string | null;\n name: string | null;\n imageUrl: string | null;\n };\n}\n\nexport interface AppleProviderOptions {\n /**\n * Scopes\n * @description select scopes to login with\n */\n scopes?: string[];\n /**\n * Nonce\n * @description nonce\n */\n nonce?: string;\n /**\n * State\n * @description state\n */\n state?: string;\n}\n\nexport interface AppleProviderResponse {\n user: string | null;\n email: string | null;\n givenName: string | null;\n familyName: string | null;\n identityToken: string | null;\n authorizationCode: string | null;\n}\n\nexport interface LoginOptions {\n /**\n * Provider\n * @description select provider to login with\n */\n provider: \"facebook\" | \"google\" | \"apple\" | \"twitter\";\n /**\n * Options\n * @description payload to login with\n */\n options: FacebookLoginOptions | GoogleLoginOptions | AppleProviderOptions;\n}\n\nexport interface LoginResult {\n /**\n * Provider\n * @description select provider to login with\n */\n provider: \"facebook\" | \"google\" | \"apple\" | \"twitter\";\n /**\n * Payload\n * @description payload to login with\n */\n result: FacebookLoginResponse | GoogleLoginResponse | AppleProviderResponse;\n}\n\nexport interface AccessToken {\n applicationId?: string;\n declinedPermissions?: string[];\n expires?: string;\n isExpired?: boolean;\n lastRefresh?: string;\n permissions?: string[];\n token: string;\n userId?: string;\n}\n\nexport interface FacebookLoginResponse {\n accessToken: AccessToken | null;\n profile: {\n userID: string;\n email: string | null;\n friendIDs: string[];\n birthday: string | null;\n ageRange: { min?: number; max?: number } | null;\n gender: string | null;\n location: { id: string; name: string } | null;\n hometown: { id: string; name: string } | null;\n profileURL: string | null;\n name: string | null;\n imageURL: string | null;\n };\n authenticationToken: string | null;\n}\n\nexport interface AuthorizationCode {\n /**\n * Jwt\n * @description A JSON web token\n */\n jwt: string;\n}\n\nexport interface AuthorizationCodeOptions {\n /**\n * Provider\n * @description Provider for the authorization code\n */\n provider: \"apple\" | \"google\" | \"facebook\";\n}\n\nexport interface isLoggedInOptions {\n /**\n * Provider\n * @description Provider for the isLoggedIn\n */\n provider: \"apple\" | \"google\" | \"facebook\";\n}\n\nexport interface SocialLoginPlugin {\n /**\n * Initialize the plugin\n * @description initialize the plugin with the required options\n */\n initialize(options: InitializeOptions): Promise<void>;\n /**\n * Login with the selected provider\n * @description login with the selected provider\n */\n login(options: LoginOptions): Promise<LoginResult>;\n /**\n * Logout\n * @description logout the user\n */\n logout(options: { provider: \"apple\" | \"google\" | \"facebook\" }): Promise<void>;\n /**\n * IsLoggedIn\n * @description logout the user\n */\n isLoggedIn(options: isLoggedInOptions): Promise<{ isLoggedIn: boolean }>;\n\n /**\n * Get the current access token\n * @description get the current access token\n */\n getAuthorizationCode(\n options: AuthorizationCodeOptions,\n ): Promise<AuthorizationCode>;\n /**\n * Refresh the access token\n * @description refresh the access token\n */\n refresh(options: LoginOptions): Promise<void>;\n}\n"]}
|
|
@@ -2,9 +2,13 @@ import Foundation
|
|
|
2
2
|
import AuthenticationServices
|
|
3
3
|
import Alamofire
|
|
4
4
|
|
|
5
|
-
struct AppleProviderResponse {
|
|
6
|
-
|
|
5
|
+
struct AppleProviderResponse: Codable {
|
|
6
|
+
let user: String
|
|
7
|
+
let email: String?
|
|
8
|
+
let givenName: String?
|
|
9
|
+
let familyName: String?
|
|
7
10
|
let identityToken: String
|
|
11
|
+
let authorizationCode: String
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
// Define the Decodable structs for the response
|
|
@@ -86,6 +90,7 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
|
|
|
86
90
|
private let TOKEN_URL = "https://appleid.apple.com/auth/token"
|
|
87
91
|
private let SHARED_PREFERENCE_NAME = "AppleProviderSharedPrefs_0eda2642"
|
|
88
92
|
private var redirectUrl = ""
|
|
93
|
+
private let USER_INFO_KEY = "AppleUserInfo"
|
|
89
94
|
|
|
90
95
|
func initialize(redirectUrl: String? = nil) {
|
|
91
96
|
if let redirectUrl = redirectUrl {
|
|
@@ -199,21 +204,27 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
|
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
func getCurrentUser(completion: @escaping (Result<AppleProviderResponse?, Error>) -> Void) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
retrieveUserInfo { userInfo in
|
|
208
|
+
if let userInfo = userInfo {
|
|
209
|
+
completion(.success(userInfo))
|
|
210
|
+
} else {
|
|
211
|
+
let appleIDProvider = ASAuthorizationAppleIDProvider()
|
|
212
|
+
appleIDProvider.getCredentialState(forUserID: "currentUserIdentifier") { (credentialState, error) in
|
|
213
|
+
if let error = error {
|
|
214
|
+
completion(.failure(error))
|
|
215
|
+
return
|
|
216
|
+
}
|
|
208
217
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
218
|
+
switch credentialState {
|
|
219
|
+
case .authorized:
|
|
220
|
+
// User is authorized, but we don't have their info
|
|
221
|
+
completion(.success(nil))
|
|
222
|
+
case .revoked, .notFound, .transferred:
|
|
223
|
+
completion(.success(nil))
|
|
224
|
+
@unknown default:
|
|
225
|
+
completion(.failure(NSError(domain: "AppleProvider", code: 0, userInfo: [NSLocalizedDescriptionKey: "Unknown credential state"])))
|
|
226
|
+
}
|
|
227
|
+
}
|
|
217
228
|
}
|
|
218
229
|
}
|
|
219
230
|
}
|
|
@@ -227,52 +238,44 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
|
|
|
227
238
|
|
|
228
239
|
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
|
|
229
240
|
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
|
|
230
|
-
let
|
|
241
|
+
let userIdentifier = appleIDCredential.user
|
|
231
242
|
let fullName = appleIDCredential.fullName
|
|
232
243
|
let email = appleIDCredential.email
|
|
233
244
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if let _ = fullName?.givenName {
|
|
260
|
-
sendRequest(code: authorizationCode, identityToken: identityToken, email: email ?? "", firstName: firstName, lastName: lastName, completion: errorCompletion, skipUser: false)
|
|
245
|
+
retrieveUserInfo { savedUserInfo in
|
|
246
|
+
let response = AppleProviderResponse(
|
|
247
|
+
user: userIdentifier,
|
|
248
|
+
email: email ?? savedUserInfo?.email,
|
|
249
|
+
givenName: fullName?.givenName ?? savedUserInfo?.givenName,
|
|
250
|
+
familyName: fullName?.familyName ?? savedUserInfo?.familyName,
|
|
251
|
+
identityToken: String(data: appleIDCredential.identityToken ?? Data(), encoding: .utf8) ?? "",
|
|
252
|
+
authorizationCode: String(data: appleIDCredential.authorizationCode ?? Data(), encoding: .utf8) ?? ""
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
// Persist user info
|
|
256
|
+
self.persistUserInfo(userInfo: response)
|
|
257
|
+
|
|
258
|
+
if !self.redirectUrl.isEmpty {
|
|
259
|
+
let firstName = fullName?.givenName ?? ""
|
|
260
|
+
let lastName = fullName?.familyName ?? ""
|
|
261
|
+
|
|
262
|
+
self.sendRequest(code: response.authorizationCode, identityToken: response.identityToken, email: email ?? "", firstName: firstName, lastName: lastName, completion: { result in
|
|
263
|
+
switch result {
|
|
264
|
+
case .success(let appleResponse):
|
|
265
|
+
self.completion?(.success(appleResponse))
|
|
266
|
+
case .failure(let error):
|
|
267
|
+
self.completion?(.failure(error))
|
|
268
|
+
}
|
|
269
|
+
}, skipUser: fullName?.givenName == nil)
|
|
261
270
|
} else {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
completion?(.success(appleResponse))
|
|
269
|
-
return
|
|
270
|
-
} catch {
|
|
271
|
-
completion?(.failure(AppleProviderError.specificJsonWritingError(error)))
|
|
272
|
-
return
|
|
271
|
+
do {
|
|
272
|
+
try self.persistState(idToken: response.identityToken, refreshToken: "", accessToken: "")
|
|
273
|
+
self.completion?(.success(response))
|
|
274
|
+
} catch {
|
|
275
|
+
self.completion?(.failure(AppleProviderError.specificJsonWritingError(error)))
|
|
276
|
+
}
|
|
273
277
|
}
|
|
274
278
|
}
|
|
275
|
-
// completion?(.success(response))
|
|
276
279
|
}
|
|
277
280
|
}
|
|
278
281
|
|
|
@@ -365,7 +368,14 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
|
|
|
365
368
|
|
|
366
369
|
do {
|
|
367
370
|
try self.persistState(idToken: idToken, refreshToken: refreshToken, accessToken: accessToken)
|
|
368
|
-
let appleResponse = AppleProviderResponse(
|
|
371
|
+
let appleResponse = AppleProviderResponse(
|
|
372
|
+
user: "", // We don't have this information at this point
|
|
373
|
+
email: nil, // We don't have this information at this point
|
|
374
|
+
givenName: nil, // We don't have this information at this point
|
|
375
|
+
familyName: nil, // We don't have this information at this point
|
|
376
|
+
identityToken: idToken,
|
|
377
|
+
authorizationCode: "" // We don't have this information at this point
|
|
378
|
+
)
|
|
369
379
|
completion(.success(appleResponse))
|
|
370
380
|
return
|
|
371
381
|
} catch {
|
|
@@ -375,8 +385,14 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
|
|
|
375
385
|
}
|
|
376
386
|
|
|
377
387
|
if (pathComponents.filter { $0.name == "ios_no_code" }).first != nil {
|
|
378
|
-
|
|
379
|
-
|
|
388
|
+
let appleResponse = AppleProviderResponse(
|
|
389
|
+
user: "", // You might want to extract this from the identityToken
|
|
390
|
+
email: email,
|
|
391
|
+
givenName: firstName,
|
|
392
|
+
familyName: lastName,
|
|
393
|
+
identityToken: identityToken,
|
|
394
|
+
authorizationCode: code
|
|
395
|
+
)
|
|
380
396
|
|
|
381
397
|
do {
|
|
382
398
|
try self.persistState(idToken: identityToken, refreshToken: "", accessToken: "")
|
|
@@ -470,7 +486,14 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
|
|
|
470
486
|
let userData = try? JSONSerialization.jsonObject(with: decodedData, options: []) as? [String: Any],
|
|
471
487
|
let userId = userData["sub"] as? String {
|
|
472
488
|
// Create the response object
|
|
473
|
-
let appleResponse = AppleProviderResponse(
|
|
489
|
+
let appleResponse = AppleProviderResponse(
|
|
490
|
+
user: userId,
|
|
491
|
+
email: nil, // You might want to extract this from the idToken if available
|
|
492
|
+
givenName: nil,
|
|
493
|
+
familyName: nil,
|
|
494
|
+
identityToken: idToken,
|
|
495
|
+
authorizationCode: code
|
|
496
|
+
)
|
|
474
497
|
|
|
475
498
|
// Log the tokens (replace with your logging mechanism)
|
|
476
499
|
print("Apple Access Token is: \(accessToken)")
|
|
@@ -513,4 +536,25 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
|
|
|
513
536
|
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
|
514
537
|
return UIApplication.shared.windows.first!
|
|
515
538
|
}
|
|
539
|
+
|
|
540
|
+
func persistUserInfo(userInfo: AppleProviderResponse) {
|
|
541
|
+
let encoder = JSONEncoder()
|
|
542
|
+
if let encoded = try? encoder.encode(userInfo) {
|
|
543
|
+
UserDefaults.standard.set(encoded, forKey: USER_INFO_KEY)
|
|
544
|
+
print("Successfully saved user info locally")
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
func retrieveUserInfo(completion: @escaping (AppleProviderResponse?) -> Void) {
|
|
549
|
+
if let savedUserInfo = UserDefaults.standard.object(forKey: USER_INFO_KEY) as? Data {
|
|
550
|
+
let decoder = JSONDecoder()
|
|
551
|
+
if let loadedUserInfo = try? decoder.decode(AppleProviderResponse.self, from: savedUserInfo) {
|
|
552
|
+
completion(loadedUserInfo)
|
|
553
|
+
} else {
|
|
554
|
+
completion(nil)
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
completion(nil)
|
|
558
|
+
}
|
|
559
|
+
}
|
|
516
560
|
}
|
|
@@ -52,10 +52,20 @@ class FacebookProvider {
|
|
|
52
52
|
return
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
DispatchQueue.main.async {
|
|
55
|
+
DispatchQueue.main.async { [weak self] in
|
|
56
|
+
guard let self = self else { return }
|
|
57
|
+
|
|
58
|
+
// Check if a user is already logged in
|
|
59
|
+
if AccessToken.current != nil {
|
|
60
|
+
// User is already logged in, return current session info
|
|
61
|
+
let response = self.createLoginResponse()
|
|
62
|
+
completion(.success(response))
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
self.loginManager.logIn(configuration: configuration) { result in
|
|
57
67
|
switch result {
|
|
58
|
-
case .success:
|
|
68
|
+
case .success(_, _, _):
|
|
59
69
|
let response = self.createLoginResponse()
|
|
60
70
|
completion(.success(response))
|
|
61
71
|
case .failed(let error):
|
|
@@ -63,7 +73,6 @@ class FacebookProvider {
|
|
|
63
73
|
case .cancelled:
|
|
64
74
|
completion(.failure(NSError(domain: "FacebookProvider", code: 0, userInfo: [NSLocalizedDescriptionKey: "Login cancelled"])))
|
|
65
75
|
}
|
|
66
|
-
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
}
|
|
@@ -123,28 +132,32 @@ class FacebookProvider {
|
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
func logout(completion: @escaping (Result<Void, Error>) -> Void) {
|
|
126
|
-
|
|
127
|
-
|
|
135
|
+
DispatchQueue.main.async { [weak self] in
|
|
136
|
+
self?.loginManager.logOut()
|
|
137
|
+
completion(.success(()))
|
|
138
|
+
}
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
func getCurrentUser(completion: @escaping (Result<[String: Any]?, Error>) -> Void) {
|
|
131
|
-
|
|
132
|
-
let
|
|
133
|
-
|
|
134
|
-
"
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
DispatchQueue.main.async {
|
|
143
|
+
if let accessToken = AccessToken.current {
|
|
144
|
+
let response: [String: Any] = [
|
|
145
|
+
"accessToken": [
|
|
146
|
+
"applicationID": accessToken.appID,
|
|
147
|
+
"declinedPermissions": accessToken.declinedPermissions.map { $0.name },
|
|
148
|
+
"expirationDate": accessToken.expirationDate,
|
|
149
|
+
"isExpired": accessToken.isExpired,
|
|
150
|
+
"refreshDate": accessToken.refreshDate,
|
|
151
|
+
"permissions": accessToken.permissions.map { $0.name },
|
|
152
|
+
"tokenString": accessToken.tokenString,
|
|
153
|
+
"userID": accessToken.userID
|
|
154
|
+
],
|
|
155
|
+
"profile": [:]
|
|
156
|
+
]
|
|
157
|
+
completion(.success(response))
|
|
158
|
+
} else {
|
|
159
|
+
completion(.success(nil))
|
|
160
|
+
}
|
|
148
161
|
}
|
|
149
162
|
}
|
|
150
163
|
|
|
@@ -199,7 +199,23 @@ public class SocialLoginPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
199
199
|
}
|
|
200
200
|
case "apple":
|
|
201
201
|
apple.getCurrentUser { result in
|
|
202
|
-
|
|
202
|
+
switch result {
|
|
203
|
+
case .success(let appleResponse):
|
|
204
|
+
if let response = appleResponse {
|
|
205
|
+
call.resolve([
|
|
206
|
+
"user": response.user,
|
|
207
|
+
"email": response.email ?? "",
|
|
208
|
+
"givenName": response.givenName ?? "",
|
|
209
|
+
"familyName": response.familyName ?? "",
|
|
210
|
+
"identityToken": response.identityToken,
|
|
211
|
+
"authorizationCode": response.authorizationCode
|
|
212
|
+
])
|
|
213
|
+
} else {
|
|
214
|
+
call.resolve([:])
|
|
215
|
+
}
|
|
216
|
+
case .failure(let error):
|
|
217
|
+
call.reject(error.localizedDescription)
|
|
218
|
+
}
|
|
203
219
|
}
|
|
204
220
|
default:
|
|
205
221
|
call.reject("Invalid provider")
|
|
@@ -280,10 +296,16 @@ public class SocialLoginPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
280
296
|
switch result {
|
|
281
297
|
case .success(let response):
|
|
282
298
|
if let appleResponse = response as? AppleProviderResponse {
|
|
299
|
+
// The user info is already persisted in the AppleProvider class
|
|
283
300
|
call.resolve([
|
|
284
301
|
"provider": "apple",
|
|
285
302
|
"result": [
|
|
286
|
-
"
|
|
303
|
+
"user": appleResponse.user,
|
|
304
|
+
"email": appleResponse.email ?? "",
|
|
305
|
+
"givenName": appleResponse.givenName ?? "",
|
|
306
|
+
"familyName": appleResponse.familyName ?? "",
|
|
307
|
+
"identityToken": appleResponse.identityToken,
|
|
308
|
+
"authorizationCode": appleResponse.authorizationCode
|
|
287
309
|
]
|
|
288
310
|
])
|
|
289
311
|
} else if let googleResponse = response as? GoogleLoginResponse {
|