@singcl/ad-execute-manager 2.0.0 → 2.0.2

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.
Files changed (43) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +558 -631
  3. package/dist/ad/{InterstitialAdFather.d.ts → src/InterstitialAdFather.d.ts} +5 -5
  4. package/dist/ad/{InterstitialAdNovel.d.ts → src/InterstitialAdNovel.d.ts} +8 -8
  5. package/dist/ad/{RewardAdFather.d.ts → src/RewardAdFather.d.ts} +6 -6
  6. package/dist/ad/{RewardAdNovel.d.ts → src/RewardAdNovel.d.ts} +65 -36
  7. package/dist/ad/src/index.d.ts +5 -0
  8. package/dist/{typings → ad/src/typings}/ad.d.ts +0 -4
  9. package/dist/analytics/{AdAnalyticsJS.d.ts → src/AdAnalyticsJS.d.ts} +1 -1
  10. package/dist/analytics/src/index.d.ts +1 -0
  11. package/dist/core/{AdExecuteManager.d.ts → src/AdExecuteManager.d.ts} +27 -10
  12. package/dist/core/src/index.d.ts +1 -0
  13. package/dist/core/src/typings/ad.d.ts +208 -0
  14. package/dist/core/typings/ad.d.ts +208 -0
  15. package/dist/helper/{PubSub.d.ts → src/PubSub.d.ts} +1 -1
  16. package/dist/helper/src/index.d.ts +6 -0
  17. package/dist/index.cjs +2 -2
  18. package/dist/index.d.ts +13 -13
  19. package/dist/index.js +2 -2
  20. package/dist/{other → tools/src}/RewardAdGlobalRecorder.d.ts +2 -2
  21. package/dist/{other → tools/src}/RewardAdSceneTriggerManager.d.ts +11 -11
  22. package/dist/tools/src/const.d.ts +3 -0
  23. package/dist/tools/src/index.d.ts +3 -0
  24. package/dist/tools/src/typings/ad.d.ts +208 -0
  25. package/dist/tools/src/typings/common.d.ts +14 -0
  26. package/dist/tools/src/typings/create-interstitial-ad.d.ts +42 -0
  27. package/dist/tools/src/typings/create-rewarded-video-ad.d.ts +42 -0
  28. package/dist/tools/src/typings/tracker.d.ts +1 -0
  29. package/package.json +6 -2
  30. /package/dist/{const → ad/src/const}/const.d.ts +0 -0
  31. /package/dist/{typings → ad/src/typings}/common.d.ts +0 -0
  32. /package/dist/{typings → ad/src/typings}/create-interstitial-ad.d.ts +0 -0
  33. /package/dist/{typings → ad/src/typings}/create-rewarded-video-ad.d.ts +0 -0
  34. /package/dist/{typings → ad/src/typings}/tracker.d.ts +0 -0
  35. /package/dist/{utils → ad/src/utils}/functional.d.ts +0 -0
  36. /package/dist/core/{_util.d.ts → src/_util.d.ts} +0 -0
  37. /package/dist/core/{compose.d.ts → src/compose.d.ts} +0 -0
  38. /package/dist/helper/{CountRecorder.d.ts → src/CountRecorder.d.ts} +0 -0
  39. /package/dist/helper/{EventEmitter.d.ts → src/EventEmitter.d.ts} +0 -0
  40. /package/dist/helper/{Logger.d.ts → src/Logger.d.ts} +0 -0
  41. /package/dist/helper/{SerializableError.d.ts → src/SerializableError.d.ts} +0 -0
  42. /package/dist/helper/{Storage.d.ts → src/Storage.d.ts} +0 -0
  43. /package/dist/{other → tools/src}/LovelUnlockManager.d.ts +0 -0
