Skip to content

Commit 52c4bed

Browse files
Merge master into release
2 parents 60ff98d + 23581c5 commit 52c4bed

File tree

16 files changed

+144
-23
lines changed

16 files changed

+144
-23
lines changed

.changeset/quiet-cycles-complain.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/webchannel-wrapper': patch
3+
---
4+
5+
Fix the new `experimentalLongPollingOptions.timeoutSeconds` setting, which was released in v9.22.0 but didn't work.

.changeset/rude-adults-rest.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@firebase/app-check-interop-types': minor
3+
'@firebase/app-check': minor
4+
'@firebase/functions': minor
5+
---
6+
7+
Add support for App Check replay protection in callable functions

common/api-review/functions.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function httpsCallableFromURL<RequestData = unknown, ResponseData = unkno
4343

4444
// @public
4545
export interface HttpsCallableOptions {
46+
limitedUseAppCheckTokens?: boolean;
4647
timeout?: number;
4748
}
4849

config/functions/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ exports.instanceIdTest = functions.https.onRequest((request, response) => {
7676
});
7777
});
7878

79+
exports.appCheckTest = functions.https.onRequest((request, response) => {
80+
cors(request, response, () => {
81+
const token = request.get('X-Firebase-AppCheck');
82+
assert.equal(token !== undefined, true);
83+
assert.deepEqual(request.body, { data: {} });
84+
response.send({ data: { token } });
85+
});
86+
});
87+
7988
exports.nullTest = functions.https.onRequest((request, response) => {
8089
cors(request, response, () => {
8190
assert.deepEqual(request.body, { data: null });

docs-devsite/firestore_.firestoresettings.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export declare interface FirestoreSettings
2323
| Property | Type | Description |
2424
| --- | --- | --- |
2525
| [cacheSizeBytes](./firestore_.firestoresettings.md#firestoresettingscachesizebytes) | number | NOTE: This field will be deprecated in a future major release. Use <code>cache</code> field instead to specify cache size, and other cache configurations.<!-- -->An approximate cache size threshold for the on-disk data. If the cache grows beyond this size, Firestore will start removing data that hasn't been recently used. The size is not a guarantee that the cache will stay below that size, only that if the cache exceeds the given size, cleanup will be attempted.<!-- -->The default value is 40 MB. The threshold must be set to at least 1 MB, and can be set to <code>CACHE_SIZE_UNLIMITED</code> to disable garbage collection. |
26-
| [experimentalAutoDetectLongPolling](./firestore_.firestoresettings.md#firestoresettingsexperimentalautodetectlongpolling) | boolean | Configures the SDK's underlying transport (WebChannel) to automatically detect if long-polling should be used. This is very similar to <code>experimentalForceLongPolling</code>, but only uses long-polling if required.<!-- -->After having had a default value of <code>false</code> since its inception in 2019, the default value of this setting was changed in mid-2023 to <code>true</code>. That is, auto-detection of long polling is now enabled by default. To disable it, set this setting to <code>false</code>, and please open a GitHub issue to share the problems that motivated you disabling long-polling auto-detection. |
26+
| [experimentalAutoDetectLongPolling](./firestore_.firestoresettings.md#firestoresettingsexperimentalautodetectlongpolling) | boolean | Configures the SDK's underlying transport (WebChannel) to automatically detect if long-polling should be used. This is very similar to <code>experimentalForceLongPolling</code>, but only uses long-polling if required.<!-- -->After having had a default value of <code>false</code> since its inception in 2019, the default value of this setting was changed in May 2023 to <code>true</code> in v9.22.0 of the Firebase JavaScript SDK. That is, auto-detection of long polling is now enabled by default. To disable it, set this setting to <code>false</code>, and please open a GitHub issue to share the problems that motivated you disabling long-polling auto-detection. |
2727
| [experimentalForceLongPolling](./firestore_.firestoresettings.md#firestoresettingsexperimentalforcelongpolling) | boolean | Forces the SDKs underlying network transport (WebChannel) to use long-polling. Each response from the backend will be closed immediately after the backend sends data (by default responses are kept open in case the backend has more data to send). This avoids incompatibility issues with certain proxies, antivirus software, etc. that incorrectly buffer traffic indefinitely. Use of this option will cause some performance degradation though.<!-- -->This setting cannot be used with <code>experimentalAutoDetectLongPolling</code> and may be removed in a future release. If you find yourself using it to work around a specific network reliability issue, please tell us about it in https://github.com/firebase/firebase-js-sdk/issues/1674. |
2828
| [experimentalLongPollingOptions](./firestore_.firestoresettings.md#firestoresettingsexperimentallongpollingoptions) | [ExperimentalLongPollingOptions](./firestore_.experimentallongpollingoptions.md#experimentallongpollingoptions_interface) | Options that configure the SDKs underlying network transport (WebChannel) when long-polling is used.<!-- -->These options are only used if <code>experimentalForceLongPolling</code> is true or if <code>experimentalAutoDetectLongPolling</code> is true and the auto-detection determined that long-polling was needed. Otherwise, these options have no effect. |
2929
| [host](./firestore_.firestoresettings.md#firestoresettingshost) | string | The hostname to connect to. |
@@ -49,7 +49,7 @@ cacheSizeBytes?: number;
4949

5050
Configures the SDK's underlying transport (WebChannel) to automatically detect if long-polling should be used. This is very similar to `experimentalForceLongPolling`<!-- -->, but only uses long-polling if required.
5151

52-
After having had a default value of `false` since its inception in 2019, the default value of this setting was changed in mid-2023 to `true`<!-- -->. That is, auto-detection of long polling is now enabled by default. To disable it, set this setting to `false`<!-- -->, and please open a GitHub issue to share the problems that motivated you disabling long-polling auto-detection.
52+
After having had a default value of `false` since its inception in 2019, the default value of this setting was changed in May 2023 to `true` in v9.22.0 of the Firebase JavaScript SDK. That is, auto-detection of long polling is now enabled by default. To disable it, set this setting to `false`<!-- -->, and please open a GitHub issue to share the problems that motivated you disabling long-polling auto-detection.
5353

5454
<b>Signature:</b>
5555

docs-devsite/functions.httpscallableoptions.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,19 @@ export interface HttpsCallableOptions
2222

2323
| Property | Type | Description |
2424
| --- | --- | --- |
25+
| [limitedUseAppCheckTokens](./functions.httpscallableoptions.md#httpscallableoptionslimiteduseappchecktokens) | boolean | If set to true, uses limited-use App Check token for callable function requests from this instance of [Functions](./functions.functions.md#functions_interface)<!-- -->. You must use limited-use tokens to call functions with replay protection enabled. By default, this is false. |
2526
| [timeout](./functions.httpscallableoptions.md#httpscallableoptionstimeout) | number | Time in milliseconds after which to cancel if there is no response. Default is 70000. |
2627

28+
## HttpsCallableOptions.limitedUseAppCheckTokens
29+
30+
If set to true, uses limited-use App Check token for callable function requests from this instance of [Functions](./functions.functions.md#functions_interface)<!-- -->. You must use limited-use tokens to call functions with replay protection enabled. By default, this is false.
31+
32+
<b>Signature:</b>
33+
34+
```typescript
35+
limitedUseAppCheckTokens?: boolean;
36+
```
37+
2738
## HttpsCallableOptions.timeout
2839

2940
Time in milliseconds after which to cancel if there is no response. Default is 70000.

packages/app-check-interop-types/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export interface FirebaseAppCheckInternal {
2020
// is present. Returns null if no token is present and no token requests are in-flight.
2121
getToken(forceRefresh?: boolean): Promise<AppCheckTokenResult>;
2222

23+
// Always returns a fresh limited-use token suitable for Replay Protection.
24+
// The returned token must be used and consumed as soon as possible.
25+
getLimitedUseToken(): Promise<AppCheckTokenResult>;
26+
2327
// Registers a listener to changes in the token state. There can be more than one listener
2428
// registered at the same time for one or more FirebaseAppAttestation instances. The
2529
// listeners call back on the UI thread whenever the current token associated with this

packages/app-check/src/factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { FirebaseApp, _FirebaseService } from '@firebase/app';
2020
import { FirebaseAppCheckInternal, ListenerType } from './types';
2121
import {
2222
getToken,
23+
getLimitedUseToken,
2324
addTokenListener,
2425
removeTokenListener
2526
} from './internal-api';
@@ -55,6 +56,7 @@ export function internalFactory(
5556
): FirebaseAppCheckInternal {
5657
return {
5758
getToken: forceRefresh => getToken(appCheck, forceRefresh),
59+
getLimitedUseToken: () => getLimitedUseToken(appCheck),
5860
addTokenListener: listener =>
5961
addTokenListener(appCheck, ListenerType.INTERNAL, listener),
6062
removeTokenListener: listener => removeTokenListener(appCheck.app, listener)

packages/app-check/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export interface FirebaseAppCheckInternal {
2424
// is present. Returns null if no token is present and no token requests are in-flight.
2525
getToken(forceRefresh?: boolean): Promise<AppCheckTokenResult>;
2626

27+
// Get a Limited use Firebase App Check token. This method should be used
28+
// only if you need to authorize requests to a non-Firebase backend. Returns null if no token is present and no token requests are in-flight.
29+
getLimitedUseToken(): Promise<AppCheckTokenResult>;
30+
2731
// Registers a listener to changes in the token state. There can be more than one listener
2832
// registered at the same time for one or more FirebaseAppAttestation instances. The
2933
// listeners call back on the UI thread whenever the current token associated with this

packages/firestore/src/api/settings.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,11 @@ export interface FirestoreSettings extends LiteSettings {
9090
* `experimentalForceLongPolling`, but only uses long-polling if required.
9191
*
9292
* After having had a default value of `false` since its inception in 2019,
93-
* the default value of this setting was changed in mid-2023 to `true`. That
94-
* is, auto-detection of long polling is now enabled by default. To disable
95-
* it, set this setting to `false`, and please open a GitHub issue to share
96-
* the problems that motivated you disabling long-polling auto-detection.
93+
* the default value of this setting was changed in May 2023 to `true` in
94+
* v9.22.0 of the Firebase JavaScript SDK. That is, auto-detection of long
95+
* polling is now enabled by default. To disable it, set this setting to
96+
* `false`, and please open a GitHub issue to share the problems that
97+
* motivated you disabling long-polling auto-detection.
9798
*/
9899
experimentalAutoDetectLongPolling?: boolean;
99100

packages/functions/src/callable.test.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ import {
3232
FirebaseAuthInternal,
3333
FirebaseAuthInternalName
3434
} from '@firebase/auth-interop-types';
35+
import {
36+
FirebaseAppCheckInternal,
37+
AppCheckInternalComponentName
38+
} from '@firebase/app-check-interop-types';
3539
import { makeFakeApp, createTestService } from '../test/utils';
3640
import { httpsCallable } from './service';
3741
import { FUNCTIONS_TYPE } from './constants';
@@ -108,7 +112,7 @@ describe('Firebase Functions > Call', () => {
108112
expect(result.data).to.equal(76);
109113
});
110114

111-
it('token', async () => {
115+
it('auth token', async () => {
112116
// mock auth-internal service
113117
const authMock: FirebaseAuthInternal = {
114118
getToken: async () => ({ accessToken: 'token' })
@@ -133,6 +137,74 @@ describe('Firebase Functions > Call', () => {
133137
stub.restore();
134138
});
135139

140+
it('app check token', async () => {
141+
const appCheckMock: FirebaseAppCheckInternal = {
142+
getToken: async () => ({ token: 'app-check-token' })
143+
} as unknown as FirebaseAppCheckInternal;
144+
const appCheckProvider = new Provider<AppCheckInternalComponentName>(
145+
'app-check-internal',
146+
new ComponentContainer('test')
147+
);
148+
appCheckProvider.setComponent(
149+
new Component(
150+
'app-check-internal',
151+
() => appCheckMock,
152+
ComponentType.PRIVATE
153+
)
154+
);
155+
const functions = createTestService(
156+
app,
157+
region,
158+
undefined,
159+
undefined,
160+
appCheckProvider
161+
);
162+
163+
// Stub out the internals to get an app check token.
164+
const stub = sinon.stub(appCheckMock, 'getToken').callThrough();
165+
const func = httpsCallable(functions, 'appCheckTest');
166+
const result = await func({});
167+
expect(result.data).to.deep.equal({ token: 'app-check-token' });
168+
169+
expect(stub.callCount).to.equal(1);
170+
stub.restore();
171+
});
172+
173+
it('app check limited use token', async () => {
174+
const appCheckMock: FirebaseAppCheckInternal = {
175+
getLimitedUseToken: async () => ({ token: 'app-check-single-use-token' })
176+
} as unknown as FirebaseAppCheckInternal;
177+
const appCheckProvider = new Provider<AppCheckInternalComponentName>(
178+
'app-check-internal',
179+
new ComponentContainer('test')
180+
);
181+
appCheckProvider.setComponent(
182+
new Component(
183+
'app-check-internal',
184+
() => appCheckMock,
185+
ComponentType.PRIVATE
186+
)
187+
);
188+
const functions = createTestService(
189+
app,
190+
region,
191+
undefined,
192+
undefined,
193+
appCheckProvider
194+
);
195+
196+
// Stub out the internals to get an app check token.
197+
const stub = sinon.stub(appCheckMock, 'getLimitedUseToken').callThrough();
198+
const func = httpsCallable(functions, 'appCheckTest', {
199+
limitedUseAppCheckTokens: true
200+
});
201+
const result = await func({});
202+
expect(result.data).to.deep.equal({ token: 'app-check-single-use-token' });
203+
204+
expect(stub.callCount).to.equal(1);
205+
stub.restore();
206+
});
207+
136208
it('instance id', async () => {
137209
// Should effectively skip this test in environments where messaging doesn't work.
138210
// (Node, IE)

packages/functions/src/context.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,13 @@ export class ContextProvider {
119119
}
120120
}
121121

122-
async getAppCheckToken(): Promise<string | null> {
122+
async getAppCheckToken(
123+
limitedUseAppCheckTokens?: boolean
124+
): Promise<string | null> {
123125
if (this.appCheck) {
124-
const result = await this.appCheck.getToken();
126+
const result = limitedUseAppCheckTokens
127+
? await this.appCheck.getLimitedUseToken()
128+
: await this.appCheck.getToken();
125129
if (result.error) {
126130
// Do not send the App Check header to the functions endpoint if
127131
// there was an error from the App Check exchange endpoint. The App
@@ -133,10 +137,10 @@ export class ContextProvider {
133137
return null;
134138
}
135139

136-
async getContext(): Promise<Context> {
140+
async getContext(limitedUseAppCheckTokens?: boolean): Promise<Context> {
137141
const authToken = await this.getAuthToken();
138142
const messagingToken = await this.getMessagingToken();
139-
const appCheckToken = await this.getAppCheckToken();
143+
const appCheckToken = await this.getAppCheckToken(limitedUseAppCheckTokens);
140144
return { authToken, messagingToken, appCheckToken };
141145
}
142146
}

packages/functions/src/public-types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ export interface HttpsCallableOptions {
4747
* Default is 70000.
4848
*/
4949
timeout?: number;
50+
/**
51+
* If set to true, uses limited-use App Check token for callable function requests from this
52+
* instance of {@link Functions}. You must use limited-use tokens to call functions with
53+
* replay protection enabled. By default, this is false.
54+
*/
55+
limitedUseAppCheckTokens?: boolean;
5056
}
5157

5258
/**

packages/functions/src/service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@ async function callAtURL(
277277

278278
// Add a header for the authToken.
279279
const headers: { [key: string]: string } = {};
280-
const context = await functionsInstance.contextProvider.getContext();
280+
const context = await functionsInstance.contextProvider.getContext(
281+
options.limitedUseAppCheckTokens
282+
);
281283
if (context.authToken) {
282284
headers['Authorization'] = 'Bearer ' + context.authToken;
283285
}

scripts/ci/notify-test-result.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ async function notifyTestResults() {
111111
req.end();
112112
});
113113

114-
return Promise.all([chatPromise, logPromise]);
114+
return Promise.all([chatPromise, logPromise]).catch(e => {
115+
console.error(e);
116+
});
115117
}
116118

117119
notifyTestResults();

scripts/release/utils/publish.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,7 @@ async function publishPackageInCI(
135135
}" >> ~/.npmrc`
136136
);
137137

138-
const spawnPromise = spawn('npm', args, { cwd: path });
139-
const childProcess = spawnPromise.childProcess;
140-
childProcess.stdout?.on('data', function (data) {
141-
console.log(`[publishing ${pkg}] stdout: `, data.toString());
142-
});
143-
childProcess.stderr?.on('data', function (data) {
144-
console.log(`[publishing ${pkg}] stderr: `, data.toString());
145-
});
146-
await spawnPromise;
147-
return spawnPromise;
138+
return spawn('npm', args, { cwd: path });
148139
} catch (err) {
149140
throw err;
150141
}

0 commit comments

Comments
 (0)