feat: add angular library

This commit is contained in:
Bogdan Ivanov 2024-06-09 08:38:36 +03:00
parent 44fc175178
commit aa20c0126c
36 changed files with 27721 additions and 993 deletions

View file

@ -0,0 +1,93 @@
![Aptabase](https://aptabase.com/og.png)
# Aptabase SDK for Angular Apps
A tiny SDK to instrument your Angular apps with Aptabase, an Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps.
## Setup
1. Install the SDK using npm or your preferred JavaScript package manager
```bash
npm add @aptabase/angular
```
2. Get your `App Key` from Aptabase, you can find it in the `Instructions` page.
3. Pass the `App Key` when initializing your app by importing a module or providing a function.
### Setup for Standalone API
Provide `provideAptabaseAnalytics` in the ApplicationConfig when bootstrapping.
```ts
import { provideAptabaseAnalytics } from '@aptabase/angular';
bootstrapApplication(AppComponent, {
providers: [..., provideAptabaseAnalytics('<YOUR_APP_KEY>')],
}).catch((err) => console.error(err));
```
[Full example here](examples/example-standalone/src/app)
### Setup for NgModules
Import `AptabaseAnalyticsModule` in your root AppModule.
```ts
import { AptabaseAnalyticsModule } from '@aptabase/angular';
@NgModule({
declarations: [AppComponent],
imports: [..., AptabaseAnalyticsModule.forRoot('<YOUR_APP_KEY>')],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
```
[Full example here](examples/example-modules/src/app)
## Advanced setup
Both versions support also support an optional second parameter `AptabaseOptions` to pass in additional options.
```ts
export type AptabaseOptions = {
// Custom host for self-hosted Aptabase.
host?: string;
// Custom path for API endpoint. Useful when using reverse proxy.
apiUrl?: string;
// Defines the app version.
appVersion?: string;
// Defines whether the app is running on debug mode.
isDebug?: boolean;
};
```
## Tracking Events with Aptabase
After the initial setup the `AptabaseAnalyticsService` can be used to start tracking events.
Simply inject the service in a component to start tracking events:
```ts
import { AptabaseAnalyticsService } from '@aptabase/angular';
export class AppComponent {
constructor(private _analyticsService: AptabaseAnalyticsService) {}
increment() {
this.counter++;
this._analyticsService.trackEvent('increment');
}
}
```
## A few important notes:
1. The SDK will automatically enhance the event with some useful information, like the OS and other properties.
2. You're in control of what gets sent to Aptabase. This SDK does not automatically track any events, you need to call `trackEvent` manually.
- Because of this, it's generally recommended to at least track an event at startup
3. You do not need to subscribe to `trackEvent` function, it'll run in the background.
4. Only strings and numeric values are allowed on custom properties.

View file

@ -0,0 +1,230 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": ".",
"projects": {
"aptabase-angular": {
"projectType": "library",
"root": "aptabase-angular",
"sourceRoot": "aptabase-angular/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "aptabase-angular/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "aptabase-angular/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "aptabase-angular/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "aptabase-angular/tsconfig.spec.json",
"polyfills": ["zone.js", "zone.js/testing"]
}
}
}
},
"example-standalone": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"inlineTemplate": true,
"inlineStyle": true,
"style": "scss",
"skipTests": true
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "examples/example-standalone",
"sourceRoot": "examples/example-standalone/src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/example-standalone",
"index": "examples/example-standalone/src/index.html",
"browser": "examples/example-standalone/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "examples/example-standalone/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["examples/example-standalone/src/favicon.ico", "examples/example-standalone/src/assets"],
"styles": ["examples/example-standalone/src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "example-standalone:build:production"
},
"development": {
"buildTarget": "example-standalone:build:development"
}
},
"options": {
"ssl": true
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "example-standalone:build"
}
}
}
},
"example-modules": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"inlineTemplate": true,
"inlineStyle": true,
"style": "scss",
"skipTests": true,
"standalone": false
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true,
"standalone": false
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true,
"standalone": false
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "examples/example-modules",
"sourceRoot": "examples/example-modules/src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/example-modules",
"index": "examples/example-modules/src/index.html",
"browser": "examples/example-modules/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "examples/example-modules/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["examples/example-modules/src/favicon.ico", "examples/example-modules/src/assets"],
"styles": ["examples/example-modules/src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "example-modules:build:production"
},
"development": {
"buildTarget": "example-modules:build:development"
}
},
"options": {
"ssl": true
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "example-modules:build"
}
}
}
}
}
}

