Skip to content

Commit

Permalink
Fixes #75: Adapter for Firebase Remote Config (#76)
Browse files Browse the repository at this point in the history
* Fixes #75: Adapter for Firebase Remote Config

* Don't fail if decode function throws
  • Loading branch information
baltpeter authored Oct 3, 2024
1 parent f9b367f commit 1434f84
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 4 deletions.
1 change: 1 addition & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"facebook-audience-network": "Meta Audience Network is a service by Facebook that allows app developers to monetize their apps with ads.[https://developers.facebook.com/docs/audience-network] Facebook offers Audience Network SDKs for Android[https://developers.facebook.com/docs/audience-network/setting-up/platform-setup/android/add-sdk], iOS[https://developers.facebook.com/docs/audience-network/setting-up/platform-setup/ios/add-sdk], and Unity[https://developers.facebook.com/docs/audience-network/setting-up/platform-steup/unity/add-sdk].",
"facebook-graph-app-events": "The Graph API is provided by Facebook to “get data into and out of the Facebook platform”.[https://developers.facebook.com/docs/graph-api/overview] It can be accessed through the Facebook SDKs for Android[https://developers.facebook.com/docs/android/graph] and iOS[https://developers.facebook.com/docs/ios/graph].\n\nThe App Events endpoint allows developers to “track actions that occur in [a] mobile app or web page such as app installs and purchase events” in order to “measure ad performance and build audiences for ad targeting”. The Facebook SDK automatically logs app installs, app sessions, and in-app purchases using this endpoint. Additionally, developers can manually log their own events.[https://developers.facebook.com/docs/marketing-api/app-event-api]",
"firebaseinstallations": "The Firebase Installations service (FIS) provides a unique identifier for each installed instance of a Firebase app, called Firebase installation ID (FID).[https://firebase.google.com/docs/projects/manage-installations] “Many Firebase services depend on the Firebase Installations API in order to identify app installs and/or authenticate client requests to their servers.”[https://console.cloud.google.com/marketplace/product/google/firebaseinstallations.googleapis.com] Other purposes include user segmentation, message delivery, performance monitoring, tracking the number of unique users, or selecting which configuration values to return.[https://firebase.google.com/docs/projects/manage-installations]\n\nFIDs can also be used by Google Analytics for attribution.[https://firebase.google.com/docs/projects/manage-installations]",
"firebaseremoteconfig": "The Firebase Remote Config service lets developers remotely change the functionality and appearance of their apps without having to publish an app update.[https://firebase.google.com/docs/remote-config]\n\nDevelopers can use user segmentation and return different configuration to different users based on app version, language, Google Analytics audience, and imported segment.[https://firebase.google.com/docs/remote-config] They can also “[use] machine learning to continuously tailor individual user experience to optimize for goals like user engagement, ad clicks, and revenue—or any custom event [they] can measure with Google Analytics”.[https://firebase.google.com/docs/remote-config] Finally, they can use A/B testing.[https://firebase.google.com/docs/remote-config]",
"googledatatransport-batchlog": "The GoogleDataTransport SDK is a transport layer used internally by many other Firebase (e.g. Crashlytics, Performance, Core) Google (e.g. ML Kit) SDKs and services.[https://github.com/firebase/firebase-ios-sdk/issues/8220#issuecomment-858040701] It batches application-specific data from within an app to Google, using a common endpoint regardless of the actual SDK that was integrated by the app developer.[https://stackoverflow.com/a/76334853]",
"infonline": "INFOnline provides digital audience measurement for websites and apps.[https://www.infonline.de/en/]\n\nThey offer two separate measurement systems: Census Measurement (IOMb[https://www.infonline.de/download/?wpdmdl=7135]) and INFOnline Measurement pseudonymous (IOMp[https://www.infonline.de/download/?wpdmdl=7135], formerly SZMnG[https://www.infonline.de/faqs/]). Census Measurement can be recognized by the URL path fragment “base.io”, whereas INFOnline Measurement pseudonymous uses “tx.io”.[https://docs.infonline.de/infonline-measurement/en/integration/web/checkliste_web_allgemein/]\n\nINFOnline boasts with constantly adapting their technology in order to circumvent browser restrictions, ad and tracking blockers, and privacy-preserving changes by operating systems.[https://www.infonline.de/measurement/]",
"infonline-pseudonymous": "Unlike Census Measurement, which works anonymously without identifiers, INFOnline Measurement pseudonymous “is designed as a pseudonymous system (with client identifiers)”.[https://docs.infonline.de/infonline-measurement/en/getting-started/verfahrensbeschreibung/]\n\nAccording INFOnline’s own documentation, “[…] the pseudonymous INFOnline Measurement may only be loaded and executed if there is active consent from the user of [the] web page. […] The following also applies to apps: Only start the session of pseudonymous measurement if you have the user's consent.”[https://docs.infonline.de/infonline-measurement/en/getting-started/rechtliche-auswirkungen/]",
Expand Down
22 changes: 22 additions & 0 deletions research-docs/google/appInstanceIdToken.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The `appInstanceIdToken` property and `X-Goog-Firebase-Installations-Auth` header contain a JWT token that is used to identify and authenticate the user.

