@singcl/ad-execute-manager
v1.16.1
Published
A powerful and flexible ad execution management library for handling reward-based ads, interstitial ads, and other advertising formats in JavaScript applications.
Downloads
5,817
Maintainers
Readme
AD Execute Manager
A powerful and flexible ad execution management library for handling reward-based ads, interstitial ads, and other advertising formats in JavaScript applications.
Table of Contents
Features
- 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
nextfunction - 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
Installation
npm install @singcl/ad-execute-managerQuick Start
Basic Usage
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 */ }
});Advanced Usage
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();Core Concepts
Ad Execution Flow
AD Execute Manager uses a middleware pattern to handle ad execution flow. Each ad task goes through the following steps:
- Initialization: Create ad instance and configure parameters
- Queue Management: Ad task enters the execution queue
- Ad Execution: Call the
admethod of the ad instance - Completion Callback: Call callback functions after ad execution completes
- Task Cleanup: Clean up task resources and execute the next task
Ad Types
- Reward Ads: Inherit from
RewardAdFather, used for scenarios where users need to complete viewing to receive rewards - Interstitial Ads: Inherit from
InterstitialAdFather, used for scenarios where ads are inserted into the application flow
Scene Management
Manage ad trigger scenes through RewardAdSceneTriggerManager, which allows executing different ad logic based on different scenes.
Frequency Control
Use CountRecorder to implement ad display frequency control, which can set daily display limits.
Example Code
1. Launch Reward Ad
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);
}
}2. Interstitial Ad
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`;
this.logger.log(msg);
res = {
apiError: {
errMsg: msg,
errorCode: 110000,
},
};
this.record(res);
} else {
this.logger.log(`Ad entering queue, will play soon, GAP: ${this._adBetweenGap}ms`);
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;3. Ad Scene Trigger Manager
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;API Reference
AdExecuteManager
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
RewardAdFather
Base class for reward ad implementations.
InterstitialAdFather
Base class for interstitial ad implementations.
Other Exports
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
Development Guide
Development Scripts
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
Project Structure
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 examplesLicense
This project is licensed under the MIT License - see the LICENSE file for details.
