feat: revenue tracking

* wip

* wip

* wip

* wip

* show revenue better on overview

* align realtime and overview counters

* update revenue docs

* always return device id

* add project settings, improve projects charts,

* fix: comments

* fixes

* fix migration

* ignore sql files

* fix comments
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-19 14:27:34 +01:00
committed by GitHub
parent d61cbf6f2c
commit 790801b728
58 changed files with 2191 additions and 23691 deletions

View File

@@ -183,6 +183,28 @@ export class OpenPanel {
});
}
async revenue(
amount: number,
properties?: TrackProperties & { deviceId?: string },
) {
const deviceId = properties?.deviceId;
delete properties?.deviceId;
return this.track('revenue', {
...(properties ?? {}),
...(deviceId ? { __deviceId: deviceId } : {}),
__revenue: amount,
});
}
async fetchDeviceId(): Promise<string> {
const result = await this.api.fetch<undefined, { deviceId: string }>(
'/track/device-id',
undefined,
{ method: 'GET', keepalive: false },
);
return result?.deviceId ?? '';
}
clear() {
this.profileId = undefined;
// should we force a session end here?

View File

@@ -20,9 +20,15 @@ function toCamelCase(str: string) {
);
}
type PendingRevenue = {
amount: number;
properties?: Record<string, unknown>;
};
export class OpenPanel extends OpenPanelBase {
private lastPath = '';
private debounceTimer: any;
private pendingRevenues: PendingRevenue[] = [];
constructor(public options: OpenPanelOptions) {
super({
@@ -34,6 +40,18 @@ export class OpenPanel extends OpenPanelBase {
if (!this.isServer()) {
console.log('OpenPanel.dev - Initialized', this.options);
try {
const pending = sessionStorage.getItem('openpanel-pending-revenues');
if (pending) {
const parsed = JSON.parse(pending);
if (Array.isArray(parsed)) {
this.pendingRevenues = parsed;
}
}
} catch {
this.pendingRevenues = [];
}
this.setGlobalProperties({
__referrer: document.referrer,
});
@@ -191,4 +209,33 @@ export class OpenPanel extends OpenPanelBase {
__title: document.title,
});
}
async flushRevenue() {
const promises = this.pendingRevenues.map((pending) =>
super.revenue(pending.amount, pending.properties),
);
await Promise.all(promises);
this.clearRevenue();
}
clearRevenue() {
this.pendingRevenues = [];
if (!this.isServer()) {
try {
sessionStorage.removeItem('openpanel-pending-revenues');
} catch {}
}
}
pendingRevenue(amount: number, properties?: Record<string, unknown>) {
this.pendingRevenues.push({ amount, properties });
if (!this.isServer()) {
try {
sessionStorage.setItem(
'openpanel-pending-revenues',
JSON.stringify(this.pendingRevenues),
);
} catch {}
}
}
}

View File

@@ -8,7 +8,11 @@ type ExposedMethodsNames =
| 'alias'
| 'increment'
| 'decrement'
| 'clear';
| 'clear'
| 'revenue'
| 'flushRevenue'
| 'clearRevenue'
| 'pendingRevenue';
export type ExposedMethods = {
[K in ExposedMethodsNames]: OpenPanel[K] extends (...args: any[]) => any