The token at least contains the [Firebase Installation ID](https://firebase.google.com/docs/projects/manage-installations) (FID), which uniquely identifies an installation of an app on a device.

### Example

[Token](https://data.tweasel.org/data/requests/monkey-april-2024,2629):

```
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6IjE6MTA2NTQ3MzU5ODYyNDphbmRyb2lkOmQ3ODIwNzU2YzVjODA4ZGIwM2Q1YzUiLCJleHAiOjE3MTUwMDY3MDAsImZpZCI6ImVkeElubzhmVGY2WDR5X0s1WjYxNVAiLCJwcm9qZWN0TnVtYmVyIjoxMDY1NDczNTk4NjI0fQ.AB2LPV8wRAIgKWmNcG6EyL96sJbjdrK4fqE2IYusPUVw-cvxvQPfAOQCIBUOMpAbN88ZsfNHvUurgwOv7w8u1zT-7t4_mx_Wp8kN
```

[Decoded](https://cyberchef.bn.al/#recipe=JWT_Decode()&input=ZXlKaGJHY2lPaUpGVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmhjSEJKWkNJNklqRTZNVEEyTlRRM016VTVPRFl5TkRwaGJtUnliMmxrT21RM09ESXdOelUyWXpWak9EQTRaR0l3TTJRMVl6VWlMQ0psZUhBaU9qRTNNVFV3TURZM01EQXNJbVpwWkNJNkltVmtlRWx1YnpobVZHWTJXRFI1WDBzMVdqWXhOVkFpTENKd2NtOXFaV04wVG5WdFltVnlJam94TURZMU5EY3pOVGs0TmpJMGZRLkFCMkxQVjh3UkFJZ0tXbU5jRzZFeUw5NnNKYmpkcks0ZnFFMklZdXNQVVZ3LWN2eHZRUGZBT1FDSUJVT01wQWJOODhac2ZOSHZVdXJnd092N3c4dTF6VC03dDRfbXhfV3A4a04):

```json
{
"appId": "1:1065473598624:android:d7820756c5c808db03d5c5",
"exp": 1715006700,
"fid": "edxIno8fTf6X4y_K5Z615P",
"projectNumber": 1065473598624
}
```
149 changes: 149 additions & 0 deletions src/adapters/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1528,4 +1528,153 @@ export const adapters: Adapter[] = [
],
containedDataPaths: containedDataPathsIdentityToolkitHeaders,
},

{
slug: 'firebaseremoteconfig',
name: 'Firebase Remote Config',
description: 'firebaseremoteconfig',
tracker,

endpointUrls: [
/^https:\/\/firebaseremoteconfig\.googleapis\.com\/v1\/projects\/.+\/namespaces\/firebase:fetch$/,
],
match: (r) => r.content?.startsWith('{"'),

decodingSteps: [
{ function: 'parseJson', input: 'body', output: 'res.body' },
{ function: 'getProperty', input: 'header', output: 'res.header', options: { path: '$' } },
],
containedDataPaths: {
appVersion: {
context: 'body',
path: 'appVersion',
reasoning: 'obvious property name',
},

country: [
{
context: 'body',
path: 'countryCode',
reasoning: 'obvious property name',
},
],

language: [
{
context: 'body',
path: 'analyticsUserProperties.language',
reasoning: 'obvious property name',
},
{
context: 'body',
path: 'languageCode',
reasoning: 'obvious property name',
},
],

installTime: [
{
context: 'body',
path: 'analyticsUserProperties.FirstAppStartTimestamp',
reasoning: 'obvious property name',
},
{
context: 'body',
path: 'firstOpenTime',
reasoning: 'obvious property name',
},
],

screenHeight: {
context: 'body',
path: 'analyticsUserProperties.Screensize',
reasoning: 'obvious property name',
},

screenWidth: {
context: 'body',
path: 'analyticsUserProperties.Screensize',
reasoning: 'obvious property name',
},

appId: [
{
context: 'body',
path: 'analyticsUserProperties.PackageName',
reasoning: 'obvious property name',
},
{
context: 'header',
path: 'X-Android-Package',
reasoning: 'obvious property name',
},
],

networkConnectionType: [
{
context: 'body',
path: 'analyticsUserProperties.network_type_name',
reasoning: 'obvious property name',
},
{
context: 'body',
path: 'analyticsUserProperties.network_type',
reasoning: 'obvious property name',
},
],

otherIdentifiers: [
{
context: 'body',
path: 'analyticsUserProperties.deviceId',
reasoning: 'obvious property name',
},
{
context: 'body',
path: 'analyticsUserProperties.device_id',
reasoning: 'obvious property name',
},
{
context: 'body',
path: 'analyticsUserProperties.uuid',
reasoning: 'obvious property name',
},
{
context: 'body',
path: 'appInstanceIdToken',
reasoning: 'google/appInstanceIdToken.md',
},
{
context: 'header',
path: 'X-Goog-Firebase-Installations-Auth',
reasoning: 'google/appInstanceIdToken.md',
},
],

osVersion: [
{
context: 'body',
path: 'analyticsUserProperties.android_sdk',
reasoning: 'obvious property name',
},
{
context: 'body',
path: 'platformVersion',
reasoning: 'obvious property name',
},
],

timezone: {
context: 'body',
path: 'timeZone',
reasoning: 'obvious property name',
},

trackerSdkVersion: {
context: 'body',
path: 'sdkVersion',
reasoning: 'obvious property name',
},
},
},
];
13 changes: 12 additions & 1 deletion src/common/decode-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { DecodingStep } from '../index';
import { Protobuf } from './protobuf.mjs';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const decodeFunctions: Record<DecodingStep['function'], (input: any, options?: any) => any> = {
const decodeFunctions: Record<DecodingStep['function'], (input: any, options?: any) => any> = {
parseQueryString: (input) => qs.parse(input.replace(/^.+?\?/, '')),
parseJson: (input) => JSON.parse(input),
decodeBase64: (input) => Buffer.from(input, 'base64').toString('binary'),
Expand All @@ -21,3 +21,14 @@ export const decodeFunctions: Record<DecodingStep['function'], (input: any, opti
return Buffer.from(uint8Array).toString('binary');
},
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const decodeFunction = (fn: DecodingStep['function'], input: any, options?: any) => {
try {
return decodeFunctions[fn](input, options);
} catch {
// Decode functions are used eagerly. If they fail, they are simply ignored, cf:
// https://github.com/tweaselORG/TrackHAR/pull/76#issuecomment-2370376873
return undefined;
}
};
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { JSONPath } from 'jsonpath-plus';
import type { LiteralUnion } from 'type-fest';
import type translations from '../i18n/en.json';
import { allAdapters } from './common/adapters';
import { decodeFunctions } from './common/decode-functions';
import { decodeFunction } from './common/decode-functions';
import type { Request } from './common/request';
import { unhar } from './common/request';
import type { ArrayOrSingle } from './common/type-utils';
Expand Down Expand Up @@ -247,15 +247,15 @@ export const decodeRequest = (r: Request, decodingSteps: DecodingStep[]) => {
if (!Array.isArray(mapInput)) throw new Error('mapInput must be an array.');
const result = mapInput
.filter((i) => i !== undefined && i !== null)
.map((i) => decodeFunctions[step.function](i, (step as { options: unknown }).options));
.map((i) => decodeFunction(step.function, i, (step as { options: unknown }).options));
if (result) set(step.output, result);
continue;
}

const input = get(step.input);
if (!input) continue;

const result = decodeFunctions[step.function](input, (step as { options: unknown }).options);
const result = decodeFunction(step.function, input, (step as { options: unknown }).options);
if (result) set(step.output, result);
}

Expand Down

0 comments on commit 1434f84

Please sign in to comment.