@virjay/playscope-cocos 0.0.0-stage → 0.2.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/CHANGELOG.md +30 -0
- package/README.md +314 -2
- package/USAGE.md +372 -0
- package/VALIDATION.md +93 -0
- package/dist/adapters/levelplay/index.d.ts +15 -0
- package/dist/adapters/levelplay/index.js +14 -0
- package/dist/cocos/Storage.d.ts +16 -0
- package/dist/cocos/Storage.js +16 -0
- package/dist/cocos/XhrJsonTransport.d.ts +8 -0
- package/dist/cocos/XhrJsonTransport.js +65 -0
- package/dist/cocos/index.d.ts +48 -0
- package/dist/cocos/index.js +134 -0
- package/dist/core/ClientOwnership.d.ts +6 -0
- package/dist/core/ClientOwnership.js +39 -0
- package/dist/core/ClientScope.d.ts +5 -0
- package/dist/core/ClientScope.js +34 -0
- package/dist/core/ConfigurationSync.d.ts +18 -0
- package/dist/core/ConfigurationSync.js +66 -0
- package/dist/core/FactEncoding.d.ts +10 -0
- package/dist/core/FactEncoding.js +29 -0
- package/dist/core/HttpError.d.ts +10 -0
- package/dist/core/HttpError.js +17 -0
- package/dist/core/OutboxStore.d.ts +31 -0
- package/dist/core/OutboxStore.js +228 -0
- package/dist/core/PersistentState.d.ts +33 -0
- package/dist/core/PersistentState.js +92 -0
- package/dist/core/PlayScopeClient.d.ts +65 -0
- package/dist/core/PlayScopeClient.js +422 -0
- package/dist/core/QueueAdmission.d.ts +4 -0
- package/dist/core/QueueAdmission.js +29 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +27 -0
- package/dist/core/revenue.d.ts +33 -0
- package/dist/core/revenue.js +23 -0
- package/dist/core/types.d.ts +155 -0
- package/dist/core/types.js +2 -0
- package/dist/core/uuid.d.ts +1 -0
- package/dist/core/uuid.js +9 -0
- package/dist/esm/adapters/levelplay/index.js +11 -0
- package/dist/esm/cocos/Storage.js +12 -0
- package/dist/esm/cocos/XhrJsonTransport.js +61 -0
- package/dist/esm/cocos/index.js +115 -0
- package/dist/esm/core/ClientOwnership.js +34 -0
- package/dist/esm/core/ClientScope.js +30 -0
- package/dist/esm/core/ConfigurationSync.js +62 -0
- package/dist/esm/core/FactEncoding.js +24 -0
- package/dist/esm/core/HttpError.js +12 -0
- package/dist/esm/core/OutboxStore.js +224 -0
- package/dist/esm/core/PersistentState.js +85 -0
- package/dist/esm/core/PlayScopeClient.js +415 -0
- package/dist/esm/core/QueueAdmission.js +25 -0
- package/dist/esm/core/index.js +7 -0
- package/dist/esm/core/revenue.js +20 -0
- package/dist/esm/core/types.js +1 -0
- package/dist/esm/core/uuid.js +6 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/protocols/game-v1/index.js +21 -0
- package/dist/protocols/game-v1/index.d.ts +8 -0
- package/dist/protocols/game-v1/index.js +25 -0
- package/doc/README.md +4 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227/README.md +585 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/adapters/levelplay/index.d.ts +15 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/cocos/Storage.d.ts +13 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/cocos/XhrJsonTransport.d.ts +8 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/cocos/index.d.ts +38 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/PlayScopeClient.d.ts +46 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/index.d.ts +3 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/revenue.d.ts +25 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/types.d.ts +81 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/exports.json +89 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/public-api.d.ts +281 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/client-context.schema.json +103 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/game-ad-properties.json +765 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/game-builtin-properties.json +479 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/game-event.json +278 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/product-events.json +2878 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/standard-contract.json +183 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257//344/272/247/345/223/201/344/272/213/344/273/266/347/264/242/345/274/225.md +87 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/235/245/346/272/220/344/270/216/347/211/210/346/234/254.json +35 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/CoreIntegration.ts +13 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/LevelPlayTelemetry.ts +119 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/PlayScopeExample.ts +46 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/QuestGarden.ts +129 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/README.md +23 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/project-fixture.json +188 -0
- package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/tsconfig.json +20 -0
- package/examples/cocos-minimal/PlayScopeExample.ts +27 -0
- package/examples/quest-garden/QuestGarden.ts +129 -0
- package/examples/quest-garden/README.md +180 -0
- package/examples/quest-garden/project-fixture.json +188 -0
- package/examples/quest-garden/tsconfig.json +12 -0
- package/package.json +73 -4
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
# PlayScope SDK 接入与使用指南
|
|
2
|
+
|
|
3
|
+
> 本文档面向**游戏客户端开发者**与**负责对接 SDK 的 AI Agent**。
|
|
4
|
+
> 语言精简、逻辑清晰,提供从零接入 PlayScope SDK(`@virjay/playscope-cocos`)的完整流程、接口定义、打点规范与最佳实践。
|
|
5
|
+
|
|
6
|
+
本指南对应 **@virjay/playscope-cocos 0.2.0**。旧 @playscope/cocos 0.1.1 tarball 不包含新增能力;升级时更换导入路径并保留旧存储键。
|
|
7
|
+
精确接口见 [全部公开类型](参考资料/public-api.d.ts) 和 [导出索引](参考资料/exports.json),事件细节见 [80 项产品字典](参考资料/服务端/product-events.json)、[产品索引](参考资料/服务端/产品事件索引.md) 与 [通用事件信封](参考资料/服务端/game-event.json)。
|
|
8
|
+
配置、广告和对局的可复制实现见 [示例目录](示例/README.md)。现有项目须核对自身冻结契约,不能仅凭本地类型通过推定服务端接收。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 目录
|
|
13
|
+
|
|
14
|
+
1. [SDK 简介与核心特性](#1-sdk-简介与核心特性)
|
|
15
|
+
2. [5分钟快速上手 (Cocos Creator)](#2-5分钟快速上手-cocos-creator)
|
|
16
|
+
3. [AI 对接标准流程 (SOP)](#3-ai-对接标准流程-sop)
|
|
17
|
+
4. [核心 API 与接口参考](#4-核心-api-与接口参考)
|
|
18
|
+
5. [游戏事件与对局归因 (Run 机制)](#5-游戏事件与对局归因-run-机制)
|
|
19
|
+
6. [广告全链路打点与 LevelPlay 桥接](#6-广告全链路打点与-levelplay-桥接)
|
|
20
|
+
7. [远程配置与实验分流](#7-远程配置与实验分流)
|
|
21
|
+
8. [服务端接口协议 (底层参考)](#8-服务端接口协议-底层参考)
|
|
22
|
+
9. [排错指南与验收清单](#9-排错指南与验收清单)
|
|
23
|
+
10. [附录:老项目迁移与 BlockDrop 约定](#10-附录老项目迁移与-blockdrop-约定)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. SDK 简介与核心特性
|
|
28
|
+
|
|
29
|
+
PlayScope SDK 是专为 Cocos Creator 及 HTML5/TypeScript 游戏设计的高性能、离线优先的数据分析与远程配置 SDK。
|
|
30
|
+
|
|
31
|
+
### 核心机制
|
|
32
|
+
- **离线优先 (Local-First Outbox)**:事件调用 `track()` 后首先写入本地存储(如 `sys.localStorage`),通过落盘检查的事实可在离线/重启后重放;存储故障时仅在内存的事实仍可能丢失,使用 trackWithResult 检查 durability。
|
|
33
|
+
- **自动会话与身份维护**:持久化安装唯一 ID(`installId`)与全局自增事件序号(`sequence`),运行时生成会话 ID(`sessionId`),并在首次启动时自动上报 `user_first_open`。
|
|
34
|
+
- **分批上传与服务端确认 (ACK)**:SDK 自动分批上报,仅当服务端持久化成功并返回 ACK 后才从本地队列移除;网络失败或限流时自动指数退避重试。
|
|
35
|
+
- **动态 Token 鉴权**:SDK 自动通过客户端凭证换取短期 Access Token 并自动静默刷新,宿主无需维护 Token 状态。
|
|
36
|
+
- **单例保护**:同一存储 Key 下仅允许一个活跃实例,防止多实例并发读写导致数据损坏。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. 5分钟快速上手 (Cocos Creator)
|
|
41
|
+
|
|
42
|
+
### 2.1 安装依赖
|
|
43
|
+
|
|
44
|
+
从 npm 安装并固定正式版本:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @virjay/playscope-cocos@0.2.0 --save-exact
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
提交 package.json 和 package-lock.json。也可使用同版本 tarball,不覆盖旧包。
|
|
51
|
+
|
|
52
|
+
### 2.2 创建常驻单例组件
|
|
53
|
+
|
|
54
|
+
在 Cocos Creator 项目中新建脚本 `GameAnalytics.ts`,挂载在**全局常驻节点**(跨场景不销毁):
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { _decorator, Component, sys, game, Game } from 'cc';
|
|
58
|
+
import { init, type CocosPlayScope, type TrackContext } from '@virjay/playscope-cocos';
|
|
59
|
+
|
|
60
|
+
const { ccclass } = _decorator;
|
|
61
|
+
|
|
62
|
+
@ccclass('GameAnalytics')
|
|
63
|
+
export class GameAnalytics extends Component {
|
|
64
|
+
public static instance: GameAnalytics | null = null;
|
|
65
|
+
private telemetry: CocosPlayScope | null = null;
|
|
66
|
+
|
|
67
|
+
onLoad(): void {
|
|
68
|
+
if (GameAnalytics.instance) {
|
|
69
|
+
this.node.destroy();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
GameAnalytics.instance = this;
|
|
73
|
+
game.addPersistRootNode(this.node); // 跨场景常驻
|
|
74
|
+
|
|
75
|
+
this.setCollectionAllowed(false); // 接宿主真实授权结果,默认不采集。
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public setCollectionAllowed(allowed: boolean): void {
|
|
79
|
+
const previous = this.telemetry;
|
|
80
|
+
this.telemetry = null;
|
|
81
|
+
previous?.dispose();
|
|
82
|
+
this.telemetry = init({
|
|
83
|
+
baseUrl: 'https://analytics.example.com', // PlayScope 服务端根地址 (末尾不带斜杠,不拼 /v1)
|
|
84
|
+
projectId: 'YOUR_PROJECT_UUID', // 后台项目 UUID
|
|
85
|
+
projectKey: 'YOUR_CLIENT_KEY', // 客户端专用的 Project Key (严禁使用管理员 Token)
|
|
86
|
+
collectionEnabled: allowed, // 来自宿主真实许可
|
|
87
|
+
client: {
|
|
88
|
+
appVersion: '1.0.0',
|
|
89
|
+
buildNumber: '100',
|
|
90
|
+
platform: sys.isNative ? (sys.os === sys.OS.ANDROID ? 'android' : 'ios') : 'web',
|
|
91
|
+
locale: sys.language || 'zh-CN',
|
|
92
|
+
timezoneOffsetMinutes: -new Date().getTimezoneOffset(),
|
|
93
|
+
},
|
|
94
|
+
}, {
|
|
95
|
+
storage: sys.localStorage,
|
|
96
|
+
lifecycle: {
|
|
97
|
+
onHide: (callback) => {
|
|
98
|
+
game.on(Game.EVENT_HIDE, callback);
|
|
99
|
+
return () => game.off(Game.EVENT_HIDE, callback);
|
|
100
|
+
},
|
|
101
|
+
onShow: (callback) => {
|
|
102
|
+
game.on(Game.EVENT_SHOW, callback);
|
|
103
|
+
return () => game.off(Game.EVENT_SHOW, callback);
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// app_launch/session_start 在真实业务入口调用,不随每次授权切换重复发送。
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
update(dt: number): void {
|
|
112
|
+
// 驱动 SDK 内部定时冲刷(约每 5 秒自动上报一次离线队列)
|
|
113
|
+
this.telemetry?.update(dt);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
onDestroy(): void {
|
|
117
|
+
this.telemetry?.dispose();
|
|
118
|
+
this.telemetry = null;
|
|
119
|
+
if (GameAnalytics.instance === this) {
|
|
120
|
+
GameAnalytics.instance = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 通用打点接口
|
|
126
|
+
* @param name 事件名称 (如 game_start, level_win)
|
|
127
|
+
* @param properties 事件自定义属性
|
|
128
|
+
* @param context 可选的上下文 (如携带对局 run 信息)
|
|
129
|
+
*/
|
|
130
|
+
public track(name: string, properties: Record<string, unknown> = {}, context?: TrackContext): boolean {
|
|
131
|
+
return this.telemetry?.track(name, properties, context) ?? false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 获取底层 SDK 实例 (用于广告转发等进阶功能) */
|
|
135
|
+
public getClient(): CocosPlayScope | null {
|
|
136
|
+
return this.telemetry;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2.3 业务打点调用
|
|
142
|
+
|
|
143
|
+
在任意业务脚本中直接调用:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// 普通页面事件不需要 run;关卡终局必须带完整 run,见第 5 节。
|
|
147
|
+
GameAnalytics.instance?.track('screen_view', { screen: 'level_selection' });
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 3. AI 对接标准流程 (SOP)
|
|
153
|
+
|
|
154
|
+
如果你是负责接入 PlayScope 的 AI 编程助手,请严格按照以下 7 个步骤推进对接:
|
|
155
|
+
|
|
156
|
+
```mermaid
|
|
157
|
+
flowchart TD
|
|
158
|
+
S1["1. 确认项目配置与凭据 (baseUrl, projectId, projectKey)"] --> S2["2. 安装依赖并创建全局单例 Owner"]
|
|
159
|
+
S2 --> S3["3. 接入隐私授权 (collectionEnabled) 与生命周期"]
|
|
160
|
+
S3 --> S4["4. 接入基础生命周期打点 (launch, session)"]
|
|
161
|
+
S4 --> S5["5. 接入对局归因 (run 对象与冻结规则)"]
|
|
162
|
+
S5 --> S6["6. 接入广告打点 (展示 impression, 点击, 收入)"]
|
|
163
|
+
S6 --> S7["7. 运行自测与验收 (落盘检查, 断网恢复, 抓包核对)"]
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 步骤清单与要点
|
|
167
|
+
|
|
168
|
+
| 步骤 | 任务内容 | 核心要求与禁止项 |
|
|
169
|
+
| :--- | :--- | :--- |
|
|
170
|
+
| **Step 1** | **凭据与环境准备** | 从服务端或配置中获取 `baseUrl`、`projectId`、`projectKey`。**绝对禁止**将管理员 Token(Admin Token)写入客户端代码。 |
|
|
171
|
+
| **Step 2** | **建立唯一单例** | 在游戏现有框架(如 `GameRoot` 或常驻节点)中创建唯一实例。**禁止**在多个场景分别创建 `CocosPlayScope`。 |
|
|
172
|
+
| **Step 3** | **隐私与授权管理** | 用户未同意隐私协议前,`collectionEnabled` 必须为 `false`(不写存储、不发请求)。同意后重建实例为 `true`。 |
|
|
173
|
+
| **Step 4** | **基础事件打点** | 自动生成的首次事件由 SDK 处理;游戏内仅需在真实时机触发 `app_launch` 和 `session_start`。切勿在 `update` 帧循环中重复触发。 |
|
|
174
|
+
| **Step 5** | **对局与归因绑定** | 开局时生成 `runId`(UUID),构建 `run` 对象(含关卡、配置版本)。局内所有事件(含局内广告)必须携带该 `run`。**局内配置必须冻结**,中途下发的新配置仅对下一局生效。 |
|
|
175
|
+
| **Step 6** | **广告链路打点** | 区分 `ad_show` 与 `ad_impression`(后台统计展示数以 `ad_impression` 为准)。收入使用 `forwardLevelPlayRevenue` 自动转发。Banner 刷新每次必须生成新 `exposureId`。 |
|
|
176
|
+
| **Step 7** | **验证与交付** | 检查控制台无初始化报错;断网产生事件并恢复网络后观察事件是否补发;确认服务端事件去重与入库正常。 |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 4. 核心 API 与接口参考
|
|
181
|
+
|
|
182
|
+
### 4.1 初始化入口:`init(options, runtime)`
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { init } from '@virjay/playscope-cocos';
|
|
186
|
+
|
|
187
|
+
const sdk = init(options, runtime);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `options: CocosOptions` 参数表
|
|
191
|
+
|
|
192
|
+
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
193
|
+
| :--- | :--- | :---: | :--- | :--- |
|
|
194
|
+
| `baseUrl` | `string` | **是** | - | 服务端根地址,如 `https://analytics.example.com`(末尾不加斜杠,不拼 `/v1/...`) |
|
|
195
|
+
| `projectId` | `string` | **是** | - | PlayScope 项目 UUID |
|
|
196
|
+
| `projectKey` | `string` | **是** | - | 客户端专用鉴权 Key |
|
|
197
|
+
| `collectionEnabled` | `boolean`| **是** | - | 隐私数据采集开关。`false` 时不读写存储、不发请求 |
|
|
198
|
+
| `client` | `ClientContext` | **是** | - | 客户端环境元信息(版本号、平台、语言、时区等) |
|
|
199
|
+
| `storageKey` | `string` | 否 | `playscope:<projectId>` | 本地持久化 Key(老项目升级时需保持历史 Key 一致) |
|
|
200
|
+
| `queuePolicy` | `QueuePolicy` | 否 | 见下文 | 本地队列容量限制(默认最大 2000 条,4 MiB) |
|
|
201
|
+
| `restoreCache` | `(saved: unknown) => unknown` | 否 | - | 读取本地远程配置缓存时的校验回调 |
|
|
202
|
+
| `resolveConfiguration` | `(ctx: ConfigurationContext) => Promise<number>` | 否 | - | 远程配置拉取与应用回调,返回下次刷新秒数 |
|
|
203
|
+
|
|
204
|
+
#### `runtime: CocosRuntime` 参数表
|
|
205
|
+
|
|
206
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
207
|
+
| :--- | :--- | :---: | :--- |
|
|
208
|
+
| `storage` | `LocalStorageLike` | **是** | 传入 `sys.localStorage` 或具备 `getItem`/`setItem`/`removeItem` 的存储对象 |
|
|
209
|
+
| `lifecycle` | `CocosLifecycle` | 否 | 前后台监听器,包含 `onHide` 与 `onShow` 方法 |
|
|
210
|
+
| `transport` | `JsonTransport` | 否 | HTTP 请求传输器,默认内部使用 XHR(超时 10 秒) |
|
|
211
|
+
| `now` | `() => number` | 否 | 时间戳生成器,默认使用 `Date.now` |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### 4.2 `CocosPlayScope` 常用实例方法
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
class CocosPlayScope {
|
|
219
|
+
/** 当前设备的唯一安装 ID(只读,采集关闭时为 null) */
|
|
220
|
+
readonly installId: string | null;
|
|
221
|
+
|
|
222
|
+
/** 上报事件。入队成功返回 true(不代表已送达服务器) */
|
|
223
|
+
track(name: string, properties?: Record<string, unknown>, context?: TrackContext): boolean;
|
|
224
|
+
|
|
225
|
+
/** 上报结构化广告收入(底层会自动校验、去重并转化为标准事件) */
|
|
226
|
+
trackAdRevenue(fact: AdRevenueFact, context?: TrackContext): boolean;
|
|
227
|
+
|
|
228
|
+
/** 开启新会话(生成新的 sessionId,后续事件将使用新会话 ID) */
|
|
229
|
+
newSession(): void;
|
|
230
|
+
|
|
231
|
+
/** 驱动定时器(请在 Cocos 的 update(dt) 中调用,dt 单位为秒) */
|
|
232
|
+
update(dt: number): void;
|
|
233
|
+
|
|
234
|
+
/** 异步执行一轮交付(最多10批),不保证一次清空或配置已应用 */
|
|
235
|
+
flush(): Promise<void>;
|
|
236
|
+
|
|
237
|
+
/** 手动触发远程配置拉取(遵守到期时间与失败退避) */
|
|
238
|
+
refreshConfiguration(): Promise<ConfigurationResult>;
|
|
239
|
+
|
|
240
|
+
/** 释放实例并解绑生命周期监听(本地已落盘队列依然安全保留) */
|
|
241
|
+
dispose(): void;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
### 4.3 核心数据结构
|
|
248
|
+
|
|
249
|
+
#### 客户端环境信息 `ClientContext`
|
|
250
|
+
```typescript
|
|
251
|
+
interface ClientContext {
|
|
252
|
+
appVersion: string; // 如 "1.0.0"
|
|
253
|
+
buildNumber: string; // 如 "101"
|
|
254
|
+
platform: 'android' | 'ios' | 'web' | 'wechat';
|
|
255
|
+
locale: string; // 如 "zh-CN"
|
|
256
|
+
timezoneOffsetMinutes: number; // 本地时区相对于 UTC 偏移分钟数,如 480 表示东八区
|
|
257
|
+
environment?: 'production' | 'test'; // scope默认production,服务端仍用独立项目隔离
|
|
258
|
+
device?: { type: 'android_id'; id: string }; // 仅Android合法可用值,SDK不主动采集
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### 事件打点上下文 `TrackContext`
|
|
263
|
+
```typescript
|
|
264
|
+
interface TrackContext {
|
|
265
|
+
run?: unknown; // 绑定的对局 run 对象(详见下文归因机制)
|
|
266
|
+
occurredAt?: string; // 事件真实发生时间(ISO 8601 UTC 字符串,默认当前时间)
|
|
267
|
+
deduplicationKey?: string; // 持久去重最近2000个键;SDK重放保持原eventId
|
|
268
|
+
transform?: (event: EventFact) => EventFact; // 仅新事实入队调用,旧队列不重编码
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 5. 游戏事件与对局归因 (Run 机制)
|
|
275
|
+
|
|
276
|
+
### 5.1 什么是 `run` 对象?
|
|
277
|
+
|
|
278
|
+
在游戏分析中,单纯记录“玩家看了广告”或“玩家死了一次”价值有限;必须知道这个行为**发生在哪个关卡、哪次对局中,以及当期生效的是哪个配置版本**。
|
|
279
|
+
|
|
280
|
+
`run` 对象是 PlayScope 用于归因对局数据的标准结构:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
interface GameRun {
|
|
284
|
+
runId: string; // 每次开局生成一个全新的 UUID(可通过 telemetryUuid() 获取)
|
|
285
|
+
mode: string; // 玩法模式,如 "endless"、"level_challenge"
|
|
286
|
+
level?: string | number | null; // 仅hasLevels项目可用,其他项目省略或null
|
|
287
|
+
config: {
|
|
288
|
+
configRevision: string; // 当前使用的配置版本号,如 "rev_20261112"
|
|
289
|
+
source: 'bundled' | 'cache' | 'remote'; // 配置来源:包体内置、本地缓存、远端下发
|
|
290
|
+
assignmentId: string | null; // remote/cache必须有服务端分配ID;bundled为null
|
|
291
|
+
experimentId: string | null; // 实验 ID
|
|
292
|
+
variantId: string | null; // 实验变体 ID
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 5.2 局内冻结原则 (Crucial!)
|
|
298
|
+
|
|
299
|
+
> **黄金法则**:开局(`game_start`)时创建的 `run` 对象在整个对局过程中**绝对不可更改**。
|
|
300
|
+
> 局内发生的所有操作(暂停、恢复、吃金币、看复活视频、终局 `run_ended`)均必须传递此 `run` 引用。
|
|
301
|
+
> 如果游戏在局中拉取到了新的远程配置,**新配置必须暂存,只对下一局生效**,严禁篡改当前局的归因!
|
|
302
|
+
|
|
303
|
+
#### 对局打点示例
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { telemetryUuid } from '@virjay/playscope-cocos';
|
|
307
|
+
|
|
308
|
+
class GamePlayManager {
|
|
309
|
+
private currentRun: GameRun | null = null;
|
|
310
|
+
|
|
311
|
+
// 开始新对局
|
|
312
|
+
public startNewGame(mode: string, level: string) {
|
|
313
|
+
this.currentRun = {
|
|
314
|
+
runId: telemetryUuid(), // 生成局唯一 UUID
|
|
315
|
+
mode,
|
|
316
|
+
level,
|
|
317
|
+
config: ConfigManager.getActiveAttribution(), // 获取当前冻结的配置归因
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
GameAnalytics.instance?.track('game_start', {}, { run: this.currentRun });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 局内复活
|
|
324
|
+
public onPlayerRevive() {
|
|
325
|
+
GameAnalytics.instance?.track('revive_success', { failureIndex: 1 }, { run: this.currentRun });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 终局结算
|
|
329
|
+
public onGameOver(finalScore: number) {
|
|
330
|
+
GameAnalytics.instance?.track('run_ended', {
|
|
331
|
+
outcome: 'lost', summary: { score: finalScore },
|
|
332
|
+
}, { run: this.currentRun });
|
|
333
|
+
|
|
334
|
+
// 终局后销毁当前局归因
|
|
335
|
+
this.currentRun = null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 6. 广告全链路打点与 LevelPlay 桥接
|
|
343
|
+
|
|
344
|
+
PlayScope 提供了针对聚合广告(以 LevelPlay 为代表)的无缝集成能力。
|
|
345
|
+
|
|
346
|
+
### 6.1 广告完整生命周期链条
|
|
347
|
+
|
|
348
|
+
完整的广告行为包含以下事件:
|
|
349
|
+
|
|
350
|
+
```mermaid
|
|
351
|
+
sequenceDiagram
|
|
352
|
+
participant Game as 游戏业务层
|
|
353
|
+
participant AdSDK as 广告聚合SDK (如 LevelPlay)
|
|
354
|
+
participant PS as PlayScope
|
|
355
|
+
|
|
356
|
+
Game->>AdSDK: 1. 请求广告 (loadAd)
|
|
357
|
+
Game->>PS: track('ad_request', { placement, ad_format, ad_request_id })
|
|
358
|
+
|
|
359
|
+
alt 加载成功
|
|
360
|
+
AdSDK-->>Game: onAdLoaded 回调
|
|
361
|
+
Game->>PS: track('ad_load_success', { cache_hit })
|
|
362
|
+
else 加载失败
|
|
363
|
+
AdSDK-->>Game: onAdLoadFailed 回调
|
|
364
|
+
Game->>PS: track('ad_load_fail', { error_code })
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
Game->>AdSDK: 2. 展示广告 (showAd)
|
|
368
|
+
AdSDK-->>Game: onAdDisplayed 回调 (曝光)
|
|
369
|
+
Game->>PS: track('ad_show', { ... })
|
|
370
|
+
Game->>PS: track('ad_impression', { ... }) << 计费展示核心事件!
|
|
371
|
+
|
|
372
|
+
opt 用户点击
|
|
373
|
+
AdSDK-->>Game: onAdClicked 回调
|
|
374
|
+
Game->>PS: track('ad_click', { ... })
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
opt 激励视频奖励
|
|
378
|
+
AdSDK-->>Game: onAdRewarded 回调
|
|
379
|
+
Game->>Game: 根据游戏实际发奖和保存结果确定 outcome / persistence
|
|
380
|
+
Game->>PS: track('ad_rewarded_grant', { sdk_reward_received, reward_outcome, reward_persistence })
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
AdSDK-->>Game: 3. 收入回调 (ImpressionData)
|
|
384
|
+
Game->>PS: forwardLevelPlayRevenue(...) << 自动上报 ad_revenue
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
### 6.2 常见大坑与避坑指南
|
|
390
|
+
|
|
391
|
+
> [!WARNING] 常见陷阱 1:只上报收入,漏报 `ad_impression`
|
|
392
|
+
> **现象**:后台有收益金额,但展示次数为 0,导致 eCPM 计算异常。
|
|
393
|
+
> **原因**:`forwardLevelPlayRevenue` 仅负责上报 `ad_revenue`(收入)。后台计算广告展示次数、人均展示与 eCPM 依赖独立的 `ad_impression` 事件!必须在广告曝光展示成功时同时触发 `ad_impression`。
|
|
394
|
+
|
|
395
|
+
> [!WARNING] 常见陷阱 2:Banner 刷新时复用同一个 ID
|
|
396
|
+
> **现象**:Banner 自动轮播多次,后台统计只计为 1 次曝光或收入去重失效。
|
|
397
|
+
> **原因**:Banner 每次自动刷新展示,都是一次**独立的曝光**。必须为每次刷新生成一个**全新且独立**的 `exposureId`!
|
|
398
|
+
|
|
399
|
+
> [!WARNING] 常见陷阱 3:迟到收入改绑新局
|
|
400
|
+
> **现象**:玩家看完广告后已经结局或进入下一局,此时广告平台才回调收入,导致收入被错误统计到下一局。
|
|
401
|
+
> **原因**:必须在广告展示时将 `exposureId`、`shownAt` 以及**当时那一局的 `run` 引用**暂存起来。当收入回调触发时,使用原始暂存的 `run` 进行上报,不可改用当前最新局。
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
### 6.3 推荐接入代码:LevelPlay 广告适配器
|
|
406
|
+
|
|
407
|
+
PlayScope 提供了官方适配函数 `forwardLevelPlayRevenue`,直接对接 LevelPlay 回调:
|
|
408
|
+
|
|
409
|
+
完整的 [LevelPlayTelemetry.ts](示例/LevelPlayTelemetry.ts) 提供请求、加载、展示、点击、关闭、奖励、收入和诊断的全部方法。
|
|
410
|
+
原生层负责初始化、唯一auction/曝光绑定及早到收入缓冲;该类只接已绑定的规范化事实。
|
|
411
|
+
每次banner刷新生成新exposureId,重复回调必须沿用同一个曝光对象;原局、展示时间、测试标记与配置归因均冻结。
|
|
412
|
+
不要使用固定的假展示延迟或从当前局补造曝光归因。forwardLevelPlayRevenue只生成收入,recordDisplay另发ad_show和ad_impression。
|
|
413
|
+
SDK奖励不等于游戏已发奖/落盘;没有明确25%/50%/75%或完播通知时,不从reward/关闭推导相应事件。
|
|
414
|
+
当前完播率卡为rewarded ad_complete / ad_first_quartile,缺分母留空。测试/模拟/演示事实不计正式展示与收入。
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 7. 远程配置与实验分流
|
|
420
|
+
|
|
421
|
+
如果你的项目需要使用 PlayScope 的云端参数配置(如关卡难度、功能开关、A/B 测试实验),可通过 `resolveConfiguration` 和 `restoreCache` 实现。
|
|
422
|
+
|
|
423
|
+
### 7.1 核心工作流程
|
|
424
|
+
|
|
425
|
+
1. **随包默认配置 (Bundled)**:包体内准备一份静态兜底配置,离线或首次启动直接可用。
|
|
426
|
+
2. **本地缓存恢复 (`restoreCache`)**:SDK 初始化时读取持久化的上一份配置,校验有效后应用。
|
|
427
|
+
3. **云端拉取更新 (`resolveConfiguration`)**:SDK 自动向服务端 `/config/resolve` 请求最新配置:
|
|
428
|
+
- 若返回 `delivery: "full"`:得到最新配置并保存缓存,等待下一局对局时应用生效。
|
|
429
|
+
- 若返回 `delivery: "not_modified"`:说明配置未变更,维持本地缓存配置。
|
|
430
|
+
|
|
431
|
+
### 7.2 实现示例
|
|
432
|
+
|
|
433
|
+
完整实现见 [QuestGarden.ts](示例/QuestGarden.ts) 的 readCache、restoreCache、resolveConfig、apply、beginQuest,项目契约见 [fixture](示例/project-fixture.json)。
|
|
434
|
+
请求必须包含 apiVersion:1、installId、client、supportedSchemas:[1]、current(null或{revision,assignmentId})。
|
|
435
|
+
full响应须校验完整配置、configRevision与assignment;not_modified必须保留有效旧配置,并校验响应归因。
|
|
436
|
+
先context.saveCache成功,再应用规则;错误抛回SDK以便独立退避,不能捕获所有失败后当成功返回。
|
|
437
|
+
remote/cache归因必须有assignmentId,experimentId与variantId同空或同有效;bundled三者全null。
|
|
438
|
+
刷新结果与事件交付独立,需要配置结果时await refreshConfiguration;它遵守到期时间,并非强制刷新。
|
|
439
|
+
QuestGarden使用自定义quest_ad_income,不自动进入标准收入报表;需要标准看板的项目使用默认game-v1收入编码。
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 8. 服务端接口协议 (底层参考)
|
|
445
|
+
|
|
446
|
+
> [!NOTE]
|
|
447
|
+
> SDK 会全自动管理以下请求与鉴权,通常无需手动发起 HTTP 请求。本节供原理理解与抓包排查参考。
|
|
448
|
+
|
|
449
|
+
所有接口前缀均为:`{baseUrl}/v1/projects/{projectId}`,请求与响应均为 `Content-Type: application/json`。
|
|
450
|
+
|
|
451
|
+
### 8.1 客户端初始化与换票:`POST /client/bootstrap`
|
|
452
|
+
- **用途**:客户端启动时使用项目客户端 Key 换取临时的安装 Token。
|
|
453
|
+
- **请求头**:`X-Project-Key: {projectKey}`
|
|
454
|
+
- **请求体**:
|
|
455
|
+
```json
|
|
456
|
+
{
|
|
457
|
+
"apiVersion": 1,
|
|
458
|
+
"installId": "10000000-0000-4000-8000-000000000001",
|
|
459
|
+
"firstOpenedAt": "2026-11-12T00:00:00.000Z",
|
|
460
|
+
"client": {
|
|
461
|
+
"appVersion": "1.0.0",
|
|
462
|
+
"buildNumber": "100",
|
|
463
|
+
"platform": "android",
|
|
464
|
+
"locale": "zh-CN",
|
|
465
|
+
"timezoneOffsetMinutes": 480
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
- **成功响应**:返回 `accessToken`(Bearer Token,有效期通常为 24 小时)及 `serverTime`。
|
|
470
|
+
|
|
471
|
+
### 8.2 批量事件上报:`POST /analytics/events`
|
|
472
|
+
- **用途**:SDK 自动分批上报本地队列中积攒的事件。
|
|
473
|
+
- **请求头**:`Authorization: Bearer {accessToken}`
|
|
474
|
+
- **请求体**:
|
|
475
|
+
```json
|
|
476
|
+
{
|
|
477
|
+
"apiVersion": 1,
|
|
478
|
+
"installId": "10000000-0000-4000-8000-000000000001",
|
|
479
|
+
"batchId": "30000000-0000-4000-8000-000000000001",
|
|
480
|
+
"sentAt": "2026-11-12T00:00:05.000Z",
|
|
481
|
+
"events": [
|
|
482
|
+
{
|
|
483
|
+
"eventVersion": 1,
|
|
484
|
+
"eventId": "40000000-0000-4000-8000-000000000001",
|
|
485
|
+
"name": "game_start",
|
|
486
|
+
"occurredAt": "2026-11-12T00:00:01.000Z",
|
|
487
|
+
"sessionId": "50000000-0000-4000-8000-000000000001",
|
|
488
|
+
"sequence": 1,
|
|
489
|
+
"client": {
|
|
490
|
+
"appVersion": "1",
|
|
491
|
+
"buildNumber": "1",
|
|
492
|
+
"platform": "android",
|
|
493
|
+
"locale": "zh-CN",
|
|
494
|
+
"timezoneOffsetMinutes": 480
|
|
495
|
+
},
|
|
496
|
+
"samplingProbability": 1,
|
|
497
|
+
"run": {
|
|
498
|
+
"runId": "20000000-0000-4000-8000-000000000001",
|
|
499
|
+
"mode": "classic",
|
|
500
|
+
"config": {
|
|
501
|
+
"configRevision": "bundled-v1",
|
|
502
|
+
"source": "bundled",
|
|
503
|
+
"assignmentId": null,
|
|
504
|
+
"experimentId": null,
|
|
505
|
+
"variantId": null
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
"properties": {}
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
- **成功响应**:返回 `acceptedEventIds: ["40000..."]`。SDK只移除本批明确接受或permanent=true拒收项,其余原文重放。批次最多100条,服务端256KiB,SDK内部250KiB保守预算。
|
|
514
|
+
|
|
515
|
+
### 8.3 远程配置获取:`POST /config/resolve`
|
|
516
|
+
- **用途**:获取或更新远程游戏配置。
|
|
517
|
+
- **请求头**:`Authorization: Bearer {accessToken}`
|
|
518
|
+
- **请求体**:包含 `supportedSchemas` 与当前的 `current: { revision, assignmentId }`。
|
|
519
|
+
- **响应**:若未修改返回 `delivery: "not_modified"`;若有更新返回 `delivery: "full"` 和最新配置对象。
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## 9. 排错指南与验收清单
|
|
524
|
+
|
|
525
|
+
### 9.1 常见状态码与处理方式
|
|
526
|
+
|
|
527
|
+
| 状态 / 错误码 | 含义 | 排查与解决办法 |
|
|
528
|
+
| :--- | :--- | :--- |
|
|
529
|
+
| `duplicate_client` | 试图对同一个存储 Key 重复创建实例 | 检查是否在多个节点创建了组件。先调用旧实例的 `dispose()`,再创建新实例。 |
|
|
530
|
+
| `collection_disabled` | 采集开关关闭 | 检查用户是否已同意隐私协议,未同意时 SDK 处于静默保护状态,属正常现象。 |
|
|
531
|
+
| `queue_full` | 本地待发送队列已满 | 检查网络连接是否长期中断,或服务端是否返回 4xx/5xx 导致事件无法出队。 |
|
|
532
|
+
| `duplicate` | 事件去重键已存在 | 同一 `deduplicationKey`(如同一笔广告收入)被重复触发,SDK 自动丢弃重复项,属正常防御。 |
|
|
533
|
+
| `HTTP 401` | Token 过期或无效 | SDK 会自动重新触发 `/client/bootstrap` 获取新 Token,无需手动干预。 |
|
|
534
|
+
| `HTTP 429 / 5xx` | 服务端限流或异常 | SDK 会自动进入退避状态(Backoff),并按指数退避算法择机重试。 |
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
### 9.2 对接验收核对清单 (Checklist)
|
|
539
|
+
|
|
540
|
+
在完成接入后,请按照以下清单逐一核对验收:
|
|
541
|
+
|
|
542
|
+
- [ ] **单例唯一性**:全局仅有一个 `CocosPlayScope` 实例,切场景不会销毁或重建第二份实例。
|
|
543
|
+
- [ ] **凭据安全**:仅使用 `projectKey`,没有把管理员 Token 写入客户端。
|
|
544
|
+
- [ ] **合规控制**:未同意隐私协议前 `collectionEnabled=false`,不产生任何本地写入与网络请求;取得实际授权后重建实例采集,已有身份与队列保留。
|
|
545
|
+
- [ ] **生命周期事件**:首次启动有 `user_first_open`,冷启动有 `app_launch`,会话边界由宿主按实际前台/广告规则触发,不每次回调盲发。
|
|
546
|
+
- [ ] **对局归因完整**:开局生成唯一 `runId`;中途事件均携带相同 `run`;对局内配置严格冻结。
|
|
547
|
+
- [ ] **广告展示与收入配对**:
|
|
548
|
+
- 广告展示时有 `ad_impression`;
|
|
549
|
+
- 广告收入使用 `forwardLevelPlayRevenue` 成功上报 `ad_revenue`;
|
|
550
|
+
- Banner 自动刷新每次均生成新的 `exposureId`;
|
|
551
|
+
- 迟到收入正确关联原曝光时的 `run` 与 `exposureId`。
|
|
552
|
+
- [ ] **离线可靠性**:开启飞行模式打点,杀进程重启,恢复网络后事件能完整补发至服务端,已落盘事实原ID/原文重放且服务端不重复计数;仅memory事件不承诺杀进程保留。
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## 10. 附录:老项目迁移与 BlockDrop 约定
|
|
557
|
+
|
|
558
|
+
### 10.1 存储 Key 与数据继承
|
|
559
|
+
- SDK 默认存储 Key 为 `playscope:<projectId>`。
|
|
560
|
+
- **老项目迁移**:若既有项目历史使用了自定义 Key(如 BlockDrop 的 `blockdrop:playscope:<projectId>`),必须在 `options.storageKey` 中显式指定,以完整保留老用户的 `installId`、事件序号和未发送队列。
|
|
561
|
+
- **Scope 声明 (`legacyScope`)**:若老数据未包含服务环境元数据,可在 `options.legacyScope` 中声明历史的 `baseUrl`、`projectId` 和 `environment`,必须先核实真实历史来源且与当前目标匹配;缺声明会拒绝初始化。已绑定来源不能改绑,不重置身份。
|
|
562
|
+
|
|
563
|
+
### 10.2 BlockDrop 既有工程注意事项
|
|
564
|
+
- **全局唯一 Owner**:BlockDrop 的生命周期已由 `GameRoot` 与 `GameTelemetry` 统一调度,底层通过 `PlayScopeClient` 门面交互,**切勿**再次创建 `CocosPlayScope` 组件。
|
|
565
|
+
- **广告链路**:BlockDrop 的 `GameTelemetry` 已经处理了 `ad_show`、`ad_impression` 与 `ad_revenue`,**切勿**在已有链路上重复调用 `forwardLevelPlayRevenue`,避免双重记账。
|
|
566
|
+
- **历史 MAX 事件**:历史存量旧事件按原文重放,无需重新转换。
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
## 11. 精确接口、存储与错误补充
|
|
571
|
+
|
|
572
|
+
- [公开类型全集](参考资料/public-api.d.ts)覆盖全部46项公开导出,按[入口索引](参考资料/exports.json)选择路径;旧0.1.1声明保留在参考资料/0.1.1-types中。
|
|
573
|
+
- CocosOptions还支持bootstrapClient、onIdentityReady、persistence、legacyScope、adRevenueEncoder;类型和必填项以声明为准。
|
|
574
|
+
- CocosPlayScope还包括start、trackWithResult、flushWithResult、retryUploads、status、getDiagnostics、getQuarantinedEvents;核心PlayScopeClient另有cache、saveCache和diagnostics,无collectionEnabled/update/实例trackAdRevenue。
|
|
575
|
+
- 配置和XHR超时/交付等待语义见类型及示例;核心不自动注册生命周期,已有调度的项目不叠加Cocos包装。
|
|
576
|
+
- trackWithResult区分accepted(persisted/memory)、duplicate、queue_full、queue_bytes_full、event_too_large、invalid_event、collection_disabled、disposed。accepted不是ACK,memory时暂停上传等待存储修复。
|
|
577
|
+
- flushWithResult返回status、accepted、rejected、quarantined、pending、durable、retryAt、error;completed是本轮结束,pending可能仍非零。
|
|
578
|
+
- 读取故障/损坏/未知版本拒绝初始化并保留原键;scope绑定baseUrl、projectId、environment,仅去baseUrl末尾斜线。缺失环境默认production,不替代独立项目隔离。
|
|
579
|
+
- 默认快照,4MiB预算计待传和完整隔离事实,不含配置/索引/迁移副本。eventLimits按整个队列长度预留;不驱逐旧事实。
|
|
580
|
+
- segmented需要严格removeItem、单键完整替换与读回。v2根和不可变段按清理日志→新段→根提交→清理保存,迁移需额外空间。已有v2不会因省略选项降级;旧包不能直接回退,需完整校验所有段及隔离记录。
|
|
581
|
+
- getQuarantinedEvents保留历史超大或单事件413原文,不等于成功;无公开自动重传/删除接口。永久拒收仅保留最近50项简要诊断,不同于完整隔离区。
|
|
582
|
+
- PlayScopeInitializationError.code为storage_unavailable、state_corrupt、state_version_unsupported、cache_restore_failed、storage_capability_missing、scope_mismatch、scope_migration_required;仅storage_unavailable标retryable。duplicate_client另由PlayScopeOwnershipError抛出。
|
|
583
|
+
- Diagnostics完整字段、ConfigurationResult/Status、FlushResult和ClientStatus的全部联合状态在公开类型中。日志不输出Key、Token或完整私密事件。
|
|
584
|
+
- 单运行时writer检测不提供跨进程/标签页锁。Node测试和SIGKILL不等于真实JSB、断电或设备广告验收。
|
|
585
|
+
- 完整广告字典及通用game placement覆盖见参考资料/服务端/game-ad-properties.json;项目实际冻结契约优先。事件字典存在不代表厂商有对应回调或后台已为每个事件提供卡片。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AdRevenueFact, TrackContext } from '../../core';
|
|
2
|
+
/** Values forwarded by the host's existing LevelPlay impression listener. */
|
|
3
|
+
export interface LevelPlayImpression {
|
|
4
|
+
revenue?: number | null;
|
|
5
|
+
precision?: string | null;
|
|
6
|
+
adNetwork?: string | null;
|
|
7
|
+
mediationAdUnitId?: string | null;
|
|
8
|
+
country?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export type LevelPlayExposure = Omit<AdRevenueFact, 'amount' | 'currency' | 'precision' | 'provider' | 'network' | 'adUnitId' | 'country' | 'countrySource'>;
|
|
11
|
+
export interface RevenueSink {
|
|
12
|
+
trackAdRevenue(fact: AdRevenueFact, context?: TrackContext): boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Does not initialize ads or replace the game's native listener. Host supplies the original exposure context. */
|
|
15
|
+
export declare function forwardLevelPlayRevenue(sink: RevenueSink, impression: LevelPlayImpression, exposure: LevelPlayExposure, context?: TrackContext): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StoragePort } from '../core';
|
|
2
|
+
export interface LocalStorageLike {
|
|
3
|
+
getItem(key: string): string | null;
|
|
4
|
+
setItem(key: string, value: string): void;
|
|
5
|
+
}
|
|
6
|
+
/** Inject sys.localStorage; failures reach the outbox and prevent unsafe uploads. */
|
|
7
|
+
export declare class CocosStorage implements StoragePort {
|
|
8
|
+
private readonly storage;
|
|
9
|
+
constructor(storage: LocalStorageLike);
|
|
10
|
+
getString(key: string): string | null;
|
|
11
|
+
setString(key: string, value: string): void;
|
|
12
|
+
flush(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HttpResponse, JsonTransport } from '../core';
|
|
2
|
+
/** XMLHttpRequest is supported by browsers and Cocos JSB Android/iOS. */
|
|
3
|
+
export declare class XhrJsonTransport implements JsonTransport {
|
|
4
|
+
private readonly pending;
|
|
5
|
+
private disposed;
|
|
6
|
+
post(url: string, headers: Record<string, string>, body: unknown): Promise<HttpResponse>;
|
|
7
|
+
dispose(): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { PlayScopeOptions, TrackContext, AdRevenueFact, JsonTransport } from '../core';
|
|
2
|
+
import type { LocalStorageLike } from './Storage';
|
|
3
|
+
export * from '../core';
|
|
4
|
+
export * from './Storage';
|
|
5
|
+
export * from './XhrJsonTransport';
|
|
6
|
+
export interface CocosLifecycle {
|
|
7
|
+
onHide(callback: () => void): () => void;
|
|
8
|
+
onShow(callback: () => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
export interface CocosRuntime {
|
|
11
|
+
storage: LocalStorageLike;
|
|
12
|
+
lifecycle?: CocosLifecycle;
|
|
13
|
+
transport?: JsonTransport;
|
|
14
|
+
now?: () => number;
|
|
15
|
+
}
|
|
16
|
+
export interface CocosOptions extends PlayScopeOptions {
|
|
17
|
+
/** Host decides consent/collection policy before calling init. */
|
|
18
|
+
collectionEnabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Host owns event semantics; this wrapper only schedules flush and releases its subscriptions. */
|
|
21
|
+
export declare class CocosPlayScope {
|
|
22
|
+
private readonly client;
|
|
23
|
+
private readonly off;
|
|
24
|
+
private elapsed;
|
|
25
|
+
private disposed;
|
|
26
|
+
constructor(options: CocosOptions, runtime: CocosRuntime);
|
|
27
|
+
start(): void;
|
|
28
|
+
get installId(): string | null;
|
|
29
|
+
getDiagnostics(): import("../core").Diagnostics;
|
|
30
|
+
track(name: string, properties?: Record<string, unknown>, context?: TrackContext): boolean;
|
|
31
|
+
trackAdRevenue(fact: AdRevenueFact, context?: TrackContext): boolean;
|
|
32
|
+
newSession(): void;
|
|
33
|
+
/** Call from a persistent Cocos component's update(dt), in seconds. */
|
|
34
|
+
update(dt: number): void;
|
|
35
|
+
flush(): Promise<void>;
|
|
36
|
+
dispose(): void;
|
|
37
|
+
}
|
|
38
|
+
export declare function init(options: CocosOptions, runtime: CocosRuntime): CocosPlayScope;
|