diff --git a/README.md b/README.md index dedd7bdf..61da93e8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +

中文

+ # Counterscale ![](/packages/server/public/counterscale-logo-300x300.webp) @@ -63,6 +65,8 @@ NOTE: _If this is your first time deploying Counterscale, it may take take a few ### Start Recording Web Traffic from Your Website(s) +**Site ID:** There is no separate "get site ID" step. You choose any string that uniquely identifies this site (e.g. your domain `example.com`, a slug `my-blog`, or a UUID). Use the same site ID in your tracking code and in the dashboard when filtering by site. + You can load the tracking code using one of two methods: #### 1. Script Loader (CDN) diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 00000000..6c699552 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,304 @@ +

English

+ +# Counterscale + +![](/packages/server/public/counterscale-logo-300x300.webp) + +![ci status](https://github.com/benvinegar/counterscale/actions/workflows/ci.yaml/badge.svg) +[![License](https://img.shields.io/github/license/benvinegar/counterscale)](https://github.com/benvinegar/counterscale/blob/master/LICENSE) +[![codecov](https://codecov.io/gh/benvinegar/counterscale/graph/badge.svg?token=NUHURNB682)](https://codecov.io/gh/benvinegar/counterscale) + +Counterscale 是一款简单的网站分析追踪与仪表盘,可在 Cloudflare 上自托管部署。 + +设计目标是易于部署与维护,运营成本趋近于零——即便在高流量下(Cloudflare [免费版](https://developers.cloudflare.com/workers/platform/pricing/#workers)理论上可支持约 10 万次/天的请求)。 + +_Counterscale 由 [Modem——开发团队的自动分类 PM](https://modem.dev) 赞助。_ + +## 许可证 + +Counterscale 为免费开源软件,采用 MIT 许可证。详见:[LICENSE](LICENSE)。 + +## 限制说明 + +Counterscale 主要基于 Cloudflare Workers 与 [Workers Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/)。截至 2025 年 2 月,Workers Analytics Engine 的**最长保留期为 90 天**,因此 Counterscale 仅能展示最近 90 天的记录数据。我们同时支持将数据长期存储在 R2 桶中(使用 Apache Arrow 文件),该长期存储默认开启,可通过 CLI 关闭。 + +## 安装 + +### 环境要求 + +* macOS 或 Linux +* Node v20 及以上 +* 有效的 [Cloudflare](https://cloudflare.com) 账号(免费或付费均可) + +### Cloudflare 准备 + +若尚未拥有账号,请[在此创建 Cloudflare 账号](https://dash.cloudflare.com/sign-up)并完成邮箱验证。 + +1. 登录 Cloudflare 控制台,若尚未配置,请先设置 [Cloudflare Workers 子域名](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/) +2. 为账号启用 [Cloudflare Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/) 测试版:进入「存储与数据库 > Analytics Engine」并点击「启用」按钮([截图](./docs/enable-analytics-engine.png))。随后弹出的「创建数据集」窗口可忽略并关闭。 + - 说明:若首次使用 Workers,需先创建一个 Worker 才能启用 Analytics Engine。进入「Workers 与 Pages > 概览」,点击「创建 Worker」按钮([截图](./docs/create-worker.png))创建一个「Hello World」Worker(名称可任意,之后可删除)。 +3. 创建 [Cloudflare API 令牌](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)。该令牌至少需要具备 `Account.Account Analytics` 权限([截图](./docs/api-token.png))。 + - _警告:请保持该页面打开或将 API 令牌妥善保存(如密码管理器),关闭窗口后将无法再次查看该令牌,需重新创建。_ + +### 部署 Counterscale + +首先登录 Cloudflare 并授权 Cloudflare CLI(Wrangler): + +```bash +npx wrangler login +``` + +然后运行 Counterscale 安装程序: + +```bash +npx @counterscale/cli@latest install +``` + +按提示操作。会要求输入之前创建的 Cloudflare API 令牌,并询问是否用密码保护仪表盘: + +- 选择 **是**(建议用于公开部署):需设置一个密码,访问分析仪表盘时需输入该密码。 +- 选择 **否**:仪表盘将公开可访问,无需认证。 + +脚本完成后,服务端应用即已部署。可访问 `https://{部署时输出的子域名}.workers.dev` 进行验证。 + +注意:_首次部署 Counterscale 时,Worker 子域名可能需要几分钟才能生效。_ + +### 开始记录网站流量 + +**站点 ID(site-id):** 不需要从别处「获取」,由你自己定义。任选一个能唯一标识该站点的字符串即可,例如域名 `example.com`、缩写 `my-blog` 或 UUID。追踪代码和仪表盘里筛选站点时使用同一个 site-id 即可。 + +可通过两种方式加载追踪代码: + +#### 1. 脚本加载器(CDN) + +部署后,Counterscale 会在部署地址提供 `tracker.js`: + +``` +https://{部署时输出的子域名}.workers.dev/tracker.js +``` + +将以下代码片段复制到网站 HTML 中即可开始上报流量: + +```html + +``` + +#### 2. 包/模块方式 + +Counterscale 追踪器以 npm 包形式发布: + +```bash +npm install @counterscale/tracker +``` + +使用站点 ID 和部署的上报端点 URL 初始化: + +```typescript +import * as Counterscale from "@counterscale/tracker"; + +Counterscale.init({ + siteId: "your-unique-site-id", + reporterUrl: "https://{部署时输出的子域名}.workers.dev/collect", +}); +``` + +**可用方法** +| 方法 | 参数 | 返回类型 | 说明 | +|--------|------------|-------------|-------------| +| `init(opts)` | `ClientOpts` | `void` | 使用站点配置初始化 Counterscale 客户端;若不存在则创建全局实例。 | +| `isInitialized()` | 无 | `boolean` | 检查客户端是否已初始化。 | +| `getInitializedClient()` | 无 | `Client \| undefined` | 返回已初始化的客户端实例,未初始化则返回 undefined。 | +| `trackPageview(opts?)` | `TrackPageviewOpts?` | `void` | 记录一次页面浏览。需先初始化客户端;未提供时自动检测 URL 与 referrer。 | +| `cleanup()` | 无 | `void` | 清理客户端实例并移除事件监听,将全局客户端设为 undefined。 | + +#### 3. 服务端模块 + +若希望在服务端而非浏览器中上报分析数据,可使用 `/server` 模块: + +```bash +npm install @counterscale/tracker +``` + +```typescript +import * as Counterscale from "@counterscale/tracker/server"; + +// 初始化追踪器 +Counterscale.init({ + siteId: "your-unique-site-id", + reporterUrl: + "https://{部署时输出的子域名}.workers.dev/collect", + reportOnLocalhost: false, // 可选,默认 false + timeout: 2000, // 可选,默认 1000ms +}); + +// 记录一次页面浏览 +await Counterscale.trackPageview({ + url: "https://example.com/page", // 或相对路径:'/page' + hostname: "example.com", // 使用相对 URL 时必填 + referrer: "https://google.com", + utmSource: "social", + utmMedium: "twitter", +}); +``` + +**服务端模块方法** +| 方法 | 参数 | 返回类型 | 说明 | +|--------|------------|-------------|-------------| +| `init(opts)` | `ServerClientOpts` | `void` | 初始化服务端追踪器。 | +| `isInitialized()` | 无 | `boolean` | 检查追踪器是否已初始化。 | +| `getInitializedClient()` | 无 | `ServerClient \| undefined` | 返回已初始化的服务端客户端实例。 | +| `trackPageview(opts)` | `TrackPageviewOpts` | `Promise` | 记录一次页面浏览。需显式传入 URL 和 hostname。 | +| `cleanup()` | 无 | `void` | 清理服务端客户端实例。 | + +服务端模块面向后端应用,与客户端版本区别如下: + +- 无依赖 DOM 的功能(自动追踪、浏览器埋点) +- 使用 fetch API 而非 XMLHttpRequest +- 需显式传入 URL 和 hostname +- 采用「发出即忘」策略,追踪错误不会抛出异常 + +## 升级 + +大多数版本升级只需重新执行 CLI 安装命令: + +```bash +npx @counterscale/cli@latest install + +# 或指定版本 +# npx @counterscale/cli@VERSION install +``` + +无需重新输入 API 密钥,数据会保留。 + +Counterscale 使用[语义化版本](https://semver.org/)。升级主版本(如 2.x、3.x、4.x)时可能有额外步骤,请参考[发布说明](https://github.com/benvinegar/counterscale/releases)。 + +## 故障排除 + +若网站无法立即访问(例如「安全连接失败」),可能是 Cloudflare 尚未激活你的子域名(yoursubdomain.workers.dev)。该过程可能需要一分钟;可在 Cloudflare 控制台(Workers & Pages → counterscale)中查看新建 Worker 的状态。 + +## 高级用法 + +### 手动记录页面浏览 + +初始化 Counterscale 追踪器时,将 `autoTrackPageviews` 设为 `false`,然后在需要记录时手动调用 `Counterscale.trackPageview()`。 + +```typescript +import * as Counterscale from "@counterscale/tracker"; + +Counterscale.init({ + siteId: "your-unique-site-id", + reporterUrl: "https://{部署时输出的子域名}.workers.dev/collect", + autoTrackPageviews: false, // <- 不要忘记此项 +}); + +// ... 在发生页面浏览时 +Counterscale.trackPageview(); +``` + +### 自定义域名 + +部署地址可改为使用你拥有的自定义域名。详见[此处](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/)。 + +## CLI 命令 + +Counterscale 提供命令行工具(CLI)用于安装、配置和管理部署。 + +### 可用命令 + +#### `install` + +安装并将 Counterscale 部署到 Cloudflare 的主命令。 + +```bash +npx @counterscale/cli@latest install +``` + +选项: + +- `--advanced` - 启用高级模式,可自定义 Worker 名称与分析数据集 +- `--verbose` - 输出更多日志 + +#### `auth` + +管理 Counterscale 部署的认证设置。 + +```bash +npx @counterscale/cli@latest auth [子命令] +``` + +子命令: + +- `enable` - 为部署启用认证 +- `disable` - 关闭认证 +- `roll` - 更新/轮换认证密码 + +##### 示例 + +启用认证: + +```bash +npx @counterscale/cli@latest auth enable +``` + +关闭认证: + +```bash +npx @counterscale/cli@latest auth disable +``` + +更新密码: + +```bash +npx @counterscale/cli@latest auth roll +``` + +#### `storage` + +管理 Counterscale 部署的长期存储设置。 + +```bash +npx @counterscale/cli@latest storage [子命令] +``` + +子命令: + +- `enable` - 为部署启用存储 +- `disable` - 关闭存储 + +##### 示例 + +启用存储: + +```bash +npx @counterscale/cli@latest storage enable +``` + +关闭存储: + +```bash +npx @counterscale/cli@latest storage disable +``` + +## 开发 + +如何参与开发请参阅 [Contributing](CONTRIBUTING.md)。 + +## 说明 + +### 数据库 + +当前仅有一个「数据库」:Cloudflare Analytics Engine 数据集,通过 HTTP 与 Cloudflare API 通信。 + +目前没有本地「测试」数据库。因此在本地开发时: + +- 写入不会生效(不会记录任何请求) +- 读取来自生产环境的 Analytics Engine 数据集(本地开发看到的是生产数据) + +### 采样 + +Cloudflare Analytics Engine 使用采样以在高流量下控制 ingestion/查询成本(与多数分析工具类似,可参考 [Google Analytics 关于采样](https://support.google.com/analytics/answer/2637192?hl=en#zippy=%2Cin-this-article))。更多关于 [CF AE 采样的说明](https://developers.cloudflare.com/analytics/analytics-engine/sampling/)可在此查看。 diff --git a/package.json b/package.json index 7e6d9969..5bbbc0c9 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "scripts": { "build": "turbo run build", "dev": "turbo run dev", - "lint": "turbo run lint" + "lint": "turbo run lint", + "deploy": "pnpm --filter @counterscale/server run deploy" }, "devDependencies": { "eslint-config-prettier": "^9.1.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 78084f91..98767287 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,7 +35,6 @@ "cli-highlight": "^2.1.11", "figlet": "^1.7.0", "inquirer": "^9.2.12", - "wrangler": "^4.23.0", "yargs": "^17.7.2", "zx": "^8.8.1" }, @@ -50,7 +49,8 @@ "rollup-plugin-node-externals": "^8.0.0", "typescript": "^5.3.3", "vite": "^7.2.2", - "vitest": "^4.0.8" + "vitest": "^4.0.8", + "wrangler": "^4.23.0" }, "overrides": { "minimatch": "5.1.2", diff --git a/packages/server/package.json b/packages/server/package.json index f790946d..6a7c0049 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,8 +21,8 @@ "scripts": { "dev": "react-router dev", "build": "react-router build", - "preview": "wrangler dev --var VERSION:`git rev-parse HEAD`", - "deploy": "wrangler deploy --var VERSION:`git rev-parse HEAD`", + "preview": "wrangler dev --config wrangler.json --var VERSION:`git rev-parse HEAD`", + "deploy": "pnpm run build && wrangler deploy --config wrangler.json --var VERSION:`git rev-parse HEAD`", "lint": "eslint .", "test": "TZ=EST vitest run", "test-ci": "TZ=EST vitest run --coverage", diff --git a/packages/server/wrangler.toml b/packages/server/wrangler.toml new file mode 100644 index 00000000..33398509 --- /dev/null +++ b/packages/server/wrangler.toml @@ -0,0 +1,21 @@ +# Wrangler 默认读取此文件;与 wrangler.json 保持一致 +main = "./workers/app.ts" +name = "counterscale" +compatibility_flags = ["nodejs_compat_v2"] +compatibility_date = "2024-12-13" + +[assets] +binding = "ASSETS" +directory = "./build/client" + +[[analytics_engine_datasets]] +binding = "WEB_COUNTER_AE" +dataset = "metricsDataset" + +[[r2_buckets]] +bucket_name = "counterscale-daily-rollups" +preview_bucket_name = "counterscale-daily-rollups-dev" +binding = "DAILY_ROLLUPS" + +[triggers] +crons = ["0 2 * * *"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea8c6dec..975312c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: version: 3.4.2 turbo: specifier: latest - version: 2.6.1 + version: 2.8.1 packages/cli: dependencies: @@ -53,9 +53,6 @@ importers: inquirer: specifier: ^9.2.12 version: 9.3.7 - wrangler: - specifier: ^4.23.0 - version: 4.23.0(@cloudflare/workers-types@4.20250803.0) yargs: specifier: ^17.7.2 version: 17.7.2 @@ -96,6 +93,9 @@ importers: vitest: specifier: ^4.0.8 version: 4.0.8(@types/node@22.13.1)(jiti@2.5.1)(jsdom@23.2.0)(tsx@4.20.6)(yaml@2.7.0) + wrangler: + specifier: ^4.23.0 + version: 4.23.0(@cloudflare/workers-types@4.20250803.0) packages/eslint-config: dependencies: @@ -4290,38 +4290,38 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo-darwin-64@2.6.1: - resolution: {integrity: sha512-Dm0HwhyZF4J0uLqkhUyCVJvKM9Rw7M03v3J9A7drHDQW0qAbIGBrUijQ8g4Q9Cciw/BXRRd8Uzkc3oue+qn+ZQ==} + turbo-darwin-64@2.8.1: + resolution: {integrity: sha512-FQ6Uqxty/H1Nvn1dpBe8KUlMRclTuiyNSc1PCeDL/ad7M9ykpWutB51YpMpf9ibTA32M6wLdIRf+D96W6hDAtQ==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.6.1: - resolution: {integrity: sha512-U0PIPTPyxdLsrC3jN7jaJUwgzX5sVUBsKLO7+6AL+OASaa1NbT1pPdiZoTkblBAALLP76FM0LlnsVQOnmjYhyw==} + turbo-darwin-arm64@2.8.1: + resolution: {integrity: sha512-4bCcEpGP2/aSXmeN2gl5SuAmS1q5ykjubnFvSoXjQoCKtDOV+vc4CTl/DduZzUUutCVUWXjl8OyfIQ+DGCaV4A==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.6.1: - resolution: {integrity: sha512-eM1uLWgzv89bxlK29qwQEr9xYWBhmO/EGiH22UGfq+uXr+QW1OvNKKMogSN65Ry8lElMH4LZh0aX2DEc7eC0Mw==} + turbo-linux-64@2.8.1: + resolution: {integrity: sha512-m99JRlWlEgXPR7mkThAbKh6jbTmWSOXM/c6rt8yd4Uxh0+wjq7+DYcQbead6aoOqmCP9akswZ8EXIv1ogKBblg==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.6.1: - resolution: {integrity: sha512-MFFh7AxAQAycXKuZDrbeutfWM5Ep0CEZ9u7zs4Hn2FvOViTCzIfEhmuJou3/a5+q5VX1zTxQrKGy+4Lf5cdpsA==} + turbo-linux-arm64@2.8.1: + resolution: {integrity: sha512-AsPlza3AsavJdl2o7FE67qyv0aLfmT1XwFQGzvwpoAO6Bj7S4a03tpUchZKNuGjNAkKVProQRFnB7PgUAScFXA==} cpu: [arm64] os: [linux] - turbo-windows-64@2.6.1: - resolution: {integrity: sha512-buq7/VAN7KOjMYi4tSZT5m+jpqyhbRU2EUTTvp6V0Ii8dAkY2tAAjQN1q5q2ByflYWKecbQNTqxmVploE0LVwQ==} + turbo-windows-64@2.8.1: + resolution: {integrity: sha512-GdqNO6bYShRsr79B+2G/2ssjLEp9uBTvLBJSWRtRCiac/SEmv8T6RYv9hu+h5oGbFALtnKNp6BQBw78RJURsPw==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.6.1: - resolution: {integrity: sha512-7w+AD5vJp3R+FB0YOj1YJcNcOOvBior7bcHTodqp90S3x3bLgpr7tE6xOea1e8JkP7GK6ciKVUpQvV7psiwU5Q==} + turbo-windows-arm64@2.8.1: + resolution: {integrity: sha512-n40E6IpkzrShRo3yMdRpgnn1/sAbGC6tZXwyNu8fe9RsufeD7KBiaoRSvw8xLyqV3pd2yoTL2rdCXq24MnTCWA==} cpu: [arm64] os: [win32] - turbo@2.6.1: - resolution: {integrity: sha512-qBwXXuDT3rA53kbNafGbT5r++BrhRgx3sAo0cHoDAeG9g1ItTmUMgltz3Hy7Hazy1ODqNpR+C7QwqL6DYB52yA==} + turbo@2.8.1: + resolution: {integrity: sha512-pbSMlRflA0RAuk/0jnAt8pzOYh1+sKaT8nVtcs75OFGVWD0evleQRmKtHJJV42QOhaC3Hx9mUUSOom/irasbjA==} hasBin: true type-check@0.4.0: @@ -4586,6 +4586,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -8848,32 +8849,32 @@ snapshots: fsevents: 2.3.3 optional: true - turbo-darwin-64@2.6.1: + turbo-darwin-64@2.8.1: optional: true - turbo-darwin-arm64@2.6.1: + turbo-darwin-arm64@2.8.1: optional: true - turbo-linux-64@2.6.1: + turbo-linux-64@2.8.1: optional: true - turbo-linux-arm64@2.6.1: + turbo-linux-arm64@2.8.1: optional: true - turbo-windows-64@2.6.1: + turbo-windows-64@2.8.1: optional: true - turbo-windows-arm64@2.6.1: + turbo-windows-arm64@2.8.1: optional: true - turbo@2.6.1: + turbo@2.8.1: optionalDependencies: - turbo-darwin-64: 2.6.1 - turbo-darwin-arm64: 2.6.1 - turbo-linux-64: 2.6.1 - turbo-linux-arm64: 2.6.1 - turbo-windows-64: 2.6.1 - turbo-windows-arm64: 2.6.1 + turbo-darwin-64: 2.8.1 + turbo-darwin-arm64: 2.8.1 + turbo-linux-64: 2.8.1 + turbo-linux-arm64: 2.8.1 + turbo-windows-64: 2.8.1 + turbo-windows-arm64: 2.8.1 type-check@0.4.0: dependencies: