react-native-oauth-essentials 1.0.12 → 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.
- package/README.md +384 -362
- package/android/src/main/java/com/oauthessentials/CredentialError.kt +4 -1
- package/android/src/main/java/com/oauthessentials/CredentialFactory.kt +24 -2
- package/android/src/main/java/com/oauthessentials/CustomTabsActivity.kt +14 -1
- package/android/src/main/java/com/oauthessentials/OauthEssentialsModule.kt +445 -187
- package/app.plugin.js +1 -0
- package/ios/AppleIdRetriever.swift +25 -26
- package/ios/CredentialError.swift +3 -0
- package/ios/CredentialFactory.swift +38 -20
- package/ios/CredentialsType.swift +1 -0
- package/ios/GoogleIdRetriever.swift +132 -56
- package/ios/HybridRetriever.swift +12 -18
- package/ios/OauthEssentials.h +0 -3
- package/ios/OauthEssentials.mm +45 -2
- package/ios/OauthEssentialsModule.swift +266 -57
- package/ios/PasskeyRetriever.swift +31 -49
- package/ios/PasswordRetriever.swift +10 -30
- package/ios/RCTPromiseSettler.swift +125 -0
- package/jest/index.js +149 -0
- package/lib/module/NativeOauthEssentials.js +10 -1
- package/lib/module/NativeOauthEssentials.js.map +1 -1
- package/lib/module/errors.js +38 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +432 -58
- package/lib/module/index.js.map +1 -1
- package/lib/module/oauth.js +97 -0
- package/lib/module/oauth.js.map +1 -0
- package/lib/module/platform.js +108 -0
- package/lib/module/platform.js.map +1 -0
- package/lib/module/types.js +46 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/NativeOauthEssentials.d.ts +70 -16
- package/lib/typescript/src/NativeOauthEssentials.d.ts.map +1 -1
- package/lib/typescript/src/errors.d.ts +18 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +235 -40
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/oauth.d.ts +32 -0
- package/lib/typescript/src/oauth.d.ts.map +1 -0
- package/lib/typescript/src/platform.d.ts +39 -0
- package/lib/typescript/src/platform.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +190 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +8 -4
- package/plugin/withOauthEssentials.js +291 -0
- package/src/NativeOauthEssentials.ts +91 -17
- package/src/errors.ts +49 -0
- package/src/index.tsx +587 -80
- package/src/oauth.ts +140 -0
- package/src/platform.ts +121 -0
- 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;
|
|
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.
|
|
3
|
+
"version": "1.1.1",
|
|
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
|
-
"
|
|
75
|
-
"mobile",
|
|
76
|
-
"web"
|
|
80
|
+
"mobile"
|
|
77
81
|
],
|
|
78
82
|
"repository": {
|
|
79
83
|
"type": "git",
|
|
@@ -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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
-
*
|
|
30
|
-
*
|
|
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
|
-
|
|
48
|
+
googleSilentSignIn(
|
|
49
|
+
androidAndIOSClientId: string,
|
|
50
|
+
options: Object
|
|
51
|
+
): Promise<Object>;
|
|
33
52
|
|
|
34
53
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
48
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
60
|
-
*
|
|
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
|
|
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
|
+
}
|