Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions common/api-review/telemetry-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { FirebaseApp } from '@firebase/app';
import { FirebaseOptions } from '@firebase/app';
import { LoggerProvider } from '@opentelemetry/sdk-logs';

// @public
export interface DynamicHeaderProvider {
getHeader(): Promise<Record<string, string> | null>;
}

// @public
export function FirebaseTelemetry({ firebaseOptions, telemetryOptions }: {
firebaseOptions?: FirebaseOptions;
Expand Down
5 changes: 5 additions & 0 deletions common/api-review/telemetry.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import { LoggerProvider } from '@opentelemetry/sdk-logs';
// @public
export function captureError(telemetry: Telemetry, error: unknown, attributes?: AnyValueMap): void;

// @public
export interface DynamicHeaderProvider {
getHeader(): Promise<Record<string, string> | null>;
}

// @public
export function flush(telemetry: Telemetry): Promise<void>;

Expand Down
4 changes: 4 additions & 0 deletions docs-devsite/_toc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -697,13 +697,17 @@ toc:
- title: telemetry
path: /docs/reference/js/telemetry_.md
section:
- title: DynamicHeaderProvider
path: /docs/reference/js/telemetry_.dynamicheaderprovider.md
- title: Telemetry
path: /docs/reference/js/telemetry_.telemetry.md
- title: TelemetryOptions
path: /docs/reference/js/telemetry_.telemetryoptions.md
- title: telemetry/react
path: /docs/reference/js/telemetry_react.md
section:
- title: DynamicHeaderProvider
path: /docs/reference/js/telemetry_react.dynamicheaderprovider.md
- title: Telemetry
path: /docs/reference/js/telemetry_react.telemetry.md
- title: TelemetryOptions
Expand Down
43 changes: 43 additions & 0 deletions docs-devsite/telemetry_.dynamicheaderprovider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Project: /docs/reference/js/_project.yaml
Book: /docs/reference/_book.yaml
page_type: reference

{% comment %}
DO NOT EDIT THIS FILE!
This is generated by the JS SDK team, and any local changes will be
overwritten. Changes should be made in the source code at
https://github.com/firebase/firebase-js-sdk
{% endcomment %}

# DynamicHeaderProvider interface
An interface for classes that provide dynamic headers.

Classes that implement this interface can be used to supply custom headers for logging.

<b>Signature:</b>

```typescript
export interface DynamicHeaderProvider
```

## Methods

| Method | Description |
| --- | --- |
| [getHeader()](./telemetry_.dynamicheaderprovider.md#dynamicheaderprovidergetheader) | Returns a record of headers to be added to a request. |

## DynamicHeaderProvider.getHeader()

Returns a record of headers to be added to a request.

<b>Signature:</b>

```typescript
getHeader(): Promise<Record<string, string> | null>;
```
<b>Returns:</b>

Promise&lt;Record&lt;string, string&gt; \| null&gt;

A that resolves to a of header key-value pairs, or null if no headers are to be added.

1 change: 1 addition & 0 deletions docs-devsite/telemetry_.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ https://github.com/firebase/firebase-js-sdk

| Interface | Description |
| --- | --- |
| [DynamicHeaderProvider](./telemetry_.dynamicheaderprovider.md#dynamicheaderprovider_interface) | An interface for classes that provide dynamic headers.<!-- -->Classes that implement this interface can be used to supply custom headers for logging. |
| [Telemetry](./telemetry_.telemetry.md#telemetry_interface) | An instance of the Firebase Telemetry SDK.<!-- -->Do not create this instance directly. Instead, use [getTelemetry()](./telemetry_.md#gettelemetry_448bdc6)<!-- -->. |
| [TelemetryOptions](./telemetry_.telemetryoptions.md#telemetryoptions_interface) | Options for initialized the Telemetry service using [getTelemetry()](./telemetry_.md#gettelemetry_448bdc6)<!-- -->. |

Expand Down
43 changes: 43 additions & 0 deletions docs-devsite/telemetry_react.dynamicheaderprovider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Project: /docs/reference/js/_project.yaml
Book: /docs/reference/_book.yaml
page_type: reference

{% comment %}
DO NOT EDIT THIS FILE!
This is generated by the JS SDK team, and any local changes will be
overwritten. Changes should be made in the source code at
https://github.com/firebase/firebase-js-sdk
{% endcomment %}

# DynamicHeaderProvider interface
An interface for classes that provide dynamic headers.

Classes that implement this interface can be used to supply custom headers for logging.

<b>Signature:</b>

```typescript
export interface DynamicHeaderProvider
```

## Methods

| Method | Description |
| --- | --- |
| [getHeader()](./telemetry_react.dynamicheaderprovider.md#dynamicheaderprovidergetheader) | Returns a record of headers to be added to a request. |

## DynamicHeaderProvider.getHeader()

Returns a record of headers to be added to a request.

<b>Signature:</b>

```typescript
getHeader(): Promise<Record<string, string> | null>;
```
<b>Returns:</b>

Promise&lt;Record&lt;string, string&gt; \| null&gt;

A that resolves to a of header key-value pairs, or null if no headers are to be added.

1 change: 1 addition & 0 deletions docs-devsite/telemetry_react.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ https://github.com/firebase/firebase-js-sdk

| Interface | Description |
| --- | --- |
| [DynamicHeaderProvider](./telemetry_react.dynamicheaderprovider.md#dynamicheaderprovider_interface) | An interface for classes that provide dynamic headers.<!-- -->Classes that implement this interface can be used to supply custom headers for logging. |
| [Telemetry](./telemetry_react.telemetry.md#telemetry_interface) | An instance of the Firebase Telemetry SDK.<!-- -->Do not create this instance directly. Instead, use [getTelemetry()](./telemetry_.md#gettelemetry_448bdc6)<!-- -->. |
| [TelemetryOptions](./telemetry_react.telemetryoptions.md#telemetryoptions_interface) | Options for initialized the Telemetry service using [getTelemetry()](./telemetry_.md#gettelemetry_448bdc6)<!-- -->. |

Expand Down
51 changes: 51 additions & 0 deletions packages/telemetry/src/logging/appcheck-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { DynamicHeaderProvider } from '../public-types';
import { Provider } from '@firebase/component';
import {
FirebaseAppCheckInternal,
AppCheckInternalComponentName
} from '@firebase/app-check-interop-types';

export class AppCheckProvider implements DynamicHeaderProvider {
appCheck: FirebaseAppCheckInternal | null;

constructor(appCheckProvider: Provider<AppCheckInternalComponentName>) {
this.appCheck = appCheckProvider?.getImmediate({ optional: true });
if (!this.appCheck) {
void appCheckProvider
?.get()
.then(appCheck => (this.appCheck = appCheck))
.catch();
}
}

async getHeader(): Promise<Record<string, string> | null> {
if (!this.appCheck) {
return null;
}

const appCheckToken = await this.appCheck.getToken();
// If the error field is defined, the token field will be populated with a dummy token
if (!appCheckToken || !!appCheckToken.error) {
return null;
}

return {'X-Firebase-AppCheck': appCheckToken.token};
}
}
78 changes: 78 additions & 0 deletions packages/telemetry/src/logging/fetch-transport.edge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import * as sinon from 'sinon';
import * as assert from 'assert';
import { DynamicHeaderProvider } from '../public-types';
import { FetchTransportEdge } from './fetch-transport.edge';
import {
ExportResponseRetryable,
Expand Down Expand Up @@ -173,5 +174,82 @@ describe('FetchTransportEdge', () => {
}, done /* catch any rejections */);
clock.tick(requestTimeout + 100);
});

it('attaches static and dynamic headers to the request', done => {
// arrange
const fetchStub = sinon
.stub(globalThis, 'fetch')
.resolves(new Response('test response', { status: 200 }));

const dynamicProvider: DynamicHeaderProvider = {
getHeader: sinon.stub().resolves({ 'dynamic-header': 'dynamic-value' })
};

const transport = new FetchTransportEdge({
...testTransportParameters,
dynamicHeaders: [dynamicProvider]
});

//act
transport.send(testPayload, requestTimeout).then(response => {
// assert
try {
assert.strictEqual(response.status, 'success');
sinon.assert.calledOnceWithMatch(
fetchStub,
testTransportParameters.url,
{
method: 'POST',
headers: {
foo: 'foo-value',
bar: 'bar-value',
'Content-Type': 'application/json',
'dynamic-header': 'dynamic-value'
},
body: testPayload
}
);
done();
} catch (e) {
done(e);
}
}, done /* catch any rejections */);
});

it('handles dynamic header providers that return null', done => {
// arrange
const fetchStub = sinon
.stub(globalThis, 'fetch')
.resolves(new Response('test response', { status: 200 }));

const dynamicProvider: DynamicHeaderProvider = {
getHeader: sinon.stub().resolves(null)
};

const transport = new FetchTransportEdge({
...testTransportParameters,
dynamicHeaders: [dynamicProvider]
});

//act
transport.send(testPayload, requestTimeout).then(response => {
// assert
try {
assert.strictEqual(response.status, 'success');
sinon.assert.calledOnceWithMatch(
fetchStub,
testTransportParameters.url,
{
method: 'POST',
headers: testTransportParameters.headers(),
body: testPayload
}
);
done();
} catch (e) {
done(e);
}
}, done /* catch any rejections */);
});
});
});
22 changes: 21 additions & 1 deletion packages/telemetry/src/logging/fetch-transport.edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
ExportResponse
} from '@opentelemetry/otlp-exporter-base';
import { diag } from '@opentelemetry/api';
import { DynamicHeaderProvider } from '../public-types';

function isExportRetryable(statusCode: number): boolean {
const retryCodes = [429, 502, 503, 504];
Expand Down Expand Up @@ -53,6 +54,7 @@ function parseRetryAfterToMills(
export interface FetchTransportParameters {
url: string;
headers: () => Record<string, string>;
dynamicHeaders?: DynamicHeaderProvider[];
}

/**
Expand All @@ -68,9 +70,27 @@ export class FetchTransportEdge implements IExporterTransport {
const timeout = setTimeout(() => abortController.abort(), timeoutMillis);
try {
const url = new URL(this.parameters.url);
const headers = this.parameters.headers();

if (
this.parameters.dynamicHeaders &&
this.parameters.dynamicHeaders.length > 0
) {
const dynamicHeaderPromises = this.parameters.dynamicHeaders.map(
provider => provider.getHeader()
);
const resolvedHeaders = await Promise.all(dynamicHeaderPromises);

for (const header of resolvedHeaders) {
if (header) {
Object.assign(headers, header);
}
}
}

const body = {
method: 'POST',
headers: this.parameters.headers(),
headers,
signal: abortController.signal,
keepalive: false,
mode: 'cors',
Expand Down
Loading
Loading