View file

@ -0,0 +1,93 @@
![Aptabase](https://aptabase.com/og.png)
# Aptabase SDK for Angular Apps
A tiny SDK to instrument your Angular apps with Aptabase, an Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps.
## Setup
1. Install the SDK using npm or your preferred JavaScript package manager
```bash
npm add @aptabase/angular
```
2. Get your `App Key` from Aptabase, you can find it in the `Instructions` page.
3. Pass the `App Key` when initializing your app by importing a module or providing a function.
### Setup for Standalone API
Provide `provideAptabaseAnalytics` in the ApplicationConfig when bootstrapping.
```ts
import { provideAptabaseAnalytics } from '@aptabase/angular';
bootstrapApplication(AppComponent, {
providers: [..., provideAptabaseAnalytics('<YOUR_APP_KEY>')],
}).catch((err) => console.error(err));
```
[Full example here](../examples/example-standalone/src/app)
### Setup for NgModules
Import `AptabaseAnalyticsModule` in your root AppModule.
```ts
import { AptabaseAnalyticsModule } from '@aptabase/angular';
@NgModule({
declarations: [AppComponent],
imports: [..., AptabaseAnalyticsModule.forRoot('<YOUR_APP_KEY>')],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
```
[Full example here](../examples/example-modules/src/app)
## Advanced setup
Both versions support also support an optional second parameter `AptabaseOptions` to pass in additional options.
```ts
export type AptabaseOptions = {
// Custom host for self-hosted Aptabase.
host?: string;
// Custom path for API endpoint. Useful when using reverse proxy.
apiUrl?: string;
// Defines the app version.
appVersion?: string;
// Defines whether the app is running on debug mode.
isDebug?: boolean;
};
```
## Tracking Events with Aptabase
After the initial setup the `AptabaseAnalyticsService` can be used to start tracking events.
Simply inject the service in a component to start tracking events:
```ts
import { AptabaseAnalyticsService } from '@aptabase/angular';
export class AppComponent {
constructor(private _analyticsService: AptabaseAnalyticsService) {}
increment() {
this.counter++;
this._analyticsService.trackEvent('increment');
}
}
```
## A few important notes:
1. The SDK will automatically enhance the event with some useful information, like the OS and other properties.
2. You're in control of what gets sent to Aptabase. This SDK does not automatically track any events, you need to call `trackEvent` manually.
- Because of this, it's generally recommended to at least track an event at startup
3. You do not need to subscribe to `trackEvent` function, it'll run in the background.
4. Only strings and numeric values are allowed on custom properties.

View file

@ -0,0 +1,7 @@
{
"$schema": "../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../dist/aptabase-angular",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View file

@ -0,0 +1,23 @@
{
"name": "@aptabase/angular",
"version": "0.0.1",
"description": "Angular SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps",
"repository": {
"type": "git",
"url": "git+https://github.com/aptabase/aptabase-js.git",
"directory": "packages/angular/aptabase-angular"
},
"bugs": {
"url": "https://github.com/aptabase/aptabase-js/issues"
},
"homepage": "https://github.com/aptabase/aptabase-js",
"license": "MIT",
"peerDependencies": {
"@angular/common": "^17.3.0",
"@angular/core": "^17.3.0"
},
"dependencies": {
"tslib": "^2.3.0"
},
"sideEffects": false
}

View file

@ -0,0 +1,37 @@
import { AptabaseOptions, getApiUrl, inMemorySessionId, sendEvent, validateAppKey } from '../shared';
// Session expires after 1 hour of inactivity
// TODO move this to shared?
const SESSION_TIMEOUT = 1 * 60 * 60;
const pkgVersion = '0.0.0'; // bog: TODO fix this version
const sdkVersion = `aptabase-angular@${pkgVersion}`;
export class AptabaseAnalyticsService {
private _apiUrl: string | undefined;
constructor(
private _appKey: string,
private _options: AptabaseOptions,
) {
if (!validateAppKey(this._appKey)) return;
this._apiUrl = this._options.apiUrl ?? getApiUrl(this._appKey, this._options);
}
async trackEvent(eventName: string, props?: Record<string, string | number | boolean>): Promise<void> {
if (!this._apiUrl) return;
const sessionId = inMemorySessionId(SESSION_TIMEOUT);
await sendEvent({
apiUrl: this._apiUrl,
sessionId,
appKey: this._appKey,
isDebug: this._options?.isDebug,
appVersion: this._options?.appVersion,
sdkVersion,
eventName,
props,
});
}
}

View file

@ -0,0 +1,28 @@
import { EnvironmentProviders, NgModule, makeEnvironmentProviders } from '@angular/core';
import { AptabaseOptions } from '../shared';
import { AptabaseAnalyticsService } from './analytics.service';
export function provideAptabaseAnalytics(appKey: string, options: AptabaseOptions = {}): EnvironmentProviders {
return makeEnvironmentProviders([
{
provide: AptabaseAnalyticsService,
useValue: new AptabaseAnalyticsService(appKey, options),
},
]);
}
@NgModule()
export class AptabaseAnalyticsModule {
static forRoot(appKey: string, options: AptabaseOptions = {}) {
return {
ngModule: AptabaseAnalyticsModule,
providers: [
{
provide: AptabaseAnalyticsService,
useValue: new AptabaseAnalyticsService(appKey, options),
},
],
};
}
}

View file

@ -0,0 +1,6 @@
/*
* Public API Surface of aptabase-angular
*/
export * from './lib/analytics.service';
export * from './lib/index';

View file

@ -0,0 +1,11 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true
},
"exclude": ["**/*.spec.ts"]
}

