Skip to content

Commit 60a7a80

Browse files
committed
Convert app-check to provider pattern
1 parent c8ac613 commit 60a7a80

File tree

9 files changed

+215
-62
lines changed

9 files changed

+215
-62
lines changed

packages/app-check/src/api.test.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import * as client from './client';
3939
import * as storage from './storage';
4040
import * as logger from './logger';
4141
import * as util from './util';
42+
import { ReCAPTCHAProvider } from './providers';
4243

4344
describe('api', () => {
4445
beforeEach(() => {
@@ -53,34 +54,59 @@ describe('api', () => {
5354

5455
it('sets activated to true', () => {
5556
expect(getState(app).activated).to.equal(false);
56-
activate(app, FAKE_SITE_KEY);
57+
activate(
58+
app,
59+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
60+
getFakePlatformLoggingProvider()
61+
);
5762
expect(getState(app).activated).to.equal(true);
5863
});
5964

6065
it('isTokenAutoRefreshEnabled value defaults to global setting', () => {
6166
app = getFakeApp({ automaticDataCollectionEnabled: false });
62-
activate(app, FAKE_SITE_KEY);
67+
activate(
68+
app,
69+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
70+
getFakePlatformLoggingProvider()
71+
);
6372
expect(getState(app).isTokenAutoRefreshEnabled).to.equal(false);
6473
});
6574

6675
it('sets isTokenAutoRefreshEnabled correctly, overriding global setting', () => {
6776
app = getFakeApp({ automaticDataCollectionEnabled: false });
68-
activate(app, FAKE_SITE_KEY, true);
77+
activate(
78+
app,
79+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
80+
getFakePlatformLoggingProvider(),
81+
true
82+
);
6983
expect(getState(app).isTokenAutoRefreshEnabled).to.equal(true);
7084
});
7185

7286
it('can only be called once', () => {
73-
activate(app, FAKE_SITE_KEY);
74-
expect(() => activate(app, FAKE_SITE_KEY)).to.throw(
75-
/AppCheck can only be activated once/
87+
activate(
88+
app,
89+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
90+
getFakePlatformLoggingProvider()
7691
);
92+
expect(() =>
93+
activate(
94+
app,
95+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
96+
getFakePlatformLoggingProvider()
97+
)
98+
).to.throw(/AppCheck can only be activated once/);
7799
});
78100

79101
it('initialize reCAPTCHA when a sitekey is provided', () => {
80102
const initReCAPTCHAStub = stub(reCAPTCHA, 'initialize').returns(
81103
Promise.resolve({} as any)
82104
);
83-
activate(app, FAKE_SITE_KEY);
105+
activate(
106+
app,
107+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
108+
getFakePlatformLoggingProvider()
109+
);
84110
expect(initReCAPTCHAStub).to.have.been.calledWithExactly(
85111
app,
86112
FAKE_SITE_KEY
@@ -90,8 +116,8 @@ describe('api', () => {
90116
it('does NOT initialize reCAPTCHA when a custom token provider is provided', () => {
91117
const fakeCustomTokenProvider = getFakeCustomTokenProvider();
92118
const initReCAPTCHAStub = stub(reCAPTCHA, 'initialize');
93-
activate(app, fakeCustomTokenProvider);
94-
expect(getState(app).customProvider).to.equal(fakeCustomTokenProvider);
119+
activate(app, fakeCustomTokenProvider, getFakePlatformLoggingProvider());
120+
expect(getState(app).provider).to.equal(fakeCustomTokenProvider);
95121
expect(initReCAPTCHAStub).to.have.not.been.called;
96122
});
97123
});
@@ -149,7 +175,7 @@ describe('api', () => {
149175
});
150176
it('Listeners work when using top-level parameters pattern', async () => {
151177
const app = getFakeApp();
152-
activate(app, FAKE_SITE_KEY, false);
178+
activate(app, new ReCAPTCHAProvider(FAKE_SITE_KEY), false);
153179
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
154180
stub(client, 'exchangeToken').returns(
155181
Promise.resolve(fakeRecaptchaAppCheckToken)
@@ -193,7 +219,7 @@ describe('api', () => {
193219

194220
it('Listeners work when using Observer pattern', async () => {
195221
const app = getFakeApp();
196-
activate(app, FAKE_SITE_KEY, false);
222+
activate(app, new ReCAPTCHAProvider(FAKE_SITE_KEY), false);
197223
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
198224
stub(client, 'exchangeToken').returns(
199225
Promise.resolve(fakeRecaptchaAppCheckToken)
@@ -238,7 +264,7 @@ describe('api', () => {
238264
it('onError() catches token errors', async () => {
239265
stub(logger.logger, 'error');
240266
const app = getFakeApp();
241-
activate(app, FAKE_SITE_KEY, false);
267+
activate(app, new ReCAPTCHAProvider(FAKE_SITE_KEY), false);
242268
stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken));
243269
stub(client, 'exchangeToken').rejects('exchange error');
244270

packages/app-check/src/api.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import {
2020
AppCheckTokenResult
2121
} from '@firebase/app-check-types';
2222
import { FirebaseApp } from '@firebase/app-types';
23+
import { Provider } from '@firebase/component';
2324
import { ERROR_FACTORY, AppCheckError } from './errors';
25+
import { ReCAPTCHAProvider, ReCAPTCHAProviderInternal } from './providers';
2426
import { initialize as initializeRecaptcha } from './recaptcha';
2527
import { getState, setState, AppCheckState, ListenerType } from './state';
2628
import {
@@ -34,14 +36,15 @@ import { ErrorFn, NextFn, PartialObserver, Unsubscribe } from '@firebase/util';
3436
/**
3537
*
3638
* @param app
37-
* @param siteKeyOrProvider - optional custom attestation provider
38-
* or reCAPTCHA siteKey
39+
* @param provider - optional custom attestation provider
40+
* or reCAPTCHA provider
3941
* @param isTokenAutoRefreshEnabled - if true, enables auto refresh
4042
* of appCheck token.
4143
*/
4244
export function activate(
4345
app: FirebaseApp,
44-
siteKeyOrProvider: string | AppCheckProvider,
46+
provider: AppCheckProvider,
47+
platformLoggerProvider: Provider<'platform-logger'>,
4548
isTokenAutoRefreshEnabled?: boolean
4649
): void {
4750
const state = getState(app);
@@ -52,11 +55,7 @@ export function activate(
5255
}
5356

5457
const newState: AppCheckState = { ...state, activated: true };
55-
if (typeof siteKeyOrProvider === 'string') {
56-
newState.siteKey = siteKeyOrProvider;
57-
} else {
58-
newState.customProvider = siteKeyOrProvider;
59-
}
58+
newState.provider = provider;
6059

6160
// Use value of global `automaticDataCollectionEnabled` (which
6261
// itself defaults to false if not specified in config) if
@@ -68,9 +67,17 @@ export function activate(
6867

6968
setState(app, newState);
7069

71-
// initialize reCAPTCHA if siteKey is provided
72-
if (newState.siteKey) {
73-
initializeRecaptcha(app, newState.siteKey).catch(() => {
70+
// initialize reCAPTCHA if provider is a ReCAPTCHAProvider
71+
if (newState.provider instanceof ReCAPTCHAProvider) {
72+
// Wrap public ReCAPTCHAProvider in an internal class that provides
73+
// platform logging and app.
74+
const internalProvider = new ReCAPTCHAProviderInternal(
75+
app,
76+
newState.provider.siteKey,
77+
platformLoggerProvider
78+
);
79+
setState(app, { ...newState, provider: internalProvider });
80+
initializeRecaptcha(app, internalProvider.siteKey).catch(() => {
7481
/* we don't care about the initialization result in activate() */
7582
});
7683
}

packages/app-check/src/factory.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,15 @@ export function factory(
4646
return {
4747
app,
4848
activate: (
49-
siteKeyOrProvider: string | AppCheckProvider,
49+
provider: AppCheckProvider,
5050
isTokenAutoRefreshEnabled?: boolean
51-
) => activate(app, siteKeyOrProvider, isTokenAutoRefreshEnabled),
51+
) =>
52+
activate(
53+
app,
54+
provider,
55+
platformLoggerProvider,
56+
isTokenAutoRefreshEnabled
57+
),
5258
setTokenAutoRefreshEnabled: (isTokenAutoRefreshEnabled: boolean) =>
5359
setTokenAutoRefreshEnabled(app, isTokenAutoRefreshEnabled),
5460
getToken: forceRefresh =>

packages/app-check/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
AppCheckComponentName
2727
} from '@firebase/app-check-types';
2828
import { factory, internalFactory } from './factory';
29+
import { ReCAPTCHAProvider } from './providers';
2930
import { initializeDebugMode } from './debug';
3031
import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types';
3132
import { name, version } from '../package.json';
@@ -46,6 +47,9 @@ function registerAppCheck(firebase: _FirebaseNamespace): void {
4647
},
4748
ComponentType.PUBLIC
4849
)
50+
.setServiceProps({
51+
ReCAPTCHAProvider
52+
})
4953
/**
5054
* AppCheck can only be initialized by explicitly calling firebase.appCheck()
5155
* We don't want firebase products that consume AppCheck to gate on AppCheck

packages/app-check/src/internal-api.test.ts

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ import {
4848
ListenerType
4949
} from './state';
5050
import { Deferred } from '@firebase/util';
51-
import { AppCheckTokenResult } from '../../app-check-interop-types';
51+
import { ReCAPTCHAProvider } from './providers';
52+
import { AppCheckTokenResult } from '../../app-check-types';
5253

5354
const fakePlatformLoggingProvider = getFakePlatformLoggingProvider();
5455

@@ -89,7 +90,7 @@ describe('internal api', () => {
8990
const customTokenProvider = getFakeCustomTokenProvider();
9091
const customProviderSpy = spy(customTokenProvider, 'getToken');
9192

92-
activate(app, customTokenProvider);
93+
activate(app, customTokenProvider, fakePlatformLoggingProvider);
9394
const token = await getToken(app, fakePlatformLoggingProvider);
9495

9596
expect(customProviderSpy).to.be.called;
@@ -98,8 +99,12 @@ describe('internal api', () => {
9899
});
99100
});
100101

101-
it('uses reCAPTCHA token to exchange for AppCheck token if no customTokenProvider is provided', async () => {
102-
activate(app, FAKE_SITE_KEY);
102+
it('uses reCAPTCHA token to exchange for AppCheck token if ReCAPTCHAProvider is provided', async () => {
103+
activate(
104+
app,
105+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
106+
fakePlatformLoggingProvider
107+
);
103108

104109
const reCAPTCHASpy = stub(reCAPTCHA, 'getToken').resolves(
105110
fakeRecaptchaToken
@@ -121,7 +126,11 @@ describe('internal api', () => {
121126

122127
it('resolves with a dummy token and an error if failed to get a token', async () => {
123128
const errorStub = stub(console, 'error');
124-
activate(app, FAKE_SITE_KEY, true);
129+
activate(
130+
app,
131+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
132+
fakePlatformLoggingProvider
133+
);
125134

126135
const reCAPTCHASpy = stub(reCAPTCHA, 'getToken').resolves(
127136
fakeRecaptchaToken
@@ -144,7 +153,11 @@ describe('internal api', () => {
144153
});
145154

146155
it('notifies listeners using cached token', async () => {
147-
activate(app, FAKE_SITE_KEY, false);
156+
activate(
157+
app,
158+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
159+
fakePlatformLoggingProvider
160+
);
148161
storageReadStub.resolves(fakeCachedAppCheckToken);
149162

150163
const listener1 = spy();
@@ -173,7 +186,11 @@ describe('internal api', () => {
173186
});
174187

175188
it('notifies listeners using new token', async () => {
176-
activate(app, FAKE_SITE_KEY, false);
189+
activate(
190+
app,
191+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
192+
fakePlatformLoggingProvider
193+
);
177194

178195
stub(reCAPTCHA, 'getToken').resolves(fakeRecaptchaToken);
179196
stub(client, 'exchangeToken').resolves(fakeRecaptchaAppCheckToken);
@@ -205,7 +222,11 @@ describe('internal api', () => {
205222

206223
it('calls 3P error handler if there is an error getting a token', async () => {
207224
stub(logger.logger, 'error');
208-
activate(app, FAKE_SITE_KEY, false);
225+
activate(
226+
app,
227+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
228+
fakePlatformLoggingProvider
229+
);
209230
stub(reCAPTCHA, 'getToken').resolves(fakeRecaptchaToken);
210231
stub(client, 'exchangeToken').rejects('exchange error');
211232
const listener1 = spy();
@@ -227,7 +248,11 @@ describe('internal api', () => {
227248
});
228249

229250
it('ignores listeners that throw', async () => {
230-
activate(app, FAKE_SITE_KEY, false);
251+
activate(
252+
app,
253+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
254+
fakePlatformLoggingProvider
255+
);
231256
stub(reCAPTCHA, 'getToken').resolves(fakeRecaptchaToken);
232257
stub(client, 'exchangeToken').resolves(fakeRecaptchaAppCheckToken);
233258
const listener1 = stub().throws(new Error());
@@ -257,7 +282,11 @@ describe('internal api', () => {
257282
});
258283

259284
it('loads persisted token to memory and returns it', async () => {
260-
activate(app, FAKE_SITE_KEY);
285+
activate(
286+
app,
287+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
288+
fakePlatformLoggingProvider
289+
);
261290

262291
storageReadStub.resolves(fakeCachedAppCheckToken);
263292

@@ -273,7 +302,11 @@ describe('internal api', () => {
273302
});
274303

275304
it('persists token to storage', async () => {
276-
activate(app, FAKE_SITE_KEY, false);
305+
activate(
306+
app,
307+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
308+
fakePlatformLoggingProvider
309+
);
277310

278311
stub(reCAPTCHA, 'getToken').resolves(fakeRecaptchaToken);
279312
stub(client, 'exchangeToken').resolves(fakeRecaptchaAppCheckToken);
@@ -286,7 +319,11 @@ describe('internal api', () => {
286319
});
287320

288321
it('returns the valid token in memory without making network request', async () => {
289-
activate(app, FAKE_SITE_KEY);
322+
activate(
323+
app,
324+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
325+
fakePlatformLoggingProvider
326+
);
290327
setState(app, { ...getState(app), token: fakeRecaptchaAppCheckToken });
291328

292329
const clientStub = stub(client, 'exchangeToken');
@@ -298,7 +335,11 @@ describe('internal api', () => {
298335
});
299336

300337
it('force to get new token when forceRefresh is true', async () => {
301-
activate(app, FAKE_SITE_KEY);
338+
activate(
339+
app,
340+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
341+
fakePlatformLoggingProvider
342+
);
302343
setState(app, { ...getState(app), token: fakeRecaptchaAppCheckToken });
303344

304345
stub(reCAPTCHA, 'getToken').resolves(fakeRecaptchaToken);
@@ -320,7 +361,11 @@ describe('internal api', () => {
320361
debugState.enabled = true;
321362
debugState.token = new Deferred();
322363
debugState.token.resolve('my-debug-token');
323-
activate(app, FAKE_SITE_KEY);
364+
activate(
365+
app,
366+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
367+
fakePlatformLoggingProvider
368+
);
324369

325370
const token = await getToken(app, fakePlatformLoggingProvider);
326371
expect(exchangeTokenStub.args[0][0].body['debug_token']).to.equal(
@@ -397,7 +442,11 @@ describe('internal api', () => {
397442
});
398443

399444
it('notifies the listener with the valid token in storage', done => {
400-
activate(app, FAKE_SITE_KEY);
445+
activate(
446+
app,
447+
new ReCAPTCHAProvider(FAKE_SITE_KEY),
448+
fakePlatformLoggingProvider
449+
);
401450
storageReadStub.resolves({
402451
token: `fake-cached-app-check-token`,
403452
expireTimeMillis: Date.now() + 60000,

0 commit comments

Comments
 (0)