Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:

strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [20.x, 22.x]

steps:
- uses: actions/checkout@v3
Expand All @@ -50,7 +50,7 @@ jobs:

strategy:
matrix:
node-version: [18.x, 20.x]
node-version: [20.x, 22.x]

steps:
- uses: actions/checkout@v3
Expand All @@ -72,7 +72,7 @@ jobs:

strategy:
matrix:
node-version: [16.x]
node-version: [20.x]

steps:
- uses: actions/checkout@v3
Expand Down
11,530 changes: 7,254 additions & 4,276 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.14.0",
"private": true,
"engines": {
"node": ">=16.4.0"
"node": ">=20.4.0"
},
"dependencies": {
"@rmwc/base": "^7.0.3",
Expand Down Expand Up @@ -42,7 +42,7 @@
"classnames": "^2.2.6",
"codemirror": "^5.61.1",
"express": "^4.17.1",
"firebase": "^10.7.1",
"firebase": "^11.6.1-firebase-studio-sdk-integration.226be0bb1",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we merging this for an official release?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we'll wait for the real release - adding a note to the descriptio n

"font-awesome": "^4.7.0",
"immer": "^9.0.14",
"keycode": "^2.2.0",
Expand Down Expand Up @@ -76,7 +76,7 @@
},
"overrides": {
"reactfire": {
"firebase": "^10.7.1"
"firebase": "^11.6.0"
},
"vite-plugin-node": {
"vite": "$vite"
Expand Down
7 changes: 5 additions & 2 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import type { ListenOptions } from 'net';
import * as path from 'path';

import express from 'express';
import fetch from 'node-fetch';

import { fetchMaybeWithCredentials } from './src/components/common/rest_api';

/*
This file defines Node.js server-side logic for the Emulator UI.
Expand Down Expand Up @@ -53,7 +54,9 @@ app.get(
'/api/config',
jsonHandler(async () => {
const hubDiscoveryUrl = new URL(`http://${hubHost}/emulators`);
const emulatorsRes = await fetch(hubDiscoveryUrl.toString());
const emulatorsRes = await fetchMaybeWithCredentials(
hubDiscoveryUrl.toString()
);
const emulators = (await emulatorsRes.json()) as any;

const json = { projectId, experiments: [], ...emulators };
Expand Down
3 changes: 2 additions & 1 deletion src/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { fetchMaybeWithCredentials } from './components/common/rest_api';
import { scrubPathData } from './routes';
import { AnalyticsSession, Config } from './store/config';

Expand Down Expand Up @@ -124,7 +125,7 @@ export function scrubAnalyticsPageData() {
export async function initGtag() {
let session: AnalyticsSession | undefined;
try {
const response = await fetch(USAGE_CONFIG_API);
const response = await fetchMaybeWithCredentials(USAGE_CONFIG_API);
const responseJson: Config = await response.json();
session = responseJson.analytics;
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion src/components/Auth/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ describe('API', () => {
const api = setup({ mockFetchResult });
const result = await api.getConfig();
expect(global.fetch).toHaveBeenCalledWith(
'//foo.example.com:9002/emulator/v1/projects/pelmen-the-project/config'
'//foo.example.com:9002/emulator/v1/projects/pelmen-the-project/config',
{}
);

expect(result).toEqual(result);
Expand Down
6 changes: 4 additions & 2 deletions src/components/Auth/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { RestApi } from '../common/rest_api';
import { RestApi, fetchMaybeWithCredentials } from '../common/rest_api';
import {
AddAuthUserPayload,
AuthUser,
Expand Down Expand Up @@ -140,7 +140,9 @@ export default class AuthApi extends RestApi {
}

async getConfig(): Promise<EmulatorV1ProjectsConfig> {
const config = await (await fetch(`${this.baseEmulatorUrl}/config`)).json();
const config = await (
await fetchMaybeWithCredentials(`${this.baseEmulatorUrl}/config`)
).json();
return config;
}

Expand Down
9 changes: 7 additions & 2 deletions src/components/Database/DataViewer/common/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
switchMap,
} from 'rxjs/operators';

import { fetchMaybeWithCredentials } from '../../../common/rest_api';
import {
DEFAULT_PAGE_SIZE,
QueryParams,
Expand Down Expand Up @@ -91,7 +92,9 @@ export function canDoRealtime(
print: 'silent',
timeout: REST_TIMEOUT,
});
return defer(() => fetch(silent, { headers: ADMIN_AUTH_HEADERS })).pipe(
return defer(() =>
fetchMaybeWithCredentials(silent, { headers: ADMIN_AUTH_HEADERS })
).pipe(
mapTo(true),
catchError(() => of(false)),
shareReplay({ bufferSize: 1, refCount: true })
Expand Down Expand Up @@ -179,7 +182,9 @@ function fetchNonRealtime(
): Observable<string[]> {
const params = getRestQueryParams(query);
const shallow = restUrl(realtimeRef, { ...params, shallow: 'true' });
return defer(() => fetch(shallow, { headers: ADMIN_AUTH_HEADERS })).pipe(
return defer(() =>
fetchMaybeWithCredentials(shallow, { headers: ADMIN_AUTH_HEADERS })
).pipe(
map((r) => r.json()),
map((data) => Object.keys(data))
);
Expand Down
3 changes: 2 additions & 1 deletion src/components/Database/DatabaseContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import useSwr from 'swr';
import { DatabaseConfig } from '../../store/config';
import { Callout } from '../common/Callout';
import { useEmulatorConfig } from '../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../common/rest_api';
import DatabasePicker from './DatabasePicker';

export type Props = {
Expand Down Expand Up @@ -111,7 +112,7 @@ async function fetchDatabases(
config: DatabaseConfig,
primary: string
): Promise<string[]> {
const res = await fetch(
const res = await fetchMaybeWithCredentials(
`//${config.hostAndPort}/.inspect/databases.json?ns=${primary}`
);
if (!res.ok) {
Expand Down
3 changes: 2 additions & 1 deletion src/components/Database/useImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { DatabaseReference } from 'firebase/database';

import { useEmulatorConfig } from '../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../common/rest_api';
import { useNamespace } from './useNamespace';

/** Get absolute path for file upload (the path with `.upload` appended) */
Expand Down Expand Up @@ -60,7 +61,7 @@ export function useImport(
}

return () =>
fetch(`//${config.hostAndPort}/${path}`, {
fetchMaybeWithCredentials(`//${config.hostAndPort}/${path}`, {
method: 'POST',
body: formData,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import useSwr from 'swr';

import { useEmulatorConfig } from '../../../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../../../common/rest_api';
import { Backend } from '../../index';
import { ExtensionBackend } from '../useExtensions';

Expand All @@ -33,7 +34,9 @@ export function useExtensionBackends(): ExtensionBackend[] {
const config = useEmulatorConfig('extensions');

const fetcher = async () => {
const response = await fetch(`//${config.hostAndPort}/backends`);
const response = await fetchMaybeWithCredentials(
`//${config.hostAndPort}/backends`
);
const json = await response.json();
return json.backends;
};
Expand Down
3 changes: 2 additions & 1 deletion src/components/Firealerts/Form/FirealertsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ChangeEvent, useEffect, useState } from 'react';

import { Callout } from '../../common/Callout';
import { useEmulatorConfig } from '../../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../../common/rest_api';
import { useFirealerts } from '../api/useFirealerts';
import { FirealertsTrigger } from '../models';
import AlertSentNotification from '../Notification/AlertSentNotification';
Expand Down Expand Up @@ -68,7 +69,7 @@ export const FirealertsForm = () => {
const event = generateCloudEventWithData(selectedAlert, alertData);
const payload = { events: [event] };
const url = `//${config.hostAndPort}/google/publishEvents`;
await fetch(url, {
await fetchMaybeWithCredentials(url, {
method: 'POST',
body: JSON.stringify(payload),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
import useSWR from 'swr';

import { useEmulatorConfig } from '../../../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../../../common/rest_api';
import { FirealertsTrigger } from '../../models';

export function useFirealertsTriggers(): FirealertsTrigger[] {
const config = useEmulatorConfig('eventarc');
const FIREALERTS_EVENT_TYPE =
'google.firebase.firebasealerts.alerts.v1.published-google';
const fetcher = async (url: URL) => {
const response = await fetch(url);
const response = await fetchMaybeWithCredentials(url.toString());
const json = await response.json();
return json;
};
Expand Down
5 changes: 4 additions & 1 deletion src/components/Storage/api/useBuckets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import useSWR from 'swr';

import { useEmulatorConfig } from '../../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../../common/rest_api';
import { useBucket } from './useBucket';

export interface Bucket {
Expand All @@ -28,7 +29,9 @@ export function useBuckets() {
const [bucket] = useBucket();

const fetcher = async () => {
const response = await fetch(`//${config.hostAndPort}/b`);
const response = await fetchMaybeWithCredentials(
`//${config.hostAndPort}/b`
);
const json = await response.json();
return json.items.map((b: Bucket) => b.name);
};
Expand Down
4 changes: 3 additions & 1 deletion src/components/Storage/api/useCreateFolder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { useEmulatorConfig } from '../../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../../common/rest_api';
import { useBucket } from './useBucket';

const EMPTY_FOLDER_DATA = `--boundary\r
Expand All @@ -35,7 +36,8 @@ export function useCreateFolder() {
const normalizedPath = fullPath.replace(/\/+/g, '/').replace(/^\//, '');
const encodedPath = encodeURIComponent(normalizedPath);

await fetch(
// TODO (joe): Fix this
await fetchMaybeWithCredentials(
`http://${
config!.hostAndPort
}/upload/storage/v1/b/${bucket}/o?name=${encodedPath}/`,
Expand Down
7 changes: 4 additions & 3 deletions src/components/Storage/api/useTokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import useSWR from 'swr';

import { useEmulatorConfig } from '../../common/EmulatorConfigProvider';
import { fetchMaybeWithCredentials } from '../../common/rest_api';
import { useBucket } from './useBucket';

export function useTokens(fullPath: string) {
Expand All @@ -29,7 +30,7 @@ export function useTokens(fullPath: string) {
fullPath
)}`;

const response = await fetch(url, {
const response = await fetchMaybeWithCredentials(url, {
method: 'GET',
headers: { Authorization: 'Bearer owner' },
});
Expand All @@ -44,7 +45,7 @@ export function useTokens(fullPath: string) {
});

async function createToken() {
await fetch(
await fetchMaybeWithCredentials(
`//${config!.hostAndPort}/v0/b/${bucket}/o/${encodeURIComponent(
fullPath
)}?create_token=true`,
Expand All @@ -59,7 +60,7 @@ export function useTokens(fullPath: string) {
}

async function deleteToken(token: string) {
await fetch(
await fetchMaybeWithCredentials(
`//${config!.hostAndPort}/v0/b/${bucket}/o/${encodeURIComponent(
fullPath
)}?delete_token=${token}`,
Expand Down
14 changes: 9 additions & 5 deletions src/components/common/EmulatorConfigProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ async function configFetcher(url: string): Promise<Config> {
// resolve it to IPv6 and connection may fail.
host = '127.0.0.1';
}
if (key == 'firestore') {
// If we remapped the host, apply it to the websocket host too.
value.webSocketHost = value.host;
}
break;
} else if (listen.address === '::') {
port = listen.port;
Expand All @@ -242,21 +246,21 @@ async function configFetcher(url: string): Promise<Config> {
// Ditto for IPv6.
host = '::1';
}
if (key == 'firestore') {
// If we remapped the host, apply it to the websocket host too.
value.webSocketHost = value.host;
}
break;
}
}

result[key as Emulator] = {
...value,
host,
port,
hostAndPort: hostAndPort(host, port),
};
}

if (result.firestore?.webSocketPort) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously you only remapped the webSocketPort if it was set, do you need to preserve this if statement?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's likely irrelevant (webSocketPort is always provided), but added it back in

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to bring this back?

// Apply the same `host` change above to the WebSocket server.
result.firestore.webSocketHost = result.firestore.host;
}
return result;
}

Expand Down
26 changes: 23 additions & 3 deletions src/components/common/rest_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export abstract class RestApi {
) {
const { accessToken } = this.getToken();

const res = await fetch(url, {
const res = await fetchMaybeWithCredentials(url, {
method,
body: data ? JSON.stringify(data) : undefined,
headers: {
Expand All @@ -61,7 +61,7 @@ export abstract class RestApi {
protected async deleteRequest(url: string) {
const { accessToken } = this.getToken();

const res = await fetch(url, {
const res = await fetchMaybeWithCredentials(url, {
method: 'DELETE',
headers: {
Authorization: 'Bearer ' + accessToken,
Expand Down Expand Up @@ -97,7 +97,7 @@ export abstract class RestApi {
// Furthermore, setting non-simple headers may cause a CORS pre-flight
// request. RTDB Emulator, for one, doesn't handle those correctly.
// See: https://fetch.spec.whatwg.org/#simple-header
const res = await fetch(url, { method, body });
const res = await fetchMaybeWithCredentials(url, { method, body });

const text = await res.text();

Expand All @@ -115,3 +115,23 @@ function toFormData(file: File) {
formData.append('upload', file);
return formData;
}

export function isCloudWorkstation(url: string) {
return url.includes('.cloudworkstations.dev');
}

// This is a very minimal wrapper around fetch that lets us more easily make
// broad changes to API calls.
// Previously, we had a ton a raw fetch calls throughout the codebase, so any broad changes needed to be made all over the place.
export async function fetchMaybeWithCredentials(
url: string,
opts?: RequestInit
): Promise<Response> {
if (!opts) {
opts = {};
}
if (isCloudWorkstation(url)) {
opts.credentials = 'include';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just do

Suggested change
opts.credentials = 'include';
opts = {...opts, credentials: 'include'};

It will handle undefined without Line 130-132 and it avoids modifying the argument

}
return fetch(url, opts);
}
Loading
Loading