react-native-oauth-essentials 1.0.12 → 1.2.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.
Files changed (53) hide show
  1. package/OauthEssentials.podspec +17 -2
  2. package/README.md +385 -361
  3. package/android/build.gradle +15 -10
  4. package/android/src/main/java/com/oauthessentials/CredentialError.kt +4 -1
  5. package/android/src/main/java/com/oauthessentials/CredentialFactory.kt +24 -2
  6. package/android/src/main/java/com/oauthessentials/CustomTabsActivity.kt +14 -1
  7. package/android/src/main/java/com/oauthessentials/OauthEssentialsModule.kt +445 -187
  8. package/app.plugin.js +1 -0
  9. package/ios/AppleIdRetriever.swift +25 -26
  10. package/ios/CredentialError.swift +3 -0
  11. package/ios/CredentialFactory.swift +38 -20
  12. package/ios/CredentialsType.swift +1 -0
  13. package/ios/GoogleIdRetriever.swift +132 -56
  14. package/ios/HybridRetriever.swift +12 -18
  15. package/ios/OauthEssentials.h +0 -3
  16. package/ios/OauthEssentials.mm +45 -2
  17. package/ios/OauthEssentialsModule.swift +266 -57
  18. package/ios/PasskeyRetriever.swift +31 -49
  19. package/ios/PasswordRetriever.swift +10 -30
  20. package/ios/RCTPromiseSettler.swift +125 -0
  21. package/jest/index.js +149 -0
  22. package/lib/module/NativeOauthEssentials.js +10 -1
  23. package/lib/module/NativeOauthEssentials.js.map +1 -1
  24. package/lib/module/errors.js +38 -0
  25. package/lib/module/errors.js.map +1 -0
  26. package/lib/module/index.js +432 -58
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/oauth.js +97 -0
  29. package/lib/module/oauth.js.map +1 -0
  30. package/lib/module/platform.js +108 -0
  31. package/lib/module/platform.js.map +1 -0
  32. package/lib/module/types.js +46 -1
  33. package/lib/module/types.js.map +1 -1
  34. package/lib/typescript/src/NativeOauthEssentials.d.ts +70 -16
  35. package/lib/typescript/src/NativeOauthEssentials.d.ts.map +1 -1
  36. package/lib/typescript/src/errors.d.ts +18 -0
  37. package/lib/typescript/src/errors.d.ts.map +1 -0
  38. package/lib/typescript/src/index.d.ts +235 -40
  39. package/lib/typescript/src/index.d.ts.map +1 -1
  40. package/lib/typescript/src/oauth.d.ts +32 -0
  41. package/lib/typescript/src/oauth.d.ts.map +1 -0
  42. package/lib/typescript/src/platform.d.ts +39 -0
  43. package/lib/typescript/src/platform.d.ts.map +1 -0
  44. package/lib/typescript/src/types.d.ts +190 -2
  45. package/lib/typescript/src/types.d.ts.map +1 -1
  46. package/package.json +9 -5
  47. package/plugin/withOauthEssentials.js +291 -0
  48. package/src/NativeOauthEssentials.ts +91 -17
  49. package/src/errors.ts +49 -0
  50. package/src/index.tsx +587 -80
  51. package/src/oauth.ts +140 -0
  52. package/src/platform.ts +121 -0
  53. package/src/types.ts +200 -1
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,qCAAqC,CAAC,EAAE,OAAO,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,eAAe,CAAC,OAAO,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC;IAC/B,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,eAAe,CAAC,SAAS,CAAC;IAChC,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,eAAe,CAAC,SAAS,CAAC;IAChC,IAAI,EAAE,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC;IAC/B,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,eAAe,CAAC,YAAY,CAAC;IACnC,IAAI,EAAE;QACJ,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,oBAAY,eAAe;IACzB,SAAS,cAAc,CAAE,4BAA4B;IACrD,QAAQ,aAAa,CAAE,4BAA4B;IACnD,QAAQ,aAAa,CAAE,+BAA+B;IACtD,YAAY,iBAAiB,CAAE,mCAAmC;IAClE,SAAS,cAAc,CAAE,gCAAgC;IACzD,OAAO,YAAY;CACpB;AAED,oBAAY,eAAe;IACzB,sBAAsB,2BAA2B;IACjD,iBAAiB,sBAAsB;IACvC,oBAAoB,yBAAyB;CAC9C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-oauth-essentials",
3
- "version": "1.0.12",
3
+ "version": "1.2.0",
4
4
  "description": "Essential Auth tools for every app.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -10,6 +10,8 @@
