@pricava/react-native-google-credential 0.1.0

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.
@@ -0,0 +1,45 @@
1
+ package com.pricava.googlecredential
2
+
3
+ import com.facebook.react.bridge.ReadableMap
4
+
5
+ internal data class ReactGoogleCredentialOptions(
6
+ val webClientId: String,
7
+ val iosClientId: String? = null,
8
+ val nonce: String? = null,
9
+ val androidFlow: String = "sign-in-button",
10
+ val androidAccountFilter: String = "authorized-first",
11
+ val androidAutoSelect: Boolean = true,
12
+ val webAutoSelect: Boolean = true,
13
+ val webUseFedCm: Boolean = true
14
+ ) {
15
+ companion object {
16
+ fun fromMap(options: ReadableMap): ReactGoogleCredentialOptions {
17
+ return ReactGoogleCredentialOptions(
18
+ webClientId = options.getString("webClientId") ?: "",
19
+ iosClientId = options.optionalString("iosClientId"),
20
+ nonce = options.optionalString("nonce"),
21
+ androidFlow = options.optionalString("androidFlow") ?: "sign-in-button",
22
+ androidAccountFilter = options.optionalString("androidAccountFilter") ?: "authorized-first",
23
+ androidAutoSelect = options.optionalBoolean("androidAutoSelect") ?: true,
24
+ webAutoSelect = options.optionalBoolean("webAutoSelect") ?: true,
25
+ webUseFedCm = options.optionalBoolean("webUseFedCm") ?: true
26
+ )
27
+ }
28
+ }
29
+ }
30
+
31
+ private fun ReadableMap.optionalString(key: String): String? {
32
+ if (!hasKey(key) || isNull(key)) {
33
+ return null
34
+ }
35
+
36
+ return getString(key)?.takeIf { it.isNotBlank() }
37
+ }
38
+
39
+ private fun ReadableMap.optionalBoolean(key: String): Boolean? {
40
+ if (!hasKey(key) || isNull(key)) {
41
+ return null
42
+ }
43
+
44
+ return getBoolean(key)
45
+ }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./plugin/withGoogleCredentialIos");
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./src";
2
+ export { default } from "./src";
@@ -0,0 +1,13 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+ #import <PricavaGoogleCredentialSpec/PricavaGoogleCredentialSpec.h>
5
+
6
+ @interface PricavaGoogleCredential : NativePricavaGoogleCredentialSpecBase <NativePricavaGoogleCredentialSpec>
7
+ @end
8
+ #else
9
+ #import <React/RCTBridgeModule.h>
10
+
11
+ @interface PricavaGoogleCredential : NSObject <RCTBridgeModule>
12
+ @end
13
+ #endif
@@ -0,0 +1,194 @@
1
+ #import "PricavaGoogleCredential.h"
2
+
3
+ #import <GoogleSignIn/GoogleSignIn.h>
4
+ #import <React/RCTUtils.h>
5
+
6
+ #include <memory>
7
+
8
+ static NSString *PricavaGoogleCredentialTrimmedString(NSString *value)
9
+ {
10
+ if (![value isKindOfClass:NSString.class]) {
11
+ return nil;
12
+ }
13
+
14
+ NSString *trimmedValue = [value stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
15
+ return trimmedValue.length > 0 ? trimmedValue : nil;
16
+ }
17
+
18
+ static NSString *PricavaGoogleCredentialBundledClientId(void)
19
+ {
20
+ NSString *infoPlistClientId = PricavaGoogleCredentialTrimmedString(
21
+ [NSBundle.mainBundle objectForInfoDictionaryKey:@"GIDClientID"]);
22
+
23
+ if (infoPlistClientId != nil) {
24
+ return infoPlistClientId;
25
+ }
26
+
27
+ NSString *path = [NSBundle.mainBundle pathForResource:@"GoogleService-Info" ofType:@"plist"];
28
+ NSDictionary *plist = path != nil ? [NSDictionary dictionaryWithContentsOfFile:path] : nil;
29
+
30
+ return PricavaGoogleCredentialTrimmedString(plist[@"CLIENT_ID"]);
31
+ }
32
+
33
+ static NSDictionary *PricavaGoogleCredentialResult(GIDGoogleUser *user, NSString *idToken)
34
+ {
35
+ GIDProfileData *profile = user.profile;
36
+ NSString *imageUrl = profile.hasImage ? [profile imageURLWithDimension:120].absoluteString : nil;
37
+
38
+ return @{
39
+ @"idToken": idToken,
40
+ @"displayName": profile.name ?: NSNull.null,
41
+ @"email": profile.email ?: NSNull.null,
42
+ @"familyName": profile.familyName ?: NSNull.null,
43
+ @"givenName": profile.givenName ?: NSNull.null,
44
+ @"id": user.userID ?: profile.email ?: NSNull.null,
45
+ @"phoneNumber": NSNull.null,
46
+ @"profilePictureUri": imageUrl ?: NSNull.null
47
+ };
48
+ }
49
+
50
+ @implementation PricavaGoogleCredential
51
+
52
+ RCT_EXPORT_MODULE(PricavaGoogleCredential)
53
+
54
+ + (BOOL)requiresMainQueueSetup
55
+ {
56
+ return NO;
57
+ }
58
+
59
+ #ifdef RCT_NEW_ARCH_ENABLED
60
+ - (void)isAvailableAsync:(RCTPromiseResolveBlock)resolve
61
+ reject:(RCTPromiseRejectBlock)reject
62
+ {
63
+ resolve(@YES);
64
+ }
65
+
66
+ - (void)signInAsync:(JS::NativePricavaGoogleCredential::GoogleCredentialOptionsSpec &)options
67
+ resolve:(RCTPromiseResolveBlock)resolve
68
+ reject:(RCTPromiseRejectBlock)reject
69
+ {
70
+ NSString *webClientId = PricavaGoogleCredentialTrimmedString(options.webClientId());
71
+ NSString *iosClientId = PricavaGoogleCredentialTrimmedString(options.iosClientId())
72
+ ?: PricavaGoogleCredentialBundledClientId();
73
+ NSString *nonce = PricavaGoogleCredentialTrimmedString(options.nonce());
74
+
75
+ [self signInWithWebClientId:webClientId
76
+ iosClientId:iosClientId
77
+ nonce:nonce
78
+ resolve:resolve
79
+ reject:reject];
80
+ }
81
+
82
+ - (void)clearCredentialStateAsync:(RCTPromiseResolveBlock)resolve
83
+ reject:(RCTPromiseRejectBlock)reject
84
+ {
85
+ [GIDSignIn.sharedInstance signOut];
86
+ resolve(nil);
87
+ }
88
+
89
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
90
+ (const facebook::react::ObjCTurboModule::InitParams &)params
91
+ {
92
+ return std::make_shared<facebook::react::NativePricavaGoogleCredentialSpecJSI>(params);
93
+ }
94
+ #else
95
+ RCT_EXPORT_METHOD(isAvailableAsync:(RCTPromiseResolveBlock)resolve
96
+ reject:(RCTPromiseRejectBlock)reject)
97
+ {
98
+ resolve(@YES);
99
+ }
100
+
101
+ RCT_EXPORT_METHOD(signInAsync:(NSDictionary *)options
102
+ resolve:(RCTPromiseResolveBlock)resolve
103
+ reject:(RCTPromiseRejectBlock)reject)
104
+ {
105
+ NSString *webClientId = PricavaGoogleCredentialTrimmedString(options[@"webClientId"]);
106
+ NSString *iosClientId = PricavaGoogleCredentialTrimmedString(options[@"iosClientId"])
107
+ ?: PricavaGoogleCredentialBundledClientId();
108
+ NSString *nonce = PricavaGoogleCredentialTrimmedString(options[@"nonce"]);
109
+
110
+ [self signInWithWebClientId:webClientId
111
+ iosClientId:iosClientId
112
+ nonce:nonce
113
+ resolve:resolve
114
+ reject:reject];
115
+ }
116
+
117
+ RCT_EXPORT_METHOD(clearCredentialStateAsync:(RCTPromiseResolveBlock)resolve
118
+ reject:(RCTPromiseRejectBlock)reject)
119
+ {
120
+ [GIDSignIn.sharedInstance signOut];
121
+ resolve(nil);
122
+ }
123
+ #endif
124
+
125
+ - (void)signInWithWebClientId:(NSString *)webClientId
126
+ iosClientId:(NSString *)iosClientId
127
+ nonce:(NSString *)nonce
128
+ resolve:(RCTPromiseResolveBlock)resolve
129
+ reject:(RCTPromiseRejectBlock)reject
130
+ {
131
+ if (webClientId == nil) {
132
+ reject(@"ERR_GOOGLE_CREDENTIAL_MISSING_CLIENT_ID", @"Missing Google webClientId.", nil);
133
+ return;
134
+ }
135
+
136
+ if (iosClientId == nil) {
137
+ reject(
138
+ @"ERR_GOOGLE_CREDENTIAL_MISSING_CLIENT_ID",
139
+ @"Missing Google iosClientId. Pass it in options or bundle GoogleService-Info.plist.",
140
+ nil);
141
+ return;
142
+ }
143
+
144
+ dispatch_async(dispatch_get_main_queue(), ^{
145
+ UIViewController *presentingViewController = RCTPresentedViewController();
146
+
147
+ if (presentingViewController == nil) {
148
+ reject(
149
+ @"ERR_GOOGLE_CREDENTIAL_MISSING_VIEW_CONTROLLER",
150
+ @"No presenting view controller found for Google sign-in.",
151
+ nil);
152
+ return;
153
+ }
154
+
155
+ GIDSignIn.sharedInstance.configuration =
156
+ [[GIDConfiguration alloc] initWithClientID:iosClientId serverClientID:webClientId];
157
+
158
+ [GIDSignIn.sharedInstance signInWithPresentingViewController:presentingViewController
159
+ hint:nil
160
+ additionalScopes:nil
161
+ nonce:nonce
162
+ completion:^(GIDSignInResult *_Nullable signInResult,
163
+ NSError *_Nullable error) {
164
+ if (error != nil) {
165
+ reject(@"ERR_GOOGLE_CREDENTIAL_REQUEST", error.localizedDescription, error);
166
+ return;
167
+ }
168
+
169
+ GIDGoogleUser *user = signInResult.user;
170
+
171
+ if (user == nil) {
172
+ reject(
173
+ @"ERR_GOOGLE_CREDENTIAL_REQUEST",
174
+ @"Google sign-in did not return a user.",
175
+ nil);
176
+ return;
177
+ }
178
+
179
+ NSString *idToken = user.idToken.tokenString;
180
+
181
+ if (idToken.length == 0) {
182
+ reject(
183
+ @"ERR_GOOGLE_CREDENTIAL_REQUEST",
184
+ @"Google sign-in did not return an ID token.",
185
+ nil);
186
+ return;
187
+ }
188
+
189
+ resolve(PricavaGoogleCredentialResult(user, idToken));
190
+ }];
191
+ });
192
+ }
193
+
194
+ @end
@@ -0,0 +1,24 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = "PricavaGoogleCredential"
3
+ s.version = "1.0.0"
4
+ s.summary = "React Native Google credential sign-in."
5
+ s.description = "React Native TurboModule support for Android Credential Manager, GoogleSignIn on iOS, and Google Identity Services on web."
6
+ s.author = "Pricava"
7
+ s.homepage = "https://pricava.com"
8
+ s.license = "UNLICENSED"
9
+ s.platforms = { :ios => "15.1" }
10
+ s.source = { :path => "." }
11
+ s.source_files = "*.{h,m,mm}"
12
+ s.pod_target_xcconfig = {
13
+ "DEFINES_MODULE" => "YES",
14
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20"
15
+ }
16
+
17
+ s.dependency "GoogleSignIn", "~> 9.0"
18
+
19
+ if respond_to?(:install_modules_dependencies, true)
20
+ install_modules_dependencies(s)
21
+ else
22
+ s.dependency "React-Core"
23
+ end
24
+ end
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@pricava/react-native-google-credential",
3
+ "version": "0.1.0",
4
+ "description": "React Native-first Google credential sign-in with Expo support.",
5
+ "license": "MIT",
6
+ "author": "Moussa Ibrahim",
7
+ "homepage": "https://github.com/moussa32/react-native-google-credential#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/moussa32/react-native-google-credential/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/moussa32/react-native-google-credential.git",
14
+ "directory": "packages/google-credential"
15
+ },
16
+ "main": "./src/index.ts",
17
+ "react-native": "./src/index.ts",
18
+ "types": "./src/index.ts",
19
+ "exports": {
20
+ ".": {
21
+ "react-native": "./src/index.ts",
22
+ "default": "./src/index.ts"
23
+ },
24
+ "./adapters": {
25
+ "react-native": "./src/adapters/index.ts",
26
+ "default": "./src/adapters/index.ts"
27
+ },
28
+ "./app.plugin": "./app.plugin.js",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "codegenConfig": {
32
+ "name": "PricavaGoogleCredentialSpec",
33
+ "type": "modules",
34
+ "jsSrcsDir": "src",
35
+ "android": {
36
+ "javaPackageName": "com.pricava.googlecredential"
37
+ },
38
+ "ios": {
39
+ "modules": {
40
+ "PricavaGoogleCredential": {
41
+ "className": "PricavaGoogleCredential"
42
+ }
43
+ }
44
+ }
45
+ },
46
+ "files": [
47
+ "android",
48
+ "ios",
49
+ "plugin",
50
+ "src",
51
+ "adapters.ts",
52
+ "app.plugin.js",
53
+ "react-native.config.js",
54
+ "index.ts",
55
+ "README.md",
56
+ "IMPLEMENTATION.md"
57
+ ],
58
+ "scripts": {
59
+ "lint": "echo \"TODO: add package lint\"",
60
+ "typecheck": "tsc --noEmit"
61
+ },
62
+ "dependencies": {},
63
+ "peerDependencies": {
64
+ "react": "*",
65
+ "react-native": "*"
66
+ },
67
+ "devDependencies": {
68
+ "typescript": "^6.0.3"
69
+ }
70
+ }
@@ -0,0 +1,39 @@
1
+ const { IOSConfig, withInfoPlist } = require("@expo/config-plugins");
2
+
3
+ function getTrimmedEnv(name) {
4
+ return process.env[name]?.trim() || null;
5
+ }
6
+
7
+ function getReversedGoogleClientId(clientId) {
8
+ const suffix = ".apps.googleusercontent.com";
9
+
10
+ if (!clientId?.endsWith(suffix)) {
11
+ return null;
12
+ }
13
+
14
+ return `com.googleusercontent.apps.${clientId.slice(0, -suffix.length)}`;
15
+ }
16
+
17
+ module.exports = function withGoogleCredentialIos(config, props = {}) {
18
+ return withInfoPlist(config, (config) => {
19
+ const iosClientId =
20
+ props.iosClientId?.trim?.() || getTrimmedEnv("GOOGLE_IOS_CLIENT_ID");
21
+ const explicitUrlScheme =
22
+ props.iosUrlScheme?.trim?.() || getTrimmedEnv("GOOGLE_IOS_URL_SCHEME");
23
+ const urlScheme =
24
+ explicitUrlScheme || getReversedGoogleClientId(iosClientId);
25
+
26
+ if (iosClientId) {
27
+ config.modResults.GIDClientID = iosClientId;
28
+ }
29
+
30
+ if (urlScheme && !IOSConfig.Scheme.hasScheme(urlScheme, config.modResults)) {
31
+ config.modResults = IOSConfig.Scheme.appendScheme(
32
+ urlScheme,
33
+ config.modResults,
34
+ );
35
+ }
36
+
37
+ return config;
38
+ });
39
+ };
@@ -0,0 +1,15 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: {
5
+ sourceDir: "./android",
6
+ packageImportPath:
7
+ "import com.pricava.googlecredential.PricavaGoogleCredentialPackage;",
8
+ packageInstance: "new PricavaGoogleCredentialPackage()",
9
+ },
10
+ ios: {
11
+ podspecPath: "./ios/PricavaGoogleCredential.podspec",
12
+ },
13
+ },
14
+ },
15
+ };
@@ -0,0 +1,32 @@
1
+ import type { TurboModule } from "react-native";
2
+ import { TurboModuleRegistry } from "react-native";
3
+
4
+ export type GoogleCredentialOptionsSpec = {
5
+ webClientId: string;
6
+ iosClientId?: string | null;
7
+ nonce?: string | null;
8
+ androidFlow: string;
9
+ androidAccountFilter: string;
10
+ androidAutoSelect: boolean;
11
+ webAutoSelect: boolean;
12
+ webUseFedCm: boolean;
13
+ };
14
+
15
+ export type GoogleCredentialResultSpec = {
16
+ idToken: string;
17
+ displayName: string | null;
18
+ email: string | null;
19
+ familyName: string | null;
20
+ givenName: string | null;
21
+ id: string | null;
22
+ phoneNumber: string | null;
23
+ profilePictureUri: string | null;
24
+ };
25
+
26
+ export interface Spec extends TurboModule {
27
+ isAvailableAsync(): Promise<boolean>;
28
+ signInAsync(options: GoogleCredentialOptionsSpec): Promise<GoogleCredentialResultSpec>;
29
+ clearCredentialStateAsync(): Promise<void>;
30
+ }
31
+
32
+ export default TurboModuleRegistry.get<Spec>("PricavaGoogleCredential");
@@ -0,0 +1,31 @@
1
+ import { NativeModules } from "react-native";
2
+
3
+ import type { PricavaGoogleCredentialNativeModule } from "./types";
4
+ import NativePricavaGoogleCredential from "./NativePricavaGoogleCredential";
5
+
6
+ const MODULE_NAME = "PricavaGoogleCredential";
7
+
8
+ function getReactNativeModule() {
9
+ return (
10
+ NativePricavaGoogleCredential ??
11
+ (NativeModules[MODULE_NAME] as
12
+ | PricavaGoogleCredentialNativeModule
13
+ | undefined)
14
+ );
15
+ }
16
+
17
+ function requireGoogleCredentialModule() {
18
+ const nativeModule = getReactNativeModule();
19
+
20
+ if (!nativeModule) {
21
+ throw new Error(
22
+ `${MODULE_NAME} native module is not available. Install the native package and rebuild the React Native or Expo development app.`,
23
+ );
24
+ }
25
+
26
+ return nativeModule;
27
+ }
28
+
29
+ const PricavaGoogleCredentialModule = requireGoogleCredentialModule();
30
+
31
+ export default PricavaGoogleCredentialModule;