react-native-oauth-essentials 1.0.11 → 1.1.1

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 (51) hide show
  1. package/README.md +384 -362
  2. package/android/src/main/java/com/oauthessentials/CredentialError.kt +4 -1
  3. package/android/src/main/java/com/oauthessentials/CredentialFactory.kt +24 -2
  4. package/android/src/main/java/com/oauthessentials/CustomTabsActivity.kt +14 -1
  5. package/android/src/main/java/com/oauthessentials/OauthEssentialsModule.kt +448 -192
  6. package/app.plugin.js +1 -0
  7. package/ios/AppleIdRetriever.swift +25 -26
  8. package/ios/CredentialError.swift +3 -0
  9. package/ios/CredentialFactory.swift +38 -20
  10. package/ios/CredentialsType.swift +1 -0
  11. package/ios/GoogleIdRetriever.swift +132 -56
  12. package/ios/HybridRetriever.swift +12 -18
  13. package/ios/OauthEssentials.h +0 -3
  14. package/ios/OauthEssentials.mm +45 -2
  15. package/ios/OauthEssentialsModule.swift +266 -57
  16. package/ios/PasskeyRetriever.swift +31 -49
  17. package/ios/PasswordRetriever.swift +10 -30
  18. package/ios/RCTPromiseSettler.swift +125 -0
  19. package/jest/index.js +149 -0
  20. package/lib/module/NativeOauthEssentials.js +10 -1
  21. package/lib/module/NativeOauthEssentials.js.map +1 -1
  22. package/lib/module/errors.js +38 -0
  23. package/lib/module/errors.js.map +1 -0
  24. package/lib/module/index.js +432 -58
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/module/oauth.js +97 -0
  27. package/lib/module/oauth.js.map +1 -0
  28. package/lib/module/platform.js +108 -0
  29. package/lib/module/platform.js.map +1 -0
  30. package/lib/module/types.js +46 -1
  31. package/lib/module/types.js.map +1 -1
  32. package/lib/typescript/src/NativeOauthEssentials.d.ts +70 -16
  33. package/lib/typescript/src/NativeOauthEssentials.d.ts.map +1 -1
  34. package/lib/typescript/src/errors.d.ts +18 -0
  35. package/lib/typescript/src/errors.d.ts.map +1 -0
  36. package/lib/typescript/src/index.d.ts +235 -40
  37. package/lib/typescript/src/index.d.ts.map +1 -1
  38. package/lib/typescript/src/oauth.d.ts +32 -0
  39. package/lib/typescript/src/oauth.d.ts.map +1 -0
  40. package/lib/typescript/src/platform.d.ts +39 -0
  41. package/lib/typescript/src/platform.d.ts.map +1 -0
  42. package/lib/typescript/src/types.d.ts +190 -3
  43. package/lib/typescript/src/types.d.ts.map +1 -1
  44. package/package.json +8 -4
  45. package/plugin/withOauthEssentials.js +291 -0
  46. package/src/NativeOauthEssentials.ts +91 -17
  47. package/src/errors.ts +49 -0
  48. package/src/index.tsx +587 -80
  49. package/src/oauth.ts +140 -0
  50. package/src/platform.ts +121 -0
  51. package/src/types.ts +200 -2
@@ -1,4 +1,6 @@
1
+ import AuthenticationServices
1
2
  import React
3
+ import UIKit
2
4
 
3
5
  public typealias EventEmitter = ((_ eventName: String, _ body: [String: Any]) -> Void)?
