@lark-apaas/observable-web-lite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +94 -0
- package/dist/index.d.mts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +196 -0
- package/dist/index.mjs +193 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lark Technologies Pte. Ltd. and/or its affiliates
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
8
|
+
IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
|
|
9
|
+
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
|
|
10
|
+
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
11
|
+
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|
12
|
+
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
|
13
|
+
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# @lark-apaas/observable-web-lite
|
|
2
|
+
|
|
3
|
+
轻量 PV/UV 上报 SDK,零外部依赖,产物 ~5KB。
|
|
4
|
+
|
|
5
|
+
适用于只需要 PV/UV 统计、不需要 Logs/Traces/Metrics 全量能力的项目。与 `@lark-apaas/observable-web` 后端完全兼容(相同 metric name + payload 格式),收集端点无需改动。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add @lark-apaas/observable-web-lite
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 快速开始
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { pvTracker } from '@lark-apaas/observable-web-lite';
|
|
17
|
+
|
|
18
|
+
const appId = window.appId || '';
|
|
19
|
+
pvTracker.start({
|
|
20
|
+
collectorUrl: `/app/${appId}/__runtime__/api/v1/observability/metrics/collect`,
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
一行代码即可启用 PV/UV 自动上报,SDK 会自动监听:
|
|
25
|
+
|
|
26
|
+
- 页面首次加载(`load`)
|
|
27
|
+
- 浏览器前进/后退(`popstate`)
|
|
28
|
+
- SPA 路由跳转(`pushState` / `replaceState`)
|
|
29
|
+
- 页面切后台/关闭时立即 flush(`visibilitychange` / `pagehide`)
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
### `pvTracker.start(config)`
|
|
34
|
+
|
|
35
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
36
|
+
|------|------|------|------|
|
|
37
|
+
| `collectorUrl` | `string` | 是 | Metric 收集端点 URL |
|
|
38
|
+
| `headers` | `Record<string, string>` | 否 | 附加 HTTP headers |
|
|
39
|
+
| `flushIntervalMs` | `number` | 否 | 批量发送间隔,默认 10000ms |
|
|
40
|
+
|
|
41
|
+
### `pvTracker.shutdown()`
|
|
42
|
+
|
|
43
|
+
停止上报并立即 flush 剩余数据。
|
|
44
|
+
|
|
45
|
+
## 完整示例
|
|
46
|
+
|
|
47
|
+
### 在 React 应用入口使用
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// client/src/index.tsx
|
|
51
|
+
import { pvTracker } from '@lark-apaas/observable-web-lite';
|
|
52
|
+
|
|
53
|
+
const appId = window.appId || '';
|
|
54
|
+
pvTracker.start({
|
|
55
|
+
collectorUrl: `/app/${appId}/__runtime__/api/v1/observability/metrics/collect`,
|
|
56
|
+
flushIntervalMs: 5000, // 5 秒 flush 一次
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 自定义实例(多实例场景)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { PvTracker } from '@lark-apaas/observable-web-lite';
|
|
66
|
+
|
|
67
|
+
const tracker = new PvTracker();
|
|
68
|
+
const appId = window.appId || '';
|
|
69
|
+
tracker.start({
|
|
70
|
+
collectorUrl: `/app/${appId}/__runtime__/api/v1/observability/metrics/collect`,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 页面卸载时
|
|
74
|
+
tracker.shutdown();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 与 observable-web 的对比
|
|
78
|
+
|
|
79
|
+
| | observable-web | observable-web-lite |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| 产物大小 | ~50KB | **~5KB** |
|
|
82
|
+
| 外部依赖 | 9 个(OTel + zone.js) | **0** |
|
|
83
|
+
| PV/UV 上报 | ✅ | ✅ |
|
|
84
|
+
| 日志(Logs) | ✅ | ❌ |
|
|
85
|
+
| 指标(Metrics) | ✅ | ❌ |
|
|
86
|
+
| 链路追踪(Traces) | ✅ | ❌ |
|
|
87
|
+
| ESM 支持 | ❌ | ✅ |
|
|
88
|
+
| tree-shake | ❌ | ✅ |
|
|
89
|
+
|
|
90
|
+
## 注意事项
|
|
91
|
+
|
|
92
|
+
- 与 `observable-web` **不要同时启用 PV 上报**,否则会重复计数
|
|
93
|
+
- 如果项目后续需要 Logs/Traces 能力,切换回 `observable-web` 即可,PV 数据无缝衔接(metric name 相同)
|
|
94
|
+
- SDK 默认读取 `window.appId` / `window.tenantId` / `window.userId` 作为上报属性,这些由服务端 ViewContextPlugin 注入
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface PvTrackerConfig {
|
|
2
|
+
/** Metric 收集端点 URL */
|
|
3
|
+
collectorUrl: string;
|
|
4
|
+
/** 附加的 HTTP headers */
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
/** 批量发送间隔(毫秒),默认 10000 */
|
|
7
|
+
flushIntervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
declare class PvTracker {
|
|
10
|
+
private static _instance;
|
|
11
|
+
private config;
|
|
12
|
+
private started;
|
|
13
|
+
private lastUrl;
|
|
14
|
+
private buffer;
|
|
15
|
+
private flushTimer;
|
|
16
|
+
static get instance(): PvTracker;
|
|
17
|
+
start(config: PvTrackerConfig): void;
|
|
18
|
+
shutdown(): void;
|
|
19
|
+
private recordPv;
|
|
20
|
+
private flush;
|
|
21
|
+
private listenRouteChanges;
|
|
22
|
+
private patchHistory;
|
|
23
|
+
private registerLifecycleListeners;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare const pvTracker: PvTracker;
|
|
27
|
+
|
|
28
|
+
export { PvTracker, type PvTrackerConfig, pvTracker };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface PvTrackerConfig {
|
|
2
|
+
/** Metric 收集端点 URL */
|
|
3
|
+
collectorUrl: string;
|
|
4
|
+
/** 附加的 HTTP headers */
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
/** 批量发送间隔(毫秒),默认 10000 */
|
|
7
|
+
flushIntervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
declare class PvTracker {
|
|
10
|
+
private static _instance;
|
|
11
|
+
private config;
|
|
12
|
+
private started;
|
|
13
|
+
private lastUrl;
|
|
14
|
+
private buffer;
|
|
15
|
+
private flushTimer;
|
|
16
|
+
static get instance(): PvTracker;
|
|
17
|
+
start(config: PvTrackerConfig): void;
|
|
18
|
+
shutdown(): void;
|
|
19
|
+
private recordPv;
|
|
20
|
+
private flush;
|
|
21
|
+
private listenRouteChanges;
|
|
22
|
+
private patchHistory;
|
|
23
|
+
private registerLifecycleListeners;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare const pvTracker: PvTracker;
|
|
27
|
+
|
|
28
|
+
export { PvTracker, type PvTrackerConfig, pvTracker };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/uuid.ts
|
|
4
|
+
function generateUUID() {
|
|
5
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
6
|
+
const bytes = new Uint8Array(8);
|
|
7
|
+
crypto.getRandomValues(bytes);
|
|
8
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9
|
+
}
|
|
10
|
+
let result = "";
|
|
11
|
+
for (let i = 0; i < 16; i++) {
|
|
12
|
+
result += Math.floor(Math.random() * 16).toString(16);
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/utils/device-id.ts
|
|
18
|
+
var STORAGE_KEY = "x_sdk_device_id";
|
|
19
|
+
function getDeviceId() {
|
|
20
|
+
try {
|
|
21
|
+
let id = localStorage.getItem(STORAGE_KEY);
|
|
22
|
+
if (!id) {
|
|
23
|
+
id = generateUUID();
|
|
24
|
+
localStorage.setItem(STORAGE_KEY, id);
|
|
25
|
+
}
|
|
26
|
+
return id;
|
|
27
|
+
} catch {
|
|
28
|
+
return generateUUID();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/utils/device-type.ts
|
|
33
|
+
function getDeviceType() {
|
|
34
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
35
|
+
if (!ua) return "desktop";
|
|
36
|
+
if (/Android/i.test(ua) && /Mobile/i.test(ua) || /iPhone|iPod/i.test(ua) || /BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
|
|
37
|
+
return "mobile";
|
|
38
|
+
}
|
|
39
|
+
return "desktop";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/utils/attributes.ts
|
|
43
|
+
function getDefaultAttributes() {
|
|
44
|
+
return {
|
|
45
|
+
app_id: window.appId || "",
|
|
46
|
+
tenant_id: window.tenantId || "",
|
|
47
|
+
user_id: window.userId || "",
|
|
48
|
+
did: getDeviceId(),
|
|
49
|
+
module: "app_web",
|
|
50
|
+
app_env: "runtime",
|
|
51
|
+
source_type: "user",
|
|
52
|
+
device_type: getDeviceType(),
|
|
53
|
+
release_commit_id: window.__RELEASE_COMMIT_ID__ || ""
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/sender.ts
|
|
58
|
+
var SDK_VERSION = "1.0.0";
|
|
59
|
+
function buildPayload(dataPoints) {
|
|
60
|
+
const defaultAttrs = getDefaultAttributes();
|
|
61
|
+
const nowNs = String(BigInt(Date.now()) * BigInt(1e6));
|
|
62
|
+
return {
|
|
63
|
+
metricsData: {
|
|
64
|
+
resource: {
|
|
65
|
+
attributes: {
|
|
66
|
+
sdk_version: SDK_VERSION,
|
|
67
|
+
sdk_lang: "javascript",
|
|
68
|
+
sdk_type: "client-frontend"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
metrics: [
|
|
72
|
+
{
|
|
73
|
+
name: "apaas_internal_metrics_pv_uv",
|
|
74
|
+
description: "Count of page views",
|
|
75
|
+
unit: "",
|
|
76
|
+
scopeName: "apaas_internal_metrics_pv_uv",
|
|
77
|
+
sum: {
|
|
78
|
+
aggregationTemporality: 1,
|
|
79
|
+
// DELTA
|
|
80
|
+
isMonotonic: true,
|
|
81
|
+
dataPoints: dataPoints.map((dp) => ({
|
|
82
|
+
attributes: {
|
|
83
|
+
...defaultAttrs,
|
|
84
|
+
url: dp.url,
|
|
85
|
+
path: dp.path,
|
|
86
|
+
referrer: dp.referrer
|
|
87
|
+
},
|
|
88
|
+
startTimeUnixNano: String(BigInt(dp.timestampMs) * BigInt(1e6)),
|
|
89
|
+
timeUnixNano: nowNs,
|
|
90
|
+
asDouble: 1
|
|
91
|
+
}))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function sendPvData(collectorUrl, dataPoints, headers = {}) {
|
|
99
|
+
if (dataPoints.length === 0) return;
|
|
100
|
+
const body = JSON.stringify(buildPayload(dataPoints));
|
|
101
|
+
try {
|
|
102
|
+
fetch(collectorUrl, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
"X-Suda-Csrf-Token": window.csrfToken || "",
|
|
107
|
+
...headers
|
|
108
|
+
},
|
|
109
|
+
body,
|
|
110
|
+
keepalive: true
|
|
111
|
+
}).catch(() => {
|
|
112
|
+
});
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/tracker.ts
|
|
118
|
+
var PvTracker = class _PvTracker {
|
|
119
|
+
constructor() {
|
|
120
|
+
this.started = false;
|
|
121
|
+
this.buffer = [];
|
|
122
|
+
}
|
|
123
|
+
static get instance() {
|
|
124
|
+
if (!this._instance) {
|
|
125
|
+
this._instance = new _PvTracker();
|
|
126
|
+
}
|
|
127
|
+
return this._instance;
|
|
128
|
+
}
|
|
129
|
+
start(config) {
|
|
130
|
+
var _a;
|
|
131
|
+
if (this.started) return;
|
|
132
|
+
this.config = config;
|
|
133
|
+
this.started = true;
|
|
134
|
+
this.listenRouteChanges();
|
|
135
|
+
this.registerLifecycleListeners();
|
|
136
|
+
const interval = (_a = config.flushIntervalMs) != null ? _a : 1e4;
|
|
137
|
+
this.flushTimer = setInterval(() => this.flush(), interval);
|
|
138
|
+
}
|
|
139
|
+
shutdown() {
|
|
140
|
+
this.flush();
|
|
141
|
+
if (this.flushTimer) {
|
|
142
|
+
clearInterval(this.flushTimer);
|
|
143
|
+
this.flushTimer = void 0;
|
|
144
|
+
}
|
|
145
|
+
this.started = false;
|
|
146
|
+
}
|
|
147
|
+
recordPv() {
|
|
148
|
+
const currentUrl = window.location.href;
|
|
149
|
+
if (this.lastUrl === currentUrl) return;
|
|
150
|
+
this.lastUrl = currentUrl;
|
|
151
|
+
this.buffer.push({
|
|
152
|
+
url: currentUrl,
|
|
153
|
+
path: window.location.pathname,
|
|
154
|
+
referrer: document.referrer,
|
|
155
|
+
timestampMs: Date.now()
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
flush() {
|
|
159
|
+
if (!this.config || this.buffer.length === 0) return;
|
|
160
|
+
const batch = this.buffer.splice(0);
|
|
161
|
+
sendPvData(this.config.collectorUrl, batch, this.config.headers);
|
|
162
|
+
}
|
|
163
|
+
listenRouteChanges() {
|
|
164
|
+
if (document.readyState === "complete") {
|
|
165
|
+
this.recordPv();
|
|
166
|
+
} else {
|
|
167
|
+
window.addEventListener("load", () => this.recordPv());
|
|
168
|
+
}
|
|
169
|
+
window.addEventListener("popstate", () => this.recordPv());
|
|
170
|
+
this.patchHistory("pushState");
|
|
171
|
+
this.patchHistory("replaceState");
|
|
172
|
+
}
|
|
173
|
+
patchHistory(method) {
|
|
174
|
+
const original = history[method];
|
|
175
|
+
const recordPv = () => this.recordPv();
|
|
176
|
+
history[method] = function(...args) {
|
|
177
|
+
const result = original.apply(this, args);
|
|
178
|
+
recordPv();
|
|
179
|
+
return result;
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
registerLifecycleListeners() {
|
|
183
|
+
document.addEventListener("visibilitychange", () => {
|
|
184
|
+
if (document.visibilityState === "hidden") {
|
|
185
|
+
this.flush();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
window.addEventListener("pagehide", () => this.flush());
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/index.ts
|
|
193
|
+
var pvTracker = PvTracker.instance;
|
|
194
|
+
|
|
195
|
+
exports.PvTracker = PvTracker;
|
|
196
|
+
exports.pvTracker = pvTracker;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// src/utils/uuid.ts
|
|
2
|
+
function generateUUID() {
|
|
3
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
4
|
+
const bytes = new Uint8Array(8);
|
|
5
|
+
crypto.getRandomValues(bytes);
|
|
6
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
7
|
+
}
|
|
8
|
+
let result = "";
|
|
9
|
+
for (let i = 0; i < 16; i++) {
|
|
10
|
+
result += Math.floor(Math.random() * 16).toString(16);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/utils/device-id.ts
|
|
16
|
+
var STORAGE_KEY = "x_sdk_device_id";
|
|
17
|
+
function getDeviceId() {
|
|
18
|
+
try {
|
|
19
|
+
let id = localStorage.getItem(STORAGE_KEY);
|
|
20
|
+
if (!id) {
|
|
21
|
+
id = generateUUID();
|
|
22
|
+
localStorage.setItem(STORAGE_KEY, id);
|
|
23
|
+
}
|
|
24
|
+
return id;
|
|
25
|
+
} catch {
|
|
26
|
+
return generateUUID();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/utils/device-type.ts
|
|
31
|
+
function getDeviceType() {
|
|
32
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
33
|
+
if (!ua) return "desktop";
|
|
34
|
+
if (/Android/i.test(ua) && /Mobile/i.test(ua) || /iPhone|iPod/i.test(ua) || /BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
|
|
35
|
+
return "mobile";
|
|
36
|
+
}
|
|
37
|
+
return "desktop";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/utils/attributes.ts
|
|
41
|
+
function getDefaultAttributes() {
|
|
42
|
+
return {
|
|
43
|
+
app_id: window.appId || "",
|
|
44
|
+
tenant_id: window.tenantId || "",
|
|
45
|
+
user_id: window.userId || "",
|
|
46
|
+
did: getDeviceId(),
|
|
47
|
+
module: "app_web",
|
|
48
|
+
app_env: "runtime",
|
|
49
|
+
source_type: "user",
|
|
50
|
+
device_type: getDeviceType(),
|
|
51
|
+
release_commit_id: window.__RELEASE_COMMIT_ID__ || ""
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/sender.ts
|
|
56
|
+
var SDK_VERSION = "1.0.0";
|
|
57
|
+
function buildPayload(dataPoints) {
|
|
58
|
+
const defaultAttrs = getDefaultAttributes();
|
|
59
|
+
const nowNs = String(BigInt(Date.now()) * BigInt(1e6));
|
|
60
|
+
return {
|
|
61
|
+
metricsData: {
|
|
62
|
+
resource: {
|
|
63
|
+
attributes: {
|
|
64
|
+
sdk_version: SDK_VERSION,
|
|
65
|
+
sdk_lang: "javascript",
|
|
66
|
+
sdk_type: "client-frontend"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
metrics: [
|
|
70
|
+
{
|
|
71
|
+
name: "apaas_internal_metrics_pv_uv",
|
|
72
|
+
description: "Count of page views",
|
|
73
|
+
unit: "",
|
|
74
|
+
scopeName: "apaas_internal_metrics_pv_uv",
|
|
75
|
+
sum: {
|
|
76
|
+
aggregationTemporality: 1,
|
|
77
|
+
// DELTA
|
|
78
|
+
isMonotonic: true,
|
|
79
|
+
dataPoints: dataPoints.map((dp) => ({
|
|
80
|
+
attributes: {
|
|
81
|
+
...defaultAttrs,
|
|
82
|
+
url: dp.url,
|
|
83
|
+
path: dp.path,
|
|
84
|
+
referrer: dp.referrer
|
|
85
|
+
},
|
|
86
|
+
startTimeUnixNano: String(BigInt(dp.timestampMs) * BigInt(1e6)),
|
|
87
|
+
timeUnixNano: nowNs,
|
|
88
|
+
asDouble: 1
|
|
89
|
+
}))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function sendPvData(collectorUrl, dataPoints, headers = {}) {
|
|
97
|
+
if (dataPoints.length === 0) return;
|
|
98
|
+
const body = JSON.stringify(buildPayload(dataPoints));
|
|
99
|
+
try {
|
|
100
|
+
fetch(collectorUrl, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
"X-Suda-Csrf-Token": window.csrfToken || "",
|
|
105
|
+
...headers
|
|
106
|
+
},
|
|
107
|
+
body,
|
|
108
|
+
keepalive: true
|
|
109
|
+
}).catch(() => {
|
|
110
|
+
});
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/tracker.ts
|
|
116
|
+
var PvTracker = class _PvTracker {
|
|
117
|
+
constructor() {
|
|
118
|
+
this.started = false;
|
|
119
|
+
this.buffer = [];
|
|
120
|
+
}
|
|
121
|
+
static get instance() {
|
|
122
|
+
if (!this._instance) {
|
|
123
|
+
this._instance = new _PvTracker();
|
|
124
|
+
}
|
|
125
|
+
return this._instance;
|
|
126
|
+
}
|
|
127
|
+
start(config) {
|
|
128
|
+
var _a;
|
|
129
|
+
if (this.started) return;
|
|
130
|
+
this.config = config;
|
|
131
|
+
this.started = true;
|
|
132
|
+
this.listenRouteChanges();
|
|
133
|
+
this.registerLifecycleListeners();
|
|
134
|
+
const interval = (_a = config.flushIntervalMs) != null ? _a : 1e4;
|
|
135
|
+
this.flushTimer = setInterval(() => this.flush(), interval);
|
|
136
|
+
}
|
|
137
|
+
shutdown() {
|
|
138
|
+
this.flush();
|
|
139
|
+
if (this.flushTimer) {
|
|
140
|
+
clearInterval(this.flushTimer);
|
|
141
|
+
this.flushTimer = void 0;
|
|
142
|
+
}
|
|
143
|
+
this.started = false;
|
|
144
|
+
}
|
|
145
|
+
recordPv() {
|
|
146
|
+
const currentUrl = window.location.href;
|
|
147
|
+
if (this.lastUrl === currentUrl) return;
|
|
148
|
+
this.lastUrl = currentUrl;
|
|
149
|
+
this.buffer.push({
|
|
150
|
+
url: currentUrl,
|
|
151
|
+
path: window.location.pathname,
|
|
152
|
+
referrer: document.referrer,
|
|
153
|
+
timestampMs: Date.now()
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
flush() {
|
|
157
|
+
if (!this.config || this.buffer.length === 0) return;
|
|
158
|
+
const batch = this.buffer.splice(0);
|
|
159
|
+
sendPvData(this.config.collectorUrl, batch, this.config.headers);
|
|
160
|
+
}
|
|
161
|
+
listenRouteChanges() {
|
|
162
|
+
if (document.readyState === "complete") {
|
|
163
|
+
this.recordPv();
|
|
164
|
+
} else {
|
|
165
|
+
window.addEventListener("load", () => this.recordPv());
|
|
166
|
+
}
|
|
167
|
+
window.addEventListener("popstate", () => this.recordPv());
|
|
168
|
+
this.patchHistory("pushState");
|
|
169
|
+
this.patchHistory("replaceState");
|
|
170
|
+
}
|
|
171
|
+
patchHistory(method) {
|
|
172
|
+
const original = history[method];
|
|
173
|
+
const recordPv = () => this.recordPv();
|
|
174
|
+
history[method] = function(...args) {
|
|
175
|
+
const result = original.apply(this, args);
|
|
176
|
+
recordPv();
|
|
177
|
+
return result;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
registerLifecycleListeners() {
|
|
181
|
+
document.addEventListener("visibilitychange", () => {
|
|
182
|
+
if (document.visibilityState === "hidden") {
|
|
183
|
+
this.flush();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
window.addEventListener("pagehide", () => this.flush());
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/index.ts
|
|
191
|
+
var pvTracker = PvTracker.instance;
|
|
192
|
+
|
|
193
|
+
export { PvTracker, pvTracker };
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lark-apaas/observable-web-lite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight PV/UV reporting SDK - zero dependencies",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"lint": "eslint . --quiet",
|
|
27
|
+
"test": "vitest",
|
|
28
|
+
"test:cov": "vitest run --coverage",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^18.17.18"
|
|
33
|
+
}
|
|
34
|
+
}
|