@playcraft/build 0.0.23 → 0.0.24
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/dist/engines/playable-scripts-adapter.d.ts +3 -2
- package/dist/engines/playable-scripts-adapter.js +21 -10
- package/dist/platforms/adikteev.js +1 -0
- package/dist/platforms/applovin.js +1 -0
- package/dist/platforms/base.d.ts +20 -0
- package/dist/platforms/base.js +93 -0
- package/dist/platforms/bigo.js +1 -0
- package/dist/platforms/facebook.js +1 -0
- package/dist/platforms/google.js +1 -0
- package/dist/platforms/inmobi.js +1 -0
- package/dist/platforms/ironsource.js +1 -0
- package/dist/platforms/liftoff.js +1 -0
- package/dist/platforms/mintegral.js +1 -0
- package/dist/platforms/moloco.js +1 -0
- package/dist/platforms/playcraft.js +3 -1
- package/dist/platforms/remerge.js +1 -0
- package/dist/platforms/snapchat.js +1 -0
- package/dist/platforms/tiktok.js +1 -0
- package/dist/platforms/unity.js +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/vite/platform-configs.js +1 -1
- package/package.json +4 -2
- package/templates/patches/playcraft-tracking-adapter.js +130 -0
|
@@ -133,9 +133,10 @@ export declare class PlayableScriptsAdapter {
|
|
|
133
133
|
private removeDirRecursive;
|
|
134
134
|
private copyToOutputDir;
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
136
|
+
* 是否对 playable-scripts 使用 --zip。
|
|
137
|
+
* Google/Facebook 等双格式渠道默认 HTML,与 CLI、Portal、GoogleAdapter 一致;仅 ZIP 的渠道仍强制 ZIP。
|
|
137
138
|
*/
|
|
138
|
-
private
|
|
139
|
+
private isZipOutput;
|
|
139
140
|
/**
|
|
140
141
|
* 获取平台默认的文件名格式
|
|
141
142
|
*/
|
|
@@ -258,9 +258,9 @@ export class PlayableScriptsAdapter {
|
|
|
258
258
|
// 输出目录 - 统一使用 dist,与 PlayCraft 标准一致
|
|
259
259
|
const outputDir = config.outputDir ?? 'dist';
|
|
260
260
|
args.push('--out-dir', outputDir);
|
|
261
|
-
// ZIP
|
|
262
|
-
const isZipFormat = this.
|
|
263
|
-
if (isZipFormat
|
|
261
|
+
// ZIP 输出:仅 ZIP-only 渠道、显式 config.zip、或构建请求 format=zip(如 Google / Facebook 选 ZIP)
|
|
262
|
+
const isZipFormat = this.isZipOutput();
|
|
263
|
+
if (isZipFormat) {
|
|
264
264
|
args.push('--zip');
|
|
265
265
|
}
|
|
266
266
|
// 自定义文件名格式 - 根据平台使用 PlayCraft 标准命名
|
|
@@ -961,17 +961,28 @@ export class PlayableScriptsAdapter {
|
|
|
961
961
|
return files;
|
|
962
962
|
}
|
|
963
963
|
/**
|
|
964
|
-
*
|
|
964
|
+
* 是否对 playable-scripts 使用 --zip。
|
|
965
|
+
* Google/Facebook 等双格式渠道默认 HTML,与 CLI、Portal、GoogleAdapter 一致;仅 ZIP 的渠道仍强制 ZIP。
|
|
965
966
|
*/
|
|
966
|
-
|
|
967
|
+
isZipOutput() {
|
|
967
968
|
const config = this.config ?? {};
|
|
968
969
|
const channels = config.channels ?? ['google'];
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
970
|
+
if (config.zip === true) {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
const zipOnlyPlatforms = new Set([
|
|
974
|
+
'tiktok',
|
|
975
|
+
'snapchat',
|
|
976
|
+
'liftoff',
|
|
977
|
+
'bigo',
|
|
978
|
+
'inmobi',
|
|
979
|
+
'mintegral',
|
|
972
980
|
]);
|
|
973
|
-
|
|
974
|
-
|
|
981
|
+
if (channels.some((ch) => zipOnlyPlatforms.has(ch))) {
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
const fmt = this.options.format;
|
|
985
|
+
return fmt === 'zip';
|
|
975
986
|
}
|
|
976
987
|
/**
|
|
977
988
|
* 获取平台默认的文件名格式
|
|
@@ -44,6 +44,7 @@ export class AdikteevAdapter extends PlatformAdapter {
|
|
|
44
44
|
html = html.replace('</head>', `${adikteevScript}${comment}</head>`);
|
|
45
45
|
// 注入统一的 CTA 适配器
|
|
46
46
|
html = await this.injectCTAAdapterAsync(html);
|
|
47
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
47
48
|
return html;
|
|
48
49
|
}
|
|
49
50
|
getPlatformScript() {
|
|
@@ -45,6 +45,7 @@ export class AppLovinAdapter extends PlatformAdapter {
|
|
|
45
45
|
html = html.replace('</head>', `${appLovinScript}${comment}</head>`);
|
|
46
46
|
// 注入统一的 CTA 适配器
|
|
47
47
|
html = await this.injectCTAAdapterAsync(html);
|
|
48
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
48
49
|
return html;
|
|
49
50
|
}
|
|
50
51
|
getPlatformScript() {
|
package/dist/platforms/base.d.ts
CHANGED
|
@@ -60,4 +60,24 @@ export declare abstract class PlatformAdapter {
|
|
|
60
60
|
* @deprecated 推荐使用 injectCTAAdapterAsync
|
|
61
61
|
*/
|
|
62
62
|
protected injectCTAAdapter(html: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* 生成 Tracking 配置注入脚本
|
|
65
|
+
* 将 tracking 配置以全局变量写入 HTML,供 Tracking Adapter 读取
|
|
66
|
+
*/
|
|
67
|
+
protected getTrackingConfigScript(): string;
|
|
68
|
+
/**
|
|
69
|
+
* 获取 Tracking 适配器脚本(异步版本,带压缩)
|
|
70
|
+
*/
|
|
71
|
+
protected getTrackingAdapterScriptAsync(): Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* 从 @playcraft/adsdk 包中提取 tracking 实现代码
|
|
74
|
+
* 读取 IIFE 版本中 `// src/tracking.ts` 到 `var tracking = {...};` 的代码段
|
|
75
|
+
* 包装为独立 IIFE 并暴露 window.PlayCraftTracking
|
|
76
|
+
*/
|
|
77
|
+
protected getAdsdkTrackingScriptAsync(): Promise<string>;
|
|
78
|
+
/**
|
|
79
|
+
* 在 HTML 中注入 Tracking 适配器(异步版本)
|
|
80
|
+
* 注入顺序:1.Config → 2.adsdk tracking 实现 → 3.Adapter 桥接脚本
|
|
81
|
+
*/
|
|
82
|
+
protected injectTrackingAdapterAsync(html: string): Promise<string>;
|
|
63
83
|
}
|
package/dist/platforms/base.js
CHANGED
|
@@ -4,6 +4,10 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import { minifyPatchCode } from '../utils/minify.js';
|
|
5
5
|
// 缓存压缩后的 CTA 适配器代码
|
|
6
6
|
let cachedMinifiedCTAAdapter = null;
|
|
7
|
+
// 缓存压缩后的 Tracking 适配器代码
|
|
8
|
+
let cachedMinifiedTrackingAdapter = null;
|
|
9
|
+
// 缓存从 @playcraft/adsdk 提取的 tracking 实现代码
|
|
10
|
+
let cachedAdsdkTrackingScript = null;
|
|
7
11
|
// 缓存压缩后的平台脚本
|
|
8
12
|
const platformScriptCache = new Map();
|
|
9
13
|
export class PlatformAdapter {
|
|
@@ -114,4 +118,93 @@ export class PlatformAdapter {
|
|
|
114
118
|
return combined + '\n' + html;
|
|
115
119
|
}
|
|
116
120
|
}
|
|
121
|
+
// ===== Tracking Adapter =====
|
|
122
|
+
/**
|
|
123
|
+
* 生成 Tracking 配置注入脚本
|
|
124
|
+
* 将 tracking 配置以全局变量写入 HTML,供 Tracking Adapter 读取
|
|
125
|
+
*/
|
|
126
|
+
getTrackingConfigScript() {
|
|
127
|
+
const tracking = this.options.tracking;
|
|
128
|
+
if (tracking?.enabled === false)
|
|
129
|
+
return '';
|
|
130
|
+
const config = {};
|
|
131
|
+
// adNetwork: 优先用显式配置,否则从 platform 自动推断
|
|
132
|
+
const adNetwork = tracking?.adNetwork || this.options.platform || '';
|
|
133
|
+
if (adNetwork)
|
|
134
|
+
config.adNetwork = adNetwork;
|
|
135
|
+
if (tracking?.totalInteractions)
|
|
136
|
+
config.totalInteractions = tracking.totalInteractions;
|
|
137
|
+
return Object.keys(config).length > 0
|
|
138
|
+
? `<script>window.__PLAYCRAFT_TRACKING_CONFIG__=${JSON.stringify(config)};</script>`
|
|
139
|
+
: '';
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 获取 Tracking 适配器脚本(异步版本,带压缩)
|
|
143
|
+
*/
|
|
144
|
+
async getTrackingAdapterScriptAsync() {
|
|
145
|
+
if (cachedMinifiedTrackingAdapter) {
|
|
146
|
+
return `<script>${cachedMinifiedTrackingAdapter}</script>`;
|
|
147
|
+
}
|
|
148
|
+
const code = await this.readTemplateFile('playcraft-tracking-adapter.js');
|
|
149
|
+
const minified = await minifyPatchCode(code, 'playcraft-tracking-adapter.js');
|
|
150
|
+
cachedMinifiedTrackingAdapter = minified;
|
|
151
|
+
return `<script>${minified}</script>`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 从 @playcraft/adsdk 包中提取 tracking 实现代码
|
|
155
|
+
* 读取 IIFE 版本中 `// src/tracking.ts` 到 `var tracking = {...};` 的代码段
|
|
156
|
+
* 包装为独立 IIFE 并暴露 window.PlayCraftTracking
|
|
157
|
+
*/
|
|
158
|
+
async getAdsdkTrackingScriptAsync() {
|
|
159
|
+
if (cachedAdsdkTrackingScript) {
|
|
160
|
+
return `<script>${cachedAdsdkTrackingScript}</script>`;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
// 从 @playcraft/adsdk 的 IIFE 构建中读取
|
|
164
|
+
const { createRequire } = await import('module');
|
|
165
|
+
const require = createRequire(import.meta.url);
|
|
166
|
+
const adsdkPath = require.resolve('@playcraft/adsdk/dist/iife/index.js');
|
|
167
|
+
const iifeCode = await fs.readFile(adsdkPath, 'utf-8');
|
|
168
|
+
// 提取 tracking 部分:从 "// src/tracking.ts" 到 "trackChallengeFailed" 对象结尾
|
|
169
|
+
const startMarker = '// src/tracking.ts';
|
|
170
|
+
const endMarker = 'trackChallengeFailed: _trackChallengeFailed';
|
|
171
|
+
const startIdx = iifeCode.indexOf(startMarker);
|
|
172
|
+
const endIdx = iifeCode.indexOf(endMarker);
|
|
173
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
174
|
+
console.warn('[Tracking] 无法从 @playcraft/adsdk 中提取 tracking 代码,使用内置 adapter');
|
|
175
|
+
return '';
|
|
176
|
+
}
|
|
177
|
+
// 找到 tracking 对象定义结束的 `};`
|
|
178
|
+
const afterEnd = iifeCode.indexOf('};', endIdx);
|
|
179
|
+
const trackingCode = iifeCode.substring(startIdx, afterEnd + 2);
|
|
180
|
+
// 包装为独立 IIFE,暴露 window.PlayCraftTracking
|
|
181
|
+
const wrappedCode = `(function(){"use strict";${trackingCode}\nwindow.PlayCraftTracking=tracking;})();`;
|
|
182
|
+
const minified = await minifyPatchCode(wrappedCode, 'adsdk-tracking-extract');
|
|
183
|
+
cachedAdsdkTrackingScript = minified;
|
|
184
|
+
return `<script>${minified}</script>`;
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
console.warn('[Tracking] 无法加载 @playcraft/adsdk:', e.message);
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 在 HTML 中注入 Tracking 适配器(异步版本)
|
|
193
|
+
* 注入顺序:1.Config → 2.adsdk tracking 实现 → 3.Adapter 桥接脚本
|
|
194
|
+
*/
|
|
195
|
+
async injectTrackingAdapterAsync(html) {
|
|
196
|
+
if (this.options.tracking?.enabled === false)
|
|
197
|
+
return html;
|
|
198
|
+
const configScript = this.getTrackingConfigScript();
|
|
199
|
+
const adsdkScript = await this.getAdsdkTrackingScriptAsync();
|
|
200
|
+
const trackingScript = await this.getTrackingAdapterScriptAsync();
|
|
201
|
+
const combined = configScript + adsdkScript + trackingScript;
|
|
202
|
+
if (html.includes('</head>')) {
|
|
203
|
+
return html.replace('</head>', `${combined}\n</head>`);
|
|
204
|
+
}
|
|
205
|
+
else if (html.includes('</body>')) {
|
|
206
|
+
return html.replace('</body>', `${combined}\n</body>`);
|
|
207
|
+
}
|
|
208
|
+
return combined + '\n' + html;
|
|
209
|
+
}
|
|
117
210
|
}
|
package/dist/platforms/bigo.js
CHANGED
|
@@ -41,6 +41,7 @@ export class BigoAdapter extends PlatformAdapter {
|
|
|
41
41
|
html = html.replace('</head>', `${sdkScript}${fallbackScript}${comment}</head>`);
|
|
42
42
|
// 注入统一的 CTA 适配器
|
|
43
43
|
html = await this.injectCTAAdapterAsync(html);
|
|
44
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
44
45
|
return html;
|
|
45
46
|
}
|
|
46
47
|
getPlatformScript() {
|
|
@@ -37,6 +37,7 @@ export class FacebookAdapter extends PlatformAdapter {
|
|
|
37
37
|
html = html.replace('</head>', `${fbScript}</head>`);
|
|
38
38
|
// 注入统一的 CTA 适配器
|
|
39
39
|
html = await this.injectCTAAdapterAsync(html);
|
|
40
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
40
41
|
return html;
|
|
41
42
|
}
|
|
42
43
|
getPlatformScript() {
|
package/dist/platforms/google.js
CHANGED
|
@@ -45,6 +45,7 @@ export class GoogleAdapter extends PlatformAdapter {
|
|
|
45
45
|
html = html.replace('</head>', `${googleScript}${comment}${mobileMeta}${orientationMeta}${meta}${lazyLoadMeta}${preloadHint}</head>`);
|
|
46
46
|
// 注入统一的 CTA 适配器
|
|
47
47
|
html = await this.injectCTAAdapterAsync(html);
|
|
48
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
48
49
|
// 注入懒加载优化脚本
|
|
49
50
|
html = this.injectLazyLoadOptimization(html);
|
|
50
51
|
return html;
|
package/dist/platforms/inmobi.js
CHANGED
|
@@ -41,6 +41,7 @@ export class InMobiAdapter extends PlatformAdapter {
|
|
|
41
41
|
html = html.replace('</head>', `${inmobiScript}${comment}</head>`);
|
|
42
42
|
// 注入统一的 CTA 适配器
|
|
43
43
|
html = await this.injectCTAAdapterAsync(html);
|
|
44
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
44
45
|
return html;
|
|
45
46
|
}
|
|
46
47
|
getPlatformScript() {
|
|
@@ -51,6 +51,7 @@ export class IronSourceAdapter extends PlatformAdapter {
|
|
|
51
51
|
html = html.replace('</head>', `${ironSourceScript}${comment}</head>`);
|
|
52
52
|
// 注入统一的 CTA 适配器
|
|
53
53
|
html = await this.injectCTAAdapterAsync(html);
|
|
54
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
54
55
|
return html;
|
|
55
56
|
}
|
|
56
57
|
getPlatformScript() {
|
|
@@ -36,6 +36,7 @@ export class LiftoffAdapter extends PlatformAdapter {
|
|
|
36
36
|
html = html.replace('</head>', `${liftoffScript}${comment}</head>`);
|
|
37
37
|
// 注入统一的 CTA 适配器
|
|
38
38
|
html = await this.injectCTAAdapterAsync(html);
|
|
39
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
39
40
|
return html;
|
|
40
41
|
}
|
|
41
42
|
getPlatformScript() {
|
package/dist/platforms/moloco.js
CHANGED
|
@@ -33,6 +33,7 @@ export class MolocoAdapter extends PlatformAdapter {
|
|
|
33
33
|
html = html.replace('</head>', `${molocoScript}${comment}</head>`);
|
|
34
34
|
// 注入统一的 CTA 适配器
|
|
35
35
|
html = await this.injectCTAAdapterAsync(html);
|
|
36
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
36
37
|
return html;
|
|
37
38
|
}
|
|
38
39
|
getPlatformScript() {
|
|
@@ -32,7 +32,9 @@ export class PlayCraftAdapter extends PlatformAdapter {
|
|
|
32
32
|
* PlayCraft 构建注入 CTA 适配器
|
|
33
33
|
*/
|
|
34
34
|
async modifyHTML(html, _assets) {
|
|
35
|
-
|
|
35
|
+
html = await this.injectCTAAdapterAsync(html);
|
|
36
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
37
|
+
return html;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* 获取平台特定的 JavaScript 代码
|
|
@@ -34,6 +34,7 @@ export class RemergeAdapter extends PlatformAdapter {
|
|
|
34
34
|
html = html.replace('</head>', `${remergeScript}${comment}${meta}</head>`);
|
|
35
35
|
// 注入统一的 CTA 适配器
|
|
36
36
|
html = await this.injectCTAAdapterAsync(html);
|
|
37
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
37
38
|
return html;
|
|
38
39
|
}
|
|
39
40
|
getPlatformScript() {
|
|
@@ -46,6 +46,7 @@ export class SnapchatAdapter extends PlatformAdapter {
|
|
|
46
46
|
html = html.replace('</head>', `${mraidScript}</head>`);
|
|
47
47
|
// 注入统一的 CTA 适配器
|
|
48
48
|
html = await this.injectCTAAdapterAsync(html);
|
|
49
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
49
50
|
return html;
|
|
50
51
|
}
|
|
51
52
|
getPlatformScript() {
|
package/dist/platforms/tiktok.js
CHANGED
|
@@ -40,6 +40,7 @@ export class TikTokAdapter extends PlatformAdapter {
|
|
|
40
40
|
html = html.replace('</head>', `${pangleScript}${comment}</head>`);
|
|
41
41
|
// 注入统一的 CTA 适配器
|
|
42
42
|
html = await this.injectCTAAdapterAsync(html);
|
|
43
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
43
44
|
return html;
|
|
44
45
|
}
|
|
45
46
|
getPlatformScript() {
|
package/dist/platforms/unity.js
CHANGED
|
@@ -44,6 +44,7 @@ export class UnityAdapter extends PlatformAdapter {
|
|
|
44
44
|
html = html.replace('</head>', `${unityScript}${comment}</head>`);
|
|
45
45
|
// 注入统一的 CTA 适配器
|
|
46
46
|
html = await this.injectCTAAdapterAsync(html);
|
|
47
|
+
html = await this.injectTrackingAdapterAsync(html);
|
|
47
48
|
return html;
|
|
48
49
|
}
|
|
49
50
|
getPlatformScript() {
|
package/dist/types.d.ts
CHANGED
|
@@ -48,6 +48,11 @@ export interface BuildOptions {
|
|
|
48
48
|
preserveESM?: boolean;
|
|
49
49
|
forceIIFE?: boolean;
|
|
50
50
|
storeUrls?: StoreUrls;
|
|
51
|
+
tracking?: {
|
|
52
|
+
enabled?: boolean;
|
|
53
|
+
adNetwork?: string;
|
|
54
|
+
totalInteractions?: number;
|
|
55
|
+
};
|
|
51
56
|
}
|
|
52
57
|
export interface BaseBuildMetadata {
|
|
53
58
|
mode: BuildMode;
|
|
@@ -201,6 +206,8 @@ export interface BaseBuildOptions {
|
|
|
201
206
|
analyzeReportPath?: string;
|
|
202
207
|
clean?: boolean;
|
|
203
208
|
engine?: EngineType;
|
|
209
|
+
/** 渠道产物格式(playable-scripts 阶段;与 Vite 阶段 format 对齐) */
|
|
210
|
+
format?: OutputFormat;
|
|
204
211
|
}
|
|
205
212
|
/**
|
|
206
213
|
* 基础构建输出
|
|
@@ -157,7 +157,7 @@ export const PLATFORM_CONFIGS = {
|
|
|
157
157
|
},
|
|
158
158
|
google: {
|
|
159
159
|
sizeLimit: 5 * 1024 * 1024, // 5MB(修正)
|
|
160
|
-
outputFormat: '
|
|
160
|
+
outputFormat: 'html', // 与 GoogleAdapter / CLI 默认一致;需要 ZIP 时显式传 format
|
|
161
161
|
minifyCSS: true,
|
|
162
162
|
minifyJS: true,
|
|
163
163
|
compressImages: false, // 禁用图片压缩,原图输出效果更好
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/build",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"release": "node scripts/release.js"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@playcraft/adsdk": "^1.0.13",
|
|
33
34
|
"@gltf-transform/core": "^4.3.0",
|
|
34
35
|
"@gltf-transform/extensions": "^4.3.0",
|
|
35
36
|
"@gltf-transform/functions": "^4.3.0",
|
|
@@ -39,16 +40,17 @@
|
|
|
39
40
|
"imagemin-mozjpeg": "^10.0.0",
|
|
40
41
|
"imagemin-pngquant": "^10.0.0",
|
|
41
42
|
"imagemin-webp": "^8.0.0",
|
|
43
|
+
"javascript-obfuscator": "^5.3.0",
|
|
42
44
|
"lightningcss": "^1.31.1",
|
|
43
45
|
"mime-types": "^2.1.35",
|
|
44
46
|
"rollup-plugin-visualizer": "^6.0.5",
|
|
45
47
|
"sharp": "^0.34.5",
|
|
46
|
-
"javascript-obfuscator": "^5.3.0",
|
|
47
48
|
"terser": "^5.46.0",
|
|
48
49
|
"vite": "^8.0.3",
|
|
49
50
|
"vite-plugin-singlefile": "^2.3.2"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
53
|
+
"@playcraft/adsdk": "^1.0.13",
|
|
52
54
|
"@types/archiver": "^6.0.4",
|
|
53
55
|
"@types/mime-types": "^2.1.4",
|
|
54
56
|
"@types/node": "^22.19.8",
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlayCraft Tracking Adapter (Bridge Layer)
|
|
3
|
+
*
|
|
4
|
+
* 职责:在正确的生命周期节点调用 window.PlayCraftTracking 的方法。
|
|
5
|
+
* 渠道埋点逻辑由 @playcraft/adsdk 的 tracking 模块提供(构建时自动提取注入)。
|
|
6
|
+
* 如果 adsdk tracking 未注入,adapter 的调用会静默忽略。
|
|
7
|
+
*/
|
|
8
|
+
(function() {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
var _config = (typeof window !== 'undefined' && window.__PLAYCRAFT_TRACKING_CONFIG__) || {};
|
|
12
|
+
var adNetwork = _config.adNetwork || '';
|
|
13
|
+
var totalInteractions = _config.totalInteractions || 0;
|
|
14
|
+
|
|
15
|
+
function getTracking() {
|
|
16
|
+
return window.PlayCraftTracking || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function call(method, args) {
|
|
20
|
+
var t = getTracking();
|
|
21
|
+
if (t && typeof t[method] === 'function') {
|
|
22
|
+
t[method].apply(t, args || []);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ===== 自动埋点 =====
|
|
27
|
+
|
|
28
|
+
// 1. setTotalInteractions + onInit(立即)
|
|
29
|
+
if (totalInteractions > 0) {
|
|
30
|
+
call('setTotalInteractions', [totalInteractions]);
|
|
31
|
+
}
|
|
32
|
+
call('onInit', [adNetwork]);
|
|
33
|
+
|
|
34
|
+
// 2. onStart(页面加载完成)
|
|
35
|
+
if (document.readyState === 'complete') {
|
|
36
|
+
setTimeout(function() { call('onStart', [adNetwork]); }, 0);
|
|
37
|
+
} else {
|
|
38
|
+
window.addEventListener('load', function() { call('onStart', [adNetwork]); });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2.5 onInteractive(游戏完全就绪,可交互)
|
|
42
|
+
// 优先级: playcraft:interactive 事件 > PlayCanvas app.on('start') fallback
|
|
43
|
+
var _interactive = false;
|
|
44
|
+
function triggerInteractive() {
|
|
45
|
+
if (_interactive) return;
|
|
46
|
+
_interactive = true;
|
|
47
|
+
call('onInteractive', [adNetwork]);
|
|
48
|
+
}
|
|
49
|
+
// 优先监听游戏主动派发的 playcraft:interactive
|
|
50
|
+
document.addEventListener('playcraft:interactive', triggerInteractive);
|
|
51
|
+
// Fallback: 如果游戏没有派发 playcraft:interactive,则在 PlayCanvas app start 后自动触发
|
|
52
|
+
var _pcPollCount = 0;
|
|
53
|
+
function pollPlayCanvasApp() {
|
|
54
|
+
if (_interactive) return; // 已被 playcraft:interactive 触发,不再 fallback
|
|
55
|
+
var app = (typeof pc !== 'undefined' && pc.Application && pc.Application.getApplication)
|
|
56
|
+
? pc.Application.getApplication() : null;
|
|
57
|
+
if (app) {
|
|
58
|
+
if (app._time > 0 || app.frame > 0) {
|
|
59
|
+
triggerInteractive();
|
|
60
|
+
} else {
|
|
61
|
+
app.once('start', triggerInteractive);
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (++_pcPollCount < 200) setTimeout(pollPlayCanvasApp, 50);
|
|
66
|
+
}
|
|
67
|
+
setTimeout(pollPlayCanvasApp, 0);
|
|
68
|
+
|
|
69
|
+
// 3. onInteraction(首次用户交互,只触发一次)
|
|
70
|
+
var _interacted = false;
|
|
71
|
+
function onFirstInteraction() {
|
|
72
|
+
if (_interacted) return;
|
|
73
|
+
_interacted = true;
|
|
74
|
+
call('onInteraction', [adNetwork]);
|
|
75
|
+
document.removeEventListener('touchstart', onFirstInteraction, true);
|
|
76
|
+
document.removeEventListener('mousedown', onFirstInteraction, true);
|
|
77
|
+
document.removeEventListener('pointerdown', onFirstInteraction, true);
|
|
78
|
+
}
|
|
79
|
+
document.addEventListener('touchstart', onFirstInteraction, true);
|
|
80
|
+
document.addEventListener('mousedown', onFirstInteraction, true);
|
|
81
|
+
document.addEventListener('pointerdown', onFirstInteraction, true);
|
|
82
|
+
|
|
83
|
+
// 4. onInstall(hook CTA adapter)
|
|
84
|
+
var _ctaAttempts = 0;
|
|
85
|
+
function hookCTA() {
|
|
86
|
+
if (window.PlayCraftCTA && window.PlayCraftCTA.jump) {
|
|
87
|
+
var _orig = window.PlayCraftCTA.jump;
|
|
88
|
+
window.PlayCraftCTA.jump = function(opts) {
|
|
89
|
+
call('onInstall', [adNetwork]);
|
|
90
|
+
_orig.call(window.PlayCraftCTA, opts);
|
|
91
|
+
};
|
|
92
|
+
if (window.jump2AppStore) window.jump2AppStore = window.PlayCraftCTA.jump;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (++_ctaAttempts < 50) setTimeout(hookCTA, 100);
|
|
96
|
+
}
|
|
97
|
+
setTimeout(hookCTA, 0);
|
|
98
|
+
|
|
99
|
+
// ===== 游戏协作事件(CustomEvent)=====
|
|
100
|
+
var once = {};
|
|
101
|
+
function listenOnce(name, method, extraArgs) {
|
|
102
|
+
document.addEventListener(name, function() {
|
|
103
|
+
if (once[name]) return; once[name] = true;
|
|
104
|
+
call(method, extraArgs || [adNetwork]);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function listen(name, method, extraArgs) {
|
|
108
|
+
document.addEventListener(name, function() {
|
|
109
|
+
call(method, extraArgs || [adNetwork]);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
listenOnce('playcraft:finish', 'onFinish');
|
|
114
|
+
listen('playcraft:success', 'trackChallengeSuccess');
|
|
115
|
+
listen('playcraft:failed', 'trackChallengeFailed');
|
|
116
|
+
document.addEventListener('playcraft:retry', function() {
|
|
117
|
+
once['playcraft:finish'] = false;
|
|
118
|
+
call('onRetry', [adNetwork]);
|
|
119
|
+
});
|
|
120
|
+
document.addEventListener('playcraft:setTotal', function(e) {
|
|
121
|
+
var d = e.detail;
|
|
122
|
+
var n = typeof d === 'number' ? d : (d && d.total);
|
|
123
|
+
if (typeof n === 'number') call('setTotalInteractions', [n]);
|
|
124
|
+
});
|
|
125
|
+
document.addEventListener('playcraft:progress', function(e) {
|
|
126
|
+
var d = e.detail;
|
|
127
|
+
var n = typeof d === 'number' ? d : (d && d.current);
|
|
128
|
+
if (typeof n === 'number') call('checkInteractionProgress', [n]);
|
|
129
|
+
});
|
|
130
|
+
})();
|