4
6
  public class RCTPromiseSettler: NSObject {
@@ -20,9 +22,132 @@ public class RCTPromiseSettler: NSObject {
20
22
  return self.resolve == nil && self.reject == nil
21
23
  }
22
24
 
25
+ internal func succeed(_ result: [String: Any]) {
26
+ self.resolve?(result)
27
+ self.emitEvent?("onCredentialSuccess", result as NSDictionary)
28
+ cleanup()
29
+ }
30
+
31
+ internal func fail(_ code: CredentialError, _ message: String, _ error: Error? = nil) {
32
+ self.reject?(code.rawValue, message, error)
33
+ self.emitEvent?("onCredentialFailure", [
34
+ "code": code.rawValue,
35
+ "message": message
36
+ ])
37
+ cleanup()
38
+ }
39
+
23
40
  internal func cleanup() -> Void {
24
41
  self.resolve = nil
25
42
  self.reject = nil
26
43
  self.emitEvent = nil
27
44
  }
45
+
46
+ /// ASAuthorizationController failure: a cancellation resolves as CANCELLED, anything else rejects.
47
+ internal func finish(authorizationError error: Error) {
48
+ let nsError = error as NSError
49
+ if nsError.domain == ASAuthorizationError.errorDomain,
50
+ nsError.code == ASAuthorizationError.canceled.rawValue {
51
+ succeed(CredentialFactory.fromCancelled())
52
+ } else {
53
+ fail(.invalidResult, error.localizedDescription, error)
54
+ }
55
+ }
56
+
57
+ /// Checks what ASWebAuthenticationSession (also used by GoogleSignIn) needs to start: no Guided
58
+ /// Access, the app active (waiting up to `browserActiveTimeout`) and a view controller in a
59
+ /// foreground active scene. A failed start is then reported as BROWSER_UNAVAILABLE without relying
60
+ /// on the error the session returns. Calls `open` with that view controller, or fails.
61
+ internal func whenBrowserCanOpen(_ name: String, _ open: @escaping (UIViewController) -> Void) {
62
+ if UIAccessibility.isGuidedAccessEnabled {
63
+ fail(.browserUnavailable, "\(name) cannot open the browser while Guided Access is enabled")
64
+ return
65
+ }
66
+
67
+ PresentationContext.whenActive(timeout: Self.browserActiveTimeout) { active in
68
+ guard active else {
69
+ self.fail(.browserUnavailable, "\(name) requires the app to be active")
70
+ return
71
+ }
72
+ guard let presenter = PresentationContext.topViewController() else {
73
+ self.fail(.noActivity, "No view controller available to present \(name)")
74
+ return
75
+ }
76
+ guard presenter.view.window?.windowScene?.activationState == .foregroundActive else {
77
+ self.fail(.browserUnavailable, "\(name) cannot open the browser: the app window is not in the foreground")
78
+ return
79
+ }
80
+ open(presenter)
81
+ }
82
+ }
83
+
84
+ /// How long to wait for the app to become active before giving up on opening the browser.
85
+ private static let browserActiveTimeout: TimeInterval = 2
86
+ }
87
+
88
+ /// Finds where to present the sign in UI. Must be called on the main thread.
89
+ enum PresentationContext {
90
+
91
+ static func activeWindowScene() -> UIWindowScene? {
92
+ let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
93
+ return scenes.first { $0.activationState == .foregroundActive }
94
+ ?? scenes.first { $0.activationState == .foregroundInactive }
95
+ }
96
+
97
+ static func activeWindow() -> UIWindow? {
98
+ guard let scene = activeWindowScene() else { return nil }
99
+ return scene.windows.first { $0.isKeyWindow }
100
+ ?? scene.windows.first { !$0.isHidden && $0.windowLevel == .normal }
101
+ }
102
+
103
+ /// The top most view controller that is attached to a window.
104
+ static func topViewController() -> UIViewController? {
105
+ var top = activeWindow()?.rootViewController
106
+ while let presented = top?.presentedViewController, !presented.isBeingDismissed {
107
+ top = presented
108
+ }
109
+ guard let viewController = top, viewController.viewIfLoaded?.window != nil else {
110
+ return nil
111
+ }
112
+ return viewController
113
+ }
114
+
115
+ static func anchor() -> ASPresentationAnchor {
116
+ if let window = activeWindow() {
117
+ return window
118
+ }
119
+ if let scene = activeWindowScene() {
120
+ return UIWindow(windowScene: scene)
121
+ }
122
+ return ASPresentationAnchor()
123
+ }
124
+
125
+ /// Calls completion(true) once the app is active, or completion(false) after the timeout.
126
+ static func whenActive(timeout: TimeInterval, _ completion: @escaping (Bool) -> Void) {
127
+ if UIApplication.shared.applicationState == .active {
128
+ completion(true)
129
+ return
130
+ }
131
+
132
+ var observer: NSObjectProtocol?
133
+ var finished = false
134
+ let finish: (Bool) -> Void = { active in
135
+ if finished { return }
136
+ finished = true
137
+ if let observer = observer {
138
+ NotificationCenter.default.removeObserver(observer)
139
+ }
140
+ completion(active)
141
+ }
142
+
143
+ observer = NotificationCenter.default.addObserver(
144
+ forName: UIApplication.didBecomeActiveNotification,
145
+ object: nil,
146
+ queue: .main
147
+ ) { _ in finish(true) }
148
+
149
+ DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
150
+ finish(UIApplication.shared.applicationState == .active)
151
+ }
152
+ }
28
153
  }
