Initial TrueGrowth source import

This commit is contained in:
2026-07-07 09:36:36 +08:00
commit 3b6781d695
2283 changed files with 691996 additions and 0 deletions

View File

@@ -0,0 +1,385 @@
# Umami Analytics API Contract
**Feature**: 005-declarative-tracking
**Date**: 2025-12-05
**API Version**: Umami v2.x
**Integration Type**: Client SDK Wrapper
## Overview
本文档定义声明式埋点系统与 Umami Analytics 的集成契约,包括事件上报接口、数据格式、错误处理。
## Integration Strategy
**Approach**: 复用现有 UmamiAnalytics 工具 (`utils/umami-analytics.ts`)
- 使用项目已有的 `analytics` 单例,避免代码重复
- UmamiAdapter 作为适配器层,调用 `analytics.track()`
- 自动注入声明式埋点专属元数据(version, url, sessionId, viewport, eventType)
- 与现有 AI 生成事件追踪保持一致的日志格式
- 保持与 Umami 更新的兼容性
**代码位置**:
- 现有工具: `packages/drawnix/src/utils/umami-analytics.ts`
- 适配器: `packages/drawnix/src/services/tracking/umami-adapter.ts`
---
## API Endpoint
### 1. Track Event (上报事件)
**Method**: Umami SDK 封装 (`umami.track()`)
**Request** (通过 SDK):
```typescript
umami.track(eventName: string, eventData?: Record<string, any>): Promise<void>
```
**Internal HTTP Request** (SDK 内部实现):
```http
POST https://{umami-domain}/api/send
Content-Type: application/json
{
"type": "event",
"payload": {
"website": "{website-id}",
"name": "event-name",
"data": {
//
"version": "1.0.0",
"url": "https://opentu.ai/editor",
"param1": "value1"
}
}
}
```
**Response** (成功):
```http
HTTP/1.1 200 OK
Content-Type: application/json
```
**Response** (失败):
```http
HTTP/1.1 400 Bad Request
Content-Type: application/json
```
---
## Data Contract
### Event Data Structure
**Outbound** (发送给 Umami):
```typescript
interface UmamiEventPayload {
/** 事件名称 */
name: string;
/** 事件数据(自定义属性) */
data: {
/** 项目版本号(必需) */
version: string;
/** 当前页面 URL(必需) */
url: string;
/** 事件参数(来自 track-params,可选) */
[key: string]: any;
};
}
```
**Example**:
```json
{
"name": "button_click_save",
"data": {
"version": "1.2.3",
"url": "https://opentu.ai/editor?id=abc123",
"buttonId": "save-btn",
"context": "editor"
}
}
```
**Validation Rules**:
- `name`: 必需,非空字符串,长度 1-100 字符
- `data`: 可选,对象类型,总大小 <10KB (Umami 限制)
- `data.version`: 必需,SemVer 格式
- `data.url`: 必需,有效 URL
- 其他 `data.*` 字段: 可选,支持字符串、数字、布尔值、对象、数组
**Constraints**:
- 事件名不能包含特殊字符(仅允许 a-z, 0-9, _, -)
- 自定义属性键名不能以 `_` 开头(Umami 保留)
- 单个事件数据大小 <10KB
- 批量上报时,单次请求最多 50 个事件
---
## Batch Upload Contract
### Batch Event Upload
虽然 Umami SDK 不直接支持批量上报,但可以通过连续调用 `umami.track()` 实现。
**Implementation**:
```typescript
async function sendBatch(events: TrackEvent[]): Promise<void> {
for (const event of events) {
try {
await umami.track(event.eventName, {
version: event.metadata.version,
url: event.metadata.url,
...event.params
});
} catch (error) {
// 单个事件失败不影响其他事件
console.error(`Failed to track ${event.eventName}:`, error);
}
}
}
```
**Alternative** (如果 Umami 支持批量端点):
```http
POST https://{umami-domain}/api/batch
Content-Type: application/json
{
"events": [
{
"type": "event",
"payload": { "website": "...", "name": "...", "data": {...} }
},
{
"type": "event",
"payload": { "website": "...", "name": "...", "data": {...} }
}
]
}
```
---
## Error Handling
### Error Codes
| HTTP Status | Meaning | Action |
|-------------|---------|--------|
| 200 OK | 成功 | 继续 |
| 400 Bad Request | 请求格式错误(验证失败) | 记录错误,不重试 |
| 401 Unauthorized | Website ID 无效 | 记录错误,不重试 |
| 429 Too Many Requests | 速率限制 | 等待后重试(指数退避) |
| 500 Internal Server Error | 服务器错误 | 重试(最多 3 次) |
| 503 Service Unavailable | 服务不可用 | 缓存并稍后重试 |
### Retry Strategy
```typescript
interface RetryConfig {
maxRetries: 3;
initialDelay: 2000; // 2 秒
exponentialBackoff: true;
maxDelay: 30000; // 最大 30 秒
}
async function uploadWithRetry(event: TrackEvent): Promise<void> {
let attempt = 0;
let delay = 2000;
while (attempt < 3) {
try {
await umami.track(event.eventName, event.data);
return; // 成功
} catch (error) {
attempt++;
if (attempt >= 3 || !isRetryable(error)) {
// 缓存到 IndexedDB
await cacheEvent(event);
throw error;
}
// 指数退避
await sleep(delay);
delay = Math.min(delay * 2, 30000);
}
}
}
function isRetryable(error: any): boolean {
const status = error.response?.status;
return status >= 500 || status === 429;
}
```
---
## Security & Authentication
### Website ID
Umami 使用 `website ID` 识别站点:
```typescript
// 初始化 Umami (通常在 index.html 或应用入口)
<script defer src="https://umami-domain/script.js" data-website-id="{website-id}"></script>
// 或在代码中配置
window.umami = {
websiteId: '{website-id}'
};
```
**Security**:
- Website ID 公开可见(前端代码),不需要保密
- Umami 通过 CORS 和域名限制保护数据
---
## Rate Limiting
**Umami 限制** (根据官方文档):
- 每秒最多 10 个事件(同一用户)
- 超过限制返回 `429 Too Many Requests`
**应对策略**:
- 客户端防抖(500ms)
- 批量上报减少请求频率
- 429 错误时使用指数退避
---
## Testing Contract
### Mock Response (开发/测试环境)
```typescript
// 模拟 Umami SDK
const mockUmami = {
track: jest.fn(async (name, data) => {
if (Math.random() < 0.1) {
// 10% 概率失败
throw new Error('Network error');
}
console.log('Mock track:', name, data);
})
};
global.umami = mockUmami;
```
### Contract Validation
**Test Cases**:
1. ✅ 成功上报: `umami.track()` 返回成功
2. ✅ 格式验证: 事件名、数据格式符合要求
3. ✅ 自定义参数: version, url 正确注入
4. ✅ 错误处理: 500 错误触发重试
5. ✅ 速率限制: 429 错误触发退避
6. ✅ 缓存: 上报失败后事件存入 IndexedDB
---
## Integration Checklist
- [ ] 确认 Umami SDK 已加载(`window.umami` 存在)
- [ ] 配置正确的 `website ID`
- [ ] 测试事件上报(开发环境)
- [ ] 验证自定义参数(version, url)在 Umami 面板中显示
- [ ] 测试失败场景(离线、500 错误)
- [ ] 验证缓存和重试机制
- [ ] 测试速率限制(快速点击)
- [ ] 验证批量上报效果(网络请求数量减少 60%+)
---
## Example Integration Code
```typescript
// services/tracking/umami-adapter.ts
import { analytics } from '../../utils/umami-analytics';
export class UmamiTrackingAdapter {
/**
* Check if Umami SDK is available
*/
isAvailable(): boolean {
return analytics.isAnalyticsEnabled();
}
/**
* Track event using existing analytics utility
*/
async track(event: TrackEvent): Promise<void> {
if (!this.isAvailable()) {
throw new Error('Umami SDK not loaded');
}
// Enrich event data with declarative tracking metadata
const enrichedData = {
...event.params,
version: event.metadata.version,
url: event.metadata.url,
timestamp: event.metadata.timestamp,
sessionId: event.metadata.sessionId,
eventType: event.metadata.eventType,
};
// Optional: Add viewport if available
if (event.metadata.viewport) {
enrichedData.viewport = `${event.metadata.viewport.width}x${event.metadata.viewport.height}`;
}
try {
// Use existing analytics.track() method (复用现有工具)
analytics.track(event.eventName, enrichedData);
} catch (error) {
console.error('[Tracking] Umami track failed:', error);
throw error; // 由上层处理重试和缓存
}
}
}
```
**集成优势**:
- ✅ 复用现有 `UmamiAnalytics` 类,避免代码重复
- ✅ 保持与 AI 生成事件追踪的日志格式一致
- ✅ 统一的错误处理和调试输出 `[Analytics]` / `[Tracking]`
- ✅ 自动受益于现有工具的任何改进
---
## API Version Compatibility
| Umami Version | Supported | Notes |
|---------------|-----------|-------|
| v1.x | ⚠️ Partial | 需要手动实现 `umami.track()` 包装 |
| v2.0+ | ✅ Yes | 原生支持 `track()` 方法 |
| v3.0+ | ✅ Yes | 向后兼容 |
---
## References
- [Umami Documentation](https://umami.is/docs)
- [Umami Tracker API](https://umami.is/docs/tracker-functions)
- [Event Data Limits](https://umami.is/docs/event-data)
---
**Next Step**: 生成 quickstart.md
- [Umami Tracker API](https://umami.is/docs/tracker-functions)
- [Event Data Limits](https://umami.is/docs/event-data)
---
**Next Step**: 生成 quickstart.md