Skip to content

Commit 6766da2

Browse files
authored
Add initial bare-bones Cordova support / popup redirect (#4379)
* Add bare-bones Cordova configuration / popup-redirect resolver * Formatting * PR feedback * Formatting
1 parent 38caaad commit 6766da2

File tree

14 files changed

+447
-28
lines changed

14 files changed

+447
-28
lines changed
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @license
3+
* Copyright 2021 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+
/**
19+
* This is the file that people using Cordova will actually import. You
20+
* should only include this file if you have something specific about your
21+
* implementation that mandates having a separate entrypoint. Otherwise you can
22+
* just use index.ts
23+
*/
24+
25+
import { FirebaseApp } from '@firebase/app-types-exp';
26+
import { Auth } from '@firebase/auth-types-exp';
27+
import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db';
28+
29+
import { initializeAuth } from './src';
30+
import { registerAuth } from './src/core/auth/register';
31+
import { ClientPlatform } from './src/core/util/version';
32+
33+
// Core functionality shared by all clients
34+
export * from './src';
35+
36+
// Cordova also supports indexedDB / browserSession / browserLocal
37+
export { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db';
38+
export { browserLocalPersistence } from './src/platform_browser/persistence/local_storage';
39+
export { browserSessionPersistence } from './src/platform_browser/persistence/session_storage';
40+
41+
export { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect';
42+
export { signInWithRedirect } from './src/platform_cordova/strategies/redirect';
43+
44+
import { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect';
45+
46+
export function getAuth(app: FirebaseApp): Auth {
47+
return initializeAuth(app, {
48+
persistence: indexedDBLocalPersistence,
49+
popupRedirectResolver: cordovaPopupRedirectResolver
50+
});
51+
}
52+
53+
registerAuth(ClientPlatform.CORDOVA);

packages-exp/auth-exp/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"main": "dist/node/index.js",
88
"react-native": "dist/rn/index.js",
99
"browser": "dist/esm5/index.js",
10+
"cordova": "dist/cordova/index.esm5.js",
1011
"module": "dist/esm5/index.js",
1112
"esm2017": "dist/esm2017/index.js",
1213
"webworker": "dist/index.webworker.esm5.js",
@@ -26,7 +27,7 @@
2627
"test:browser:integration": "karma start --single-run --integration",
2728
"test:browser:debug": "karma start --auto-watch",
2829
"test:browser:unit:debug": "karma start --auto-watch --unit",
29-
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/!(platform_browser|platform_react_native)/**/*.test.ts' --file index.node.ts --config ../../config/mocharc.node.js",
30+
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/!(platform_browser|platform_react_native|platform_cordova)/**/*.test.ts' --file index.node.ts --config ../../config/mocharc.node.js",
3031
"api-report": "api-extractor run --local --verbose",
3132
"predoc": "node ../../scripts/exp/remove-exp.js temp",
3233
"doc": "api-documenter markdown --input temp --output docs",

packages-exp/auth-exp/src/core/auth/auth_impl.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,17 @@ export class AuthImpl implements Auth, _FirebaseService {
101101
persistenceHierarchy: Persistence[],
102102
popupRedirectResolver?: externs.PopupRedirectResolver
103103
): Promise<void> {
104+
if (popupRedirectResolver) {
105+
this._popupRedirectResolver = _getInstance(popupRedirectResolver);
106+
}
107+
104108
// Have to check for app deletion throughout initialization (after each
105109
// promise resolution)
106110
this._initializationPromise = this.queue(async () => {
107111
if (this._deleted) {
108112
return;
109113
}
110114

111-
if (popupRedirectResolver) {
112-
this._popupRedirectResolver = _getInstance(popupRedirectResolver);
113-
}
114-
115115
this.persistenceManager = await PersistenceUserManager.create(
116116
this,
117117
persistenceHierarchy

packages-exp/auth-exp/src/core/auth/register.ts

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ function getVersionForPlatform(
4141
return 'rn';
4242
case ClientPlatform.WORKER:
4343
return 'webworker';
44+
case ClientPlatform.CORDOVA:
45+
return 'cordova';
4446
default:
4547
return undefined;
4648
}

packages-exp/auth-exp/src/core/errors.ts

+4
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,10 @@ export interface AuthErrorParams extends GenericAuthErrorParams {
402402
appName: AppName;
403403
serverResponse: IdTokenMfaResponse;
404404
};
405+
[AuthErrorCode.INVALID_CORDOVA_CONFIGURATION]: {
406+
appName: AppName;
407+
missingPlugin?: string;
408+
};
405409
}
406410

407411
export const _DEFAULT_AUTH_ERROR_FACTORY = new ErrorFactory<
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* @license
3+
* Copyright 2021 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+
import * as externs from '@firebase/auth-types-exp';
19+
import { Auth } from '../../model/auth';
20+
import { PopupRedirectResolver } from '../../model/popup_redirect';
21+
import { AuthErrorCode } from '../errors';
22+
import { _assert } from './assert';
23+
import { _getInstance } from './instantiator';
24+
25+
/**
26+
* Chooses a popup/redirect resolver to use. This prefers the override (which
27+
* is directly passed in), and falls back to the property set on the auth
28+
* object. If neither are available, this function errors w/ an argument error.
29+
*
30+
* @internal
31+
*/
32+
export function _withDefaultResolver(
33+
auth: Auth,
34+
resolverOverride: externs.PopupRedirectResolver | undefined
35+
): PopupRedirectResolver {
36+
if (resolverOverride) {
37+
return _getInstance(resolverOverride);
38+
}
39+
40+
_assert(auth._popupRedirectResolver, auth, AuthErrorCode.ARGUMENT_ERROR);
41+
42+
return auth._popupRedirectResolver;
43+
}

packages-exp/auth-exp/src/core/util/version.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const enum ClientPlatform {
2727
BROWSER = 'Browser',
2828
NODE = 'Node',
2929
REACT_NATIVE = 'ReactNative',
30+
CORDOVA = 'Cordova',
3031
WORKER = 'Worker'
3132
}
3233

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

-21
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import { SDK_VERSION } from '@firebase/app-exp';
1919
import * as externs from '@firebase/auth-types-exp';
2020
import { isEmpty, querystring } from '@firebase/util';
21-
import { _getInstance } from '../core/util/instantiator';
2221

2322
import { AuthEventManager } from '../core/auth/auth_event_manager';
2423
import { AuthErrorCode } from '../core/errors';
@@ -72,26 +71,6 @@ interface ManagerOrPromise {
7271
promise?: Promise<EventManager>;
7372
}
7473

75-
/**
76-
* Chooses a popup/redirect resolver to use. This prefers the override (which
77-
* is directly passed in), and falls back to the property set on the auth
78-
* object. If neither are available, this function errors w/ an argument error.
79-
*
80-
* @internal
81-
*/
82-
export function _withDefaultResolver(
83-
auth: Auth,
84-
resolverOverride: externs.PopupRedirectResolver | undefined
85-
): PopupRedirectResolver {
86-
if (resolverOverride) {
87-
return _getInstance(resolverOverride);
88-
}
89-
90-
_assert(auth._popupRedirectResolver, auth, AuthErrorCode.ARGUMENT_ERROR);
91-
92-
return auth._popupRedirectResolver;
93-
}
94-
9574
class BrowserPopupRedirectResolver implements PopupRedirectResolver {
9675
private readonly eventManagers: Record<string, ManagerOrPromise> = {};
9776
private readonly iframes: Record<string, gapi.iframes.Iframe> = {};

packages-exp/auth-exp/src/platform_browser/strategies/popup.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
PopupRedirectResolver
3030
} from '../../model/popup_redirect';
3131
import { User } from '../../model/user';
32-
import { _withDefaultResolver } from '../popup_redirect';
32+
import { _withDefaultResolver } from '../../core/util/resolver';
3333
import { AuthPopup } from '../util/popup';
3434
import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation';
3535

packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
PopupRedirectResolver
3131
} from '../../model/popup_redirect';
3232
import { User, UserCredential } from '../../model/user';
33-
import { _withDefaultResolver } from '../popup_redirect';
33+
import { _withDefaultResolver } from '../../core/util/resolver';
3434
import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation';
3535

3636
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @license
3+
* Copyright 2021 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+
// For some reason, the linter doesn't recognize that these are used elsewhere
19+
// in the SDK
20+
/* eslint-disable @typescript-eslint/no-unused-vars */
21+
22+
declare namespace cordova.plugins.browsertab {
23+
function isAvailable(cb: (available: boolean) => void): void;
24+
function openUrl(url: string): void;
25+
}
26+
27+
declare namespace cordova.InAppBrowser {
28+
function open(url: string, target: string, options: string): unknown;
29+
}
30+
31+
declare namespace universalLinks {
32+
function subscribe(n: null, cb: (event: unknown) => void): void;
33+
}
34+
35+
declare namespace BuildInfo {
36+
const packageName: string;
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* @license
3+
* Copyright 2021 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+
import * as chaiAsPromised from 'chai-as-promised';
19+
import * as sinonChai from 'sinon-chai';
20+
import { expect, use } from 'chai';
21+
import { testAuth, TestAuth } from '../../test/helpers/mock_auth';
22+
import { SingletonInstantiator } from '../core/util/instantiator';
23+
import { AuthEventType, PopupRedirectResolver } from '../model/popup_redirect';
24+
import { cordovaPopupRedirectResolver } from './popup_redirect';
25+
import { GoogleAuthProvider } from '../core/providers/google';
26+
import { FirebaseError } from '@firebase/util';
27+
28+
use(chaiAsPromised);
29+
use(sinonChai);
30+
31+
describe('platform_cordova/popup_redirect', () => {
32+
let auth: TestAuth;
33+
let resolver: PopupRedirectResolver;
34+
35+
beforeEach(async () => {
36+
auth = await testAuth();
37+
attachExpectedPlugins();
38+
resolver = new (cordovaPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolver>)();
39+
});
40+
41+
describe('_openRedirect plugin checks', () => {
42+
// TODO: Rest of the tests go here
43+
it('does not reject if all plugins installed', () => {
44+
expect(() =>
45+
resolver._openRedirect(
46+
auth,
47+
new GoogleAuthProvider(),
48+
AuthEventType.SIGN_IN_VIA_REDIRECT
49+
)
50+
).not.to.throw;
51+
});
52+
53+
it('rejects if universal links is missing', () => {
54+
removeProp(window, 'universalLinks');
55+
expect(() =>
56+
resolver._openRedirect(
57+
auth,
58+
new GoogleAuthProvider(),
59+
AuthEventType.SIGN_IN_VIA_REDIRECT
60+
)
61+
)
62+
.to.throw(FirebaseError, 'auth/invalid-cordova-configuration')
63+
.that.has.deep.property('customData', {
64+
appName: 'test-app',
65+
missingPlugin: 'cordova-universal-links-plugin-fix'
66+
});
67+
});
68+
69+
it('rejects if build info is missing', () => {
70+
removeProp(window.BuildInfo, 'packageName');
71+
expect(() =>
72+
resolver._openRedirect(
73+
auth,
74+
new GoogleAuthProvider(),
75+
AuthEventType.SIGN_IN_VIA_REDIRECT
76+
)
77+
)
78+
.to.throw(FirebaseError, 'auth/invalid-cordova-configuration')
79+
.that.has.deep.property('customData', {
80+
appName: 'test-app',
81+
missingPlugin: 'cordova-plugin-buildInfo'
82+
});
83+
});
84+
85+
it('rejects if browsertab openUrl is missing', () => {
86+
removeProp(window.cordova.plugins.browsertab, 'openUrl');
87+
expect(() =>
88+
resolver._openRedirect(
89+
auth,
90+
new GoogleAuthProvider(),
91+
AuthEventType.SIGN_IN_VIA_REDIRECT
92+
)
93+
)
94+
.to.throw(FirebaseError, 'auth/invalid-cordova-configuration')
95+
.that.has.deep.property('customData', {
96+
appName: 'test-app',
97+
missingPlugin: 'cordova-plugin-browsertab'
98+
});
99+
});
100+
101+
it('rejects if InAppBrowser is missing', () => {
102+
removeProp(window.cordova.InAppBrowser, 'open');
103+
expect(() =>
104+
resolver._openRedirect(
105+
auth,
106+
new GoogleAuthProvider(),
107+
AuthEventType.SIGN_IN_VIA_REDIRECT
108+
)
109+
)
110+
.to.throw(FirebaseError, 'auth/invalid-cordova-configuration')
111+
.that.has.deep.property('customData', {
112+
appName: 'test-app',
113+
missingPlugin: 'cordova-plugin-inappbrowser'
114+
});
115+
});
116+
});
117+
});
118+
119+
function attachExpectedPlugins(): void {
120+
// Eventually these will be replaced with full mocks
121+
const win = (window as unknown) as Record<string, unknown>;
122+
win.cordova = {
123+
plugins: {
124+
browsertab: {
125+
isAvailable: () => {},
126+
openUrl: () => {}
127+
}
128+
},
129+
InAppBrowser: {
130+
open: () => {}
131+
}
132+
};
133+
win.universalLinks = {
134+
subscribe: () => {}
135+
};
136+
win.BuildInfo = {
137+
packageName: 'com.example.name.package'
138+
};
139+
}
140+
141+
function removeProp(obj: unknown, prop: string): void {
142+
delete (obj as Record<string, unknown>)[prop];
143+
}

0 commit comments

Comments
 (0)