10
10
  "types": "./lib/typescript/src/index.d.ts",
11
11
  "default": "./lib/module/index.js"
12
12
  },
13
+ "./jest": "./jest/index.js",
14
+ "./app.plugin.js": "./app.plugin.js",
13
15
  "./package.json": "./package.json"
14
16
  },
15
17
  "files": [
@@ -20,6 +22,9 @@
20
22
  "cpp",
21
23
  "*.podspec",
22
24
  "react-native.config.js",
25
+ "app.plugin.js",
26
+ "plugin",
27
+ "jest",
23
28
  "!ios/build",
24
29
  "!android/build",
25
30
  "!android/gradle",
@@ -50,6 +55,7 @@
50
55
  "ios",
51
56
  "android",
52
57
  "expo",
58
+ "expo-config-plugin",
53
59
  "oauth",
54
60
  "oauth2",
55
61
  "auth",
@@ -71,9 +77,7 @@
71
77
  "google-sign-in",
72
78
  "openid",
73
79
  "openid-connect",
74
- "biometric",
75
- "mobile",
76
- "web"
80
+ "mobile"
77
81
  ],
78
82
  "repository": {
79
83
  "type": "git",
@@ -115,7 +119,7 @@
115
119
  },
116
120
  "peerDependencies": {
117
121
  "react": "*",
118
- "react-native": "*"
122
+ "react-native": ">=0.77.0"
119
123
  },
