Skip to content

Add internal API to collect platform version info #2368

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

Merged
merged 10 commits into from
Dec 3, 2019
Merged

Conversation

hsubox76
Copy link
Contributor

@hsubox76 hsubox76 commented Nov 18, 2019

Adds an internal VersionService component that each package can create and register to report its own version.

Adds an internal PlatformLoggerService component initialized by the app package, with one method, getPlatformInfoString(), which collects version info from all version services and returns a string formatted according to Firebase's cross-platform X-firebase-client header standards. This string can then be sent to the backend as appropriate (to be implemented in future PRs).

@hsubox76 hsubox76 requested review from mmermerkaya and removed request for bojeil-google and wti806 November 18, 2019 22:38
Copy link
Contributor

@mmermerkaya mmermerkaya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few style comments. Also left some comments on the design doc.

// version components.
return providers
.map(provider => {
const service = provider.getImmediate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const service = provider.getImmediate() as VersionService; and then you can remove the other casts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const component = provider.getComponent();
if (service && component && component.type === ComponentType.VERSION) {
const platformString =
platformLogString[(service as VersionService).library] ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?? instead of || 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for parent branch to merge so I can rebase and upgrade Typescript version to 3.7.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

.map(provider => {
const service = provider.getImmediate();
const component = provider.getComponent();
if (service && component && component.type === ComponentType.VERSION) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

service && component?.type === ComponentType.VERSION

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

import { platformLogString } from './constants';

export class PlatformLoggerService {
constructor(private container: ComponentContainer) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -16,3 +16,17 @@
*/

export const DEFAULT_ENTRY_NAME = '[DEFAULT]';

export const platformLogString: { [key: string]: string } = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to widen the type? Do we expect this const to be extended or modified?

Also, CONST_CASE? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
}

describe('Platform Logger Service Unit Tests', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can nest the two describe blocks in one main describe block.

describe('Platform Logger Service', () => {
  describe('Unit Tests', () => {
    // ...
  }

  describe('Integration Tests', () => {
    // ...
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return null;
}
})
.filter((logString: string | null) => logString)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS should be able to infer this type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type removed.

'remote-config': 'fire-rc',
'storage': 'fire-gcs',
'firestore': 'fire-fst'
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} as const; to make them readonly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return registerComponent(
new Component(
`${library}-version` as Name,
() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

() => new VersionService(library, version),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
registerComponent(
new Component(
`${library}-version` as Name,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this cast TBH, since it defeats the purpose of the Name type. Maybe we shouldn't consider versions actual Components and instead create a different type for them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it might be a good idea in the long term to have separate Map in ComponentContainer in addition to the Providers Map, and this new Map just stores key/value pairs. Can be used for versions now, and other possible key/value pair data going forward. But that requires a bit of modification to ComponentContainer that might not be a good idea right now as we're planning to do our first release with the migration to the component model next week right before a holiday. Since the actual registerVersion API wouldn't change we can change the implementation later when the component model is in use and stable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, sounds good.

@Feiyang1 Feiyang1 force-pushed the fei-component-integration2 branch from 6caf8ef to ae956b1 Compare November 22, 2019 19:06
@hsubox76 hsubox76 changed the base branch from fei-component-integration2 to master November 27, 2019 00:18
@hsubox76 hsubox76 force-pushed the ch-platform branch 2 times, most recently from dcab55e to 5cb2514 Compare November 27, 2019 00:51
this._addComponent(
new Component(
'platform-logger',
(container: ComponentContainer) => new PlatformLoggerService(container),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS should be able to infer this type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

// a good whitelist system.
const platformString =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(PLATFORM_LOG_STRING as any)[service.library] ?? service.library;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of as any you can do this: PLATFORM_LOG_STRING[service.library as keyof typeof PLATFORM_LOG_STRING]. It's a bit longer but you wouldn't have to enable any.

Or in VersionService you can set the type of library to that, but that prevents setting arbitrary library versions and I think that was one of the requirements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

@@ -318,7 +318,6 @@ export class WindowController extends BaseController {
message_time: data[FN_CAMPAIGN_TIME],
message_device_time: Math.floor(Date.now() / 1000)
}
/* eslint-enable camelcase */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why? :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh, that's a bad merge, I keep putting it back in after merges/rebases but it keeps removing it again. I'll put it back.

@@ -60,6 +64,28 @@ export class FirebaseAppImpl implements FirebaseApp {

// add itself to container
this._addComponent(new Component('app', () => this, ComponentType.PUBLIC));
this._addComponent(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component registrations should be moved to index files at the top level, e.g. app/index.ts, app/index.node.ts and etc.

The reason FirebaseApp registration has to happen here is because the container lives within an app instance, but I plan to move the container outside, so FirebaseApp can register just like the other components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a registerPlatformLogger function for the shared registration code between these files.

@@ -234,6 +236,22 @@ export function createFirebaseNamespaceCore(
: null;
}

function registerVersion(library: string, version: string): void {
if (library.match(/\s|\//)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check version too? I'd also change the warning message to include version and to make it clear it is registering a version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a pretty robust warning message now!

// version components.
return providers
.map(provider => {
const service = provider.getImmediate() as VersionService;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

provider.getImmediate() will throw in case of placeholder providers. You can call it with optional flag to make getImmediate() return null instead of throwing. Then check if service is null.

const service = provider.getImmediate({optional: true}) as VersionService;

We can also remove the type cast by using a custom type guard, but it needs a hack:

provider => {

        if (isVersionServiceProvider(provider)) {
          const service = provider.getImmediate();
          // TODO: We can use this check to whitelist strings when/if we set up
          // a good whitelist system.
          const platformString =
            PLATFORM_LOG_STRING[
            service.library as keyof typeof PLATFORM_LOG_STRING
            ] ?? service.library;
          return `${platformString}/${service.version}`;
        } else {
          return null;
        }
      }

/**
 * 
 * @param provider check if this provider provides a VersionService
 * 
 * NOTE: Using Provider<'app-version'> is a hack to indicate that the provider provides VersionService
 * The provider is not necessarily a 'app-version' provider.
 */
function isVersionServiceProvider(provider: Provider<Name>): provider is Provider<'app-version'> {
  const component = provider.getComponent();
  return component?.type === ComponentType.VERSION;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* limitations under the License.
*/

export class VersionService {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel like we can just use a plain Object instead of a class to save an additional file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH classes have very little overhead, but if we're going to use a plain object, could we also export and use a function like this:

function makeVersionService(library: string, service: string) {
  return {library, service};
}

It would make things more readable and would be useful in case we want to change the fields later or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also type this object with an interface, so it's still type safe.

I remember classes gave us treeshaking problems before (#2006), so I'm a little cautious of using them, but the output you linked doesn't have Object.create which was the culprit. Maybe Object.create is being avoided now, or it is only emitted under certain condition?

@@ -48,4 +50,5 @@ export function registerFunctions(instance: _FirebaseNamespace): void {
.setServiceProps(namespaceExports)
.setMultipleInstances(true)
);
instance.registerVersion('functions', version);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning to add this to other SDKs in a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, planning to do a separate PR for every approver group.

Copy link
Member

@Feiyang1 Feiyang1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Just a couple of nits.

)
);
// Register `app` package.
firebase.registerVersion(name, version);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename registerPlatformLogger to registerComponentsAndVersions() or something else that's not specific to platform logger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to registerCoreComponents

}
function isVersionServiceProvider(
provider: Provider<Name>
): provider is Provider<'app-version'> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a comment that Provider<'app-version'> is a hack to indicate that it's a version component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@hsubox76 hsubox76 merged commit fe11de0 into master Dec 3, 2019
@hsubox76 hsubox76 added this to the next milestone Dec 10, 2019
@firebase firebase locked and limited conversation to collaborators Jan 3, 2020
@hsubox76 hsubox76 deleted the ch-platform branch September 15, 2020 16:44
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants