Skip to content

Commit fe11de0

Browse files
authored
Add internal API to collect platform version info (#2368)
Add platform version tracking and logging API.
1 parent e4a987e commit fe11de0

26 files changed

+405
-7
lines changed

config/tsconfig.base.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
],
1818
"module": "ES2015",
1919
"moduleResolution": "node",
20+
"resolveJsonModule": true,
2021
"sourceMap": true,
2122
"target": "es5",
2223
"typeRoots": [

packages/analytics/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import {
2828
} from '@firebase/component';
2929
import { ERROR_FACTORY, AnalyticsError } from './src/errors';
3030

31+
import { name, version } from './package.json';
32+
3133
declare global {
3234
interface Window {
3335
[key: string]: unknown;
@@ -62,6 +64,8 @@ export function registerAnalytics(instance: _FirebaseNamespace): void {
6264
new Component('analytics-internal', internalFactory, ComponentType.PRIVATE)
6365
);
6466

67+
instance.registerVersion(name, version);
68+
6569
function internalFactory(
6670
container: ComponentContainer
6771
): FirebaseAnalyticsInternal {

packages/analytics/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"rollup-plugin-commonjs": "10.1.0",
3636
"rollup-plugin-json": "4.0.0",
3737
"rollup-plugin-node-resolve": "5.2.0",
38+
"rollup-plugin-typescript2": "0.25.2",
3839
"rollup-plugin-uglify": "6.0.3",
3940
"typescript": "3.7.2"
4041
},

packages/app-types/index.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,26 @@ export interface FirebaseNamespace {
9898
*/
9999
apps: FirebaseApp[];
100100

101+
/**
102+
* Registers a library's name and version for platform logging purposes.
103+
* @param library Name of 1p or 3p library (e.g. firestore, angularfire)
104+
* @param version Current version of that library.
105+
*/
106+
registerVersion(library: string, version: string): void;
107+
101108
// The current SDK version.
102109
SDK_VERSION: string;
103110
}
104111

112+
export interface VersionService {
113+
library: string;
114+
version: string;
115+
}
116+
105117
declare module '@firebase/component' {
106118
interface NameServiceMapping {
107119
'app': FirebaseApp;
120+
'app-version': VersionService;
121+
'platform-identifier': VersionService;
108122
}
109123
}

packages/app-types/private.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export interface FirebaseServiceFactory {
5656
): FirebaseService;
5757
}
5858

59+
export interface PlatformLoggerService {
60+
getPlatformInfoString(): string;
61+
}
62+
5963
/**
6064
* All ServiceNamespaces extend from FirebaseServiceNamespace
6165
*/
@@ -154,3 +158,9 @@ export interface _FirebaseNamespace extends FirebaseNamespace {
154158
ErrorFactory: typeof ErrorFactory;
155159
};
156160
}
161+
162+
declare module '@firebase/component' {
163+
interface NameServiceMapping {
164+
'platform-logger': PlatformLoggerService;
165+
}
166+
}

packages/app/index.lite.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
*/
1717

1818
import { createFirebaseNamespaceLite } from './src/lite/firebaseNamespaceLite';
19+
import { registerCoreComponents } from './src/registerCoreComponents';
1920

2021
export const firebase = createFirebaseNamespaceLite();
2122

23+
registerCoreComponents(firebase);
24+
2225
// eslint-disable-next-line import/no-default-export
2326
export default firebase;

packages/app/index.node.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { firebase as _firebase } from './src/firebaseNamespace';
2323
import Storage from 'dom-storage';
2424
// @ts-ignore
2525
import { XMLHttpRequest } from 'xmlhttprequest';
26+
import { registerCoreComponents } from './src/registerCoreComponents';
2627

2728
(_firebase as _FirebaseNamespace).INTERNAL.extendNamespace({
2829
INTERNAL: {
@@ -36,5 +37,7 @@ import { XMLHttpRequest } from 'xmlhttprequest';
3637

3738
export const firebase = _firebase as FirebaseNamespace;
3839

40+
registerCoreComponents(firebase);
41+
3942
// eslint-disable-next-line import/no-default-export
4043
export default firebase;

packages/app/index.rn.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import { FirebaseNamespace } from '@firebase/app-types';
1919
import { _FirebaseNamespace } from '@firebase/app-types/private';
2020
import { firebase as _firebase } from './src/firebaseNamespace';
21+
import { registerCoreComponents } from './src/registerCoreComponents';
22+
2123
/**
2224
* To avoid having to include the @types/react-native package, which breaks
2325
* some of our tests because of duplicate symbols, we are using require syntax
@@ -36,5 +38,7 @@ const { AsyncStorage } = require('react-native');
3638

3739
export const firebase = _firebase as FirebaseNamespace;
3840

41+
registerCoreComponents(firebase);
42+
3943
// eslint-disable-next-line import/no-default-export
4044
export default firebase;

packages/app/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { FirebaseNamespace } from '@firebase/app-types';
1919
import { firebase as firebaseNamespace } from './src/firebaseNamespace';
2020
import { isNode, isBrowser } from '@firebase/util';
2121
import { logger } from './src/logger';
22+
import { registerCoreComponents } from './src/registerCoreComponents';
2223

2324
// Firebase Lite detection
2425
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -67,5 +68,7 @@ firebaseNamespace.initializeApp = function(...args: any) {
6768

6869
export const firebase = firebaseNamespace;
6970

71+
registerCoreComponents(firebase);
72+
7073
// eslint-disable-next-line import/no-default-export
7174
export default firebase;

packages/app/src/constants.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,33 @@
1616
*/
1717

1818
export const DEFAULT_ENTRY_NAME = '[DEFAULT]';
19+
import { name as appName } from '../package.json';
20+
import { name as analyticsName } from '../../analytics/package.json';
21+
import { name as authName } from '../../auth/package.json';
22+
import { name as databaseName } from '../../database/package.json';
23+
import { name as functionsName } from '../../functions/package.json';
24+
import { name as messagingName } from '../../messaging/package.json';
25+
import { name as performanceName } from '../../performance/package.json';
26+
import { name as remoteConfigName } from '../../remote-config/package.json';
27+
import { name as storageName } from '../../storage/package.json';
28+
import { name as firestoreName } from '../../firestore/package.json';
29+
30+
export const PLATFORM_LOG_STRING = {
31+
[appName]: 'fire-core',
32+
[analyticsName]: 'fire-analytics',
33+
[authName]: 'fire-auth',
34+
[databaseName]: 'fire-rtdb',
35+
[functionsName]: 'fire-fn',
36+
[messagingName]: 'fire-fcm',
37+
[performanceName]: 'fire-perf',
38+
[remoteConfigName]: 'fire-rc',
39+
[storageName]: 'fire-gcs',
40+
[firestoreName]: 'fire-fst',
41+
'fire-js': 'fire-js', // Platform identifier for JS SDK.
42+
'fire-js-all-app': 'fire-js-all-app', // firebase/app import
43+
'fire-js-all': 'fire-js-all', // 'firebase' import
44+
'fire-js-all-node': 'fire-js-all-node',
45+
'fire-js-all-rn': 'fire-js-all-rn',
46+
'fire-js-all-lite': 'fire-js-all-lite',
47+
'fire-js-all-cdn': 'fire-js-all-cdn'
48+
} as const;

packages/app/src/firebaseNamespaceCore.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ import { deepExtend, contains } from '@firebase/util';
3131
import { FirebaseAppImpl } from './firebaseApp';
3232
import { ERROR_FACTORY, AppError } from './errors';
3333
import { FirebaseAppLiteImpl } from './lite/firebaseAppLite';
34-
import { DEFAULT_ENTRY_NAME } from './constants';
34+
import { DEFAULT_ENTRY_NAME, PLATFORM_LOG_STRING } from './constants';
3535
import { version } from '../../firebase/package.json';
3636
import { logger } from './logger';
37-
import { Component, ComponentType } from '@firebase/component';
37+
import { Component, ComponentType, Name } from '@firebase/component';
3838

3939
/**
4040
* Because auth can't share code with other components, we attach the utility functions
@@ -59,6 +59,7 @@ export function createFirebaseNamespaceCore(
5959
initializeApp,
6060
// @ts-ignore
6161
app,
62+
registerVersion,
6263
// @ts-ignore
6364
apps: null,
6465
SDK_VERSION: version,
@@ -234,6 +235,41 @@ export function createFirebaseNamespaceCore(
234235
: null;
235236
}
236237

238+
function registerVersion(libraryKeyOrName: string, version: string): void {
239+
// TODO: We can use this check to whitelist strings when/if we set up
240+
// a good whitelist system.
241+
const library = PLATFORM_LOG_STRING[libraryKeyOrName] ?? libraryKeyOrName;
242+
const libraryMismatch = library.match(/\s|\//);
243+
const versionMismatch = version.match(/\s|\//);
244+
if (libraryMismatch || versionMismatch) {
245+
const warning = [
246+
`Unable to register library "${library}" with version "${version}":`
247+
];
248+
if (libraryMismatch) {
249+
warning.push(
250+
`library name "${library}" contains illegal characters (whitespace or "/")`
251+
);
252+
}
253+
if (libraryMismatch && versionMismatch) {
254+
warning.push('and');
255+
}
256+
if (versionMismatch) {
257+
warning.push(
258+
`version name "${version}" contains illegal characters (whitespace or "/")`
259+
);
260+
}
261+
logger.warn(warning.join(' '));
262+
return;
263+
}
264+
registerComponent(
265+
new Component(
266+
`${library}-version` as Name,
267+
() => ({ library, version }),
268+
ComponentType.VERSION
269+
)
270+
);
271+
}
272+
237273
// Map the requested service to a registered service name
238274
// (used to map auth to serverAuth service when needed).
239275
function useAsService(app: FirebaseApp, name: string): string | null {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
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 {
19+
ComponentContainer,
20+
ComponentType,
21+
Provider,
22+
Name
23+
} from '@firebase/component';
24+
25+
export class PlatformLoggerService {
26+
constructor(private readonly container: ComponentContainer) {}
27+
// In initial implementation, this will be called by installations on
28+
// auth token refresh, and installations will send this string.
29+
getPlatformInfoString(): string {
30+
const providers = this.container.getProviders();
31+
// Loop through providers and get library/version pairs from any that are
32+
// version components.
33+
return providers
34+
.map(provider => {
35+
if (isVersionServiceProvider(provider)) {
36+
const service = provider.getImmediate();
37+
return `${service.library}/${service.version}`;
38+
} else {
39+
return null;
40+
}
41+
})
42+
.filter(logString => logString)
43+
.join(' ');
44+
}
45+
}
46+
/**
47+
*
48+
* @param provider check if this provider provides a VersionService
49+
*
50+
* NOTE: Using Provider<'app-version'> is a hack to indicate that the provider
51+
* provides VersionService. The provider is not necessarily a 'app-version'
52+
* provider.
53+
*/
54+
function isVersionServiceProvider(
55+
provider: Provider<Name>
56+
): provider is Provider<'app-version'> {
57+
const component = provider.getComponent();
58+
return component?.type === ComponentType.VERSION;
59+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
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 { FirebaseNamespace } from '@firebase/app-types';
19+
import { _FirebaseNamespace } from '@firebase/app-types/private';
20+
import { Component, ComponentType } from '@firebase/component';
21+
import { PlatformLoggerService } from './platformLoggerService';
22+
import { name, version } from '../package.json';
23+
24+
export function registerCoreComponents(firebase: FirebaseNamespace): void {
25+
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
26+
new Component(
27+
'platform-logger',
28+
container => new PlatformLoggerService(container),
29+
ComponentType.PRIVATE
30+
)
31+
);
32+
// Register `app` package.
33+
firebase.registerVersion(name, version);
34+
// Register platform SDK identifier (no version).
35+
firebase.registerVersion('fire-js', '');
36+
}

packages/app/test/firebaseApp.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,52 @@ function executeFirebaseTests(): void {
121121
);
122122
});
123123
});
124+
125+
describe('Firebase Version Registration', () => {
126+
let firebase: FirebaseNamespace;
127+
128+
beforeEach(() => {
129+
firebase = createFirebaseNamespace();
130+
});
131+
132+
it('will register an official version component without warnings', () => {
133+
const warnStub = stub(console, 'warn');
134+
const { components } = (firebase as _FirebaseNamespace).INTERNAL;
135+
const initialSize = components.size;
136+
137+
firebase.registerVersion('@firebase/analytics', '1.2.3');
138+
expect(components.get('fire-analytics-version')).to.exist;
139+
expect(components.size).to.equal(initialSize + 1);
140+
141+
expect(warnStub.called).to.be.false;
142+
});
143+
144+
it('will register an arbitrary version component without warnings', () => {
145+
const warnStub = stub(console, 'warn');
146+
const { components } = (firebase as _FirebaseNamespace).INTERNAL;
147+
const initialSize = components.size;
148+
149+
firebase.registerVersion('angularfire', '1.2.3');
150+
expect(components.get('angularfire-version')).to.exist;
151+
expect(components.size).to.equal(initialSize + 1);
152+
153+
expect(warnStub.called).to.be.false;
154+
});
155+
156+
it('will do nothing if registerVersion() is given illegal characters', () => {
157+
const warnStub = stub(console, 'warn');
158+
const { components } = (firebase as _FirebaseNamespace).INTERNAL;
159+
const initialSize = components.size;
160+
161+
firebase.registerVersion('remote config', '1.2.3');
162+
expect(warnStub.args[0][1]).to.include('library name "remote config"');
163+
expect(components.size).to.equal(initialSize);
164+
165+
firebase.registerVersion('remote-config', '1.2/3');
166+
expect(warnStub.args[1][1]).to.include('version name "1.2/3"');
167+
expect(components.size).to.equal(initialSize);
168+
});
169+
});
124170
}
125171

126172
function executeFirebaseLiteTests(): void {

0 commit comments

Comments
 (0)