Skip to content

Add Emulator Overlay #8977

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 68 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
02c52c6
Added test project to fdc
maneesht Apr 11, 2025
37d0f18
Fixed formatting
maneesht Apr 11, 2025
a4d7813
Fix linting
maneesht Apr 11, 2025
170b15a
Excluded example integration from tsconfig
maneesht Apr 11, 2025
ec405ae
Fixed example-integration package.json
maneesht Apr 11, 2025
248f654
Added ssl check for firestore
maneesht Apr 23, 2025
14ef1bc
Added ssl checks for RTDB
maneesht Apr 23, 2025
a8fe7de
Merge remote-tracking branch 'origin/main' into mtewani/add-ssl-check…
maneesht Apr 23, 2025
df4a115
Removed unnecessary files
maneesht Apr 23, 2025
f867d3b
Removed unnecessary data connect changes
maneesht Apr 23, 2025
4f23f33
Create gentle-laws-kneel.md
maneesht Apr 23, 2025
55b1c0a
Added code to pass on credentials if using a cloud workstation
maneesht Apr 23, 2025
e0a59c5
Create nine-pugs-crash.md
maneesht Apr 23, 2025
8d93780
Addressed comments
maneesht Apr 24, 2025
78142b9
Merge branch 'mtewani/fix-auth-redirects-studio' of https://github.co…
maneesht Apr 24, 2025
5e76a0a
Removed unused import
maneesht Apr 24, 2025
b8b485d
include storage changes
maneesht Apr 24, 2025
21ecf95
Updated api review
maneesht Apr 24, 2025
24c1575
Removed unnecessary import
maneesht Apr 25, 2025
7c7f3ae
Fix formatting
maneesht Apr 25, 2025
d62409a
Fixed data connect test
maneesht Apr 25, 2025
82faa08
Included extra url in externs
maneesht Apr 25, 2025
32c461a
Fixed fdc tests
maneesht Apr 25, 2025
da5b7bf
Passed in emulator information
maneesht Apr 25, 2025
b424e58
Fixed formattign
maneesht Apr 25, 2025
7d2313f
WIP
maneesht Apr 25, 2025
6e0761d
Added emulator status
maneesht Apr 25, 2025
e7b292d
Removed firebaseapp import
maneesht Apr 25, 2025
beff97f
Merge remote-tracking branch 'origin/main' into mtewani/add-emulator-…
maneesht May 1, 2025
6ca5c24
Removed app check change
maneesht May 1, 2025
6d0af9d
Removed eslintrc change
maneesht May 1, 2025
445c490
Removed unnecessary test
maneesht May 1, 2025
d97337f
Fixed failing tests
maneesht May 1, 2025
8f6167e
WIP
maneesht May 5, 2025
0aede90
Implemented emulator overlay
maneesht May 5, 2025
0d05eeb
Merge remote-tracking branch 'origin/main' into mtewani/add-emulator-…
maneesht May 5, 2025
fa3a56f
Removed changeset
maneesht May 5, 2025
6cff61f
Create three-singers-wonder.md
maneesht May 5, 2025
9a4d8ba
Merge branch 'mtewani/add-emulator-overlay' of https://github.com/fir…
maneesht May 5, 2025
eeebee1
Removed unused line
maneesht May 5, 2025
c439abe
Removed unnecessary changes
maneesht May 5, 2025
adb9c76
Updated storage changeset
maneesht May 5, 2025
32433f3
Fixed button click
maneesht May 5, 2025
1db0a40
Fixed linting errors
maneesht May 5, 2025
25f3a21
fixed formatting
maneesht May 5, 2025
b0e7566
Addressed comments
maneesht May 5, 2025
8d70b12
Removed extra document check
maneesht May 5, 2025
bffdabd
Removed extra export
maneesht May 5, 2025
cdbf663
Trigger Build
maneesht May 5, 2025
927dadd
Fixed formatting
maneesht May 5, 2025
83b84f0
Addressed comments
maneesht May 6, 2025
c824b35
Fixed linting
maneesht May 6, 2025
9ee4aa0
Fixed linting
maneesht May 6, 2025
1e7039a
Updated to only trigger on Studio
maneesht May 6, 2025
2e57ab7
Fixed linting
maneesht May 6, 2025
c2246ca
Trigger Build
maneesht May 6, 2025
fe1264c
Persisted dismiss
maneesht May 6, 2025
a3edf83
Removed change to check every request
maneesht May 7, 2025
db73a96
Used disableWarnings in auth
maneesht May 7, 2025
1c4c6ee
Fixed linting
maneesht May 7, 2025
3a4ba47
Fixed linting
maneesht May 8, 2025
613bd04
Updated for copy
maneesht May 8, 2025
243d24d
Removed testing changed
maneesht May 8, 2025
0da1b9f
Updated to include external link
maneesht May 8, 2025
3d0a24f
Added back emulator banner change
maneesht May 8, 2025
c553d99
Added back emulator banner change
maneesht May 8, 2025
e5a945c
Fixed linting
maneesht May 8, 2025
a528caf
Removed trace
maneesht May 8, 2025
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
10 changes: 10 additions & 0 deletions .changeset/three-singers-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@firebase/auth": patch
"@firebase/firestore": patch
"@firebase/util": patch
"@firebase/database": patch
"@firebase/storage": patch
"@firebase/functions": patch
---