package/jest/index.js ADDED
@@ -0,0 +1,149 @@
1
+ /* global jest */
2
+ /**
3
+ * Jest mock of react-native-oauth-essentials. Every method is a `jest.fn()` resolving a plausible
4
+ * result; override it per test with `mockResolvedValueOnce` / `mockRejectedValueOnce`.
5
+ *
6
+ * jest.mock('react-native-oauth-essentials', () =>
7
+ * require('react-native-oauth-essentials/jest')
8
+ * );
9
+ *
10
+ * Plain CommonJS on purpose: the React Native Jest preset does not transform this package.
11
+ */
12
+
13
+ const CredentialsType = {
14
+ GOOGLE_ID: 'GOOGLE_ID',
15
+ PASSWORD: 'PASSWORD',
16
+ APPLE_ID: 'APPLE_ID',
17
+ WEB_APPLE_ID: 'WEB_APPLE_ID',
18
+ CANCELLED: 'CANCELLED',
19
+ PASSKEY: 'PASSKEY',
20
+ OAUTH: 'OAUTH',
21
+ };
22
+
23
+ const AppleIdCredentialState = {
24
+ AUTHORIZED: 'AUTHORIZED',
25
+ REVOKED: 'REVOKED',
26
+ NOT_FOUND: 'NOT_FOUND',
27
+ TRANSFERRED: 'TRANSFERRED',
28
+ };
29
+
30
+ const CredentialError = {
31
+ NO_PLAY_SERVICES_ERROR: 'NO_PLAY_SERVICES_ERROR',
32
+ NO_ACTIVITY_ERROR: 'NO_ACTIVITY_ERROR',
33
+ INVALID_RESULT_ERROR: 'INVALID_RESULT_ERROR',
34
+ NO_SIGN_IN_AVAILABLE: 'NO_SIGN_IN_AVAILABLE',
35
+ SIMULTANEOUS_CALL_ERROR: 'SIMULTANEOUS_CALL_ERROR',
36
+ NOT_SUPPORTED_ERROR: 'NOT_SUPPORTED_ERROR',
37
+ BROWSER_UNAVAILABLE: 'BROWSER_UNAVAILABLE',
38
+ CONFIGURATION_ERROR: 'CONFIGURATION_ERROR',
39
+ NETWORK_ERROR: 'NETWORK_ERROR',
40
+ };
41
+
42
+ const CODES = new Set(Object.values(CredentialError));
43
+
44
+ function isCredentialError(error, code) {
45
+ if (typeof error !== 'object' || error === null) {
46
+ return false;
47
+ }
48
+ const value = error.code;
49
+ if (typeof value !== 'string' || !CODES.has(value)) {
50
+ return false;
51
+ }
52
+ return code === undefined || value === code;
53
+ }
54
+
55
+ const OAUTH_REDIRECT_URI = 'com.example.app://oauth/callback';
56
+
57
+ const googleResult = () => ({
58
+ type: CredentialsType.GOOGLE_ID,
59
+ data: {
60
+ id: 'mock-google-id',
61
+ idToken: 'mock-google-id-token',
62
+ displayName: 'Mock User',
63
+ givenName: 'Mock',
64
+ familyName: 'User',
65
+ email: 'mock.user@example.com',
66
+ profilePictureUri: null,
67
+ phoneNumber: null,
68
+ accessToken: null,
69
+ serverAuthCode: null,
70
+ grantedScopes: null,
71
+ },
72
+ });
73
+
74
+ const appleResult = () => ({
75
+ type: CredentialsType.APPLE_ID,
76
+ data: {
77
+ idToken: 'mock-apple-id-token',
78
+ authorizationCode: 'mock-apple-authorization-code',
79
+ user: 'mock-apple-user',
80
+ email: 'mock.user@example.com',
81
+ fullName: 'Mock User',
82
+ likelyReal: true,
83
+ state: null,
84
+ },
85
+ });
86
+
87
+ const passwordResult = () => ({
88
+ type: CredentialsType.PASSWORD,
89
+ data: { username: 'mock.user@example.com', password: 'mock-password' },
90
+ });
91
+
92
+ const passkeyResult = () => ({
93
+ type: CredentialsType.PASSKEY,
94
+ data: JSON.stringify({ id: 'mock-passkey', type: 'public-key' }),
95
+ });
96
+
97
+ const subscription = () => ({ remove: jest.fn() });
98
+
99
+ module.exports = {
100
+ __esModule: true,
101
+
102
+ // Methods
103
+ googleSignIn: jest.fn(async () => googleResult()),
104
+ googleSilentSignIn: jest.fn(async () => googleResult()),
105
+ signOut: jest.fn(async () => true),
106
+ getPassword: jest.fn(async () => passwordResult()),
107
+ passwordSignIn: jest.fn(async () => true),
108
+ appleSignIn: jest.fn(async () => appleResult()),
109
+ getAppleIdCredentialState: jest.fn(
110
+ async () => AppleIdCredentialState.AUTHORIZED
111
+ ),
112
+ hybridSignIn: jest.fn(async () => googleResult()),
113
+ createPassKey: jest.fn(async () => passkeyResult()),
114
+ getPassKey: jest.fn(async () => passkeyResult()),
115
+ cancelPassKeyAutoFill: jest.fn(async () => false),
116
+ oauthSignIn: jest.fn(async (options) => ({
117
+ type: CredentialsType.OAUTH,
118
+ data: {
119
+ code: 'mock-authorization-code',
120
+ state: 'mock-state',
121
+ redirectUri: (options && options.redirectUri) || OAUTH_REDIRECT_URI,
122
+ codeVerifier: 'mock-code-verifier',
123
+ params: { code: 'mock-authorization-code', state: 'mock-state' },
124
+ tokens: null,
125
+ },
126
+ })),
127
+
128
+ // Events
129
+ addCredentialSuccessListener: jest.fn(subscription),
130
+ addCredentialFailureListener: jest.fn(subscription),
131
+ addAppleIdCredentialRevokedListener: jest.fn(subscription),
132
+
133
+ // Errors
134
+ isCredentialError,
135
+
136
+ // Enums
137
+ CredentialsType,
138
+ AppleIdCredentialState,
139
+ CredentialError,
140
+
141
+ // Constants
142
+ GOOGLE_PLAY_SERVICES_SUPPORTED: true,
143
+ PASSWORD_SUPPORTED: true,
144
+ GOOGLE_ID_SUPPORTED: true,
145
+ APPLE_ID_SUPPORTED: true,
146
+ HYBRID_SUPPORTED: true,
147
+ PASSKEYS_SUPPORTED: true,
148
+ OAUTH_REDIRECT_URI,
149
+ };
@@ -1,5 +1,14 @@
1
1
  "use strict";