package/README.md CHANGED
@@ -1,632 +1,559 @@
1
- # AD Execute Manager
2
-
3
- A powerful and flexible ad execution management library for handling reward-based ads, interstitial ads, and other advertising formats in JavaScript applications.
4
-
5
- ## Table of Contents
6
-
7
- - [Features](#features)
8
- - [Installation](#installation)
9
- - [Quick Start](#quick-start)
10
- - [Core Concepts](#core-concepts)
11
- - [Usage Examples](#usage-examples)
12
- - [API Reference](#api-reference)
13
- - [Development Guide](#development-guide)
14
- - [License](#license)
15
-
16
- ## Features
17
-
18
- - **Unified Ad Execution Interface**: Single interface for managing different types of ads
19
- - **Task Queue Management**: Handles multiple ad execution tasks in a queue
20
- - **Flexible Control Flow**: Manual control over ad execution flow with `next` function
21
- - **Comprehensive Error Handling**: Complete error handling and logging
22
- - **State Persistence**: Built-in storage for ad state management
23
- - **Analytics Integration**: Built-in analytics support
24
- - **Middleware Pattern**: Uses middleware pattern for ad execution flow
25
- - **Cancellation Support**: Ability to clear and cancel pending tasks
26
-
27
- ## Installation
28
-
29
- ```bash
30
- npm install @singcl/ad-execute-manager
31
- ```
32
-
33
- ## Quick Start
34
-
35
- ### Basic Usage
36
-
37
- ```javascript
38
- import { RewardAdNovel, InterstitialAdNovel } from '@singcl/ad-execute-manager';
39
-
40
- // Create reward ad instance
41
- const rewardAd = RewardAdNovel.new({
42
- adConfig: { /* ad configuration */ },
43
- collection: {
44
- always: (args) => console.log('Ad always callback', args),
45
- halfway: (args) => console.log('Ad halfway callback', args),
46
- finished: (args) => console.log('Ad finished callback', args),
47
- before: (args) => console.log('Ad before callback', args),
48
- success: (args) => console.log('Ad success callback', args),
49
- prelude: (args) => console.log('Ad prelude callback', args)
50
- }
51
- });
52
-
53
- // Initialize ad
54
- rewardAd.initialize({
55
- adUnitId: 'your-reward-ad-unit-id'
56
- });
57
-
58
- // Execute ad
59
- const rewardResult = await rewardAd.addExecuteManager({
60
- options: {
61
- scene: 'reward_ad_scene',
62
- timeout: 8000 // Optional timeout
63
- }
64
- });
65
-
66
- // Create interstitial ad instance
67
- const interstitialAd = InterstitialAdNovel.new({
68
- adConfig: { /* ad configuration */ },
69
- collection: {
70
- always: (args) => console.log('Interstitial ad always callback', args),
71
- halfway: (args) => console.log('Interstitial ad halfway callback', args),
72
- finished: (args) => console.log('Interstitial ad finished callback', args),
73
- before: (args) => console.log('Interstitial ad before callback', args),
74
- success: (args) => console.log('Interstitial ad success callback', args),
75
- prelude: (args) => console.log('Interstitial ad prelude callback', args)
76
- }
77
- });
78
-
79
- // Initialize ad
80
- interstitialAd.initialize({
81
- adUnitId: 'your-interstitial-ad-unit-id'
82
- });
83
-
84
- // Execute ad
85
- const interstitialResult = await interstitialAd.addExecuteManager({
86
- options: {
87
- scene: 'interstitial_ad_scene'
88
- }
89
- });
90
- ```
91
-
92
- ### Advanced Usage
93
-
94
- ```javascript
95
- import { RewardAdNovel, InterstitialAdNovel, RewardAdSceneTriggerManager } from '@singcl/ad-execute-manager';
96
-
97
- // Create reward ad with logging enabled
98
- const rewardAd = RewardAdNovel.new({
99
- log: true, // Enable logging
100
- adConfig: { /* ad configuration */ },
101
- collection: { /* callback collection */ }
102
- });
103
-
104
- // Initialize ad with custom configuration
105
- rewardAd.initialize({
106
- adUnitId: 'your-reward-ad-unit-id',
107
- // Other ad configuration options
108
- });
109
-
110
- // Execute ad with custom options
111
- const result = await rewardAd.addExecuteManager({
112
- options: {
113
- scene: 'reward_ad_scene',
114
- timeout: 10000 // Custom timeout
115
- },
116
- collection: {
117
- always: (args) => {
118
- console.log('Ad execution completed', args);
119
- // Handle completion
120
- },
121
- halfway: (args) => {
122
- console.log('Ad interrupted', args);
123
- // Handle interruption
124
- },
125
- finished: (args) => {
126
- console.log('Ad finished successfully', args);
127
- // Handle success
128
- }
129
- }
130
- });
131
-
132
- // Use scene trigger manager
133
- const sceneManager = RewardAdSceneTriggerManager.build({ sign: 'init_sign' });
134
- sceneManager.addScene('custom_ad_scene');
135
-
136
- // Create interstitial ad with custom settings
137
- const interstitialAd = InterstitialAdNovel.new({
138
- needEndOnTimeout: true, // Enable timeout handling
139
- adConfig: { /* ad configuration */ }
140
- });
141
-
142
- // Initialize and execute
143
- interstitialAd.initialize({
144
- adUnitId: 'your-interstitial-ad-unit-id'
145
- });
146
-
147
- const interstitialResult = await interstitialAd.addExecuteManager({
148
- options: {
149
- scene: 'interstitial_ad_scene'
150
- }
151
- });
152
- ```
153
-
154
- ## Core Concepts
155
-
156
- ### Ad Execution Flow
157
-
158
- RewardAdNovel and InterstitialAdNovel use a middleware pattern to handle ad execution flow. Each ad task goes through the following steps:
159
-
160
- 1. **Initialization**: Create ad instance and configure parameters using `new()` or `build()`
161
- 2. **Configuration**: Call `initialize()` to set up ad unit ID and other configuration
162
- 3. **Execution**: Call `addExecuteManager()` to execute the ad with custom options
163
- 4. **Completion Callback**: Call callback functions after ad execution completes
164
- 5. **Cleanup**: Automatically clean up resources after execution
165
-
166
- ### Ad Types
167
-
168
- - **Reward Ads**: Use `RewardAdNovel` for scenarios where users need to complete viewing to receive rewards
169
- - **Interstitial Ads**: Use `InterstitialAdNovel` for scenarios where ads are inserted into the application flow
170
-
171
- ### Scene Management
172
-
173
- Manage ad trigger scenes through `RewardAdSceneTriggerManager`, which allows executing different ad logic based on different scenes.
174
-
175
- ### Frequency Control
176
-
177
- Use `CountRecorder` to implement ad display frequency control, which can set daily display limits.
178
-
179
- ## Usage Examples
180
-
181
- ### 1. Launch Reward Ad
182
-
183
- ```javascript
184
- // 启动激励视频
185
- import { CountRecorder, PubSub } from '@singcl/ad-execute-manager';
186
- import CommonSettings from './CommonSettings';
187
- import { SCENT_TEXT_OBJ } from './const';
188
- import RewardAdNovelExb from './RewardAdNovelExb';
189
-
190
- class RewardAdLaunch extends RewardAdNovelExb {
191
- _scene = SCENT_TEXT_OBJ.launch_ad; // 广告执行场景
192
-
193
- constructor(args) {
194
- super(args);
195
- this.launchSettings = RewardAdLaunchSettings.new();
196
- }
197
-
198
- adCloseLister(args) {
199
- this._clearAdTimeout();
200
- this._adCloseGlobalRecorder(args);
201
- this._adCloseSuccessAnalytics({ scene: this._scene, ad_is_completed: args.isEnded ? 1 : 0, ad_count: args.count });
202
-
203
- this.launchSettings.updateToday(); // 更新今天
204
- this.launchSettings.updateFrequency();
205
- const baseArgs = { ...args, scene: this._scene };
206
- const _end = (ctx) => {
207
- this.adDestroy();
208
- this._resolve?.(Object.assign({}, args, { scene: this._scene }, ctx));
209
- this._resolve = null;
210
- this._next?.(); // 执行下一个任务的回调函数,以继续执行流程
211
- this._next = null;
212
- };
213
-
214
- const _circle = () => {
215
- if (this.launchSettings.remainFrequency() > 0) {
216
- this._adInner({ options: { scene: this._scene }, collection: { resolve: this._resolve } }, this._next);
217
- } else {
218
- _end({ frequency: this.launchSettings.getFrequency() });
219
- }
220
- };
221
- if (args.isEnded) {
222
- this.launchSettings.updateFinished();
223
- if (this.launchSettings.remainFrequency() == 0) {
224
- this.launchSettings.updateLastFinished(true);
225
- }
226
- this._outerFinishedCallback(baseArgs);
227
- this._onFinish?.(baseArgs);
228
- } else {
229
- this._outerHalfwayCallback(baseArgs);
230
- this._onHalfway?.(baseArgs);
231
- }
232
- this._outerCloseCallback();
233
- _circle();
234
- }
235
-
236
- static build(args) {
237
- if (!RewardAdLaunch.instance) {
238
- RewardAdLaunch.instance = new RewardAdLaunch(args);
239
- }
240
- return RewardAdLaunch.instance;
241
- }
242
-
243
- static getInstance() {
244
- if (!RewardAdLaunch.instance) {
245
- throw new Error('RewardAdLaunch instance is not init');
246
- }
247
- return RewardAdLaunch.instance;
248
- }
249
-
250
- static new(args) {
251
- return new RewardAdLaunch(args);
252
- }
253
-
254
- static ad = new Promise((resolve) => {
255
- RewardAdLaunch.adResolve = resolve;
256
- });
257
-
258
- static satisfy(options, callback) {
259
- const con = RewardAdLaunchSettings.new().condition();
260
- if (typeof callback !== 'function') {
261
- return Promise.resolve();
262
- }
263
- return callback(con ? { options } : null);
264
- }
265
-
266
- static eventEmitter = new PubSub();
267
- }
268
-
269
- export default RewardAdLaunch;
270
-
271
- class RewardAdLaunchSettings {
272
- _fixedFrequency = null; // 固定展示次数,如果为null, 表示使用配置
273
- frequency = {
274
- total: 0,
275
- current: 0,
276
- finished: 0,
277
- lastFinished: false, // 最后一个是否完成
278
- };
279
- constructor() {
280
- this.commonSettings = CommonSettings.new();
281
- this.countRecorder = CountRecorder.new({
282
- local_sign: 'launch_count',
283
- total: this._adTimes(),
284
- userId: this.commonSettings.getUserId(),
285
- });
286
- this.frequency.total = this.timesPerFrequency();
287
- }
288
-
289
- _adTimes() {
290
- // 从系统配置中获取启动广告的配置项
291
- const { inter_site_pop_ups, inter_site_pop_ups_num } = this.commonSettings.getSysConfig();
292
- // 如果配置存在则返回配置值,否则返回0
293
- return Number(inter_site_pop_ups) > 0 && inter_site_pop_ups_num >= 1 ? Number(inter_site_pop_ups_num) : 0;
294
- }
295
-
296
- updateToday() {
297
- this.countRecorder.updateToday();
298
- }
299
-
300
- updateFrequency() {
301
- this.frequency.current += 1;
302
- }
303
-
304
- updateFinished() {
305
- this.frequency.finished += 1;
306
- }
307
-
308
- updateLastFinished(v = true) {
309
- this.frequency.lastFinished = v;
310
- }
311
-
312
- remainFrequency() {
313
- return this.frequency.total - this.frequency.current;
314
- }
315
-
316
- getFrequency() {
317
- return Object.assign({}, this.frequency);
318
- }
319
-
320
- timesPerFrequency() {
321
- const { inter_site_pop_ups } = this.commonSettings.getSysConfig();
322
- const ups = this._fixedFrequency ?? Number(inter_site_pop_ups);
323
- const count = Math.min(Math.max(0, Number(ups)), this._remain());
324
- return Math.max(0, count);
325
- }
326
-
327
- _remain() {
328
- return this.countRecorder.remain();
329
- }
330
-
331
- condition() {
332
- const remain = this._remain();
333
- return Number(remain) > 0 && this.timesPerFrequency() > 0;
334
- }
335
-
336
- static new(args) {
337
- return new RewardAdLaunchSettings(args);
338
- }
339
- }
340
- ```
341
-
342
- ### 2. Interstitial Ad
343
-
344
- ```javascript
345
- // 启动插屏广告
346
- import AdExecuteManager, { CountRecorder, Logger } from '@singcl/ad-execute-manager';
347
- import { matchErrorWithKeywords, getCurrentPageInterScene } from './_utils';
348
- import { SCENT_TEXT_OBJ } from './const';
349
- import InterstitialAdNovelExb from './InterstitialAdNovelExb';
350
- import RewardAdGlobalRecorder from './RewardAdGlobalRecorder';
351
- import CommonSettings from './CommonSettings';
352
-
353
- class InterstitialAdNormal extends InterstitialAdNovelExb {
354
- _scene = SCENT_TEXT_OBJ.other;
355
- _launchGap = 30 * 1000;
356
- _adBetweenGap = 60 * 1000;
357
- _timer = null;
358
- _adClose = 20000;
359
- _backgroundRetryTime = 3000; // app在后台时,重试间隔时间
360
- _foregroundRetryTime = 5000; // app在前台时,重试间隔时间
361
-
362
- constructor(args) {
363
- super(args);
364
- this.logger = new Logger({ prefix: InterstitialAdNormal.name });
365
- this.commonSettings = CommonSettings.new();
366
- this.countRecorder = CountRecorder.new({
367
- local_sign: 'interstitial_show_count',
368
- total: this.commonSettings.getCpAdDetails().dayAd,
369
- userId: this.commonSettings.getUserId(),
370
- });
371
- this._launchGap = this.commonSettings.getCpAdDetails().firstAdGap * 1000;
372
- this._adBetweenGap = this.commonSettings.getCpAdDetails().secondAdGap * 1000;
373
- this._adClose = this.commonSettings.getCpAdDetails().adClose ?? 20000;
374
- }
375
-
376
- _adCloseSuccessAnalytics(_args) {
377
- // ExbAnalyticsJS.getInstance().track('incentive_ad_close', {
378
- // scene: _args.scene,
379
- // ad_is_completed: _args.ad_is_completed,
380
- // });
381
- }
382
-
383
- _onInnerAdShowSuccess() {
384
- this.countRecorder.updateToday(); // 更新今日展示次数
385
- if (this._adClose) {
386
- setTimeout(() => {
387
- this.adCloseLister();
388
- }, this._adClose);
389
- }
390
- }
391
-
392
- async launch() {
393
- const recordIns = RewardAdGlobalRecorder.getInstance();
394
- const launchAdLastShowTime = recordIns.launchAdLastShowTime;
395
- const startLaunchTime = Math.max(this._launchGap - (new Date().getTime() - launchAdLastShowTime), 0);
396
-
397
- const circle = () => {
398
- return new Promise((resolve) => {
399
- const _fn = async () => {
400
- await AdExecuteManager.getInstance().whenAllTasksComplete();
401
- const nowTime = new Date().getTime();
402
- const rewardAdLastShowTime = recordIns.rewardAdLastShowTime;
403
- const interstitialAdLastShowTime = recordIns.interstitialAdLastShowTime;
404
-
405
- const remain = Math.max(
406
- this._adBetweenGap - (nowTime - Math.max(rewardAdLastShowTime, interstitialAdLastShowTime)),
407
- 0
408
- );
409
- if (remain > 0) {
410
- setTimeout(_fn, remain);
411
- } else {
412
- resolve();
413
- }
414
- };
415
- _fn();
416
- });
417
- };
418
-
419
- const fn2 = async (t) => {
420
- this._timer = setTimeout(async () => {
421
- if (this.countRecorder.remain() <= 0) {
422
- clearTimeout(this._timer);
423
- this._timer = null;
424
- return;
425
- }
426
- await circle();
427
- let res = null;
428
- if (this.getAppDisplayStatus() === 'hide') {
429
- const msg = `app in background: pause ad show, interstitial ad, time retry: ${this._backgroundRetryTime}ms`;
430
- this.logger.log(msg);
431
- res = {
432
- apiError: {
433
- errMsg: msg,
434
- errorCode: 110000,
435
- },
436
- };
437
- this.record(res);
438
- } else {
439
- this.logger.log(`Ad 进入队列,即将播放,GAP: ${this._adBetweenGap}ms`);
440
- res = await this.addExecuteManager({
441
- options: { retry: 0, scene: getCurrentPageInterScene() || this._scene },
442
- });
443
- }
444
- if (res && !res.apiError) {
445
- clearTimeout(this._timer);
446
- this._timer = null;
447
- fn2(this._adBetweenGap);
448
- } else {
449
- const e = res?.apiError;
450
- if (matchErrorWithKeywords(this._ttErrorMsgs, e?.errMsg) || this._ttErrorCodes.includes(e?.errorCode)) {
451
- clearTimeout(this._timer);
452
- this._timer = null;
453
- return;
454
- }
455
- setTimeout(
456
- () => {
457
- clearTimeout(this._timer);
458
- this._timer = null;
459
- fn2(0);
460
- },
461
- this.getAppDisplayStatus() === 'hide' ? this._backgroundRetryTime : this._foregroundRetryTime
462
- );
463
- }
464
- }, t);
465
- };
466
-
467
- fn2(startLaunchTime);
468
- }
469
-
470
- getAppDisplayStatus() {
471
- return RewardAdGlobalRecorder.getInstance().appDisplayStatus;
472
- }
473
-
474
- static build(args) {
475
- if (!InterstitialAdNormal.instance) {
476
- InterstitialAdNormal.instance = new InterstitialAdNormal(args);
477
- }
478
- return InterstitialAdNormal.instance;
479
- }
480
-
481
- static getInstance() {
482
- if (!InterstitialAdNormal.instance) {
483
- throw new Error('InterstitialAdNormal instance is not init');
484
- }
485
- return InterstitialAdNormal.instance;
486
- }
487
-
488
- static new(args) {
489
- return new InterstitialAdNormal(args);
490
- }
491
- }
492
-
493
- export default InterstitialAdNormal;
494
- ```
495
-
496
- ### 3. Ad Scene Trigger Manager
497
-
498
- ```javascript
499
- import { Logger } from '@singcl/ad-execute-manager';
500
- import { SCENT_TYPE_OBJ } from './const';
501
-
502
- class RewardAdSceneTriggerManager {
503
- static instance = null;
504
- _initSign = ''; // 初始化标识
505
- _currScene = null; // 当前场景
506
-
507
- constructor(args) {
508
- if (RewardAdSceneTriggerManager.instance) {
509
- return RewardAdSceneTriggerManager.instance;
510
- }
511
- this._initSign = args?.sign ?? ''; // 初始化标识
512
- this.logger = new Logger({ prefix: '' });
513
- RewardAdSceneTriggerManager.instance = this;
514
- }
515
-
516
- initialize(args) {
517
- // 初始化逻辑
518
- }
519
-
520
- addScene(value) {
521
- this.logger.log('-------------AD trigger scene:--------------', value);
522
- this._currScene = value;
523
- return this;
524
- }
525
-
526
- addSceneType(args) {
527
- this.addScene(SCENT_TYPE_OBJ[args.scene]);
528
- return this;
529
- }
530
-
531
- getCurrentScene() {
532
- return this._currScene;
533
- }
534
-
535
- placeholder() {
536
- return null;
537
- }
538
-
539
- static build(args) {
540
- if (!RewardAdSceneTriggerManager.instance) {
541
- RewardAdSceneTriggerManager.instance = new RewardAdSceneTriggerManager(args);
542
- }
543
- return RewardAdSceneTriggerManager.instance;
544
- }
545
-
546
- static getInstance() {
547
- if (!RewardAdSceneTriggerManager.instance) {
548
- throw new Error('RewardAdSceneTriggerManager instance is not init');
549
- }
550
- return RewardAdSceneTriggerManager.instance;
551
- }
552
- }
553
-
554
- export default RewardAdSceneTriggerManager;
555
- ```
556
-
557
- ## API Reference
558
-
559
- ### RewardAdNovel
560
-
561
- Main class for reward ad implementations, providing a complete solution for reward-based ads.
562
-
563
- #### Methods
564
-
565
- - `new(args)`: Create a new instance of RewardAdNovel
566
- - `build(args)`: Get or create the singleton instance of RewardAdNovel
567
- - `getInstance()`: Get the singleton instance of RewardAdNovel
568
- - `initialize(args, callback)`: Initialize the ad with configuration
569
- - `addExecuteManager(ctx)`: Execute the ad with custom options and callbacks
570
- - `ad(ctx, next)`: Core ad execution method
571
- - `adLoad()`: Load the ad
572
- - `adShow()`: Show the ad
573
- - `adDestroy()`: Destroy the ad instance
574
- - `clear()`: Clear all resources
575
-
576
- ### InterstitialAdNovel
577
-
578
- Main class for interstitial ad implementations, providing a complete solution for interstitial ads.
579
-
580
- #### Methods
581
-
582
- - `new(args)`: Create a new instance of InterstitialAdNovel
583
- - `build(args)`: Get or create the singleton instance of InterstitialAdNovel
584
- - `getInstance()`: Get the singleton instance of InterstitialAdNovel
585
- - `initialize(args, callback)`: Initialize the ad with configuration
586
- - `addExecuteManager(ctx)`: Execute the ad with custom options and callbacks
587
- - `ad(ctx, next)`: Core ad execution method
588
- - `adLoad()`: Load the ad
589
- - `adShow()`: Show the ad
590
- - `adDestroy()`: Destroy the ad instance
591
- - `clear()`: Clear all resources
592
-
593
- ### Other Exports
594
-
595
- - `SerializableError`: Error class that can be serialized
596
- - `Logger`: Logging utility
597
- - `Storage`: Storage management
598
- - `CountRecorder`: Ad execution counter
599
- - `RewardAdGlobalRecorder`: Global ad recorder
600
- - `RewardAdSceneTriggerManager`: Scene-based ad trigger manager
601
- - `AdAnalyticsJS`: Analytics integration
602
- - `PubSub`: Publish-subscribe pattern implementation
603
-
604
- ## Development Guide
605
-
606
- ### Development Scripts
607
-
608
- - `npm run build`: Build the library for production
609
- - `npm run dev`: Turn on watch mode
610
- - `npm run lint`: Lint your code
611
- - `npm run format`: Format your code
612
- - `npm run test`: Run tests
613
-
614
- ### Project Structure
615
-
616
- ```
617
- src/
618
- ad/ # Core ad-related code
619
- helper/ # Helper utilities
620
- typings/ # Type definitions
621
- utils/ # Common utilities
622
- index.js # Entry file
623
- example/ # Example code
624
- AdUnlock/ # Ad unlock related examples
625
- mini_program/ # Mini program examples
626
- typings/ # Example type definitions
627
- *.js # Various ad implementation examples
628
- ```
629
-
630
- ## License
631
-
1
+ # AD Execute Manager
2
+
3
+ A powerful and flexible ad execution management library for handling reward-based ads, interstitial ads, and other advertising formats in JavaScript applications.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [Core Concepts](#core-concepts)
11
+ - [Example Code](#example-code)
12
+ - [API Reference](#api-reference)
13
+ - [Development Guide](#development-guide)
14
+ - [License](#license)
15
+
16
+ ## Features
17
+
18
+ - **Unified Ad Execution Interface**: Single interface for managing different types of ads
19
+ - **Task Queue Management**: Handles multiple ad execution tasks in a queue
20
+ - **Flexible Control Flow**: Manual control over ad execution flow with `next` function
21
+ - **Comprehensive Error Handling**: Complete error handling and logging
22
+ - **State Persistence**: Built-in storage for ad state management
23
+ - **Analytics Integration**: Built-in analytics support
24
+ - **Middleware Pattern**: Uses middleware pattern for ad execution flow
25
+ - **Cancellation Support**: Ability to clear and cancel pending tasks
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install @singcl/ad-execute-manager
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### Basic Usage
36
+
37
+ ```javascript
38
+ import { AdExecuteManager, RewardAdFather } from '@singcl/ad-execute-manager';
39
+
40
+ // Get the singleton instance
41
+ const adManager = AdExecuteManager.getInstance();
42
+
43
+ // Create an ad instance (extend RewardAdFather)
44
+ class MyRewardAd extends RewardAdFather {
45
+ async ad(ctx, next) {
46
+ // Your ad logic here
47
+ console.log('Executing reward ad');
48
+
49
+ // Call next when ready to proceed
50
+ await next();
51
+
52
+ return { success: true, message: 'Ad executed successfully' };
53
+ }
54
+ }
55
+
56
+ // Create ad instance
57
+ const myAd = new MyRewardAd();
58
+
59
+ // Add task to execution queue
60
+ const result = await adManager.addTask(myAd, {
61
+ options: { /* ad options */ },
62
+ collection: { /* callback collection */ }
63
+ });
64
+ ```
65
+
66
+ ### Advanced Usage
67
+
68
+ ```javascript
69
+ import { AdExecuteManager, RewardAdSceneTriggerManager } from '@singcl/ad-execute-manager';
70
+
71
+ // Initialize with logging enabled
72
+ const adManager = AdExecuteManager.getInstance({ log: true });
73
+
74
+ // Check if manager is running
75
+ if (adManager.isRunning()) {
76
+ console.log('Ad manager is currently executing tasks');
77
+ }
78
+
79
+ // Get current task ID
80
+ const currentTaskId = adManager.getCurrentTaskId();
81
+
82
+ // Get total number of pending tasks
83
+ const taskCount = adManager.getTaskCount();
84
+
85
+ // Wait for all tasks to complete
86
+ await adManager.whenAllTasksComplete();
87
+
88
+ // Clear all pending tasks
89
+ adManager.clearTasks();
90
+ ```
91
+
92
+ ## Core Concepts
93
+
94
+ ### Ad Execution Flow
95
+
96
+ AD Execute Manager uses a middleware pattern to handle ad execution flow. Each ad task goes through the following steps:
97
+
98
+ 1. **Initialization**: Create ad instance and configure parameters
99
+ 2. **Queue Management**: Ad task enters the execution queue
100
+ 3. **Ad Execution**: Call the `ad` method of the ad instance
101
+ 4. **Completion Callback**: Call callback functions after ad execution completes
102
+ 5. **Task Cleanup**: Clean up task resources and execute the next task
103
+
104
+ ### Ad Types
105
+
106
+ - **Reward Ads**: Inherit from `RewardAdFather`, used for scenarios where users need to complete viewing to receive rewards
107
+ - **Interstitial Ads**: Inherit from `InterstitialAdFather`, used for scenarios where ads are inserted into the application flow
108
+
109
+ ### Scene Management
110
+
111
+ Manage ad trigger scenes through `RewardAdSceneTriggerManager`, which allows executing different ad logic based on different scenes.
112
+
113
+ ### Frequency Control
114
+
115
+ Use `CountRecorder` to implement ad display frequency control, which can set daily display limits.
116
+
117
+ ## Example Code
118
+
119
+ ### 1. Launch Reward Ad
120
+
121
+ ```javascript
122
+ import { CountRecorder, PubSub } from '@singcl/ad-execute-manager';
123
+ import CommonSettings from './CommonSettings';
124
+ import { SCENT_TEXT_OBJ } from './const';
125
+ import RewardAdNovelExb from './RewardAdNovelExb';
126
+
127
+ class RewardAdLaunch extends RewardAdNovelExb {
128
+ _scene = SCENT_TEXT_OBJ.launch_ad; // Ad execution scene
129
+
130
+ constructor(args) {
131
+ super(args);
132
+ this.launchSettings = RewardAdLaunchSettings.new();
133
+ }
134
+
135
+ adCloseLister(args) {
136
+ this._clearAdTimeout();
137
+ this._adCloseGlobalRecorder(args);
138
+ this._adCloseSuccessAnalytics({ scene: this._scene, ad_is_completed: args.isEnded ? 1 : 0, ad_count: args.count });
139
+
140
+ this.launchSettings.updateToday(); // Update today
141
+ this.launchSettings.updateFrequency();
142
+ const baseArgs = { ...args, scene: this._scene };
143
+ const _end = (ctx) => {
144
+ this.adDestroy();
145
+ this._resolve?.(Object.assign({}, args, { scene: this._scene }, ctx));
146
+ this._resolve = null;
147
+ this._next?.(); // Execute the next task's callback function to continue the flow
148
+ this._next = null;
149
+ };
150
+
151
+ const _circle = () => {
152
+ if (this.launchSettings.remainFrequency() > 0) {
153
+ this._adInner({ options: { scene: this._scene }, collection: { resolve: this._resolve } }, this._next);
154
+ } else {
155
+ _end({ frequency: this.launchSettings.getFrequency() });
156
+ }
157
+ };
158
+ if (args.isEnded) {
159
+ this.launchSettings.updateFinished();
160
+ if (this.launchSettings.remainFrequency() == 0) {
161
+ this.launchSettings.updateLastFinished(true);
162
+ }
163
+ this._outerFinishedCallback(baseArgs);
164
+ this._onFinish?.(baseArgs);
165
+ } else {
166
+ this._outerHalfwayCallback(baseArgs);
167
+ this._onHalfway?.(baseArgs);
168
+ }
169
+ this._outerCloseCallback();
170
+ _circle();
171
+ }
172
+
173
+ static build(args) {
174
+ if (!RewardAdLaunch.instance) {
175
+ RewardAdLaunch.instance = new RewardAdLaunch(args);
176
+ }
177
+ return RewardAdLaunch.instance;
178
+ }
179
+
180
+ static getInstance() {
181
+ if (!RewardAdLaunch.instance) {
182
+ throw new Error('RewardAdLaunch instance is not init');
183
+ }
184
+ return RewardAdLaunch.instance;
185
+ }
186
+
187
+ static new(args) {
188
+ return new RewardAdLaunch(args);
189
+ }
190
+
191
+ static ad = new Promise((resolve) => {
192
+ RewardAdLaunch.adResolve = resolve;
193
+ });
194
+
195
+ static satisfy(options, callback) {
196
+ const con = RewardAdLaunchSettings.new().condition();
197
+ if (typeof callback !== 'function') {
198
+ return Promise.resolve();
199
+ }
200
+ return callback(con ? { options } : null);
201
+ }
202
+
203
+ static eventEmitter = new PubSub();
204
+ }
205
+
206
+ export default RewardAdLaunch;
207
+
208
+ class RewardAdLaunchSettings {
209
+ _fixedFrequency = null; // Fixed display frequency, if null, use configuration
210
+ frequency = {
211
+ total: 0,
212
+ current: 0,
213
+ finished: 0,
214
+ lastFinished: false, // Whether the last one is completed
215
+ };
216
+ constructor() {
217
+ this.commonSettings = CommonSettings.new();
218
+ this.countRecorder = CountRecorder.new({
219
+ local_sign: 'launch_count',
220
+ total: this._adTimes(),
221
+ userId: this.commonSettings.getUserId(),
222
+ });
223
+ this.frequency.total = this.timesPerFrequency();
224
+ }
225
+
226
+ _adTimes() {
227
+ // Get launch ad configuration from system configuration
228
+ const { inter_site_pop_ups, inter_site_pop_ups_num } = this.commonSettings.getSysConfig();
229
+ // Return configuration value if it exists, otherwise return 0
230
+ return Number(inter_site_pop_ups) > 0 && inter_site_pop_ups_num >= 1 ? Number(inter_site_pop_ups_num) : 0;
231
+ }
232
+
233
+ updateToday() {
234
+ this.countRecorder.updateToday();
235
+ }
236
+
237
+ updateFrequency() {
238
+ this.frequency.current += 1;
239
+ }
240
+
241
+ updateFinished() {
242
+ this.frequency.finished += 1;
243
+ }
244
+
245
+ updateLastFinished(v = true) {
246
+ this.frequency.lastFinished = v;
247
+ }
248
+
249
+ remainFrequency() {
250
+ return this.frequency.total - this.frequency.current;
251
+ }
252
+
253
+ getFrequency() {
254
+ return Object.assign({}, this.frequency);
255
+ }
256
+
257
+ timesPerFrequency() {
258
+ const { inter_site_pop_ups } = this.commonSettings.getSysConfig();
259
+ const ups = this._fixedFrequency ?? Number(inter_site_pop_ups);
260
+ const count = Math.min(Math.max(0, Number(ups)), this._remain());
261
+ return Math.max(0, count);
262
+ }
263
+
264
+ _remain() {
265
+ return this.countRecorder.remain();
266
+ }
267
+
268
+ condition() {
269
+ const remain = this._remain();
270
+ return Number(remain) > 0 && this.timesPerFrequency() > 0;
271
+ }
272
+
273
+ static new(args) {
274
+ return new RewardAdLaunchSettings(args);
275
+ }
276
+ }
277
+ ```
278
+
279
+ ### 2. Interstitial Ad
280
+
281
+ ```javascript
282
+ import AdExecuteManager, { CountRecorder, Logger } from '@singcl/ad-execute-manager';
283
+ import { matchErrorWithKeywords, getCurrentPageInterScene } from './_utils';
284
+ import { SCENT_TEXT_OBJ } from './const';
285
+ import InterstitialAdNovelExb from './InterstitialAdNovelExb';
286
+ import RewardAdGlobalRecorder from './RewardAdGlobalRecorder';
287
+ import CommonSettings from './CommonSettings';
288
+
289
+ class InterstitialAdNormal extends InterstitialAdNovelExb {
290
+ _scene = SCENT_TEXT_OBJ.other;
291
+ _launchGap = 30 * 1000;
292
+ _adBetweenGap = 60 * 1000;
293
+ _timer = null;
294
+ _adClose = 20000;
295
+ _backgroundRetryTime = 3000; // Retry interval when app is in background
296
+ _foregroundRetryTime = 5000; // Retry interval when app is in foreground
297
+
298
+ constructor(args) {
299
+ super(args);
300
+ this.logger = new Logger({ prefix: InterstitialAdNormal.name });
301
+ this.commonSettings = CommonSettings.new();
302
+ this.countRecorder = CountRecorder.new({
303
+ local_sign: 'interstitial_show_count',
304
+ total: this.commonSettings.getCpAdDetails().dayAd,
305
+ userId: this.commonSettings.getUserId(),
306
+ });
307
+ this._launchGap = this.commonSettings.getCpAdDetails().firstAdGap * 1000;
308
+ this._adBetweenGap = this.commonSettings.getCpAdDetails().secondAdGap * 1000;
309
+ this._adClose = this.commonSettings.getCpAdDetails().adClose ?? 20000;
310
+ }
311
+
312
+ _adCloseSuccessAnalytics(_args) {
313
+ // ExbAnalyticsJS.getInstance().track('incentive_ad_close', {
314
+ // scene: _args.scene,
315
+ // ad_is_completed: _args.ad_is_completed,
316
+ // });
317
+ }
318
+
319
+ _onInnerAdShowSuccess() {
320
+ this.countRecorder.updateToday(); // Update today's display count
321
+ if (this._adClose) {
322
+ setTimeout(() => {
323
+ this.adCloseLister();
324
+ }, this._adClose);
325
+ }
326
+ }
327
+
328
+ async launch() {
329
+ const recordIns = RewardAdGlobalRecorder.getInstance();
330
+ const launchAdLastShowTime = recordIns.launchAdLastShowTime;
331
+ const startLaunchTime = Math.max(this._launchGap - (new Date().getTime() - launchAdLastShowTime), 0);
332
+
333
+ const circle = () => {
334
+ return new Promise((resolve) => {
335
+ const _fn = async () => {
336
+ await AdExecuteManager.getInstance().whenAllTasksComplete();
337
+ const nowTime = new Date().getTime();
338
+ const rewardAdLastShowTime = recordIns.rewardAdLastShowTime;
339
+ const interstitialAdLastShowTime = recordIns.interstitialAdLastShowTime;
340
+
341
+ const remain = Math.max(
342
+ this._adBetweenGap - (nowTime - Math.max(rewardAdLastShowTime, interstitialAdLastShowTime)),
343
+ 0
344
+ );
345
+ if (remain > 0) {
346
+ setTimeout(_fn, remain);
347
+ } else {
348
+ resolve();
349
+ }
350
+ };
351
+ _fn();
352
+ });
353
+ };
354
+
355
+ const fn2 = async (t) => {
356
+ this._timer = setTimeout(async () => {
357
+ if (this.countRecorder.remain() <= 0) {
358
+ clearTimeout(this._timer);
359
+ this._timer = null;
360
+ return;
361
+ }
362
+ await circle();
363
+ let res = null;
364
+ if (this.getAppDisplayStatus() === 'hide') {
365
+ const msg = `app in background: pause ad show, interstitial ad, time retry: ${this._backgroundRetryTime}ms`;
366
+ this.logger.log(msg);
367
+ res = {
368
+ apiError: {
369
+ errMsg: msg,
370
+ errorCode: 110000,
371
+ },
372
+ };
373
+ this.record(res);
374
+ } else {
375
+ this.logger.log(`Ad entering queue, will play soon, GAP: ${this._adBetweenGap}ms`);
376
+ res = await this.addExecuteManager({
377
+ options: { retry: 0, scene: getCurrentPageInterScene() || this._scene },
378
+ });
379
+ }
380
+ if (res && !res.apiError) {
381
+ clearTimeout(this._timer);
382
+ this._timer = null;
383
+ fn2(this._adBetweenGap);
384
+ } else {
385
+ const e = res?.apiError;
386
+ if (matchErrorWithKeywords(this._ttErrorMsgs, e?.errMsg) || this._ttErrorCodes.includes(e?.errorCode)) {
387
+ clearTimeout(this._timer);
388
+ this._timer = null;
389
+ return;
390
+ }
391
+ setTimeout(
392
+ () => {
393
+ clearTimeout(this._timer);
394
+ this._timer = null;
395
+ fn2(0);
396
+ },
397
+ this.getAppDisplayStatus() === 'hide' ? this._backgroundRetryTime : this._foregroundRetryTime
398
+ );
399
+ }
400
+ }, t);
401
+ };
402
+
403
+ fn2(startLaunchTime);
404
+ }
405
+
406
+ getAppDisplayStatus() {
407
+ return RewardAdGlobalRecorder.getInstance().appDisplayStatus;
408
+ }
409
+
410
+ static build(args) {
411
+ if (!InterstitialAdNormal.instance) {
412
+ InterstitialAdNormal.instance = new InterstitialAdNormal(args);
413
+ }
414
+ return InterstitialAdNormal.instance;
415
+ }
416
+
417
+ static getInstance() {
418
+ if (!InterstitialAdNormal.instance) {
419
+ throw new Error('InterstitialAdNormal instance is not init');
420
+ }
421
+ return InterstitialAdNormal.instance;
422
+ }
423
+
424
+ static new(args) {
425
+ return new InterstitialAdNormal(args);
426
+ }
427
+ }
428
+
429
+ export default InterstitialAdNormal;
430
+ ```
431
+
432
+ ### 3. Ad Scene Trigger Manager
433
+
434
+ ```javascript
435
+ import { Logger } from '@singcl/ad-execute-manager';
436
+ import { SCENT_TYPE_OBJ } from './const';
437
+
438
+ class RewardAdSceneTriggerManager {
439
+ static instance = null;
440
+ _initSign = ''; // Initialization sign
441
+ _currScene = null; // Current scene
442
+
443
+ constructor(args) {
444
+ if (RewardAdSceneTriggerManager.instance) {
445
+ return RewardAdSceneTriggerManager.instance;
446
+ }
447
+ this._initSign = args?.sign ?? ''; // Initialization sign
448
+ this.logger = new Logger({ prefix: '' });
449
+ RewardAdSceneTriggerManager.instance = this;
450
+ }
451
+
452
+ initialize(args) {
453
+ // Initialization logic
454
+ }
455
+
456
+ addScene(value) {
457
+ this.logger.log('-------------AD trigger scene:--------------', value);
458
+ this._currScene = value;
459
+ return this;
460
+ }
461
+
462
+ addSceneType(args) {
463
+ this.addScene(SCENT_TYPE_OBJ[args.scene]);
464
+ return this;
465
+ }
466
+
467
+ getCurrentScene() {
468
+ return this._currScene;
469
+ }
470
+
471
+ placeholder() {
472
+ return null;
473
+ }
474
+
475
+ static build(args) {
476
+ if (!RewardAdSceneTriggerManager.instance) {
477
+ RewardAdSceneTriggerManager.instance = new RewardAdSceneTriggerManager(args);
478
+ }
479
+ return RewardAdSceneTriggerManager.instance;
480
+ }
481
+
482
+ static getInstance() {
483
+ if (!RewardAdSceneTriggerManager.instance) {
484
+ throw new Error('RewardAdSceneTriggerManager instance is not init');
485
+ }
486
+ return RewardAdSceneTriggerManager.instance;
487
+ }
488
+ }
489
+
490
+ export default RewardAdSceneTriggerManager;
491
+ ```
492
+
493
+ ## API Reference
494
+
495
+ ### AdExecuteManager
496
+
497
+ The main class for managing ad execution flow.
498
+
499
+ #### Methods
500
+
501
+ - `getInstance(args)`: Get the singleton instance of AdExecuteManager
502
+ - `getSafeInstance()`: Get the instance, returns null if not initialized
503
+ - `addTask(adInstance, ctx)`: Add an ad task to the execution queue
504
+ - `clearTasks()`: Cancel all pending tasks
505
+ - `getTaskCount()`: Get the number of pending tasks
506
+ - `isRunning()`: Check if tasks are currently running
507
+ - `getCurrentTaskId()`: Get the ID of the current executing task
508
+ - `whenAllTasksComplete()`: Returns a Promise that resolves when all tasks are complete
509
+
510
+ ### RewardAdFather
511
+
512
+ Base class for reward ad implementations.
513
+
514
+ ### InterstitialAdFather
515
+
516
+ Base class for interstitial ad implementations.
517
+
518
+ ### Other Exports
519
+
520
+ - `SerializableError`: Error class that can be serialized
521
+ - `Logger`: Logging utility
522
+ - `Storage`: Storage management
523
+ - `CountRecorder`: Ad execution counter
524
+ - `RewardAdGlobalRecorder`: Global ad recorder
525
+ - `RewardAdSceneTriggerManager`: Scene-based ad trigger manager
526
+ - `AdAnalyticsJS`: Analytics integration
527
+ - `RewardAdNovel`: Novel-specific reward ad implementation
528
+ - `InterstitialAdNovel`: Novel-specific interstitial ad implementation
529
+ - `PubSub`: Publish-subscribe pattern implementation
530
+
531
+ ## Development Guide
532
+
533
+ ### Development Scripts
534
+
535
+ - `npm run build`: Build the library for production
536
+ - `npm run dev`: Turn on watch mode
537
+ - `npm run lint`: Lint your code
538
+ - `npm run format`: Format your code
539
+ - `npm run test`: Run tests
540
+
541
+ ### Project Structure
542
+
543
+ ```
544
+ src/
545
+ ad/ # Core ad-related code
546
+ helper/ # Helper utilities
547
+ typings/ # Type definitions
548
+ utils/ # Common utilities
549
+ index.js # Entry file
550
+ example/ # Example code
551
+ AdUnlock/ # Ad unlock related examples
552
+ mini_program/ # Mini program examples
553
+ typings/ # Example type definitions
554
+ *.js # Various ad implementation examples
555
+ ```
556
+
557
+ ## License
558
+
632
559
  This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.