Skip to content

Commit c8dc2c1

Browse files
yuchenshiFeiyang1
authored andcommitted
Implement rest of RUTv2 features. (#5360)
* Implement loading rules and withFunctionTriggersDisabled. * Implement clearFirestore and storage. * Add missing await. * Add default bucketUrl. * Use alternative method to clear bucket. * Use default param (review feedback).
1 parent d998661 commit c8dc2c1

File tree

13 files changed

+482
-118
lines changed

13 files changed

+482
-118
lines changed

packages/messaging/src/api.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ import {
3131
import { MessagingService } from './messaging-service';
3232
import { deleteToken as _deleteToken } from './api/deleteToken';
3333
import { getToken as _getToken } from './api/getToken';
34+
import { isSwSupported, isWindowSupported } from './api/isSupported';
3435
import { onBackgroundMessage as _onBackgroundMessage } from './api/onBackgroundMessage';
3536
import { onMessage as _onMessage } from './api/onMessage';
3637
import { _setDeliveryMetricsExportedToBigQueryEnabled } from './api/setDeliveryMetricsExportedToBigQueryEnabled';
38+
import { ERROR_FACTORY, ErrorCode } from './util/errors';
3739

3840
/**
3941
* Retrieves a Firebase Cloud Messaging instance.
@@ -43,6 +45,22 @@ import { _setDeliveryMetricsExportedToBigQueryEnabled } from './api/setDeliveryM
4345
* @public
4446
*/
4547
export function getMessagingInWindow(app: FirebaseApp = getApp()): Messaging {
48+
// Conscious decision to make this async check non-blocking during the messaging instance
49+
// initialization phase for performance consideration. An error would be thrown latter for
50+
// developer's information. Developers can then choose to import and call `isSupported` for
51+
// special handling.
52+
isWindowSupported().then(
53+
isSupported => {
54+
// If `isWindowSupported()` resolved, but returned false.
55+
if (!isSupported) {
56+
throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER);
57+
}
58+
},
59+
_ => {
60+
// If `isWindowSupported()` rejected.
61+
throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED);
62+
}
63+
);
4664
return _getProvider(getModularInstance(app), 'messaging').getImmediate();
4765
}
4866

@@ -54,10 +72,23 @@ export function getMessagingInWindow(app: FirebaseApp = getApp()): Messaging {
5472
* @public
5573
*/
5674
export function getMessagingInSw(app: FirebaseApp = getApp()): Messaging {
57-
return _getProvider(
58-
getModularInstance(app),
59-
'messaging-sw'
60-
).getImmediate();
75+
// Conscious decision to make this async check non-blocking during the messaging instance
76+
// initialization phase for performance consideration. An error would be thrown latter for
77+
// developer's information. Developers can then choose to import and call `isSupported` for
78+
// special handling.
79+
isSwSupported().then(
80+
isSupported => {
81+
// If `isSwSupported()` resolved, but returned false.
82+
if (!isSupported) {
83+
throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER);
84+
}
85+
},
86+
_ => {
87+
// If `isSwSupported()` rejected.
88+
throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED);
89+
}
90+
);
91+
return _getProvider(getModularInstance(app), 'messaging-sw').getImmediate();
6192
}
6293

