Skip to content

Commit ff5b3a3

Browse files
authored
Create overridable constants file (#9390)
* Create overridable constants file * Switch auto constants to object for extensibility * Refactor * Add doc comment * add es build
1 parent ad0366b commit ff5b3a3

File tree

6 files changed

+145
-37
lines changed

6 files changed

+145
-37
lines changed

packages/telemetry/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"@firebase/app": "0.14.6",
113113
"@opentelemetry/sdk-trace-web": "2.1.0",
114114
"@rollup/plugin-json": "6.1.0",
115+
"@rollup/plugin-replace": "6.0.2",
115116
"@testing-library/dom": "10.4.1",
116117
"@testing-library/react": "16.3.0",
117118
"@types/react": "19.1.13",

packages/telemetry/rollup.config.js

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,25 @@
1717

1818
import json from '@rollup/plugin-json';
1919
import copy from 'rollup-plugin-copy';
20+
import replacePlugin from '@rollup/plugin-replace';
2021
import typescriptPlugin from 'rollup-plugin-typescript2';
2122
import typescript from 'typescript';
2223
import pkg from './package.json';
2324
import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_package_file';
2425

25-
const deps = Object.keys(
26-
Object.assign({}, pkg.peerDependencies, pkg.dependencies)
27-
);
26+
const deps = [
27+
...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)),
28+
'./auto-constants'
29+
];
30+
31+
function replaceSource(path) {
32+
return replacePlugin({
33+
'./src/auto-constants': `'${path}'`,
34+
'../auto-constants': `'${path}'`,
35+
delimiters: ["'", "'"],
36+
preventAssignment: true
37+
});
38+
}
2839

2940
const buildPlugins = [typescriptPlugin({ typescript }), json()];
3041

@@ -36,7 +47,7 @@ const browserBuilds = [
3647
format: 'es',
3748
sourcemap: true
3849
},
39-
plugins: buildPlugins,
50+
plugins: [...buildPlugins, replaceSource('./auto-constants.mjs')],
4051
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
4152
},
4253
{
@@ -46,7 +57,7 @@ const browserBuilds = [
4657
format: 'cjs',
4758
sourcemap: true
4859
},
49-
plugins: buildPlugins,
60+
plugins: [...buildPlugins, replaceSource('./auto-constants.js')],
5061
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
5162
}
5263
];
@@ -59,7 +70,7 @@ const nodeBuilds = [
5970
format: 'cjs',
6071
sourcemap: true
6172
},
62-
plugins: buildPlugins,
73+
plugins: [...buildPlugins, replaceSource('./auto-constants.js')],
6374
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
6475
},
6576
{
@@ -69,7 +80,11 @@ const nodeBuilds = [
6980
format: 'es',
7081
sourcemap: true
7182
},
72-
plugins: [...buildPlugins, emitModulePackageFile()],
83+
plugins: [
84+
...buildPlugins,
85+
emitModulePackageFile(),
86+
replaceSource('../auto-constants.mjs')
87+
],
7388
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
7489
}
7590
];
@@ -100,7 +115,8 @@ const reactBuilds = [
100115
dest: 'dist'
101116
}
102117
]
103-
})
118+
}),
119+
replaceSource('../auto-constants.mjs')
104120
],
105121
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
106122
},
@@ -117,7 +133,8 @@ const reactBuilds = [
117133
typescript,
118134
tsconfig: 'tsconfig.react.json'
119135
}),
120-
json()
136+
json(),
137+
replaceSource('../auto-constants.js')
121138
],
122139
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
123140
}
@@ -148,7 +165,8 @@ const angularBuilds = [
148165
dest: 'dist'
149166
}
150167
]
151-
})
168+
}),
169+
replaceSource('../auto-constants.mjs')
152170
],
153171
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
154172
},
@@ -164,15 +182,36 @@ const angularBuilds = [
164182
typescript,
165183
tsconfig: 'tsconfig.angular.json'
166184
}),
167-
json()
185+
json(),
186+
replaceSource('../auto-constants.js')
168187
],
169188
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
170189
}
171190
];
172191

192+
const autoinitBuild = [
193+
{
194+
input: './src/auto-constants.ts',
195+
output: {
196+
file: './dist/auto-constants.js',
197+
format: 'cjs'
198+
},
199+
plugins: buildPlugins
200+
},
201+
{
202+
input: './src/auto-constants.ts',
203+
output: {
204+
file: './dist/auto-constants.mjs',
205+
format: 'es'
206+
},
207+
plugins: buildPlugins
208+
}
209+
];
210+
173211
export default [
174212
...browserBuilds,
175213
...nodeBuilds,
176214
...reactBuilds,
177-
...angularBuilds
215+
...angularBuilds,
216+
...autoinitBuild
178217
];

packages/telemetry/src/api.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
import { TelemetryService } from './service';
4343
import { registerTelemetry } from './register';
4444
import { _FirebaseInstallationsInternal } from '@firebase/installations';
45+
import { AUTO_CONSTANTS } from './auto-constants';
4546

4647
const PROJECT_ID = 'my-project';
4748
const APP_ID = 'my-appid';
@@ -278,6 +279,7 @@ describe('Top level API', () => {
278279
});
279280

