Skip to content

Commit 0ffd3e2

Browse files
committed
Fix merge conflict
2 parents 0af1006 + 7b585e8 commit 0ffd3e2

File tree

27 files changed

+916
-76
lines changed

27 files changed

+916
-76
lines changed

.changeset/cyan-wasps-worry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/util": patch
3+
---
4+
5+
Added a utility function and type for compat interop API

packages-exp/auth-compat-exp/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"test:browser:integration": "karma start --single-run --integration",
2626
"test:node": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts",
2727
"test:node:integration": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --integration",
28-
"test:integration": "run-s test:browser:integration test:node:integration"
28+
"test:webdriver": "rollup -c test/integration/webdriver/static/rollup.config.js && ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --webdriver",
29+
"test:integration": "run-s test:browser:integration test:node:integration test:webdriver"
2930
},
3031
"peerDependencies": {
3132
"@firebase/app-compat": "0.x"
@@ -41,8 +42,8 @@
4142
"license": "Apache-2.0",
4243
"devDependencies": {
4344
"@firebase/app-compat": "0.x",
44-
"rollup": "2.35.1",
4545
"@rollup/plugin-json": "4.1.0",
46+
"rollup": "^2.35.1",
4647
"rollup-plugin-replace": "2.2.0",
4748
"rollup-plugin-typescript2": "0.29.0",
4849
"typescript": "4.2.2"

packages-exp/auth-compat-exp/scripts/run_node_tests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ const nyc = resolve(__dirname, '../../../node_modules/.bin/nyc');
3333
const mocha = resolve(__dirname, '../../../node_modules/.bin/mocha');
3434

3535
process.env.TS_NODE_COMPILER_OPTIONS = '{"module":"commonjs", "target": "es6"}';
36+
process.env.COMPAT_LAYER = 'true';
3637

3738
let testConfig = ['src/**/*.test.ts'];
3839

3940
if (argv.integration) {
4041
testConfig = ['test/integration/flows/**.test.ts'];
4142
} else if (argv.webdriver) {
42-
testConfig = ['test/integration/webdriver/**.test.ts', '--delay'];
43+
testConfig = ['../auth-exp/test/integration/webdriver/*.test.ts', '--delay'];
4344
}
4445

4546
let args = [

packages-exp/auth-compat-exp/src/auth.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,18 @@ import {
3434
convertConfirmationResult,
3535
convertCredential
3636
} from './user_credential';
37+
import { ReverseWrapper, unwrap, Wrapper } from './wrap';
3738

3839
const _assert: typeof exp._assert = exp._assert;
3940

40-
export class Auth implements compat.FirebaseAuth, _FirebaseService {
41+
export class Auth
42+
implements compat.FirebaseAuth, Wrapper<exp.Auth>, _FirebaseService {
4143
readonly _delegate: exp.AuthImpl;
4244

4345
constructor(readonly app: FirebaseApp, provider: Provider<'auth-exp'>) {
4446
if (provider.isInitialized()) {
4547
this._delegate = provider.getImmediate() as exp.AuthImpl;
48+
this.linkUnderlyingAuth();
4649
return;
4750
}
4851

@@ -87,6 +90,7 @@ export class Auth implements compat.FirebaseAuth, _FirebaseService {
8790
}) as exp.AuthImpl;
8891

8992
this._delegate._updateErrorMap(exp.debugErrorMap);
93+
this.linkUnderlyingAuth();
9094
}
9195

9296
get emulatorConfig(): compat.EmulatorConfig | null {
@@ -215,7 +219,9 @@ export class Auth implements compat.FirebaseAuth, _FirebaseService {
215219
break;
216220
case Persistence.LOCAL:
217221
// Not using isIndexedDBAvailable() since it only checks if indexedDB is defined.
218-
const isIndexedDBFullySupported = await (exp.indexedDBLocalPersistence as exp.PersistenceInternal)._isAvailable();
222+
const isIndexedDBFullySupported = await exp
223+
._getInstance<exp.PersistenceInternal>(exp.indexedDBLocalPersistence)
224+
._isAvailable();
219225
converted = isIndexedDBFullySupported
220226
? exp.indexedDBLocalPersistence
221227
: exp.browserLocalPersistence;
@@ -284,7 +290,7 @@ export class Auth implements compat.FirebaseAuth, _FirebaseService {
284290
exp.signInWithPhoneNumber(
285291
this._delegate,
286292
phoneNumber,
287-
applicationVerifier
293+
unwrap(applicationVerifier)
288294
)
289295
);
290296
}
@@ -322,7 +328,7 @@ export class Auth implements compat.FirebaseAuth, _FirebaseService {
322328
updateCurrentUser(user: compat.User | null): Promise<void> {
323329
// remove ts-ignore once overloads are defined for exp functions to accept compat objects
324330
// @ts-ignore
325-
return this._delegate.updateCurrentUser(user);
331+
return this._delegate.updateCurrentUser(unwrap(user));
326332
}
327333
verifyPasswordResetCode(code: string): Promise<string> {
328334
return exp.verifyPasswordResetCode(this._delegate, code);
@@ -333,6 +339,9 @@ export class Auth implements compat.FirebaseAuth, _FirebaseService {
333339
_delete(): Promise<void> {
334340
return this._delegate._delete();
335341
}
342+
private linkUnderlyingAuth(): void {
343+
((this._delegate as unknown) as ReverseWrapper<Auth>).wrapped = () => this;
344+
}
336345
}
337346

338347
function wrapObservers(

packages-exp/auth-compat-exp/src/popup_redirect.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,17 @@ export class CompatPopupRedirectResolver
3939
) => Promise<exp.UserCredential | null> = exp._getRedirectResult;
4040

4141
async _initialize(auth: exp.AuthImpl): Promise<exp.EventManager> {
42-
if (this.underlyingResolver) {
43-
return this.underlyingResolver._initialize(auth);
44-
}
45-
46-
// We haven't yet determined whether or not we're in Cordova; go ahead
47-
// and determine that state now.
48-
const isCordova = await _isCordova();
49-
this.underlyingResolver = isCordova ? CORDOVA_RESOLVER : BROWSER_RESOLVER;
42+
await this.selectUnderlyingResolver();
5043
return this.assertedUnderlyingResolver._initialize(auth);
5144
}
5245

53-
_openPopup(
46+
async _openPopup(
5447
auth: exp.AuthImpl,
5548
provider: exp.AuthProvider,
5649
authType: exp.AuthEventType,
5750
eventId?: string
5851
): Promise<exp.AuthPopup> {
52+
await this.selectUnderlyingResolver();
5953
return this.assertedUnderlyingResolver._openPopup(
6054
auth,
6155
provider,
@@ -64,12 +58,13 @@ export class CompatPopupRedirectResolver
6458
);
6559
}
6660

67-
_openRedirect(
61+
async _openRedirect(
6862
auth: exp.AuthImpl,
6963
provider: exp.AuthProvider,
7064
authType: exp.AuthEventType,
7165
eventId?: string
7266
): Promise<void> {
67+
await this.selectUnderlyingResolver();
7368
return this.assertedUnderlyingResolver._openRedirect(
7469
auth,
7570
provider,
@@ -97,4 +92,15 @@ export class CompatPopupRedirectResolver
9792
_assert(this.underlyingResolver, exp.AuthErrorCode.INTERNAL_ERROR);
9893
return this.underlyingResolver;
9994
}
95+
96+
private async selectUnderlyingResolver(): Promise<void> {
97+
if (this.underlyingResolver) {
98+
return;
99+
}
100+
101+
// We haven't yet determined whether or not we're in Cordova; go ahead
102+
// and determine that state now.
103+
const isCordova = await _isCordova();
104+
this.underlyingResolver = isCordova ? CORDOVA_RESOLVER : BROWSER_RESOLVER;
105+
}
100106
}

packages-exp/auth-compat-exp/src/user_credential.ts

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,84 @@
1717

1818
import * as exp from '@firebase/auth-exp/internal';
1919
import * as compat from '@firebase/auth-types';
20+
import { FirebaseError } from '@firebase/util';
21+
import { Auth } from './auth';
2022
import { User } from './user';
23+
import { unwrap, wrapped } from './wrap';
2124

2225
function credentialFromResponse(
2326
userCredential: exp.UserCredentialInternal
2427
): exp.AuthCredential | null {
25-
const { providerId, _tokenResponse } = userCredential;
28+
return credentialFromObject(userCredential);
29+
}
30+
31+
function attachExtraErrorFields(auth: exp.Auth, e: FirebaseError): void {
32+
// The response contains all fields from the server which may or may not
33+
// actually match the underlying type
34+
const response = ((e.customData as exp.TaggedWithTokenResponse | undefined)
35+
?._tokenResponse as unknown) as Record<string, string>;
36+
if (e.code === 'auth/multi-factor-auth-required') {
37+
const mfaErr = e as compat.MultiFactorError;
38+
mfaErr.resolver = new MultiFactorResolver(
39+
auth,
40+
exp.getMultiFactorResolver(auth, e as exp.MultiFactorError)
41+
);
42+
} else if (response) {
43+
const credential = credentialFromObject(e);
44+
const credErr = e as compat.AuthError;
45+
if (credential) {
46+
credErr.credential = credential;
47+
credErr.tenantId = response.tenantId || undefined;
48+
credErr.email = response.email || undefined;
49+
credErr.phoneNumber = response.phoneNumber || undefined;
50+
}
51+
}
52+
}
53+
54+
function credentialFromObject(
55+
object: FirebaseError | exp.UserCredential
56+
): exp.AuthCredential | null {
57+
const { _tokenResponse } = (object instanceof FirebaseError
58+
? object.customData
59+
: object) as exp.TaggedWithTokenResponse;
2660
if (!_tokenResponse) {
2761
return null;
2862
}
63+
2964
// Handle phone Auth credential responses, as they have a different format
30-
// from other backend responses (i.e. no providerId).
31-
if ('temporaryProof' in _tokenResponse && 'phoneNumber' in _tokenResponse) {
32-
return exp.PhoneAuthProvider.credentialFromResult(userCredential);
65+
// from other backend responses (i.e. no providerId). This is also only the
66+
// case for user credentials (does not work for errors).
67+
if (!(object instanceof FirebaseError)) {
68+
if ('temporaryProof' in _tokenResponse && 'phoneNumber' in _tokenResponse) {
69+
return exp.PhoneAuthProvider.credentialFromResult(object);
70+
}
3371
}
72+
73+
const providerId = _tokenResponse.providerId;
74+
3475
// Email and password is not supported as there is no situation where the
3576
// server would return the password to the client.
3677
if (!providerId || providerId === exp.ProviderId.PASSWORD) {
3778
return null;
3879
}
3980

81+
let provider: Pick<
82+
typeof exp.OAuthProvider,
83+
'credentialFromResult' | 'credentialFromError'
84+
>;
4085
switch (providerId) {
4186
case exp.ProviderId.GOOGLE:
42-
return exp.GoogleAuthProvider.credentialFromResult(userCredential);
87+
provider = exp.GoogleAuthProvider;
88+
break;
4389
case exp.ProviderId.FACEBOOK:
44-
return exp.FacebookAuthProvider.credentialFromResult(userCredential!);
90+
provider = exp.FacebookAuthProvider;
91+
break;
4592
case exp.ProviderId.GITHUB:
46-
return exp.GithubAuthProvider.credentialFromResult(userCredential!);
93+
provider = exp.GithubAuthProvider;
94+
break;
4795
case exp.ProviderId.TWITTER:
48-
return exp.TwitterAuthProvider.credentialFromResult(userCredential);
96+
provider = exp.TwitterAuthProvider;
97+
break;
4998
default:
5099
const {
51100
oauthIdToken,
@@ -63,27 +112,30 @@ function credentialFromResponse(
63112
return null;
64113
}
65114
// TODO(avolkovi): uncomment this and get it working with SAML & OIDC
66-
// if (pendingToken) {
67-
// if (providerId.indexOf(compat.constants.SAML_PREFIX) == 0) {
68-
// return new impl.SAMLAuthCredential(providerId, pendingToken);
69-
// } else {
70-
// // OIDC and non-default providers excluding Twitter.
71-
// return new impl.OAuthCredential(
72-
// providerId,
73-
// {
74-
// pendingToken,
75-
// idToken: oauthIdToken,
76-
// accessToken: oauthAccessToken
77-
// },
78-
// providerId);
79-
// }
80-
// }
115+
if (pendingToken) {
116+
if (providerId.startsWith('saml.')) {
117+
return exp.SAMLAuthCredential._create(providerId, pendingToken);
118+
} else {
119+
// OIDC and non-default providers excluding Twitter.
120+
return exp.OAuthCredential._fromParams({
121+
providerId,
122+
signInMethod: providerId,
123+
pendingToken,
124+
idToken: oauthIdToken,
125+
accessToken: oauthAccessToken
126+
});
127+
}
128+
}
81129
return new exp.OAuthProvider(providerId).credential({
82130
idToken: oauthIdToken,
83131
accessToken: oauthAccessToken,
84132
rawNonce: nonce
85133
});
86134
}
135+
136+
return object instanceof FirebaseError
137+
? provider.credentialFromError(object)
138+
: provider.credentialFromResult(object);
87139
}
88140

89141
export async function convertCredential(
@@ -94,12 +146,12 @@ export async function convertCredential(
94146
try {
95147
credential = await credentialPromise;
96148
} catch (e) {
97-
if (e.code === 'auth/multi-factor-auth-required') {
98-
e.resolver = exp.getMultiFactorResolver(auth, e);
149+
if (e instanceof FirebaseError) {
150+
attachExtraErrorFields(auth, e);
99151
}
100152
throw e;
101153
}
102-
const { operationType, user } = await credential;
154+
const { operationType, user } = credential;
103155

104156
return {
105157
operationType,
@@ -124,3 +176,30 @@ export async function convertConfirmationResult(
124176
convertCredential(auth, confirmationResultExp.confirm(verificationCode))
125177
};
126178
}
179+
180+
class MultiFactorResolver implements compat.MultiFactorResolver {
181+
readonly auth: Auth;
182+
constructor(
183+
auth: exp.Auth,
184+
private readonly resolver: exp.MultiFactorResolver
185+
) {
186+
this.auth = wrapped(auth);
187+
}
188+
189+
get session(): compat.MultiFactorSession {
190+
return this.resolver.session;
191+
}
192+
193+
get hints(): compat.MultiFactorInfo[] {
194+
return this.resolver.hints;
195+
}
196+
197+
resolveSignIn(
198+
assertion: compat.MultiFactorAssertion
199+
): Promise<compat.UserCredential> {
200+
return convertCredential(
201+
unwrap(this.auth),
202+
this.resolver.resolveSignIn(assertion as exp.MultiFactorAssertion)
203+
);
204+
}
205+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/** Forward direction wrapper from Compat --unwrap-> Exp */
19+
export interface Wrapper<T> {
20+
unwrap(): T;
21+
}
22+
23+
/** Reverse direction wrapper from Exp --wrapped--> Compat */
24+
export interface ReverseWrapper<T> {
25+
wrapped(): T;
26+
}
27+
28+
export function unwrap<T>(object: unknown): T {
29+
return (object as Wrapper<T>).unwrap();
30+
}
31+
32+
export function wrapped<T>(object: unknown): T {
33+
return (object as ReverseWrapper<T>).wrapped();
34+
}

0 commit comments

Comments
 (0)