A powerful and flexible ad execution management library for handling reward-based ads, interstitial ads, and other advertising formats in JavaScript applications.
npm install @singcl/ad-execute-managerA powerful and flexible ad execution management library for handling reward-based ads, interstitial ads, and other advertising formats in JavaScript applications.
- Features
- Installation
- Quick Start
- Core Concepts
- Example Code
- API Reference
- Development Guide
- License
- Unified Ad Execution Interface: Single interface for managing different types of ads
- Task Queue Management: Handles multiple ad execution tasks in a queue
- Flexible Control Flow: Manual control over ad execution flow with next function
- Comprehensive Error Handling: Complete error handling and logging
- State Persistence: Built-in storage for ad state management
- Analytics Integration: Built-in analytics support
- Middleware Pattern: Uses middleware pattern for ad execution flow
- Cancellation Support: Ability to clear and cancel pending tasks
``bash`
npm install @singcl/ad-execute-manager
`javascript
import { AdExecuteManager, RewardAdFather } from '@singcl/ad-execute-manager';
// Get the singleton instance
const adManager = AdExecuteManager.getInstance();
// Create an ad instance (extend RewardAdFather)
class MyRewardAd extends RewardAdFather {
async ad(ctx, next) {
// Your ad logic here
console.log('Executing reward ad');
// Call next when ready to proceed
await next();
return { success: true, message: 'Ad executed successfully' };
}
}
// Create ad instance
const myAd = new MyRewardAd();
// Add task to execution queue
const result = await adManager.addTask(myAd, {
options: { / ad options / },
collection: { / callback collection / }
});
`
`javascript
import { AdExecuteManager, RewardAdSceneTriggerManager } from '@singcl/ad-execute-manager';
// Initialize with logging enabled
const adManager = AdExecuteManager.getInstance({ log: true });
// Check if manager is running
if (adManager.isRunning()) {
console.log('Ad manager is currently executing tasks');
}
// Get current task ID
const currentTaskId = adManager.getCurrentTaskId();
// Get total number of pending tasks
const taskCount = adManager.getTaskCount();
// Wait for all tasks to complete
await adManager.whenAllTasksComplete();
// Clear all pending tasks
adManager.clearTasks();
`
AD Execute Manager uses a middleware pattern to handle ad execution flow. Each ad task goes through the following steps:
1. Initialization: Create ad instance and configure parameters
2. Queue Management: Ad task enters the execution queue
3. Ad Execution: Call the ad method of the ad instance
4. Completion Callback: Call callback functions after ad execution completes
5. Task Cleanup: Clean up task resources and execute the next task
- Reward Ads: Inherit from RewardAdFather, used for scenarios where users need to complete viewing to receive rewardsInterstitialAdFather
- Interstitial Ads: Inherit from , used for scenarios where ads are inserted into the application flow
Manage ad trigger scenes through RewardAdSceneTriggerManager, which allows executing different ad logic based on different scenes.
Use CountRecorder to implement ad display frequency control, which can set daily display limits.
`javascript
import { CountRecorder, PubSub } from '@singcl/ad-execute-manager';
import CommonSettings from './CommonSettings';
import { SCENT_TEXT_OBJ } from './const';
import RewardAdNovelExb from './RewardAdNovelExb';
class RewardAdLaunch extends RewardAdNovelExb {
_scene = SCENT_TEXT_OBJ.launch_ad; // Ad execution scene
constructor(args) {
super(args);
this.launchSettings = RewardAdLaunchSettings.new();
}
adCloseLister(args) {
this._clearAdTimeout();
this._adCloseGlobalRecorder(args);
this._adCloseSuccessAnalytics({ scene: this._scene, ad_is_completed: args.isEnded ? 1 : 0, ad_count: args.count });
this.launchSettings.updateToday(); // Update today
this.launchSettings.updateFrequency();
const baseArgs = { ...args, scene: this._scene };
const _end = (ctx) => {
this.adDestroy();
this._resolve?.(Object.assign({}, args, { scene: this._scene }, ctx));
this._resolve = null;
this._next?.(); // Execute the next task's callback function to continue the flow
this._next = null;
};
const _circle = () => {
if (this.launchSettings.remainFrequency() > 0) {
this._adInner({ options: { scene: this._scene }, collection: { resolve: this._resolve } }, this._next);
} else {
_end({ frequency: this.launchSettings.getFrequency() });
}
};
if (args.isEnded) {
this.launchSettings.updateFinished();
if (this.launchSettings.remainFrequency() == 0) {
this.launchSettings.updateLastFinished(true);
}
this._outerFinishedCallback(baseArgs);
this._onFinish?.(baseArgs);
} else {
this._outerHalfwayCallback(baseArgs);
this._onHalfway?.(baseArgs);
}
this._outerCloseCallback();
_circle();
}
static build(args) {
if (!RewardAdLaunch.instance) {
RewardAdLaunch.instance = new RewardAdLaunch(args);
}
return RewardAdLaunch.instance;
}
static getInstance() {
if (!RewardAdLaunch.instance) {
throw new Error('RewardAdLaunch instance is not init');
}
return RewardAdLaunch.instance;
}
static new(args) {
return new RewardAdLaunch(args);
}
static ad = new Promise((resolve) => {
RewardAdLaunch.adResolve = resolve;
});
static satisfy(options, callback) {
const con = RewardAdLaunchSettings.new().condition();
if (typeof callback !== 'function') {
return Promise.resolve();
}
return callback(con ? { options } : null);
}
static eventEmitter = new PubSub();
}
export default RewardAdLaunch;
class RewardAdLaunchSettings {
_fixedFrequency = null; // Fixed display frequency, if null, use configuration
frequency = {
total: 0,
current: 0,
finished: 0,
lastFinished: false, // Whether the last one is completed
};
constructor() {
this.commonSettings = CommonSettings.new();
this.countRecorder = CountRecorder.new({
local_sign: 'launch_count',
total: this._adTimes(),
userId: this.commonSettings.getUserId(),
});
this.frequency.total = this.timesPerFrequency();
}
_adTimes() {
// Get launch ad configuration from system configuration
const { inter_site_pop_ups, inter_site_pop_ups_num } = this.commonSettings.getSysConfig();
// Return configuration value if it exists, otherwise return 0
return Number(inter_site_pop_ups) > 0 && inter_site_pop_ups_num >= 1 ? Number(inter_site_pop_ups_num) : 0;
}
updateToday() {
this.countRecorder.updateToday();
}
updateFrequency() {
this.frequency.current += 1;
}
updateFinished() {
this.frequency.finished += 1;
}
updateLastFinished(v = true) {
this.frequency.lastFinished = v;
}
remainFrequency() {
return this.frequency.total - this.frequency.current;
}
getFrequency() {
return Object.assign({}, this.frequency);
}
timesPerFrequency() {
const { inter_site_pop_ups } = this.commonSettings.getSysConfig();
const ups = this._fixedFrequency ?? Number(inter_site_pop_ups);
const count = Math.min(Math.max(0, Number(ups)), this._remain());
return Math.max(0, count);
}
_remain() {
return this.countRecorder.remain();
}
condition() {
const remain = this._remain();
return Number(remain) > 0 && this.timesPerFrequency() > 0;
}
static new(args) {
return new RewardAdLaunchSettings(args);
}
}
`
`javascript
import AdExecuteManager, { CountRecorder, Logger } from '@singcl/ad-execute-manager';
import { matchErrorWithKeywords, getCurrentPageInterScene } from './_utils';
import { SCENT_TEXT_OBJ } from './const';
import InterstitialAdNovelExb from './InterstitialAdNovelExb';
import RewardAdGlobalRecorder from './RewardAdGlobalRecorder';
import CommonSettings from './CommonSettings';
class InterstitialAdNormal extends InterstitialAdNovelExb {
_scene = SCENT_TEXT_OBJ.other;
_launchGap = 30 * 1000;
_adBetweenGap = 60 * 1000;
_timer = null;
_adClose = 20000;
_backgroundRetryTime = 3000; // Retry interval when app is in background
_foregroundRetryTime = 5000; // Retry interval when app is in foreground
constructor(args) {
super(args);
this.logger = new Logger({ prefix: InterstitialAdNormal.name });
this.commonSettings = CommonSettings.new();
this.countRecorder = CountRecorder.new({
local_sign: 'interstitial_show_count',
total: this.commonSettings.getCpAdDetails().dayAd,
userId: this.commonSettings.getUserId(),
});
this._launchGap = this.commonSettings.getCpAdDetails().firstAdGap * 1000;
this._adBetweenGap = this.commonSettings.getCpAdDetails().secondAdGap * 1000;
this._adClose = this.commonSettings.getCpAdDetails().adClose ?? 20000;
}
_adCloseSuccessAnalytics(_args) {
// ExbAnalyticsJS.getInstance().track('incentive_ad_close', {
// scene: _args.scene,
// ad_is_completed: _args.ad_is_completed,
// });
}
_onInnerAdShowSuccess() {
this.countRecorder.updateToday(); // Update today's display count
if (this._adClose) {
setTimeout(() => {
this.adCloseLister();
}, this._adClose);
}
}
async launch() {
const recordIns = RewardAdGlobalRecorder.getInstance();
const launchAdLastShowTime = recordIns.launchAdLastShowTime;
const startLaunchTime = Math.max(this._launchGap - (new Date().getTime() - launchAdLastShowTime), 0);
const circle = () => {
return new Promise((resolve) => {
const _fn = async () => {
await AdExecuteManager.getInstance().whenAllTasksComplete();
const nowTime = new Date().getTime();
const rewardAdLastShowTime = recordIns.rewardAdLastShowTime;
const interstitialAdLastShowTime = recordIns.interstitialAdLastShowTime;
const remain = Math.max(
this._adBetweenGap - (nowTime - Math.max(rewardAdLastShowTime, interstitialAdLastShowTime)),
0
);
if (remain > 0) {
setTimeout(_fn, remain);
} else {
resolve();
}
};
_fn();
});
};
const fn2 = async (t) => {
this._timer = setTimeout(async () => {
if (this.countRecorder.remain() <= 0) {
clearTimeout(this._timer);
this._timer = null;
return;
}
await circle();
let res = null;
if (this.getAppDisplayStatus() === 'hide') {
const msg = app in background: pause ad show, interstitial ad, time retry: ${this._backgroundRetryTime}ms;Ad entering queue, will play soon, GAP: ${this._adBetweenGap}ms
this.logger.log(msg);
res = {
apiError: {
errMsg: msg,
errorCode: 110000,
},
};
this.record(res);
} else {
this.logger.log();
res = await this.addExecuteManager({
options: { retry: 0, scene: getCurrentPageInterScene() || this._scene },
});
}
if (res && !res.apiError) {
clearTimeout(this._timer);
this._timer = null;
fn2(this._adBetweenGap);
} else {
const e = res?.apiError;
if (matchErrorWithKeywords(this._ttErrorMsgs, e?.errMsg) || this._ttErrorCodes.includes(e?.errorCode)) {
clearTimeout(this._timer);
this._timer = null;
return;
}
setTimeout(
() => {
clearTimeout(this._timer);
this._timer = null;
fn2(0);
},
this.getAppDisplayStatus() === 'hide' ? this._backgroundRetryTime : this._foregroundRetryTime
);
}
}, t);
};
fn2(startLaunchTime);
}
getAppDisplayStatus() {
return RewardAdGlobalRecorder.getInstance().appDisplayStatus;
}
static build(args) {
if (!InterstitialAdNormal.instance) {
InterstitialAdNormal.instance = new InterstitialAdNormal(args);
}
return InterstitialAdNormal.instance;
}
static getInstance() {
if (!InterstitialAdNormal.instance) {
throw new Error('InterstitialAdNormal instance is not init');
}
return InterstitialAdNormal.instance;
}
static new(args) {
return new InterstitialAdNormal(args);
}
}
export default InterstitialAdNormal;
`
`javascript
import { Logger } from '@singcl/ad-execute-manager';
import { SCENT_TYPE_OBJ } from './const';
class RewardAdSceneTriggerManager {
static instance = null;
_initSign = ''; // Initialization sign
_currScene = null; // Current scene
constructor(args) {
if (RewardAdSceneTriggerManager.instance) {
return RewardAdSceneTriggerManager.instance;
}
this._initSign = args?.sign ?? ''; // Initialization sign
this.logger = new Logger({ prefix: '' });
RewardAdSceneTriggerManager.instance = this;
}
initialize(args) {
// Initialization logic
}
addScene(value) {
this.logger.log('-------------AD trigger scene:--------------', value);
this._currScene = value;
return this;
}
addSceneType(args) {
this.addScene(SCENT_TYPE_OBJ[args.scene]);
return this;
}
getCurrentScene() {
return this._currScene;
}
placeholder() {
return null;
}
static build(args) {
if (!RewardAdSceneTriggerManager.instance) {
RewardAdSceneTriggerManager.instance = new RewardAdSceneTriggerManager(args);
}
return RewardAdSceneTriggerManager.instance;
}
static getInstance() {
if (!RewardAdSceneTriggerManager.instance) {
throw new Error('RewardAdSceneTriggerManager instance is not init');
}
return RewardAdSceneTriggerManager.instance;
}
}
export default RewardAdSceneTriggerManager;
`
The main class for managing ad execution flow.
#### Methods
- getInstance(args): Get the singleton instance of AdExecuteManagergetSafeInstance()
- : Get the instance, returns null if not initializedaddTask(adInstance, ctx)
- : Add an ad task to the execution queueclearTasks()
- : Cancel all pending tasksgetTaskCount()
- : Get the number of pending tasksisRunning()
- : Check if tasks are currently runninggetCurrentTaskId()
- : Get the ID of the current executing taskwhenAllTasksComplete()
- : Returns a Promise that resolves when all tasks are complete
Base class for reward ad implementations.
Base class for interstitial ad implementations.
- SerializableError: Error class that can be serializedLogger
- : Logging utilityStorage
- : Storage managementCountRecorder
- : Ad execution counterRewardAdGlobalRecorder
- : Global ad recorderRewardAdSceneTriggerManager
- : Scene-based ad trigger managerAdAnalyticsJS
- : Analytics integrationRewardAdNovel
- : Novel-specific reward ad implementationInterstitialAdNovel
- : Novel-specific interstitial ad implementationPubSub
- : Publish-subscribe pattern implementation
- npm run build: Build the library for productionnpm run dev
- : Turn on watch modenpm run lint
- : Lint your codenpm run format
- : Format your codenpm run test
- : Run tests
```
src/
ad/ # Core ad-related code
helper/ # Helper utilities
typings/ # Type definitions
utils/ # Common utilities
index.js # Entry file
example/ # Example code
AdUnlock/ # Ad unlock related examples
mini_program/ # Mini program examples
typings/ # Example type definitions
*.js # Various ad implementation examples
This project is licensed under the MIT License - see the LICENSE file for details.