diff --git a/.changeset/cool-grapes-attend.md b/.changeset/cool-grapes-attend.md new file mode 100644 index 00000000000..8f6d8b289b6 --- /dev/null +++ b/.changeset/cool-grapes-attend.md @@ -0,0 +1,6 @@ +--- +'@firebase/firestore': patch +'firebase': patch +--- + +Fix Firestore failing to raise initial snapshot from empty local cache result diff --git a/.changeset/cool-seas-confess.md b/.changeset/cool-seas-confess.md new file mode 100644 index 00000000000..14cd3fd3f9c --- /dev/null +++ b/.changeset/cool-seas-confess.md @@ -0,0 +1,5 @@ +--- +"@firebase/database": patch +--- + +Fixed `endBefore` and `push` documentation typos in RTDB diff --git a/.changeset/large-lamps-reflect.md b/.changeset/large-lamps-reflect.md new file mode 100644 index 00000000000..25bbb80bfb7 --- /dev/null +++ b/.changeset/large-lamps-reflect.md @@ -0,0 +1,6 @@ +--- +"@firebase/storage": patch +--- + +Fixed bug where upload status wasn't being checked after an upload failure. +Implemented exponential backoff and max retry strategy. diff --git a/.changeset/metal-goats-unite.md b/.changeset/metal-goats-unite.md new file mode 100644 index 00000000000..bc810b44630 --- /dev/null +++ b/.changeset/metal-goats-unite.md @@ -0,0 +1,8 @@ +--- +'@firebase/firestore': minor +'@firebase/firestore-compat': minor +'@firebase/webchannel-wrapper': minor +'firebase': minor +--- + +Set withCredentials=true when making requests via non-streaming RPCs, like is done for streaming RPCs. diff --git a/.changeset/slow-forks-tease.md b/.changeset/slow-forks-tease.md new file mode 100644 index 00000000000..24b8cb953e9 --- /dev/null +++ b/.changeset/slow-forks-tease.md @@ -0,0 +1,5 @@ +--- +'@firebase/analytics': patch +--- + +Update to allow for multiple instance of gtag with different datalayer names diff --git a/.changeset/strong-squids-dress.md b/.changeset/strong-squids-dress.md new file mode 100644 index 00000000000..dc68a002421 --- /dev/null +++ b/.changeset/strong-squids-dress.md @@ -0,0 +1,5 @@ +--- +'@firebase/util': patch +--- + +Remove `__FIREBASE_DEFAULTS_PATH__` option for now, as the current implementation causes Webpack warnings. Also fix `process.env` check to work in environments where `process` exists but `process.env` does not. diff --git a/README.md b/README.md index 25b95289b9b..58f7547b254 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,26 @@ database. When prompted to select the set of initial security rules, select any option (e.g. "Start in Production Mode") since these permission settings will be overwritten below. +#### Storage Setup + +Visit the "Storage" section of the console and create a storage bucket. In +order to run the tests, you will need to update your bucket's CORS rules. + +1. Create a new file called `cors.json` with the contents: +```json +[ + { + "origin": ["http://localhost:8089"], + "method": ["GET"], + "maxAgeSeconds": 3600 + } +] +``` +2. Install `gsutil` from https://cloud.google.com/storage/docs/gsutil_install +3. Run `gsutil cors set cors.json gs://` + +For more information, visit https://firebase.google.com/docs/storage/web/download-files#cors_configuration + #### Authentication Support Visit the authentication config in your project and enable the `Anonymous` diff --git a/common/api-review/storage.api.md b/common/api-review/storage.api.md index 4715253d510..04df972b727 100644 --- a/common/api-review/storage.api.md +++ b/common/api-review/storage.api.md @@ -83,7 +83,7 @@ export class _FirebaseStorageImpl implements FirebaseStorage { // Warning: (ae-forgotten-export) The symbol "Request" needs to be exported by the entry point index.d.ts // // (undocumented) - _makeRequest(requestInfo: RequestInfo_2, requestFactory: () => Connection, authToken: string | null, appCheckToken: string | null): Request_2; + _makeRequest(requestInfo: RequestInfo_2, requestFactory: () => Connection, authToken: string | null, appCheckToken: string | null, retry?: boolean): Request_2; // (undocumented) makeRequestWithTokens(requestInfo: RequestInfo_2, requestFactory: () => Connection): Promise; _makeStorageReference(loc: _Location): _Reference; @@ -319,11 +319,13 @@ export class _UploadTask { _blob: _FbsBlob; cancel(): boolean; catch(onRejected: (p1: StorageError_2) => T | Promise): Promise; + // (undocumented) + isExponentialBackoffExpired(): boolean; // Warning: (ae-forgotten-export) The symbol "Metadata" needs to be exported by the entry point index.d.ts _metadata: Metadata | null; // Warning: (ae-forgotten-export) The symbol "Unsubscribe" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Subscribe" needs to be exported by the entry point index.d.ts - on(type: _TaskEvent, nextOrObserver?: StorageObserver | null | ((snapshot: UploadTaskSnapshot) => unknown), error?: ((a: StorageError_2) => unknown) | null, completed?: Unsubscribe_2 | null): Unsubscribe_2 | Subscribe_2; + on(type: _TaskEvent, nextOrObserver?: StorageObserver | null | ((snapshot: UploadTaskSnapshot) => unknown), error?: ((a: StorageError_2) => unknown) | null, completed?: CompleteFn | null): Unsubscribe_2 | Subscribe_2; pause(): boolean; resume(): boolean; get snapshot(): UploadTaskSnapshot; diff --git a/packages/analytics/src/helpers.test.ts b/packages/analytics/src/helpers.test.ts index 0a67fafcf60..1175ff0f61e 100644 --- a/packages/analytics/src/helpers.test.ts +++ b/packages/analytics/src/helpers.test.ts @@ -29,6 +29,7 @@ import { import { GtagCommand } from './constants'; import { Deferred } from '@firebase/util'; import { ConsentSettings } from './public-types'; +import { removeGtagScripts } from '../testing/gtag-script-util'; const fakeMeasurementId = 'abcd-efgh-ijkl'; const fakeAppId = 'my-test-app-1234'; @@ -46,6 +47,10 @@ const fakeDynamicConfig: DynamicConfig = { const fakeDynamicConfigPromises = [Promise.resolve(fakeDynamicConfig)]; describe('Gtag wrapping functions', () => { + afterEach(() => { + removeGtagScripts(); + }); + it('getOrCreateDataLayer is able to create a new data layer if none exists', () => { delete window['dataLayer']; expect(getOrCreateDataLayer('dataLayer')).to.deep.equal([]); @@ -57,14 +62,24 @@ describe('Gtag wrapping functions', () => { }); it('insertScriptIfNeeded inserts script tag', () => { - expect(findGtagScriptOnPage()).to.be.null; - insertScriptTag('customDataLayerName', fakeMeasurementId); - const scriptTag = findGtagScriptOnPage(); + const customDataLayerName = 'customDataLayerName'; + expect(findGtagScriptOnPage(customDataLayerName)).to.be.null; + insertScriptTag(customDataLayerName, fakeMeasurementId); + const scriptTag = findGtagScriptOnPage(customDataLayerName); expect(scriptTag).to.not.be.null; expect(scriptTag!.src).to.contain(`l=customDataLayerName`); expect(scriptTag!.src).to.contain(`id=${fakeMeasurementId}`); }); + // The test above essentially already touches this functionality but it is still valuable + it('findGtagScriptOnPage returns gtag instance with matching data layer name', () => { + const defaultDataLayerName = 'dataLayer'; + insertScriptTag(defaultDataLayerName, fakeMeasurementId); + const scriptTag = findGtagScriptOnPage(defaultDataLayerName); + expect(scriptTag!.src).to.contain(`l=${defaultDataLayerName}`); + expect(findGtagScriptOnPage('NON_EXISTENT_DATA_LAYER_ID')).to.be.null; + }); + describe('wrapOrCreateGtag() when user has not previously inserted a gtag script tag on this page', () => { afterEach(() => { delete window['gtag']; diff --git a/packages/analytics/src/helpers.ts b/packages/analytics/src/helpers.ts index be713c2c8a0..1fb8a38319c 100644 --- a/packages/analytics/src/helpers.ts +++ b/packages/analytics/src/helpers.ts @@ -318,12 +318,19 @@ export function wrapOrCreateGtag( } /** - * Returns first script tag in DOM matching our gtag url pattern. + * Returns the script tag in the DOM matching both the gtag url pattern + * and the provided data layer name. */ -export function findGtagScriptOnPage(): HTMLScriptElement | null { +export function findGtagScriptOnPage( + dataLayerName: string +): HTMLScriptElement | null { const scriptTags = window.document.getElementsByTagName('script'); for (const tag of Object.values(scriptTags)) { - if (tag.src && tag.src.includes(GTAG_URL)) { + if ( + tag.src && + tag.src.includes(GTAG_URL) && + tag.src.includes(dataLayerName) + ) { return tag; } } diff --git a/packages/analytics/src/index.test.ts b/packages/analytics/src/index.test.ts index 607c2e74a5d..e83eb7f9a77 100644 --- a/packages/analytics/src/index.test.ts +++ b/packages/analytics/src/index.test.ts @@ -26,7 +26,7 @@ import { import { FirebaseApp } from '@firebase/app'; import { GtagCommand } from './constants'; import { findGtagScriptOnPage } from './helpers'; -import { removeGtagScript } from '../testing/gtag-script-util'; +import { removeGtagScripts } from '../testing/gtag-script-util'; import { Deferred } from '@firebase/util'; import { AnalyticsError } from './errors'; import { logEvent } from './api'; @@ -150,7 +150,7 @@ describe('FirebaseAnalytics instance tests', () => { after(() => { delete window['gtag']; delete window['dataLayer']; - removeGtagScript(); + removeGtagScripts(); fetchStub.restore(); clock.restore(); idbOpenStub.restore(); @@ -208,7 +208,7 @@ describe('FirebaseAnalytics instance tests', () => { afterEach(() => { delete window['gtag']; delete window['dataLayer']; - removeGtagScript(); + removeGtagScripts(); fetchStub.restore(); clock.restore(); warnStub.restore(); @@ -304,7 +304,7 @@ describe('FirebaseAnalytics instance tests', () => { after(() => { delete window[customGtagName]; delete window[customDataLayerName]; - removeGtagScript(); + removeGtagScripts(); fetchStub.restore(); clock.restore(); idbOpenStub.restore(); @@ -349,13 +349,13 @@ describe('FirebaseAnalytics instance tests', () => { // Successfully resolves fake IDB open request. fakeRequest.onsuccess(); await initializationPromisesMap[fakeAppParams.appId]; - expect(findGtagScriptOnPage()).to.not.be.null; + expect(findGtagScriptOnPage('dataLayer')).to.not.be.null; expect(typeof window['gtag']).to.equal('function'); expect(Array.isArray(window['dataLayer'])).to.be.true; delete window['gtag']; delete window['dataLayer']; - removeGtagScript(); + removeGtagScripts(); fetchStub.restore(); idbOpenStub.restore(); }); diff --git a/packages/analytics/src/initialize-analytics.test.ts b/packages/analytics/src/initialize-analytics.test.ts index da2c8f964b6..8760a63b2f1 100644 --- a/packages/analytics/src/initialize-analytics.test.ts +++ b/packages/analytics/src/initialize-analytics.test.ts @@ -28,7 +28,7 @@ import { DynamicConfig } from './types'; import { FirebaseApp } from '@firebase/app'; import { Deferred } from '@firebase/util'; import { _FirebaseInstallationsInternal } from '@firebase/installations'; -import { removeGtagScript } from '../testing/gtag-script-util'; +import { removeGtagScripts } from '../testing/gtag-script-util'; import { setDefaultEventParameters } from './api'; import { defaultConsentSettingsForInit, @@ -68,7 +68,7 @@ describe('initializeAnalytics()', () => { }); afterEach(() => { fetchStub.restore(); - removeGtagScript(); + removeGtagScripts(); }); it('gets FID and measurement ID and calls gtag config with them', async () => { stubFetch(); diff --git a/packages/analytics/src/initialize-analytics.ts b/packages/analytics/src/initialize-analytics.ts index f89fcf1ddf7..da79ad9108b 100644 --- a/packages/analytics/src/initialize-analytics.ts +++ b/packages/analytics/src/initialize-analytics.ts @@ -119,8 +119,9 @@ export async function _initializeAnalytics( fidPromise ]); - // Detect if user has already put the gtag