Add Emulator Overlay
3 changes: 3 additions & 0 deletions common/api-review/util.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,9 @@ export interface Subscribe<T> {
// @public (undocumented)
export type Unsubscribe = () => void;

// @public
export function updateEmulatorBanner(name: string, isRunningEmulator: boolean): void;

// Warning: (ae-missing-release-tag) "validateArgCount" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
Expand Down
7 changes: 5 additions & 2 deletions packages/auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
FirebaseError,
isCloudflareWorker,
isCloudWorkstation,
querystring
querystring,
updateEmulatorBanner
} from '@firebase/util';

import { AuthErrorCode, NamedErrorParams } from '../core/errors';
Expand Down Expand Up @@ -198,7 +199,9 @@ export async function _performFetchWithErrorHandling<V>(
customErrorMap: Partial<ServerErrorMap<ServerError>>,
fetchFn: () => Promise<Response>
): Promise<V> {
(auth as AuthInternal)._canInitEmulator = false;
const authInternal = auth as AuthInternal;
updateEmulatorBanner('Auth', authInternal.emulatorConfig !== null);
authInternal._canInitEmulator = false;
const errorMap = { ...SERVER_ERROR_MAP, ...customErrorMap };
try {
const networkTimeout = new NetworkTimeout<Response>(auth);
Expand Down
10 changes: 8 additions & 2 deletions packages/auth/src/core/auth/emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import { Auth } from '../../model/public_types';
import { AuthErrorCode } from '../errors';
import { _assert } from '../util/assert';
import { _castAuth } from './auth_impl';
import { deepEqual, isCloudWorkstation, pingServer } from '@firebase/util';
import {
deepEqual,
isCloudWorkstation,
pingServer,
updateEmulatorBanner
} from '@firebase/util';

/**
* Changes the {@link Auth} instance to communicate with the Firebase Auth Emulator, instead of production
Expand Down Expand Up @@ -97,13 +102,14 @@ export function connectAuthEmulator(
authInternal.emulatorConfig = emulatorConfig;
authInternal.settings.appVerificationDisabledForTesting = true;

if (!disableWarnings) {
if (!disableWarnings && !isCloudWorkstation(host)) {
emitEmulatorWarning();
}

// Workaround to get cookies in Firebase Studio
if (isCloudWorkstation(host)) {
void pingServer(`${protocol}//${host}${portStr}`);
updateEmulatorBanner('Auth', true);
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/database/src/api/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
EmulatorMockTokenOptions,
getDefaultEmulatorHostnameAndPort,
isCloudWorkstation,
pingServer
pingServer,
updateEmulatorBanner
} from '@firebase/util';

import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider';
Expand Down Expand Up @@ -393,6 +394,7 @@ export function connectDatabaseEmulator(
// Workaround to get cookies in Firebase Studio
if (isCloudWorkstation(host)) {
void pingServer(host);
updateEmulatorBanner('Database', true);
}

// Modify the repo to apply emulator settings
Expand Down
4 changes: 3 additions & 1 deletion packages/database/src/core/Repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
isEmpty,
map,
safeGet,
stringify
stringify,
updateEmulatorBanner
} from '@firebase/util';

import { ValueEventRegistration } from '../api/Reference_impl';
Expand Down Expand Up @@ -328,6 +329,7 @@ export function repoStart(
repo.server_.unlisten(query, tag);
}
});
updateEmulatorBanner('Database', repo.repoInfo_.isUsingEmulator);
}

/**
Expand Down
13 changes: 10 additions & 3 deletions packages/firestore/src/lite-api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
EmulatorMockTokenOptions,
getDefaultEmulatorHostnameAndPort,
isCloudWorkstation,
updateEmulatorBanner,
pingServer
} from '@firebase/util';

Expand Down Expand Up @@ -142,6 +143,9 @@ export class Firestore implements FirestoreService {

_freezeSettings(): FirestoreSettingsImpl {
this._settingsFrozen = true;
if (isCloudWorkstation(this._settings.host)) {
updateEmulatorBanner('Firestore', this._settings.isUsingEmulator);
}
return this._settings;
}

Expand Down Expand Up @@ -334,9 +338,7 @@ export function connectFirestoreEmulator(
emulatorOptions: firestore._getEmulatorOptions()
};
const newHostSetting = `${host}:${port}`;
if (useSsl) {
void pingServer(`https://${newHostSetting}`);
}

if (settings.host !== DEFAULT_HOST && settings.host !== newHostSetting) {
logWarn(
'Host has been set in both settings() and connectFirestoreEmulator(), emulator host ' +
Expand All @@ -357,6 +359,11 @@ export function connectFirestoreEmulator(

firestore._setSettings(newConfig);

if (useSsl) {
void pingServer(`https://${newHostSetting}`);
updateEmulatorBanner('Firestore', true);
}

if (options.mockUserToken) {
let token: string;
let user: User;
Expand Down
9 changes: 8 additions & 1 deletion packages/functions/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ import { Provider } from '@firebase/component';
import { FirebaseAuthInternalName } from '@firebase/auth-interop-types';
import { MessagingInternalComponentName } from '@firebase/messaging-interop-types';
import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types';
import { isCloudWorkstation, pingServer } from '@firebase/util';
import {
isCloudWorkstation,
pingServer,
updateEmulatorBanner
} from '@firebase/util';

export const DEFAULT_REGION = 'us-central1';

Expand Down Expand Up @@ -182,6 +186,7 @@ export function connectFunctionsEmulator(
// Workaround to get cookies in Firebase Studio
if (useSsl) {
void pingServer(functionsInstance.emulatorOrigin);
updateEmulatorBanner('Functions', true);
}
}

Expand All @@ -195,6 +200,7 @@ export function httpsCallable<RequestData, ResponseData, StreamData = unknown>(
name: string,
options?: HttpsCallableOptions
): HttpsCallable<RequestData, ResponseData, StreamData> {
updateEmulatorBanner('Functions', functionsInstance.emulatorOrigin !== null);
const callable = (
data?: RequestData | null
): Promise<HttpsCallableResult> => {
Expand Down Expand Up @@ -225,6 +231,7 @@ export function httpsCallableFromURL<
url: string,
options?: HttpsCallableOptions
): HttpsCallable<RequestData, ResponseData, StreamData> {
updateEmulatorBanner('Functions', functionsInstance.emulatorOrigin !== null);
const callable = (
data?: RequestData | null
): Promise<HttpsCallableResult> => {
Expand Down
5 changes: 4 additions & 1 deletion packages/storage/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ import {
createMockUserToken,
EmulatorMockTokenOptions,
isCloudWorkstation,
pingServer
pingServer,
updateEmulatorBanner
} from '@firebase/util';
import { Connection, ConnectionType } from './implementation/connection';

Expand Down Expand Up @@ -150,6 +151,7 @@ export function connectStorageEmulator(
// Workaround to get cookies in Firebase Studio
if (useSsl) {
void pingServer(`https://${storage.host}`);
updateEmulatorBanner('Storage', true);
}
storage._isUsingEmulator = true;
storage._protocol = useSsl ? 'https' : 'http';
Expand Down Expand Up @@ -324,6 +326,7 @@ export class FirebaseStorageImpl implements FirebaseStorage {
appCheckToken: string | null,
retry = true
): Request<O> {
updateEmulatorBanner('Service', this._isUsingEmulator);
if (!this._deleted) {
const request = makeRequest(
requestInfo,
Expand Down
151 changes: 151 additions & 0 deletions packages/util/src/emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

import { base64urlEncodeWithoutPadding } from './crypt';
import { isCloudWorkstation } from './url';

// Firebase Auth tokens contain snake_case claims following the JWT standard / convention.
/* eslint-disable camelcase */
Expand Down Expand Up @@ -140,3 +141,153 @@ export function createMockUserToken(
signature
].join('.');
}

interface EmulatorStatusMap {
[name: string]: boolean;
}
const emulatorStatus: EmulatorStatusMap = {};

interface EmulatorSummary {
prod: string[];
emulator: string[];
}

// Checks whether any products are running on an emulator
function getEmulatorSummary(): EmulatorSummary {
const summary: EmulatorSummary = {
prod: [],
emulator: []
};
for (const key of Object.keys(emulatorStatus)) {
if (emulatorStatus[key]) {
summary.emulator.push(key);
} else {
summary.prod.push(key);
}
}
return summary;
}

function getOrCreateEl(id: string): { created: boolean; element: HTMLElement } {
let parentDiv = document.getElementById(id);
let created = false;
if (!parentDiv) {
parentDiv = document.createElement('div');
parentDiv.setAttribute('id', id);
created = true;
}
return { created, element: parentDiv };
}

/**
* Updates Emulator Banner. Primarily used for Firebase Studio
* @param name
* @param isRunningEmulator
* @public
*/
export function updateEmulatorBanner(
name: string,
isRunningEmulator: boolean
): void {
if (
typeof window === 'undefined' ||
typeof document === 'undefined' ||
!isCloudWorkstation(window.location.host) ||
emulatorStatus[name] === isRunningEmulator
) {
return;
}

emulatorStatus[name] = isRunningEmulator;

function prefixedId(id: string): string {
return `__firebase__banner__${id}`;
}
const bannerId = '__firebase__banner';
const summary = getEmulatorSummary();
const showError = summary.prod.length > 0;

function tearDown(): void {
const element = document.getElementById(bannerId);
if (element) {
element.remove();
}
}

function setupDom(): void {
const banner = getOrCreateEl(bannerId);
const firebaseTextId = prefixedId('text');
const firebaseText: HTMLSpanElement =
document.getElementById(firebaseTextId) || document.createElement('span');
const learnMoreId = prefixedId('learnmore');
const learnMoreLink: HTMLAnchorElement =
(document.getElementById(learnMoreId) as HTMLAnchorElement) ||
document.createElement('a');
const prependIconId = prefixedId('preprendIcon');
const prependIcon =
document.getElementById(prependIconId) ||
document.createElementNS('http://www.w3.org/2000/svg', 'svg');
if (banner.created) {
// update styles
const bannerEl = banner.element;
bannerEl.style.display = 'flex';
bannerEl.style.background = '#7faaf0';
bannerEl.style.position = 'absolute';
bannerEl.style.bottom = '5px';
bannerEl.style.left = '5px';
bannerEl.style.padding = '.5em';
bannerEl.style.borderRadius = '5px';
bannerEl.style.alignContent = 'center';
const closeBtn = document.createElement('span');
closeBtn.style.cursor = 'pointer';
closeBtn.style.paddingLeft = '5px';
closeBtn.innerHTML = ' &times;';
closeBtn.onclick = () => {
tearDown();
};
learnMoreLink.setAttribute('id', learnMoreId);
learnMoreLink.href =
'http://firebase.google.com/docs/studio/deploy-app#emulator ';
bannerEl.appendChild(prependIcon);
bannerEl.appendChild(firebaseText);
bannerEl.appendChild(learnMoreLink);
bannerEl.appendChild(closeBtn);
prependIcon.setAttribute('width', '24');
prependIcon.setAttribute('id', prependIconId);
prependIcon.setAttribute('height', '24');
prependIcon.setAttribute('viewBox', '0 0 24 24');
prependIcon.setAttribute('fill', 'none');
document.body.appendChild(bannerEl);
}

if (showError) {
firebaseText.innerText = `Running in Production`;
prependIcon.innerHTML = `<g clip-path="url(#clip0_6013_33858)">
<path d="M4.8 17.6L12 5.6L19.2 17.6H4.8ZM6.91667 16.4H17.0833L12 7.93333L6.91667 16.4ZM12 15.6C12.1667 15.6 12.3056 15.5444 12.4167 15.4333C12.5389 15.3111 12.6 15.1667 12.6 15C12.6 14.8333 12.5389 14.6944 12.4167 14.5833C12.3056 14.4611 12.1667 14.4 12 14.4C11.8333 14.4 11.6889 14.4611 11.5667 14.5833C11.4556 14.6944 11.4 14.8333 11.4 15C11.4 15.1667 11.4556 15.3111 11.5667 15.4333C11.6889 15.5444 11.8333 15.6 12 15.6ZM11.4 13.6H12.6V10.4H11.4V13.6Z" fill="#212121"/>
</g>
<defs>
<clipPath id="clip0_6013_33858">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>`;
} else {
prependIcon.innerHTML = `<g clip-path="url(#clip0_6083_34804)">
<path d="M11.4 15.2H12.6V11.2H11.4V15.2ZM12 10C12.1667 10 12.3056 9.94444 12.4167 9.83333C12.5389 9.71111 12.6 9.56667 12.6 9.4C12.6 9.23333 12.5389 9.09444 12.4167 8.98333C12.3056 8.86111 12.1667 8.8 12 8.8C11.8333 8.8 11.6889 8.86111 11.5667 8.98333C11.4556 9.09444 11.4 9.23333 11.4 9.4C11.4 9.56667 11.4556 9.71111 11.5667 9.83333C11.6889 9.94444 11.8333 10 12 10ZM12 18.4C11.1222 18.4 10.2944 18.2333 9.51667 17.9C8.73889 17.5667 8.05556 17.1111 7.46667 16.5333C6.88889 15.9444 6.43333 15.2611 6.1 14.4833C5.76667 13.7056 5.6 12.8778 5.6 12C5.6 11.1111 5.76667 10.2833 6.1 9.51667C6.43333 8.73889 6.88889 8.06111 7.46667 7.48333C8.05556 6.89444 8.73889 6.43333 9.51667 6.1C10.2944 5.76667 11.1222 5.6 12 5.6C12.8889 5.6 13.7167 5.76667 14.4833 6.1C15.2611 6.43333 15.9389 6.89444 16.5167 7.48333C17.1056 8.06111 17.5667 8.73889 17.9 9.51667C18.2333 10.2833 18.4 11.1111 18.4 12C18.4 12.8778 18.2333 13.7056 17.9 14.4833C17.5667 15.2611 17.1056 15.9444 16.5167 16.5333C15.9389 17.1111 15.2611 17.5667 14.4833 17.9C13.7167 18.2333 12.8889 18.4 12 18.4ZM12 17.2C13.4444 17.2 14.6722 16.6944 15.6833 15.6833C16.6944 14.6722 17.2 13.4444 17.2 12C17.2 10.5556 16.6944 9.32778 15.6833 8.31667C14.6722 7.30555 13.4444 6.8 12 6.8C10.5556 6.8 9.32778 7.30555 8.31667 8.31667C7.30556 9.32778 6.8 10.5556 6.8 12C6.8 13.4444 7.30556 14.6722 8.31667 15.6833C9.32778 16.6944 10.5556 17.2 12 17.2Z" fill="#212121"/>
</g>
<defs>
<clipPath id="clip0_6083_34804">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>`;
firebaseText.innerText = 'Using emulated backend';
learnMoreLink.href =
'https://firebase.google.com/docs/studio/solution-build-with-ai';
}
firebaseText.setAttribute('id', firebaseTextId);
}
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', setupDom);
} else {
setupDom();
}
}
Loading