280281
it('should use explicit app version when provided', () => {
282+
AUTO_CONSTANTS.appVersion = '1.2.3'; // Unused
281283
const telemetry = new TelemetryService(
282284
fakeTelemetry.app,
283285
fakeTelemetry.loggerProvider
@@ -296,6 +298,19 @@ describe('Top level API', () => {
296298
});
297299
});
298300

301+
it('should use auto constants if available', () => {
302+
AUTO_CONSTANTS.appVersion = '1.2.3';
303+
304+
captureError(fakeTelemetry, 'a string error');
305+
306+
expect(emittedLogs.length).to.equal(1);
307+
const log = emittedLogs[0];
308+
expect(log.attributes).to.deep.equal({
309+
[LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION]: '1.2.3',
310+
[LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID]: MOCK_SESSION_ID
311+
});
312+
});
313+
299314
describe('Session Metadata', () => {
300315
it('should generate and store a new session ID if none exists', () => {
301316
captureError(fakeTelemetry, 'error');

packages/telemetry/src/api.ts

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,13 @@
1616
*/
1717

1818
import { _getProvider, FirebaseApp, getApp } from '@firebase/app';
19-
import {
20-
LOG_ENTRY_ATTRIBUTE_KEYS,
21-
TELEMETRY_SESSION_ID_KEY,
22-
TELEMETRY_TYPE
23-
} from './constants';
19+
import { LOG_ENTRY_ATTRIBUTE_KEYS, TELEMETRY_TYPE } from './constants';
2420
import { Telemetry, TelemetryOptions } from './public-types';
2521
import { Provider } from '@firebase/component';
2622
import { AnyValueMap, SeverityNumber } from '@opentelemetry/api-logs';
2723
import { trace } from '@opentelemetry/api';
2824
import { TelemetryService } from './service';
25+
import { getAppVersion, getSessionId } from './helpers';
2926

3027
declare module '@firebase/component' {
3128
interface NameServiceMapping {
@@ -97,28 +94,12 @@ export function captureError(
9794
}
9895

9996
// Add app version metadata
100-
let appVersion = 'unset';
101-
// TODO: implement app version fallback logic
102-
if ((telemetry as TelemetryService).options?.appVersion) {
103-
appVersion = (telemetry as TelemetryService).options!.appVersion!;
104-
}
105-
customAttributes[LOG_ENTRY_ATTRIBUTE_KEYS.APP_VERSION] = appVersion;
97+
customAttributes['app.version'] = getAppVersion(telemetry);
10698

10799
// Add session ID metadata
108-
if (
109-
typeof sessionStorage !== 'undefined' &&
110-
typeof crypto?.randomUUID === 'function'
111-
) {
112-
try {
113-
let sessionId = sessionStorage.getItem(TELEMETRY_SESSION_ID_KEY);
114-
if (!sessionId) {
115-
sessionId = crypto.randomUUID();
116-
sessionStorage.setItem(TELEMETRY_SESSION_ID_KEY, sessionId);
117-
}
118-
customAttributes[LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID] = sessionId;
119-
} catch (e) {
120-
// Ignore errors accessing sessionStorage (e.g. security restrictions)
121-
}
100+
const sessionId = getSessionId();
101+
if (sessionId) {
102+
customAttributes[LOG_ENTRY_ATTRIBUTE_KEYS.SESSION_ID] = sessionId;
122103
}
123104

124105
if (error instanceof Error) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @license
3+
* Copyright 2025 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+
/**
19+
* A map of constants intended to be optionally overwritten during the application build process.
20+
* The supported keys are:
21+
* - appVersion: string indicating the version of source code being deployed (eg. git commit hash)
22+
*/
23+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
24+
export const AUTO_CONSTANTS: any = {};

packages/telemetry/src/helpers.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @license
3+
* Copyright 2025 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 * as constants from './auto-constants';
19+
import { TELEMETRY_SESSION_ID_KEY } from './constants';
20+
import { Telemetry } from './public-types';
21+
import { TelemetryService } from './service';
22+
23+
export function getAppVersion(telemetry: Telemetry): string {
24+
if ((telemetry as TelemetryService).options?.appVersion) {
25+
return (telemetry as TelemetryService).options!.appVersion!;
26+
} else if (constants.AUTO_CONSTANTS?.appVersion) {
27+
return constants.AUTO_CONSTANTS.appVersion;
28+
}
29+
return 'unset';
30+
}
31+
32+
export function getSessionId(): string | undefined {
33+
if (
34+
typeof sessionStorage !== 'undefined' &&
35+
typeof crypto?.randomUUID === 'function'
36+
) {
37+
try {
38+
let sessionId = sessionStorage.getItem(TELEMETRY_SESSION_ID_KEY);
39+
if (!sessionId) {
40+
sessionId = crypto.randomUUID();
41+
sessionStorage.setItem(TELEMETRY_SESSION_ID_KEY, sessionId);
42+
}
43+
return sessionId;
44+
} catch (e) {
45+
// Ignore errors accessing sessionStorage (e.g. security restrictions)
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)