Skip to content

Commit

Permalink
[web] Include flags in custom events (#140)
Browse files Browse the repository at this point in the history
* [web] Include flags in custom events

* Rename property to plainValues

* Rename properties

* Unify options and require flags list

* Rename flags variables

* Rename option

* Update options

* Update options type

* Add `setDefaultProps`

* Rename property

* Rename function

* Remove initialProps again and allow raw passing of `flags`

* Cleanup

* Remove unused type

* Update type and consider max. amount of flags

* Always report the first 5 flags

* Do not use default flags

* Remove comment

* Refactor flags server integration

Co-Authored-By: Damien Simonin Feugas <damien.feugas@gmail.com>

* Update package.json

Co-Authored-By: Damien Simonin Feugas <damien.feugas@gmail.com>

---------

Co-authored-by: Tobias Lins <me@tobi.sh>
Co-authored-by: Damien Simonin Feugas <damien.feugas@gmail.com>
  • Loading branch information
3 people authored May 23, 2024
1 parent 5cfe56c commit 74942cd
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 18 deletions.
2 changes: 1 addition & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vercel/analytics",
"version": "1.2.2",
"version": "1.3.0",
"description": "Gain real-time traffic insights with Vercel Web Analytics",
"keywords": [
"analytics",
Expand Down
14 changes: 11 additions & 3 deletions packages/web/src/generic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { name as packageName, version } from '../package.json';
import { initQueue } from './queue';
import type { AllowedPropertyValues, AnalyticsProps } from './types';
import type {
AllowedPropertyValues,
AnalyticsProps,
FlagsDataInput,
} from './types';
import {
isBrowser,
parseProperties,
Expand Down Expand Up @@ -89,7 +93,10 @@ function inject(
*/
function track(
name: string,
properties?: Record<string, AllowedPropertyValues>
properties?: Record<string, AllowedPropertyValues>,
options?: {
flags?: FlagsDataInput;
}
): void {
if (!isBrowser()) {
const msg =
Expand All @@ -106,7 +113,7 @@ function track(
}

if (!properties) {
window.va?.('event', { name });
window.va?.('event', { name, options });
return;
}

Expand All @@ -118,6 +125,7 @@ function track(
window.va?.('event', {
name,
data: props,
options,
});
} catch (err) {
if (err instanceof Error && isDevelopment()) {
Expand Down
69 changes: 55 additions & 14 deletions packages/web/src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/* eslint-disable no-console -- Allow logging on the server */
import type { AllowedPropertyValues } from '../types';
import type {
AllowedPropertyValues,
FlagsDataInput,
PlainFlags,
} from '../types';
import { isProduction, parseProperties } from '../utils';

type HeadersObject = Record<string, string | string[] | undefined>;
Expand All @@ -10,20 +14,21 @@ function isHeaders(headers?: AllowedHeaders): headers is Headers {
return typeof (headers as HeadersObject).entries === 'function';
}

interface ContextWithRequest {
request: { headers: AllowedHeaders };
interface Options {
flags?: FlagsDataInput;
headers?: AllowedHeaders;
request?: { headers: AllowedHeaders };
}
interface ContextWithHeaders {
headers: AllowedHeaders;
}

type Context = ContextWithRequest | ContextWithHeaders;

interface RequestContext {
get: () => {
headers: Record<string, string | undefined>;
url: string;
waitUntil?: (promise: Promise<unknown>) => void;
flags?: {
getValues: () => PlainFlags;
reportValue: (key: string, value: unknown) => void;
};
};
}

Expand All @@ -33,7 +38,7 @@ const logPrefix = '[Vercel Web Analytics]';
export async function track(
eventName: string,
properties?: Record<string, AllowedPropertyValues>,
context?: Context
options?: Options
): Promise<void> {
const ENDPOINT =
process.env.VERCEL_WEB_ANALYTICS_ENDPOINT || process.env.VERCEL_URL;
Expand Down Expand Up @@ -75,10 +80,10 @@ export async function track(

let headers: AllowedHeaders | undefined;

if (context && 'headers' in context) {
headers = context.headers;
} else if (context?.request) {
headers = context.request.headers;
if (options && 'headers' in options) {
headers = options.headers;
} else if (options?.request) {
headers = options.request.headers;
} else if (requestContext?.headers) {
// not explicitly passed in context, so take it from async storage
headers = requestContext.headers;
Expand All @@ -104,6 +109,7 @@ export async function track(
r: '',
en: eventName,
ed: props,
f: safeGetFlags(options?.flags, requestContext),
};

const hasHeaders = Boolean(headers);
Expand Down Expand Up @@ -135,7 +141,7 @@ export async function track(
body: JSON.stringify(body),
method: 'POST',
})
// We want to always consume to body; some cloud providers track fetch concurrency
// We want to always consume the body; some cloud providers track fetch concurrency
// and may not release the connection until the body is consumed.
.then((response) => response.text())
.catch((err: unknown) => {
Expand All @@ -157,3 +163,38 @@ export async function track(
console.error(err);
}
}

function safeGetFlags(
flags: Options['flags'],
requestContext?: ReturnType<RequestContext['get']>
):
| {
p: PlainFlags;
}
| undefined {
try {
if (!requestContext || !flags) return;
// In the case plain flags are passed, just return them
if (!Array.isArray(flags)) {
return { p: flags };
}

const plainFlags: Record<string, unknown> = {};
// returns all available plain flags
const resolvedPlainFlags = requestContext.flags?.getValues() ?? {};

for (const flag of flags) {
if (typeof flag === 'string') {
// only picks the desired flags
plainFlags[flag] = resolvedPlainFlags[flag];
} else {
// merge user-provided values with resolved values
Object.assign(plainFlags, flag);
}
}

return { p: plainFlags };
} catch {
/* empty */
}
}
3 changes: 3 additions & 0 deletions packages/web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ declare global {
vam?: Mode;
}
}

export type PlainFlags = Record<string, unknown>;
export type FlagsDataInput = (string | PlainFlags)[] | PlainFlags;

0 comments on commit 74942cd

Please sign in to comment.