Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/browser-telemetry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: telemetry/browser-telemetry

on:
push:
branches: [main, 'feat/**']
paths-ignore:
- '**.md' #Do not need to run CI for markdown changes.
pull_request:
branches: [main, 'feat/**']
paths-ignore:
- '**.md'

jobs:
build-test-browser-telemetry:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
registry-url: 'https://registry.npmjs.org'
- id: shared
name: Shared CI Steps
uses: ./actions/ci
with:
workspace_name: '@launchdarkly/browser-telemetry'
workspace_path: packages/telemetry/browser-telemetry
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"packages/sdk/browser/contract-tests/adapter",
"packages/sdk/server-ai",
"packages/sdk/server-ai/examples/bedrock",
"packages/sdk/server-ai/examples/openai"
"packages/sdk/server-ai/examples/openai",
"packages/telemetry/browser-telemetry"
],
"private": true,
"scripts": {
Expand Down
41 changes: 41 additions & 0 deletions packages/telemetry/browser-telemetry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Telemetry integration for LaunchDarkly browser SDKs.

# ⛔️⛔️⛔️⛔️

> [!WARNING]
> This is an alpha version. The API is not stabilized and will introduce breaking changes.

TODO Add badges

## LaunchDarkly overview

[LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!

[![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly)

## Compatibility

TODO

## Setup

TODO

## Contributing

We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK.

## About LaunchDarkly

- LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
- Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
- Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
- Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
- Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan).
- Disable parts of your application to facilitate maintenance, without taking everything offline.
- LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
- Explore LaunchDarkly
- [launchdarkly.com](https://www.launchdarkly.com/ 'LaunchDarkly Main Website') for more information
- [docs.launchdarkly.com](https://docs.launchdarkly.com/ 'LaunchDarkly Documentation') for our documentation and SDK reference guides
- [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation
- [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
it('runs tests', () => {
// Placeholder so CI can run tests.
expect(true).toBeTruthy();
});
16 changes: 16 additions & 0 deletions packages/telemetry/browser-telemetry/jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"verbose": true,
"testEnvironment": "jest-environment-jsdom",
"testPathIgnorePatterns": ["./dist", "./src"],
"testMatch": ["**.test.ts"],
"setupFiles": ["./setup-jest.js"],
"transform": {
"^.+\\.ts$": [
"ts-jest",
{
"tsConfig": "tsconfig.test.json"
}
],
"^.+.tsx?$": ["ts-jest", {}]
}
}
77 changes: 77 additions & 0 deletions packages/telemetry/browser-telemetry/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "@launchdarkly/browser-telemetry",
"version": "0.0.9",
"packageManager": "yarn@3.4.1",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": {
"types": "./dist/index.d.cts",
"require": "./dist/index.cjs"
},
"import": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"description": "Telemetry integration for LaunchDarkly browser SDKs.",
"scripts": {
"test": "npx jest --runInBand",
"build": "tsup",
"prettier": "prettier --write 'src/*.@(js|ts|tsx|json)'",
"check": "yarn && yarn prettier && yarn lint && tsc && yarn test",
"lint": "npx eslint . --ext .ts"
},
"homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/telemetry/browser-telemetry",
"repository": {
"type": "git",
"url": "git+https://github.com/launchdarkly/js-core.git"
},
"keywords": [
"launchdarkly",
"analytics",
"telemetry"
],
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/launchdarkly/js-core/issues"
},
"dependencies": {
"rrweb": "2.0.0-alpha.4",
"tracekit": "^0.4.6"
},
"peerDependencies": {
"launchdarkly-js-client-sdk": "^3.4.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/css-font-loading-module": "^0.0.13",
"@types/jest": "^29.5.11",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"eslint": "^8.45.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.6.3",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"launchdarkly-js-test-helpers": "^2.2.0",
"prettier": "^3.0.0",
"rimraf": "^5.0.5",
"ts-jest": "^29.1.1",
"tsup": "^8.3.5",
"typedoc": "0.25.0",
"typescript": "^5.5.3"
}
}
24 changes: 24 additions & 0 deletions packages/telemetry/browser-telemetry/setup-jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { TextEncoder, TextDecoder } = require('node:util');
const crypto = require('node:crypto');

global.TextEncoder = TextEncoder;

Object.assign(window, { TextDecoder, TextEncoder });

// Based on:
// https://stackoverflow.com/a/71750830

Object.defineProperty(global.self, 'crypto', {
value: {
getRandomValues: (arr) => crypto.randomBytes(arr.length),
subtle: {
digest: (algorithm, data) => {
return new Promise((resolve) =>
resolve(
crypto.createHash(algorithm.toLowerCase().replace('-', '')).update(data).digest(),
),
);
},
},
},
});
163 changes: 163 additions & 0 deletions packages/telemetry/browser-telemetry/src/api/Breadcrumb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* Defines the 'class' of the breadcrumb.
*/
export type BreadcrumbClass =
| 'custom'
| 'log'
| 'navigation'
| 'feature-management'
| 'ui'
| 'http';

/**
* Indicates the severity of the breadcrumb.
*/
export type BreadcrumbLevel = 'error' | 'warning' | 'info' | 'debug';

/**
* Types of data support with breadcrumbs.
*/
export type BreadcrumbDataValue = boolean | number | string;

/**
* Defines arbitrary data that may be associated with a breadcrumb.
*/
export type BreadcrumbData = Record<string, BreadcrumbDataValue>;

/**
* Interface which defines a breadcrumb.
*/
export interface Breadcrumb {
/**
* The class of the breadcrumb. This is the top level categorization of breadcrumbs.
*/
class: BreadcrumbClass;

/**
* When the event associated with the breadcrumb happened. The timestamp is in milliseconds since January 1, 1970
* Universal Coordinated Time (UTC)
*
* For most breadcrumbs this will not be different than the time of breadcrumb creation, but if there is a delay
* between the event and breadcrumb capture, then the time of the event should be used instead.
*/
timestamp: number;

/**
* The level of severity of the breadcrumb. The default choice of level should be `info` if there isn't a clear
* reason to use a different level.
*/
level: BreadcrumbLevel;

/**
* The type of the breadcrumb. Each class may be split into multiple types with the type more specifically
* categorizing the type of event.
*/
type?: string;

/**
* A message associated with the breadcrumb.
*/
message?: string;

/**
* Any data associated with the breadcrumb.
*/
data?: BreadcrumbData;
}

/**
* Utility type which allows for easy extension of base breadcrumb type.
*/
type ImplementsCrumb<U extends Breadcrumb> = U;

/**
* Type for custom breadcrumbs.
*/
export type CustomBreadcrumb = ImplementsCrumb<{
class: 'custom';
timestamp: number;
level: BreadcrumbLevel;
type?: string;
message?: string;
data?: BreadcrumbData;
}>;

/**
* Type for log breadcrumbs.
*/
export type LogBreadcrumb = ImplementsCrumb<{
class: 'log';
timestamp: number;
level: BreadcrumbLevel;
message: string;
data?: BreadcrumbData;
}>;

/**
* Type for navigation breadcrumbs.
*/
export type NavigationBreadcrumb = ImplementsCrumb<{
class: 'navigation';
timestamp: number;
level: 'info';
type?: string;
data?: {
/**
* The location being navigated from. In a web application this would typically be a URL.
*/
from?: string;
/**
* The location being navigated to. In a web application this would typically be a URL.
*/
to?: string;
};
}>;

/**
* Type for feature management breadcrumbs.
*/
export type FeatureManagementBreadcrumb = ImplementsCrumb<{
class: 'feature-management';
timestamp: number;
level: 'info';
type: 'flag-evaluated' | 'flag-detail-changed';
data?: {
/**
* The flag key.
*/
key?: string;
// Not supporting JSON flags in breadcrumbs. As noted in design we may want to eventually support none of the
// values in the breadcrumb.
/**
* The evaluated value for simple types.
*/
value?: boolean | string | number;
};
}>;

/**
* Type for UI breadcrumbs.
*/
export type UiBreadcrumb = ImplementsCrumb<{
class: 'ui';
timestamp: number;
level: 'info';
type: 'click' | 'input';
message: string;
}>;

/**
* Type for HTTP breadcrumbs.
*/
export type HttpBreadcrumb = ImplementsCrumb<{
class: 'http';
timestamp: number;
level: 'error' | 'info'; // Error if an error status code?
type: 'xhr' | 'fetch';
data?: {
url?: string;
method?: string;
statusCode: number;
statusText: string;
};
}>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { LDClient, LDInspection } from 'launchdarkly-js-client-sdk';

import { Recorder } from './Recorder';

/**
* Interface LaunchDarkly browser telemetry.
*/
export interface BrowserTelemetry extends Recorder {
/**
* Get inspectors to use with the LaunchDarkly client.
*/
inspectors(): LDInspection[];

// TODO: Consider hooks as well. Hooks will allow registration to happen in a
// single step.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this TODO for this PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. When I add the actual implementations from the proto branch I need to make some decisions. All the other typing here was basically working with the prototype, but I have not addressed the functionality with old and new SDK versions.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for checking.


/**
* Register the telemetry instance with the LaunchDarkly client.
*
* @param client The LaunchDarkly client.
*/
register(client: LDClient): void;

/**
* Close the telemetry client.
*/
close(): void;
}
Loading