6394
/**

packages/messaging/src/helpers/register.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import {
2121
ComponentType,
2222
InstanceFactory
2323
} from '@firebase/component';
24-
import { ERROR_FACTORY, ErrorCode } from '../util/errors';
25-
import { isSwSupported, isWindowSupported } from '../api/isSupported';
2624
import {
2725
onNotificationClick,
2826
onPush,
@@ -40,8 +38,6 @@ import { messageEventListener } from '../listeners/window-listener';
4038
const WindowMessagingFactory: InstanceFactory<'messaging'> = (
4139
container: ComponentContainer
4240
) => {
43-
maybeThrowWindowError();
44-
4541
const messaging = new MessagingService(
4642
container.getProvider('app').getImmediate(),
4743
container.getProvider('installations-internal').getImmediate(),
@@ -58,8 +54,6 @@ const WindowMessagingFactory: InstanceFactory<'messaging'> = (
5854
const WindowMessagingInternalFactory: InstanceFactory<'messaging-internal'> = (
5955
container: ComponentContainer
6056
) => {
61-
maybeThrowWindowError();
62-
6357
const messaging = container
6458
.getProvider('messaging')
6559
.getImmediate() as MessagingService;
@@ -71,40 +65,10 @@ const WindowMessagingInternalFactory: InstanceFactory<'messaging-internal'> = (
7165
return messagingInternal;
7266
};
7367

74-
function maybeThrowWindowError(): void {
75-
// Conscious decision to make this async check non-blocking during the messaging instance
76-
// initialization phase for performance consideration. An error would be thrown latter for
77-
// developer's information. Developers can then choose to import and call `isSupported` for
78-
// special handling.
79-
isWindowSupported()
80-
.then(isSupported => {
81-
if (!isSupported) {
82-
throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER);
83-
}
84-
})
85-
.catch(_ => {
86-
throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED);
87-
});
88-
}
89-
9068
declare const self: ServiceWorkerGlobalScope;
9169
const SwMessagingFactory: InstanceFactory<'messaging'> = (
9270
container: ComponentContainer
9371
) => {
94-
// Conscious decision to make this async check non-blocking during the messaging instance
95-
// initialization phase for performance consideration. An error would be thrown latter for
96-
// developer's information. Developers can then choose to import and call `isSupported` for
97-
// special handling.
98-
isSwSupported()
99-
.then(isSupported => {
100-
if (!isSupported) {
101-
throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER);
102-
}
103-
})
104-
.catch(_ => {
105-
throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED);
106-
});
107-
10872
const messaging = new MessagingService(
10973
container.getProvider('app').getImmediate(),
11074
container.getProvider('installations-internal').getImmediate(),

packages/rules-unit-testing/src/impl/discovery.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import { EmulatorConfig, HostAndPort } from '../public_types';
1919
import nodeFetch from 'node-fetch';
20+
import { makeUrl, fixHostname } from './url';
2021

2122
/**
2223
* Use the Firebase Emulator hub to discover other running emulators.
@@ -79,20 +80,6 @@ export interface DiscoveredEmulators {
7980
hub?: HostAndPort;
8081
}
8182

82-
function makeUrl(hostAndPort: HostAndPort | string, path: string): URL {
83-
if (typeof hostAndPort === 'object') {
84-
const { host, port } = hostAndPort;
85-
if (host.includes(':')) {
86-
hostAndPort = `[${host}]:${port}`;
87-
} else {
88-
hostAndPort = `${host}:${port}`;
89-
}
90-
}
91-
const url = new URL(`http://${hostAndPort}/`);
92-
url.pathname = path;
93-
return url;
94-
}
95-
9683
/**
9784
* @private
9885
*/
@@ -169,21 +156,3 @@ function emulatorFromEnvVar(envVar: string): HostAndPort | undefined {
169156
}
170157
return { host, port };
171158
}
172-
173-
/**
174-
* Return a connectable hostname, replacing wildcard 0.0.0.0 or :: with loopback
175-
* addresses 127.0.0.1 / ::1 correspondingly. See below for why this is needed:
176-
* https://github.com/firebase/firebase-tools-ui/issues/286
177-
*
178-
* This assumes emulators are running on the same device as the Emulator UI
179-
* server, which should hold if both are started from the same CLI command.
180-
*/
181-
function fixHostname(host: string, fallbackHost?: string): string {
182-
host = host.replace('[', '').replace(']', ''); // Remove IPv6 brackets
183-
if (host === '0.0.0.0') {
184-
host = fallbackHost || '127.0.0.1';
185-
} else if (host === '::') {
186-
host = fallbackHost || '::1';
187-
}
188-
return host;
189-
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 { HostAndPort } from '../public_types';
19+
import { makeUrl } from './url';
20+
import fetch from 'node-fetch';
21+
22+
/**
23+
* @private
24+
*/
25+
export async function loadDatabaseRules(
26+
hostAndPort: HostAndPort,
27+
databaseName: string,
28+
rules: string
29+
): Promise<void> {
30+
const url = makeUrl(hostAndPort, '/.settings/rules.json');
31+
url.searchParams.append('ns', databaseName);
32+
const resp = await fetch(url, {
33+
method: 'PUT',
34+
headers: { Authorization: 'Bearer owner' },
35+
body: rules
36+
});
37+
38+
if (!resp.ok) {
39+
throw new Error(await resp.text());
40+
}
41+
}
42+
43+
/**
44+
* @private
45+
*/
46+
export async function loadFirestoreRules(
47+
hostAndPort: HostAndPort,
48+
projectId: string,
49+
rules: string
50+
): Promise<void> {
51+
const resp = await fetch(
52+
makeUrl(hostAndPort, `/emulator/v1/projects/${projectId}:securityRules`),
53+
{
54+
method: 'PUT',
55+
body: JSON.stringify({
56+
rules: {
57+
files: [{ content: rules }]
58+
}
59+
})
60+
}
61+
);
62+
63+
if (!resp.ok) {
64+
throw new Error(await resp.text());
65+
}
66+
}
67+
68+
/**
69+
* @private
70+
*/
71+
export async function loadStorageRules(
72+
hostAndPort: HostAndPort,
73+
rules: string
74+
): Promise<void> {
75+
const resp = await fetch(makeUrl(hostAndPort, '/internal/setRules'), {
76+
method: 'PUT',
77+
headers: {
78+
'Content-Type': 'application/json'
79+
},
80+
body: JSON.stringify({
81+
rules: {
82+
files: [{ name: 'storage.rules', content: rules }]
83+
}
84+
})
85+
});
86+
if (!resp.ok) {
87+
throw new Error(await resp.text());
88+
}
89+
}

packages/rules-unit-testing/src/impl/test_environment.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import fetch from 'node-fetch';
1819
import firebase from 'firebase/compat/app';
1920
import 'firebase/firestore/compat';
2021
import 'firebase/database/compat';
@@ -28,6 +29,7 @@ import {
2829
} from '../public_types';
2930

3031
import { DiscoveredEmulators } from './discovery';
32+
import { makeUrl } from './url';
3133

3234
/**
3335
* An implementation of {@code RulesTestEnvironment}. This is private to hide the constructor,
@@ -100,14 +102,35 @@ export class RulesTestEnvironmentImpl implements RulesTestEnvironment {
100102
});
101103
}
102104

103-
clearFirestore(): Promise<void> {
105+
async clearFirestore(): Promise<void> {
104106
this.checkNotDestroyed();
105-
throw new Error('Method not implemented.');
107+
assertEmulatorRunning(this.emulators, 'firestore');
108+
109+
const resp = await fetch(
110+
makeUrl(
111+
this.emulators.firestore,
112+
`/emulator/v1/projects/${this.projectId}/databases/(default)/documents`
113+
),
114+
{
115+
method: 'DELETE'
116+
}
117+
);
118+
119+
if (!resp.ok) {
120+
throw new Error(await resp.text());
121+
}
106122
}
107123

108124
clearStorage(): Promise<void> {
109125
this.checkNotDestroyed();
110-
throw new Error('Method not implemented.');
126+
return this.withSecurityRulesDisabled(async context => {
127+
const { items } = await context.storage().ref().listAll();
128+
await Promise.all(
129+
items.map(item => {
130+
return item.delete();
131+
})
132+
);
133+
});
111134
}
112135

113136
async cleanup(): Promise<void> {
@@ -177,7 +200,7 @@ class RulesTestContextImpl implements RulesTestContext {
177200
);
178201
return database;
179202
}
180-
storage(bucketUrl?: string): firebase.storage.Storage {
203+
storage(bucketUrl = `gs://${this.projectId}`): firebase.storage.Storage {
181204
assertEmulatorRunning(this.emulators, 'storage');
182205
const storage = this.getApp().storage(bucketUrl);
183206
storage.useEmulator(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 { HostAndPort } from '../public_types';
19+
20+
/**
21+
* Return a connectable hostname, replacing wildcard 0.0.0.0 or :: with loopback
22+
* addresses 127.0.0.1 / ::1 correspondingly. See below for why this is needed:
23+
* https://github.com/firebase/firebase-tools-ui/issues/286
24+
*
25+
* This assumes emulators are running on the same device as fallbackHost (e.g.
26+
* hub), which should hold if both are started from the same CLI command.
27+
* @private
28+
*/
29+
export function fixHostname(host: string, fallbackHost?: string): string {
30+
host = host.replace('[', '').replace(']', ''); // Remove IPv6 brackets
31+
if (host === '0.0.0.0') {
32+
host = fallbackHost || '127.0.0.1';
33+
} else if (host === '::') {
34+
host = fallbackHost || '::1';
35+
}
36+
return host;
37+
}
38+
39+
/**
40+
* Create a URL with host, port, and path. Handles IPv6 bracketing correctly.
41+
* @private
42+
*/
43+
export function makeUrl(hostAndPort: HostAndPort | string, path: string): URL {
44+
if (typeof hostAndPort === 'object') {
45+
const { host, port } = hostAndPort;
46+
if (host.includes(':')) {
47+
hostAndPort = `[${host}]:${port}`;
48+
} else {
49+
hostAndPort = `${host}:${port}`;
50+
}
51+
}
52+
const url = new URL(`http://${hostAndPort}/`);
53+
url.pathname = path;
54+
return url;
55+
}

0 commit comments

Comments
 (0)