2
2
 
3
3
  import { TurboModuleRegistry } from 'react-native';
4
- export default TurboModuleRegistry.getEnforcing('OauthEssentials');
4
+ import { missingNativeModuleMessage } from "./platform.js";
5
+ // `get` instead of `getEnforcing` to explain Expo Go and missing rebuilds instead of a generic error.
6
+ function getNativeModule() {
7
+ const module = TurboModuleRegistry.get('OauthEssentials');
8
+ if (!module) {
9
+ throw new Error(missingNativeModuleMessage());
10
+ }
11
+ return module;
12
+ }
13
+ export default getNativeModule();
5
14
  //# sourceMappingURL=NativeOauthEssentials.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeOauthEssentials.ts"],"mappings":";;AAAA,SAGEA,mBAAmB,QACd,cAAc;AA4DrB,eAAeA,mBAAmB,CAACC,YAAY,CAAO,iBAAiB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","missingNativeModuleMessage","getNativeModule","module","get","Error"],"sourceRoot":"../../src","sources":["NativeOauthEssentials.ts"],"mappings":";;AAAA,SAGEA,mBAAmB,QACd,cAAc;AACrB,SAASC,0BAA0B,QAAQ,eAAY;AAwHvD;AACA,SAASC,eAAeA,CAAA,EAAS;EAC/B,MAAMC,MAAM,GAAGH,mBAAmB,CAACI,GAAG,CAAO,iBAAiB,CAAC;EAC/D,IAAI,CAACD,MAAM,EAAE;IACX,MAAM,IAAIE,KAAK,CAACJ,0BAA0B,CAAC,CAAC,CAAC;EAC/C;EACA,OAAOE,MAAM;AACf;AAEA,eAAeD,eAAe,CAAC,CAAC","ignoreList":[]}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ import { CredentialError } from "./types.js";
