@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.
- package/IMPLEMENTATION.md +351 -0
- package/README.md +63 -0
- package/adapters.ts +1 -0
- package/android/build.gradle +25 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/pricava/googlecredential/PricavaGoogleCredentialPackage.kt +35 -0
- package/android/src/main/java/com/pricava/googlecredential/PricavaGoogleCredentialReactModule.kt +172 -0
- package/android/src/main/java/com/pricava/googlecredential/ReactGoogleCredentialOptions.kt +45 -0
- package/app.plugin.js +1 -0
- package/index.ts +2 -0
- package/ios/PricavaGoogleCredential.h +13 -0
- package/ios/PricavaGoogleCredential.mm +194 -0
- package/ios/PricavaGoogleCredential.podspec +24 -0
- package/package.json +70 -0
- package/plugin/withGoogleCredentialIos.js +39 -0
- package/react-native.config.js +15 -0
- package/src/NativePricavaGoogleCredential.ts +32 -0
- package/src/PricavaGoogleCredentialModule.ts +31 -0
- package/src/PricavaGoogleCredentialModule.web.ts +296 -0
- package/src/adapters/google-auth-provider.ts +155 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/supabase.ts +28 -0
- package/src/index.ts +98 -0
- package/src/types.ts +54 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GoogleCredentialResult,
|
|
3
|
+
PricavaGoogleCredentialNativeModule,
|
|
4
|
+
ResolvedGoogleCredentialOptions,
|
|
5
|
+
} from "./types";
|
|
6
|
+
|
|
7
|
+
type GoogleCredentialResponse = {
|
|
8
|
+
credential?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type GooglePromptMomentNotification = {
|
|
12
|
+
isDismissedMoment(): boolean;
|
|
13
|
+
isDisplayed(): boolean;
|
|
14
|
+
isNotDisplayed(): boolean;
|
|
15
|
+
isSkippedMoment(): boolean;
|
|
16
|
+
getDismissedReason(): string;
|
|
17
|
+
getNotDisplayedReason(): string;
|
|
18
|
+
getSkippedReason(): string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type GoogleIdentityService = {
|
|
22
|
+
accounts: {
|
|
23
|
+
id: {
|
|
24
|
+
cancel(): void;
|
|
25
|
+
disableAutoSelect(): void;
|
|
26
|
+
initialize(config: {
|
|
27
|
+
auto_select?: boolean;
|
|
28
|
+
callback(response: GoogleCredentialResponse): void;
|
|
29
|
+
cancel_on_tap_outside?: boolean;
|
|
30
|
+
client_id: string;
|
|
31
|
+
nonce?: string;
|
|
32
|
+
use_fedcm_for_prompt?: boolean;
|
|
33
|
+
}): void;
|
|
34
|
+
prompt(
|
|
35
|
+
callback?: (notification: GooglePromptMomentNotification) => void,
|
|
36
|
+
): void;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
declare global {
|
|
42
|
+
interface Window {
|
|
43
|
+
google?: GoogleIdentityService;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type GoogleIdTokenPayload = {
|
|
48
|
+
email?: string;
|
|
49
|
+
family_name?: string;
|
|
50
|
+
given_name?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
phone_number?: string;
|
|
53
|
+
picture?: string;
|
|
54
|
+
sub?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const GOOGLE_IDENTITY_SCRIPT_ID = "google-identity-services-script";
|
|
58
|
+
const GOOGLE_IDENTITY_SCRIPT_SRC = "https://accounts.google.com/gsi/client";
|
|
59
|
+
const SIGN_IN_TIMEOUT_MS = 120_000;
|
|
60
|
+
|
|
61
|
+
let scriptLoadPromise: Promise<void> | null = null;
|
|
62
|
+
let pendingSignInReject: ((error: Error) => void) | null = null;
|
|
63
|
+
|
|
64
|
+
function getGoogleIdentityService() {
|
|
65
|
+
return typeof window === "undefined" ? undefined : window.google;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toErrorMessage(reason: string | null | undefined) {
|
|
69
|
+
if (reason === "unregistered_origin") {
|
|
70
|
+
const origin =
|
|
71
|
+
typeof window === "undefined"
|
|
72
|
+
? "the current web origin"
|
|
73
|
+
: window.location.origin;
|
|
74
|
+
|
|
75
|
+
return [
|
|
76
|
+
"Google sign-in was not completed: this web origin is not authorized for the configured Google OAuth web client.",
|
|
77
|
+
`Add ${origin} to Authorized JavaScript origins for GOOGLE_WEB_CLIENT_ID in Google Cloud Console.`,
|
|
78
|
+
"For localhost development, Google also requires http://localhost and the exact ported origin, for example http://localhost:8081.",
|
|
79
|
+
].join(" ");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return reason?.trim()
|
|
83
|
+
? `Google sign-in was not completed: ${reason}.`
|
|
84
|
+
: "Google sign-in was not completed.";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createGoogleSignInError(reason?: string | null) {
|
|
88
|
+
return new Error(toErrorMessage(reason));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function loadGoogleIdentityScript() {
|
|
92
|
+
if (getGoogleIdentityService()) {
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (scriptLoadPromise) {
|
|
97
|
+
return scriptLoadPromise;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
scriptLoadPromise = new Promise((resolve, reject) => {
|
|
101
|
+
const existingScript = document.getElementById(
|
|
102
|
+
GOOGLE_IDENTITY_SCRIPT_ID,
|
|
103
|
+
) as HTMLScriptElement | null;
|
|
104
|
+
|
|
105
|
+
if (existingScript) {
|
|
106
|
+
if (getGoogleIdentityService()) {
|
|
107
|
+
resolve();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
existingScript.addEventListener("load", () => resolve(), { once: true });
|
|
112
|
+
existingScript.addEventListener(
|
|
113
|
+
"error",
|
|
114
|
+
() => reject(new Error("Failed to load Google Identity Services.")),
|
|
115
|
+
{ once: true },
|
|
116
|
+
);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const script = document.createElement("script");
|
|
121
|
+
script.id = GOOGLE_IDENTITY_SCRIPT_ID;
|
|
122
|
+
script.src = GOOGLE_IDENTITY_SCRIPT_SRC;
|
|
123
|
+
script.async = true;
|
|
124
|
+
script.defer = true;
|
|
125
|
+
script.onload = () => resolve();
|
|
126
|
+
script.onerror = () => {
|
|
127
|
+
scriptLoadPromise = null;
|
|
128
|
+
reject(new Error("Failed to load Google Identity Services."));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
document.head.appendChild(script);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return scriptLoadPromise;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function decodeBase64UrlJson<T>(value: string): T {
|
|
138
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
139
|
+
const paddedBase64 = base64.padEnd(
|
|
140
|
+
base64.length + ((4 - (base64.length % 4)) % 4),
|
|
141
|
+
"=",
|
|
142
|
+
);
|
|
143
|
+
const decoded = window.atob(paddedBase64);
|
|
144
|
+
const bytes = Uint8Array.from(decoded, (character) =>
|
|
145
|
+
character.charCodeAt(0),
|
|
146
|
+
);
|
|
147
|
+
const json = new TextDecoder().decode(bytes);
|
|
148
|
+
|
|
149
|
+
return JSON.parse(json) as T;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function decodeGoogleIdToken(idToken: string) {
|
|
153
|
+
const [, payload] = idToken.split(".");
|
|
154
|
+
|
|
155
|
+
if (!payload) {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
return decodeBase64UrlJson<GoogleIdTokenPayload>(payload);
|
|
161
|
+
} catch {
|
|
162
|
+
return {};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function mapCredentialResult(idToken: string): GoogleCredentialResult {
|
|
167
|
+
const payload = decodeGoogleIdToken(idToken);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
idToken,
|
|
171
|
+
displayName: payload.name ?? null,
|
|
172
|
+
email: payload.email ?? null,
|
|
173
|
+
familyName: payload.family_name ?? null,
|
|
174
|
+
givenName: payload.given_name ?? null,
|
|
175
|
+
id: payload.sub ?? payload.email ?? null,
|
|
176
|
+
phoneNumber: payload.phone_number ?? null,
|
|
177
|
+
profilePictureUri: payload.picture ?? null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function rejectPendingSignIn(error: Error) {
|
|
182
|
+
pendingSignInReject?.(error);
|
|
183
|
+
pendingSignInReject = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const module: PricavaGoogleCredentialNativeModule = {
|
|
187
|
+
async isAvailableAsync() {
|
|
188
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
189
|
+
},
|
|
190
|
+
async signInAsync(options: ResolvedGoogleCredentialOptions) {
|
|
191
|
+
if (!options.webClientId.trim()) {
|
|
192
|
+
throw new Error("Missing Google web client ID.");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!(await module.isAvailableAsync())) {
|
|
196
|
+
throw new Error("Google sign-in is not available in this environment.");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
rejectPendingSignIn(
|
|
200
|
+
createGoogleSignInError("another sign-in request started"),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await loadGoogleIdentityScript();
|
|
204
|
+
|
|
205
|
+
const google = getGoogleIdentityService();
|
|
206
|
+
|
|
207
|
+
if (!google) {
|
|
208
|
+
throw new Error("Google Identity Services is not available.");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return new Promise<GoogleCredentialResult>((resolve, reject) => {
|
|
212
|
+
let settled = false;
|
|
213
|
+
const timeoutId = window.setTimeout(() => {
|
|
214
|
+
if (settled) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
settled = true;
|
|
219
|
+
pendingSignInReject = null;
|
|
220
|
+
google.accounts.id.cancel();
|
|
221
|
+
reject(createGoogleSignInError("request timed out"));
|
|
222
|
+
}, SIGN_IN_TIMEOUT_MS);
|
|
223
|
+
|
|
224
|
+
function settleWithCredential(idToken: string) {
|
|
225
|
+
if (settled) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
settled = true;
|
|
230
|
+
pendingSignInReject = null;
|
|
231
|
+
window.clearTimeout(timeoutId);
|
|
232
|
+
resolve(mapCredentialResult(idToken));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function settleWithError(error: Error) {
|
|
236
|
+
if (settled) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
settled = true;
|
|
241
|
+
pendingSignInReject = null;
|
|
242
|
+
window.clearTimeout(timeoutId);
|
|
243
|
+
reject(error);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
pendingSignInReject = settleWithError;
|
|
247
|
+
|
|
248
|
+
google.accounts.id.initialize({
|
|
249
|
+
client_id: options.webClientId,
|
|
250
|
+
nonce: options.nonce ?? undefined,
|
|
251
|
+
auto_select: options.webAutoSelect,
|
|
252
|
+
cancel_on_tap_outside: true,
|
|
253
|
+
use_fedcm_for_prompt: options.webUseFedCm,
|
|
254
|
+
callback(response) {
|
|
255
|
+
if (response.credential) {
|
|
256
|
+
settleWithCredential(response.credential);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
settleWithError(createGoogleSignInError("missing credential"));
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
google.accounts.id.prompt((notification) => {
|
|
265
|
+
if (settled || notification.isDisplayed()) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (notification.isNotDisplayed()) {
|
|
270
|
+
settleWithError(
|
|
271
|
+
createGoogleSignInError(notification.getNotDisplayedReason()),
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (notification.isSkippedMoment()) {
|
|
277
|
+
settleWithError(
|
|
278
|
+
createGoogleSignInError(notification.getSkippedReason()),
|
|
279
|
+
);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (notification.isDismissedMoment()) {
|
|
284
|
+
settleWithError(
|
|
285
|
+
createGoogleSignInError(notification.getDismissedReason()),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
async clearCredentialStateAsync() {
|
|
292
|
+
getGoogleIdentityService()?.accounts.id.disableAutoSelect();
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export default module;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type GoogleCredentialOptions,
|
|
3
|
+
type GoogleCredentialResult,
|
|
4
|
+
signInWithGoogleCredential,
|
|
5
|
+
} from "../index";
|
|
6
|
+
|
|
7
|
+
const BASE64_URL_ALPHABET =
|
|
8
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
9
|
+
|
|
10
|
+
type ExpoCryptoModule = {
|
|
11
|
+
CryptoDigestAlgorithm: {
|
|
12
|
+
SHA256: string;
|
|
13
|
+
};
|
|
14
|
+
CryptoEncoding: {
|
|
15
|
+
HEX: string;
|
|
16
|
+
};
|
|
17
|
+
digestStringAsync(
|
|
18
|
+
algorithm: string,
|
|
19
|
+
value: string,
|
|
20
|
+
options: { encoding: string },
|
|
21
|
+
): Promise<string>;
|
|
22
|
+
getRandomBytes(byteCount: number): Uint8Array;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type OptionalRequire = (id: string) => unknown;
|
|
26
|
+
|
|
27
|
+
declare const require: OptionalRequire | undefined;
|
|
28
|
+
|
|
29
|
+
function getExpoCrypto() {
|
|
30
|
+
if (typeof require !== "function") {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
return require("expo-crypto") as ExpoCryptoModule;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getRandomBytes(byteCount: number) {
|
|
42
|
+
const bytes = new Uint8Array(byteCount);
|
|
43
|
+
const webCrypto = globalThis.crypto;
|
|
44
|
+
|
|
45
|
+
if (webCrypto?.getRandomValues) {
|
|
46
|
+
webCrypto.getRandomValues(bytes);
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const expoCrypto = getExpoCrypto();
|
|
51
|
+
|
|
52
|
+
if (expoCrypto) {
|
|
53
|
+
return expoCrypto.getRandomBytes(byteCount);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error(
|
|
57
|
+
"Google auth provider adapter requires crypto.getRandomValues or expo-crypto.",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function toHex(bytes: Uint8Array) {
|
|
62
|
+
return Array.from(bytes)
|
|
63
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
64
|
+
.join("");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function sha256Hex(value: string) {
|
|
68
|
+
const subtle = globalThis.crypto?.subtle;
|
|
69
|
+
|
|
70
|
+
if (subtle) {
|
|
71
|
+
const encodedValue = new TextEncoder().encode(value);
|
|
72
|
+
const digest = await subtle.digest("SHA-256", encodedValue);
|
|
73
|
+
|
|
74
|
+
return toHex(new Uint8Array(digest));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const expoCrypto = getExpoCrypto();
|
|
78
|
+
|
|
79
|
+
if (expoCrypto) {
|
|
80
|
+
return expoCrypto.digestStringAsync(
|
|
81
|
+
expoCrypto.CryptoDigestAlgorithm.SHA256,
|
|
82
|
+
value,
|
|
83
|
+
{ encoding: expoCrypto.CryptoEncoding.HEX },
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Google auth provider adapter requires crypto.subtle.digest or expo-crypto.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type GoogleAuthProviderExchangeInput = {
|
|
93
|
+
credential: GoogleCredentialResult;
|
|
94
|
+
idToken: string;
|
|
95
|
+
nonce: string;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type GoogleAuthProviderExchange<TResult> = (
|
|
99
|
+
input: GoogleAuthProviderExchangeInput,
|
|
100
|
+
) => Promise<TResult>;
|
|
101
|
+
|
|
102
|
+
export type SignInWithGoogleAuthProviderOptions<TResult> = {
|
|
103
|
+
credentialOptions: GoogleCredentialOptions;
|
|
104
|
+
exchangeCredential: GoogleAuthProviderExchange<TResult>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
function toBase64Url(bytes: Uint8Array) {
|
|
108
|
+
let output = "";
|
|
109
|
+
|
|
110
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
111
|
+
const first = bytes[index];
|
|
112
|
+
const second = bytes[index + 1];
|
|
113
|
+
const third = bytes[index + 2];
|
|
114
|
+
const hasSecond = index + 1 < bytes.length;
|
|
115
|
+
const hasThird = index + 2 < bytes.length;
|
|
116
|
+
const chunk = (first << 16) | ((second ?? 0) << 8) | (third ?? 0);
|
|
117
|
+
|
|
118
|
+
output += BASE64_URL_ALPHABET[(chunk >> 18) & 63];
|
|
119
|
+
output += BASE64_URL_ALPHABET[(chunk >> 12) & 63];
|
|
120
|
+
|
|
121
|
+
if (hasSecond) {
|
|
122
|
+
output += BASE64_URL_ALPHABET[(chunk >> 6) & 63];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (hasThird) {
|
|
126
|
+
output += BASE64_URL_ALPHABET[chunk & 63];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return output;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function createOidcNoncePair() {
|
|
134
|
+
const nonce = toBase64Url(getRandomBytes(32));
|
|
135
|
+
const hashedNonce = await sha256Hex(nonce);
|
|
136
|
+
|
|
137
|
+
return { nonce, hashedNonce };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function signInWithGoogleAuthProvider<TResult>({
|
|
141
|
+
credentialOptions,
|
|
142
|
+
exchangeCredential,
|
|
143
|
+
}: SignInWithGoogleAuthProviderOptions<TResult>) {
|
|
144
|
+
const { nonce, hashedNonce } = await createOidcNoncePair();
|
|
145
|
+
const credential = await signInWithGoogleCredential({
|
|
146
|
+
...credentialOptions,
|
|
147
|
+
nonce: credentialOptions.nonce ?? hashedNonce,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return exchangeCredential({
|
|
151
|
+
credential,
|
|
152
|
+
idToken: credential.idToken,
|
|
153
|
+
nonce,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { GoogleCredentialOptions } from "../types";
|
|
2
|
+
import { signInWithGoogleAuthProvider } from "./google-auth-provider";
|
|
3
|
+
|
|
4
|
+
export type SupabaseGoogleIdTokenExchange<TResult> = (input: {
|
|
5
|
+
idToken: string;
|
|
6
|
+
nonce: string;
|
|
7
|
+
}) => Promise<TResult>;
|
|
8
|
+
|
|
9
|
+
export type SupabaseGoogleAuthAdapterOptions<TResult> = {
|
|
10
|
+
credentialOptions: GoogleCredentialOptions;
|
|
11
|
+
exchangeGoogleIdToken: SupabaseGoogleIdTokenExchange<TResult>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function createSupabaseGoogleAuthAdapter<TResult>({
|
|
15
|
+
credentialOptions,
|
|
16
|
+
exchangeGoogleIdToken,
|
|
17
|
+
}: SupabaseGoogleAuthAdapterOptions<TResult>) {
|
|
18
|
+
return function signInWithGoogleUsingSupabase() {
|
|
19
|
+
return signInWithGoogleAuthProvider({
|
|
20
|
+
credentialOptions,
|
|
21
|
+
exchangeCredential: ({ idToken, nonce }) =>
|
|
22
|
+
exchangeGoogleIdToken({
|
|
23
|
+
idToken,
|
|
24
|
+
nonce,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import PricavaGoogleCredentialModule from "./PricavaGoogleCredentialModule";
|
|
2
|
+
import type {
|
|
3
|
+
AndroidGoogleCredentialAccountFilter,
|
|
4
|
+
AndroidGoogleCredentialFlow,
|
|
5
|
+
GoogleCredentialOptions,
|
|
6
|
+
GoogleCredentialResult,
|
|
7
|
+
ResolvedGoogleCredentialOptions,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
export * from "./types";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_ANDROID_FLOW: AndroidGoogleCredentialFlow = "sign-in-button";
|
|
13
|
+
const DEFAULT_ANDROID_ACCOUNT_FILTER: AndroidGoogleCredentialAccountFilter =
|
|
14
|
+
"authorized-first";
|
|
15
|
+
const DEFAULT_ANDROID_AUTO_SELECT = true;
|
|
16
|
+
const DEFAULT_WEB_AUTO_SELECT = true;
|
|
17
|
+
const DEFAULT_WEB_USE_FED_CM = true;
|
|
18
|
+
|
|
19
|
+
export class GoogleCredentialUnavailableError extends Error {
|
|
20
|
+
constructor(message = "Google credential sign-in is not available.") {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "GoogleCredentialUnavailableError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function isAvailableAsync(): Promise<boolean> {
|
|
27
|
+
return PricavaGoogleCredentialModule.isAvailableAsync();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function isGoogleCredentialAvailable(): Promise<boolean> {
|
|
31
|
+
return isAvailableAsync();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function signInAsync(
|
|
35
|
+
options: GoogleCredentialOptions,
|
|
36
|
+
): Promise<GoogleCredentialResult> {
|
|
37
|
+
return PricavaGoogleCredentialModule.signInAsync(
|
|
38
|
+
resolveGoogleCredentialOptions(options),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function signInWithGoogleCredential(
|
|
43
|
+
options: GoogleCredentialOptions,
|
|
44
|
+
): Promise<GoogleCredentialResult> {
|
|
45
|
+
if (!options.webClientId.trim()) {
|
|
46
|
+
throw new Error("Missing Google web client ID.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isAvailable = await isGoogleCredentialAvailable();
|
|
50
|
+
|
|
51
|
+
if (!isAvailable) {
|
|
52
|
+
throw new GoogleCredentialUnavailableError(
|
|
53
|
+
"Google credential sign-in is not available on this platform.",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return signInAsync(options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveGoogleCredentialOptions(
|
|
61
|
+
options: GoogleCredentialOptions,
|
|
62
|
+
): ResolvedGoogleCredentialOptions {
|
|
63
|
+
return {
|
|
64
|
+
webClientId: options.webClientId,
|
|
65
|
+
iosClientId: options.iosClientId,
|
|
66
|
+
nonce: options.nonce,
|
|
67
|
+
androidFlow: options.android?.flow ?? DEFAULT_ANDROID_FLOW,
|
|
68
|
+
androidAccountFilter:
|
|
69
|
+
options.android?.accountFilter ?? DEFAULT_ANDROID_ACCOUNT_FILTER,
|
|
70
|
+
androidAutoSelect:
|
|
71
|
+
options.android?.autoSelect ?? DEFAULT_ANDROID_AUTO_SELECT,
|
|
72
|
+
webAutoSelect: options.web?.autoSelect ?? DEFAULT_WEB_AUTO_SELECT,
|
|
73
|
+
webUseFedCm: options.web?.useFedCm ?? DEFAULT_WEB_USE_FED_CM,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function clearCredentialStateAsync(): Promise<void> {
|
|
78
|
+
return PricavaGoogleCredentialModule.clearCredentialStateAsync();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function clearGoogleCredentialState(): Promise<void> {
|
|
82
|
+
const isAvailable = await isGoogleCredentialAvailable();
|
|
83
|
+
|
|
84
|
+
if (!isAvailable) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return clearCredentialStateAsync();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default {
|
|
92
|
+
clearGoogleCredentialState,
|
|
93
|
+
isAvailableAsync,
|
|
94
|
+
isGoogleCredentialAvailable,
|
|
95
|
+
signInAsync,
|
|
96
|
+
signInWithGoogleCredential,
|
|
97
|
+
clearCredentialStateAsync,
|
|
98
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type AndroidGoogleCredentialFlow = "credential" | "sign-in-button";
|
|
2
|
+
|
|
3
|
+
export type AndroidGoogleCredentialAccountFilter =
|
|
4
|
+
| "all"
|
|
5
|
+
| "authorized-first";
|
|
6
|
+
|
|
7
|
+
export type GoogleCredentialAndroidOptions = {
|
|
8
|
+
flow?: AndroidGoogleCredentialFlow;
|
|
9
|
+
accountFilter?: AndroidGoogleCredentialAccountFilter;
|
|
10
|
+
autoSelect?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type GoogleCredentialWebOptions = {
|
|
14
|
+
autoSelect?: boolean;
|
|
15
|
+
useFedCm?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type GoogleCredentialOptions = {
|
|
19
|
+
webClientId: string;
|
|
20
|
+
iosClientId?: string | null;
|
|
21
|
+
nonce?: string | null;
|
|
22
|
+
android?: GoogleCredentialAndroidOptions;
|
|
23
|
+
web?: GoogleCredentialWebOptions;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ResolvedGoogleCredentialOptions = {
|
|
27
|
+
webClientId: string;
|
|
28
|
+
iosClientId?: string | null;
|
|
29
|
+
nonce?: string | null;
|
|
30
|
+
androidFlow: AndroidGoogleCredentialFlow;
|
|
31
|
+
androidAccountFilter: AndroidGoogleCredentialAccountFilter;
|
|
32
|
+
androidAutoSelect: boolean;
|
|
33
|
+
webAutoSelect: boolean;
|
|
34
|
+
webUseFedCm: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type GoogleCredentialResult = {
|
|
38
|
+
idToken: string;
|
|
39
|
+
displayName: string | null;
|
|
40
|
+
email: string | null;
|
|
41
|
+
familyName: string | null;
|
|
42
|
+
givenName: string | null;
|
|
43
|
+
id: string | null;
|
|
44
|
+
phoneNumber: string | null;
|
|
45
|
+
profilePictureUri: string | null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type PricavaGoogleCredentialNativeModule = {
|
|
49
|
+
isAvailableAsync(): Promise<boolean>;
|
|
50
|
+
signInAsync(
|
|
51
|
+
options: ResolvedGoogleCredentialOptions,
|
|
52
|
+
): Promise<GoogleCredentialResult>;
|
|
53
|
+
clearCredentialStateAsync(): Promise<void>;
|
|
54
|
+
};
|