120
124
  "workspaces": [
121
125
  "example",
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Expo config plugin of react-native-oauth-essentials.
3
+ *
4
+ * plugins: [
5
+ * ['react-native-oauth-essentials', {
6
+ * google: { iosClientId: '1234-abcd.apps.googleusercontent.com', webClientId: '...' },
7
+ * appleSignIn: true,
8
+ * webCredentialsDomains: ['example.com'],
9
+ * }],
10
+ * ]
11
+ *
12
+ * - google: `{ iosClientId?, webClientId? }`, or `true` to only use the Firebase files
13
+ * (`ios.googleServicesFile` / `android.googleServicesFile`) for the missing ids. Writes:
14
+ * - iOS: the reversed iOS client id URL scheme (without it GoogleSignIn cannot open), and
15
+ * GIDClientID / GIDServerClientID to Info.plist;
16
+ * - Android: the web client id as the `com.oauthessentials.GOOGLE_WEB_CLIENT_ID` meta-data.
17
+ * So the app can call `googleSignIn()` without client ids.
18
+ * - appleSignIn: adds the Sign in with Apple entitlement.
19
+ * - webCredentialsDomains: adds `webcredentials:<domain>` associated domains (passkeys and
20
+ * saved passwords).
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ const PLUGIN_NAME = 'react-native-oauth-essentials';
27
+ const APPLE_SIGN_IN_ENTITLEMENT = 'com.apple.developer.applesignin';
28
+ const ASSOCIATED_DOMAINS_ENTITLEMENT = 'com.apple.developer.associated-domains';
29
+ const GOOGLE_WEB_CLIENT_ID_META_DATA =
30
+ 'com.oauthessentials.GOOGLE_WEB_CLIENT_ID';
31
+
32
+ // OAuth client types in google-services.json.
33
+ const IOS_CLIENT_TYPE = 2;
34
+ const WEB_CLIENT_TYPE = 3;
35
+
36
+ /**
37
+ * Same rule as GoogleSignIn (GIDSignInCallbackSchemes): the client id components in reverse order.
38
+ */
39
+ function googleUrlScheme(iosClientId) {
40
+ return iosClientId.split('.').reverse().join('.');
41
+ }
42
+
43
+ function nonEmpty(value) {
44
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
45
+ }
46
+
47
+ /**
48
+ * `CLIENT_ID` (the iOS client id) of a Firebase GoogleService-Info.plist, which Firebase always
49
+ * writes as XML.
50
+ */
51
+ function googleIdsFromServicesPlist(xml) {
52
+ const match = /<key>\s*CLIENT_ID\s*<\/key>\s*<string>([^<]*)<\/string>/.exec(
53
+ xml
54
+ );
55
+ return { iosClientId: nonEmpty(match && match[1]) };
56
+ }
57
+
58
+ /**
59
+ * Web and iOS client ids of a Firebase google-services.json: the `client` of `androidPackage`
60
+ * (or the first one), its `oauth_client` and `appinvite_service.other_platform_oauth_client`.
61
+ * The iOS client must match `bundleIdentifier` when it is known.
62
+ */
63
+ function googleIdsFromServicesJson(
64
+ json,
65
+ { androidPackage, bundleIdentifier } = {}
66
+ ) {
67
+ const clients = Array.isArray(json && json.client) ? json.client : [];
68
+ const client =
69
+ clients.find(
70
+ (item) =>
71
+ androidPackage &&
72
+ item.client_info &&
73
+ item.client_info.android_client_info &&
74
+ item.client_info.android_client_info.package_name === androidPackage
75
+ ) || clients[0];
76
+ if (!client) {
77
+ return {};
78
+ }
79
+ const oauthClients = [
80
+ ...(client.oauth_client || []),
81
+ ...((client.services &&
82
+ client.services.appinvite_service &&
83
+ client.services.appinvite_service.other_platform_oauth_client) ||
84
+ []),
85
+ ];
86
+ const web = oauthClients.find((item) => item.client_type === WEB_CLIENT_TYPE);
87
+ const ios = oauthClients.find(
88
+ (item) =>
89
+ item.client_type === IOS_CLIENT_TYPE &&
90
+ (!bundleIdentifier ||
91
+ (item.ios_info && item.ios_info.bundle_id === bundleIdentifier))
92
+ );
93
+ return {
94
+ webClientId: nonEmpty(web && web.client_id),
95
+ iosClientId: nonEmpty(ios && ios.client_id),
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Explicit ids win over the ones read from the Firebase files.
101
+ */
102
+ function resolveGoogleIds(google, fromPlist = {}, fromJson = {}) {
103
+ const explicit = typeof google === 'object' && google !== null ? google : {};
104
+ return {
105
+ iosClientId:
106
+ nonEmpty(explicit.iosClientId) ||
107
+ fromPlist.iosClientId ||
108
+ fromJson.iosClientId,
109
+ webClientId: nonEmpty(explicit.webClientId) || fromJson.webClientId,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Reads the Firebase files set in the app config (paths relative to the project root).
115
+ */
116
+ function loadGoogleIds(config, google, projectRoot) {
117
+ const read = (file) =>
118
+ file ? fs.readFileSync(path.resolve(projectRoot, file), 'utf8') : null;
119
+ const plist = read(config.ios && config.ios.googleServicesFile);
120
+ const json = read(config.android && config.android.googleServicesFile);
121
+ return resolveGoogleIds(
122
+ google,
123
+ plist ? googleIdsFromServicesPlist(plist) : {},
124
+ json
125
+ ? googleIdsFromServicesJson(JSON.parse(json), {
126
+ androidPackage: config.android && config.android.package,
127
+ bundleIdentifier: config.ios && config.ios.bundleIdentifier,
128
+ })
129
+ : {}
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Leaves Info.plist unchanged without an iOS client id (Google may be used on Android only).
135
+ */
136
+ function setGoogleInfoPlist(infoPlist, google) {
137
+ const iosClientId = google && nonEmpty(google.iosClientId);
138
+ if (!iosClientId) {
139
+ return infoPlist;
140
+ }
141
+ const scheme = googleUrlScheme(iosClientId);
142
+ const urlTypes = Array.isArray(infoPlist.CFBundleURLTypes)
143
+ ? infoPlist.CFBundleURLTypes
144
+ : [];
145
+ const registered = urlTypes.some(
146
+ (type) =>
147
+ Array.isArray(type.CFBundleURLSchemes) &&
148
+ type.CFBundleURLSchemes.some(
149
+ (value) => String(value).toLowerCase() === scheme.toLowerCase()
150
+ )
151
+ );
152
+
153
+ const result = {
154
+ ...infoPlist,
155
+ CFBundleURLTypes: registered
156
+ ? urlTypes
157
+ : [...urlTypes, { CFBundleURLSchemes: [scheme] }],
158
+ GIDClientID: iosClientId,
159
+ };
160
+ const webClientId = nonEmpty(google.webClientId);
161
+ if (webClientId) {
162
+ result.GIDServerClientID = webClientId;
163
+ }
164
+ return result;
165
+ }
166
+
167
+ /**
168
+ * Sets the web client id `<meta-data>` of `<application>` (AndroidManifest as parsed by Expo),
169
+ * replacing a previous value.
170
+ */
171
+ function setGoogleWebClientIdMetaData(androidManifest, webClientId) {
172
+ const manifest = androidManifest.manifest;
173
+ const applications = manifest.application || [];
174
+ if (applications.length === 0) {
175
+ throw new Error(`${PLUGIN_NAME}: AndroidManifest.xml has no <application>`);
176
+ }
177
+ const [application, ...rest] = applications;
178
+ const metaData = (application['meta-data'] || []).filter(
179
+ (item) =>
180
+ !item.$ || item.$['android:name'] !== GOOGLE_WEB_CLIENT_ID_META_DATA
181
+ );
182
+ metaData.push({
183
+ $: {
184
+ 'android:name': GOOGLE_WEB_CLIENT_ID_META_DATA,
185
+ 'android:value': webClientId,
186
+ },
187
+ });
188
+ return {
189
+ ...androidManifest,
190
+ manifest: {
191
+ ...manifest,
192
+ application: [{ ...application, 'meta-data': metaData }, ...rest],
193
+ },
194
+ };
195
+ }
196
+
197
+ function setEntitlements(entitlements, options) {
198
+ const result = { ...entitlements };
199
+ if (options.appleSignIn) {
200
+ result[APPLE_SIGN_IN_ENTITLEMENT] = ['Default'];
201
+ }
202
+ const domains = options.webCredentialsDomains || [];
203
+ if (domains.length > 0) {
204
+ const current = Array.isArray(result[ASSOCIATED_DOMAINS_ENTITLEMENT])
205
+ ? result[ASSOCIATED_DOMAINS_ENTITLEMENT]
206
+ : [];
207
+ const added = domains.map((domain) =>
208
+ domain.startsWith('webcredentials:') ? domain : `webcredentials:${domain}`
209
+ );
210
+ result[ASSOCIATED_DOMAINS_ENTITLEMENT] = [
211
+ ...new Set([...current, ...added]),
212
+ ];
213
+ }
214
+ return result;
215
+ }
216
+
217
+ function withOauthEssentials(config, options = {}) {
218
+ // Loaded lazily: only Expo apps have it, and the helpers above are tested without it.
219
+ const {
220
+ withInfoPlist,
221
+ withEntitlementsPlist,
222
+ withAndroidManifest,
223
+ WarningAggregator,
224
+ } = require('expo/config-plugins');
225
+
226
+ let result = config;
227
+ if (options.google) {
228
+ result = withInfoPlist(result, (mod) => {
229
+ const ids = loadGoogleIds(
230
+ mod,
231
+ options.google,
232
+ mod.modRequest.projectRoot
233
+ );
234
+ if (!ids.iosClientId) {
235
+ WarningAggregator.addWarningIOS(
236
+ PLUGIN_NAME,
237
+ 'no Google iOS client id, so Info.plist is not changed and googleSignIn() rejects with ' +
238
+ 'CONFIGURATION_ERROR on iOS. Set "google": { "iosClientId": "..." } or ' +
239
+ '"ios.googleServicesFile" (ignore this if you only use Google on Android).'
240
+ );
241
+ return mod;
242
+ }
243
+ if (!ids.webClientId) {
244
+ WarningAggregator.addWarningIOS(
245
+ PLUGIN_NAME,
246
+ 'no Google web client id, so GIDServerClientID is not set (the idToken audience is the iOS ' +
247
+ 'client id). Set "google": { "webClientId": "..." } or "android.googleServicesFile".'
248
+ );
249
+ }
250
+ mod.modResults = setGoogleInfoPlist(mod.modResults, ids);
251
+ return mod;
252
+ });
253
+ result = withAndroidManifest(result, (mod) => {
254
+ const ids = loadGoogleIds(
255
+ mod,
256
+ options.google,
257
+ mod.modRequest.projectRoot
258
+ );
259
+ if (!ids.webClientId) {
260
+ WarningAggregator.addWarningAndroid(
261
+ PLUGIN_NAME,
262
+ 'no Google web client id: googleSignIn() rejects with CONFIGURATION_ERROR on Android unless ' +
263
+ 'it receives { webClientId }. Set "google": { "webClientId": "..." } or ' +
264
+ '"android.googleServicesFile".'
265
+ );
266
+ return mod;
267
+ }
268
+ mod.modResults = setGoogleWebClientIdMetaData(
269
+ mod.modResults,
270
+ ids.webClientId
271
+ );
272
+ return mod;
273
+ });
274
+ }
275
+ if (options.appleSignIn || (options.webCredentialsDomains || []).length > 0) {
276
+ result = withEntitlementsPlist(result, (mod) => {
277
+ mod.modResults = setEntitlements(mod.modResults, options);
278
+ return mod;
279
+ });
280
+ }
281
+ return result;
282
+ }
283
+
284
+ module.exports = withOauthEssentials;
285
+ module.exports.googleUrlScheme = googleUrlScheme;
286
+ module.exports.googleIdsFromServicesPlist = googleIdsFromServicesPlist;
287
+ module.exports.googleIdsFromServicesJson = googleIdsFromServicesJson;
288
+ module.exports.resolveGoogleIds = resolveGoogleIds;
289
+ module.exports.setGoogleInfoPlist = setGoogleInfoPlist;
290
+ module.exports.setGoogleWebClientIdMetaData = setGoogleWebClientIdMetaData;
291
+ module.exports.setEntitlements = setEntitlements;
@@ -3,12 +3,20 @@ import {
3
3
  type TurboModule,
4
4
  TurboModuleRegistry,
5
5
  } from 'react-native';
6
+ import { missingNativeModuleMessage } from './platform';
6
7
 
7
8
  export interface Spec extends TurboModule {
8
9
  // Events
10
+ /** Emitted with the `{ type, data }` a credential method resolves with, including `CANCELLED`. */
9
11
  readonly onCredentialSuccess: CodegenTypes.EventEmitter<CredentialSuccessEvent>;
12
+ /** Emitted with `{ code, message }` when a credential method rejects. */
10
13
  readonly onCredentialFailure: CodegenTypes.EventEmitter<CredentialErrorEvent>;
14
+ /** iOS only: emitted with `{ type: 'REVOKED' }` on `credentialRevokedNotification`. */
15
+ readonly onAppleIdCredentialRevoked: CodegenTypes.EventEmitter<AppleIdCredentialRevokedEvent>;
11
16
 
17
+ /**
18
+ * Read once when the module loads. See the exported constants in `index.tsx` for the values.
19
+ */
12
20
  getConstants(): {
13
21
  readonly GOOGLE_PLAY_SERVICES_SUPPORTED: boolean;
14
22
  readonly PASSWORD_SUPPORTED: boolean;
@@ -16,53 +24,115 @@ export interface Spec extends TurboModule {
16
24
  readonly APPLE_ID_SUPPORTED: boolean;
17
25
  readonly HYBRID_SUPPORTED: boolean;
18
26
  readonly PASSKEYS_SUPPORTED: boolean;
27
+ readonly OAUTH_REDIRECT_URI: string;
19
28
  };
20
29
 
21
30
  /**
22
- * Performs the google sign in.
23
- * @param androidAndIOSClientId
24
- * @param options
31
+ * iOS: GoogleSignIn `signIn` from the top most view controller. Android: Credential Manager
32
+ * Google ID / "Sign in with Google" option, then the Authorization API when `scopes` or
33
+ * `offlineAccess` are set. Resolves `GOOGLE_ID` or `CANCELLED`.
34
+ * @param androidAndIOSClientId - Web client id on Android, iOS client id on iOS. '' reads it from
35
+ * the app: iOS `GIDClientID`, then `CLIENT_ID` of GoogleService-Info.plist; Android the
36
+ * `com.oauthessentials.GOOGLE_WEB_CLIENT_ID` meta-data, then the `default_web_client_id` string
37
+ * (Firebase). Rejects `CONFIGURATION_ERROR` when it is missing or, on iOS, when its reversed URL
38
+ * scheme is not registered.
39
+ * @param options - GoogleSignInOptions, already normalized by `index.tsx`.
25
40
  */
26
41
  googleSignIn(androidAndIOSClientId: string, options: Object): Promise<Object>;
27
42
 
28
43
  /**
29
- * Sign ins with apple.
30
- * @param androidWebUrl
44
+ * iOS: GoogleSignIn `restorePreviousSignIn` (no UI). Android: Credential Manager filtered to
45
+ * authorized accounts with auto select, authorization without UI.
46
+ * Resolves `GOOGLE_ID` or `CANCELLED`, rejects `NO_SIGN_IN_AVAILABLE` when there is nothing to restore.
31
47
  */
32
- appleSignIn(androidWebUrl?: string): Promise<Object>;
48
+ googleSilentSignIn(
49
+ androidAndIOSClientId: string,
50
+ options: Object
51
+ ): Promise<Object>;
33
52
 
34
53
  /**
35
- * Saves the username/password credential
36
- * @param username
37
- * @param password
54
+ * iOS: GoogleSignIn `signOut`. Android: Credential Manager `clearCredentialState`.
55
+ * Resolves `true`. Does not emit events.
56
+ */
57
+ signOut(): Promise<boolean>;
58
+
59
+ /**
60
+ * iOS: native Apple ID request (`fullName`, `email`, `options.nonce`, `options.state`); resolves
61
+ * `APPLE_ID` or `CANCELLED`, `androidWebUrl` is ignored.
62
+ * Android: opens `androidWebUrl` in a Custom Tab and resolves `WEB_APPLE_ID` with the redirect
63
+ * url or `CANCELLED`; `options` is ignored.
64
+ * Unlike `appleSignIn` in `index.tsx`, it does not fill `email`/`fullName` from previous results.
65
+ */
66
+ appleSignIn(androidWebUrl: string | null, options: Object): Promise<Object>;
67
+
68
+ /**
69
+ * iOS: `ASAuthorizationAppleIDProvider.getCredentialState`, resolves `AUTHORIZED`, `REVOKED`,
70
+ * `TRANSFERRED` or `NOT_FOUND`. Android: always rejects `NOT_SUPPORTED_ERROR`.
71
+ */
72
+ getAppleIdCredentialState(userId: string): Promise<string>;
73
+
74
+ /**
75
+ * iOS: resolves `false` without doing anything. Android: Credential Manager
76
+ * `CreatePasswordRequest`, resolves `true` when saved and `false` on any error.
38
77
  */
39
78
  passwordSignIn(username: string, password: string): Promise<boolean>;
40
79
 
41
80
  /**
42
- * Gets the saved password.
81
+ * Shows the saved passwords. Resolves `PASSWORD`, or `CANCELLED` when there is none or the user
82
+ * dismisses it.
43
83
  */
44
84
  getPassword(): Promise<Object>;
45
85
 
46
86
  /**
47
- * Performs the called so "hybrid sign in"
48
- * @param googleClientId
49
- * @param options
87
+ * iOS: Apple ID + password request (`googleClientId` ignored). Android: Google ID + password
88
+ * Credential Manager request. Resolves the picked credential or `CANCELLED`.
50
89
  */
51
90
  hybridSignIn(googleClientId: string, options: Object): Promise<Object>;
52
91
 
53
92
  /**
54
- * Creates the passkey.
93
+ * Registers a passkey from `options.requestJson` (WebAuthn creation options).
94
+ * Resolves `PASSKEY` with the registration response JSON string in `data`, or `CANCELLED`.
55
95
  */
56
96
  createPassKey(options: Object): Promise<Object>;
57
97
 
58
98
  /**
59
- * Gets the passkey
60
- * @param options
99
+ * Asserts a passkey from `options.requestJson` (WebAuthn request options), with
100
+ * `options.mediation === 'autofill'` on iOS for QuickType bar AutoFill.
101
+ * Resolves `PASSKEY` with the authentication response JSON string in `data`, or `CANCELLED`.
61
102
  */
62
103
  getPassKey(options: Object): Promise<Object>;
104
+
105
+ /**
106
+ * iOS 16+: cancels a pending AutoFill `getPassKey` (which resolves `CANCELLED`) and resolves
107
+ * `true`, or resolves `false` when there is none. Android: resolves `false`.
108
+ */
109
+ cancelPassKeyAutoFill(): Promise<boolean>;
110
+
111
+ /**
112
+ * Resolves `{ codeVerifier, codeChallenge, state }`: a 32 byte random base64url verifier, its
113
+ * SHA-256 base64url challenge (PKCE S256) and a 16 byte random base64url state.
114
+ */
115
+ generatePkce(): Promise<Object>;
116
+
117
+ /**
118
+ * Opens `url` in `ASWebAuthenticationSession` (iOS, `options.callbackScheme`,
119
+ * `options.prefersEphemeralSession`) or a Custom Tab (Android, http/https only, `options`
120
+ * ignored). Resolves `{ type: 'REDIRECT', data: { url } }` with the redirect url, or `CANCELLED`.
121
+ * Does not emit events.
122
+ */
123
+ openAuthSession(url: string, options: Object): Promise<Object>;
124
+ }
125
+
126
+ // `get` instead of `getEnforcing` to explain Expo Go and missing rebuilds instead of a generic error.
127
+ function getNativeModule(): Spec {
128
+ const module = TurboModuleRegistry.get<Spec>('OauthEssentials');
129
+ if (!module) {
130
+ throw new Error(missingNativeModuleMessage());
131
+ }
132
+ return module;
63
133
  }
64
134
 
65
- export default TurboModuleRegistry.getEnforcing<Spec>('OauthEssentials');
135
+ export default getNativeModule();
66
136
 
67
137
  type CredentialSuccessEvent = {
68
138
  type: string;
@@ -73,3 +143,7 @@ type CredentialErrorEvent = {
73
143
  code: string;
74
144
  message: string;
75
145
  };
146
+
147
+ type AppleIdCredentialRevokedEvent = {
148
+ type: string;
149
+ };
package/src/errors.ts ADDED
@@ -0,0 +1,49 @@
1
+ import {
2
+ CredentialError,
3
+ type CredentialErrorCode,
4
+ type OauthEssentialsError,
5
+ } from './types';
6
+
7
+ const CODES = new Set<string>(Object.values(CredentialError));
8
+
9
+ /**
10
+ * Narrows an unknown error (e.g. in a `catch`) to an error rejected by this library, optionally
11
+ * with a specific code.
12
+ *
13
+ * ```ts
14
+ * try {
15
+ * await googleSignIn({ iosClientId, webClientId });
16
+ * } catch (e) {
17
+ * if (isCredentialError(e, CredentialError.BROWSER_UNAVAILABLE)) retryLater();
18
+ * else if (isCredentialError(e)) report(e.code, e.message);
19
+ * else throw e;
20
+ * }
21
+ * ```
22
+ */
23
+ export function isCredentialError<
24
+ C extends CredentialErrorCode = CredentialErrorCode
25
+ >(error: unknown, code?: C): error is OauthEssentialsError<C> {
26
+ if (typeof error !== 'object' || error === null) {
27
+ return false;
28
+ }
29
+ const value = (error as { code?: unknown }).code;
30
+ if (typeof value !== 'string' || !CODES.has(value)) {
31
+ return false;
32
+ }
33
+ return code === undefined || value === code;
34
+ }
35
+
36
+ export function createCredentialError<C extends CredentialErrorCode>(
37
+ code: C,
38
+ message: string,
39
+ cause?: unknown
40
+ ): OauthEssentialsError<C> {
41
+ const error = new Error(message) as OauthEssentialsError<C> & {
42
+ cause?: unknown;
43
+ };
44
+ error.code = code;
45
+ if (cause !== undefined) {
46
+ error.cause = cause;
47
+ }
48
+ return error;
49
+ }