4
+ const CODES = new Set(Object.values(CredentialError));
5
+
6
+ /**
7
+ * Narrows an unknown error (e.g. in a `catch`) to an error rejected by this library, optionally
8
+ * with a specific code.
9
+ *
10
+ * ```ts
11
+ * try {
12
+ * await googleSignIn({ iosClientId, webClientId });
13
+ * } catch (e) {
14
+ * if (isCredentialError(e, CredentialError.BROWSER_UNAVAILABLE)) retryLater();
15
+ * else if (isCredentialError(e)) report(e.code, e.message);
16
+ * else throw e;
17
+ * }
18
+ * ```
19
+ */
20
+ export function isCredentialError(error, code) {
21
+ if (typeof error !== 'object' || error === null) {
22
+ return false;
23
+ }
24
+ const value = error.code;
25
+ if (typeof value !== 'string' || !CODES.has(value)) {
26
+ return false;
27
+ }
28
+ return code === undefined || value === code;
29
+ }
30
+ export function createCredentialError(code, message, cause) {
31
+ const error = new Error(message);
32
+ error.code = code;
33
+ if (cause !== undefined) {
34
+ error.cause = cause;
35
+ }
36
+ return error;
37
+ }
38
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["CredentialError","CODES","Set","Object","values","isCredentialError","error","code","value","has","undefined","createCredentialError","message","cause","Error"],"sourceRoot":"../../src","sources":["errors.ts"],"mappings":";;AAAA,SACEA,eAAe,QAGV,YAAS;AAEhB,MAAMC,KAAK,GAAG,IAAIC,GAAG,CAASC,MAAM,CAACC,MAAM,CAACJ,eAAe,CAAC,CAAC;;AAE7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASK,iBAAiBA,CAE/BC,KAAc,EAAEC,IAAQ,EAAoC;EAC5D,IAAI,OAAOD,KAAK,KAAK,QAAQ,IAAIA,KAAK,KAAK,IAAI,EAAE;IAC/C,OAAO,KAAK;EACd;EACA,MAAME,KAAK,GAAIF,KAAK,CAAwBC,IAAI;EAChD,IAAI,OAAOC,KAAK,KAAK,QAAQ,IAAI,CAACP,KAAK,CAACQ,GAAG,CAACD,KAAK,CAAC,EAAE;IAClD,OAAO,KAAK;EACd;EACA,OAAOD,IAAI,KAAKG,SAAS,IAAIF,KAAK,KAAKD,IAAI;AAC7C;AAEA,OAAO,SAASI,qBAAqBA,CACnCJ,IAAO,EACPK,OAAe,EACfC,KAAe,EACU;EACzB,MAAMP,KAAK,GAAG,IAAIQ,KAAK,CAACF,OAAO,CAE9B;EACDN,KAAK,CAACC,IAAI,GAAGA,IAAI;EACjB,IAAIM,KAAK,KAAKH,SAAS,EAAE;IACvBJ,KAAK,CAACO,KAAK,GAAGA,KAAK;EACrB;EACA,OAAOP,KAAK;AACd","ignoreList":[]}