View file

@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

View file

@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View file

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View file

@ -0,0 +1,23 @@
import { Component } from '@angular/core';
import { AptabaseAnalyticsService } from '@aptabase/angular';
@Component({
selector: 'app-root',
template: `
<h1>Welcome to {{ title }}!</h1>
<button (click)="trackButtonClick()">Track event</button>
<router-outlet />
`,
styles: [],
})
export class AppComponent {
constructor(private _analyticsService: AptabaseAnalyticsService) {}
title = 'example-modules';
trackButtonClick() {
this._analyticsService.trackEvent('module_btn_click');
}
}

View file

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AptabaseAnalyticsModule } from '@aptabase/angular';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, AptabaseAnalyticsModule.forRoot('A-EU-1280395555')],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ExampleModules</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View file

@ -0,0 +1,7 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View file

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View file

@ -0,0 +1,9 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/app"
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}

View file

@ -0,0 +1,26 @@
import { Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AptabaseAnalyticsService } from '@aptabase/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
template: `
<h1>Welcome to {{ title }}!</h1>
<button (click)="trackButtonClick()">Track event</button>
<router-outlet />
`,
styles: [],
})
export class AppComponent {
private _analytics = inject(AptabaseAnalyticsService);
title = 'example-standalone';
trackButtonClick() {
this._analytics.trackEvent('home_btn_click', { customProp: 'Hello from Aptabase' });
}
}

View file

@ -0,0 +1,9 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAptabaseAnalytics } from '@aptabase/angular';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideAptabaseAnalytics('A-DEV-1624170742')],
};

View file

@ -0,0 +1,3 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ExampleStandalone</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View file

@ -0,0 +1,5 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

View file

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View file

@ -0,0 +1,9 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/app"
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}

12758
packages/angular/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
{
"name": "@aptabase/angular",
"version": "0.0.1",
"description": "Angular SDK for Aptabase: Open Source, Privacy-First and Simple Analytics for Mobile, Desktop and Web Apps",
"scripts": {
"ng": "ng",
"start": "ng serve example-standalone",
"start-modules-sample": "ng serve example-modules",
"pre-build": "cp ../shared.ts ./aptabase-angular/src",
"build": "npm run pre-build && ng build aptabase-angular",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.0",
"@angular/common": "^17.0.0",
"@angular/compiler": "^17.0.0",
"@angular/core": "^17.0.0",
"@angular/forms": "^17.0.0",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"@angular/router": "^17.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.7",
"@angular/cli": "^17.0.8",
"@angular/compiler-cli": "^17.0.0",
"@types/jasmine": "~5.1.0",
"@types/node": "20.5.7",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"ng-packagr": "^17.3.0",
"typescript": "~5.2.2"
}
}

View file

@ -0,0 +1,36 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"include": ["**/*.ts", "../shared/*.ts"],
"compilerOptions": {
"paths": {
"@aptabase/angular": ["./aptabase-angular/src/public-api.ts"],
"@aptabase/angular/*": ["./aptabase-angular/src/*"]
},
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"],
"types": ["node", "chrome"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View file

@ -135,7 +135,7 @@ function getBrowserLocale(): string | undefined {
}
function getIsDebug(): boolean {
if (process.env.NODE_ENV === 'development') {
if (process.env['NODE_ENV'] === 'development') {
return true;
}