@ad-execute-manager/core 2.0.0-alpha.2 → 2.0.0-alpha.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 singcl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @singcl/core
2
+
3
+ Core functionality for ad execution management including AdExecuteManager, utility functions, and middleware composition.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @singcl/core
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **AdExecuteManager**: A powerful ad execution management class for handling reward-based ads, interstitial ads, and other advertising formats
14
+ - **compose**: A middleware composition utility inspired by Koa
15
+ - **needRetryAdError**: A utility function for determining if an ad error should be retried
16
+
17
+ ## Usage
18
+
19
+ ### AdExecuteManager
20
+
21
+ ```javascript
22
+ import { AdExecuteManager } from '@singcl/core';
23
+
24
+ const adManager = new AdExecuteManager({
25
+ // Configuration options
26
+ });
27
+
28
+ // Initialize ad
29
+ const result = await adManager.init();
30
+
31
+ // Show ad
32
+ const showResult = await adManager.show();
33
+ ```
34
+
35
+ ### Middleware Composition
36
+
37
+ ```javascript
38
+ import { compose } from '@singcl/core';
39
+
40
+ const middlewares = [
41
+ async (ctx, next) => {
42
+ console.log('Middleware 1 start');
43
+ await next();
44
+ console.log('Middleware 1 end');
45
+ },
46
+ async (ctx, next) => {
47
+ console.log('Middleware 2 start');
48
+ await next();
49
+ console.log('Middleware 2 end');
50
+ }
51
+ ];
52
+
53
+ const composedMiddleware = compose(middlewares);
54
+ await composedMiddleware({});
55
+ ```
56
+
57
+ ### Error Retry Utility
58
+
59
+ ```javascript
60
+ import { needRetryAdError } from '@singcl/core';
61
+
62
+ const apiError = {
63
+ errMsg: 'ad_show_timeout: normal',
64
+ timeout: 5000
65
+ };
66
+
67
+ const shouldRetry = needRetryAdError({
68
+ apiError,
69
+ configuredAdTimeout: 5000,
70
+ errorRetryStrategy: {
71
+ timeout: true,
72
+ background: true
73
+ }
74
+ });
75
+
76
+ console.log('Should retry:', shouldRetry);
77
+ ```
78
+
79
+ ## API
80
+
81
+ ### AdExecuteManager
82
+
83
+ The main class for managing ad execution with support for initialization, showing, and error handling.
84
+
85
+ ### compose
86
+
87
+ ```typescript
88
+ function compose(middlewares: Array<(ctx: any, next: () => Promise<void>) => Promise<void>>): (ctx: any) => Promise<void>
89
+ ```
90
+
91
+ ### needRetryAdError
92
+
93
+ ```typescript
94
+ function needRetryAdError({
95
+ apiError,
96
+ configuredAdTimeout,
97
+ errorRetryStrategy
98
+ }: {
99
+ apiError: { errMsg?: string; timeout?: number };
100
+ configuredAdTimeout: number;
101
+ errorRetryStrategy?: {
102
+ timeout?: boolean;
103
+ background?: boolean;
104
+ };
105
+ }): boolean
106
+ ```
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,252 @@
1
+ export default AdExecuteManager;
2
+ export type IAdInstance = {
3
+ /**
4
+ * 超时时间
5
+ */
6
+ _adTimeoutTime: number;
7
+ /**
8
+ * 初始化广告
9
+ */
10
+ initialize: (ctx: unknown) => IAdInstance;
11
+ /**
12
+ * 显示广告
13
+ */
14
+ ad: (options: unknown, ctx: unknown, recovered: unknown) => Promise<unknown>;
15
+ clear: () => void;
16
+ };
17
+ export type IRewordAdConfig = import("./typings/ad.js").IRewordAdConfig;
18
+ export type CallbackCollection = import("./typings/ad.js").CallbackCollection;
19
+ export type AdTask = {
20
+ /**
21
+ * RewardAdFather的子类实例
22
+ */
23
+ adInstance: IAdInstance;
24
+ /**
25
+ * 广告执行选项
26
+ */
27
+ options: IRewordAdConfig;
28
+ /**
29
+ * 回调集合
30
+ */
31
+ callbackCollection: CallbackCollection;
32
+ /**
33
+ * 广告执行成功的回调函数
34
+ */
35
+ resolve: Function;
36
+ /**
37
+ * 广告执行失败的回调函数
38
+ */
39
+ reject: Function;
40
+ /**
41
+ * 广告任务的唯一标识符
42
+ */
43
+ id: string;
44
+ /**
45
+ * 广告任务是否已成功解决
46
+ */
47
+ _isResolved: boolean;
48
+ /**
49
+ * 广告任务是否已被拒绝
50
+ */
51
+ _isRejected: boolean;
52
+ /**
53
+ * 广告任务后台恢复前台时预估重试次数
54
+ */
55
+ _retryCount: number;
56
+ /**
57
+ * 广告任务后台恢复前台时预估重试次数的原因
58
+ */
59
+ _retryMsg: string;
60
+ };
61
+ export type ErrorRetryStrategy = {
62
+ /**
63
+ * 是否重试超时错误
64
+ */
65
+ timeout?: boolean;
66
+ /**
67
+ * 是否重试后台错误
68
+ */
69
+ background?: boolean;
70
+ };
71
+ export type IConstructArgs = {
72
+ /**
73
+ * 广告执行选项
74
+ */
75
+ options?: object;
76
+ /**
77
+ * 是否打印日志
78
+ */
79
+ log?: boolean;
80
+ /**
81
+ * 是否启用前后台监听, 进入后台时候未执行的任务放入任务栈,等待进入前台恢复执行
82
+ */
83
+ enableVisibilityListener?: boolean;
84
+ /**
85
+ * 最大重试次数, 默认为1 0表示不重试
86
+ */
87
+ maxRetryCount?: number;
88
+ /**
89
+ * 进入后台时广告展示错误时重试策略
90
+ */
91
+ errorRetryStrategy?: ErrorRetryStrategy;
92
+ };
93
+ declare class AdExecuteManager {
94
+ /**
95
+ * 单例实例
96
+ * @type {AdExecuteManager|null}
97
+ */
98
+ static _instance: AdExecuteManager | null;
99
+ /**
100
+ * 获取单例实例
101
+ * @param {IConstructArgs} [args]
102
+ * @returns {AdExecuteManager}
103
+ */
104
+ static getInstance(args?: IConstructArgs): AdExecuteManager;
105
+ /**
106
+ * 获取单例实例
107
+ * @param {IConstructArgs} [args]
108
+ * @returns {AdExecuteManager}
109
+ */
110
+ static build(args?: IConstructArgs): AdExecuteManager;
111
+ /**
112
+ * 获取单例实例
113
+ * @param {IConstructArgs} [args]
114
+ * @returns {AdExecuteManager}
115
+ */
116
+ static "new"(args?: IConstructArgs): AdExecuteManager;
117
+ /**
118
+ * 获取单例实例
119
+ * @returns {AdExecuteManager}
120
+ */
121
+ static getSafeInstance(): AdExecuteManager;
122
+ /**
123
+ *
124
+ * @param {IConstructArgs} args
125
+ * @returns
126
+ */
127
+ constructor(args: IConstructArgs);
128
+ /**
129
+ * @type {Array.<AdTask>}
130
+ */
131
+ _taskStack: Array<AdTask>;
132
+ /**
133
+ * @type {Array.<AdTask>}
134
+ */
135
+ _currentBatchTasks: Array<AdTask>;
136
+ /**
137
+ * @type {boolean}
138
+ */
139
+ _isRunning: boolean;
140
+ /**
141
+ * @type {AdTask|null}
142
+ */
143
+ _currentTask: AdTask | null;
144
+ /**
145
+ * @type {boolean}
146
+ */
147
+ _isForeground: boolean;
148
+ /**
149
+ * @type {Function|null}
150
+ */
151
+ _appShowListener: Function | null;
152
+ /**
153
+ * @type {Function|null}
154
+ */
155
+ _appHideListener: Function | null;
156
+ /**
157
+ * @type {boolean}
158
+ */
159
+ _enableVisibilityListener: boolean;
160
+ /**
161
+ * @type {number}
162
+ */
163
+ _maxRetryCount: number;
164
+ /**
165
+ * @type {Array.<AdTask>}
166
+ */
167
+ _retryQueue: Array<AdTask>;
168
+ /**
169
+ * 进入后台时广告展示错误时重试策略
170
+ */
171
+ _errorRetryStrategy: {
172
+ timeout: boolean;
173
+ background: boolean;
174
+ };
175
+ logger: Logger;
176
+ initialize(_args: any): this;
177
+ /**
178
+ * 初始化前后台监听器
179
+ * @private
180
+ */
181
+ private _initVisibilityListener;
182
+ /**
183
+ * 处理应用进入后台
184
+ * @private
185
+ */
186
+ private _handleAppHide;
187
+ /**
188
+ * 处理应用进入前台
189
+ * @private
190
+ */
191
+ private _handleAppShow;
192
+ /**
193
+ * 销毁前后台监听器
194
+ */
195
+ destroyVisibilityListener(): void;
196
+ /**
197
+ * 启用前后台监听
198
+ */
199
+ enableVisibilityListener(): void;
200
+ /**
201
+ * 禁用前后台监听
202
+ */
203
+ disableVisibilityListener(): void;
204
+ /**
205
+ * 获取前后台监听器状态
206
+ * @returns {boolean} 是否启用
207
+ */
208
+ isVisibilityListenerEnabled(): boolean;
209
+ /**
210
+ * 添加广告任务
211
+ * @param {IAdInstance} adInstance RewardAdFather的子类实例
212
+ * @param {Object} ctx 广告执行上下文
213
+ * @param {IRewordAdConfig} ctx.options 广告执行选项
214
+ * @param {CallbackCollection} ctx.collection 回调集合
215
+ * @returns {Promise} 广告执行结果的Promise
216
+ */
217
+ addTask(adInstance: IAdInstance, ctx: {
218
+ options: IRewordAdConfig;
219
+ collection: CallbackCollection;
220
+ }): Promise<any>;
221
+ /**
222
+ * 组合所有任务
223
+ * @private
224
+ */
225
+ private _compose;
226
+ /**
227
+ * 清空任务栈并取消所有任务(包括正在执行的任务和middleware链中等待执行的任务)
228
+ */
229
+ clearTasks(): void;
230
+ /**
231
+ * 获取当前未完成的任务总数
232
+ * 包括任务栈中待执行的任务、中间件链中未完成的任务和重试队列中的任务
233
+ * @returns {number} 未完成的任务数量
234
+ */
235
+ getTaskCount(): number;
236
+ /**
237
+ * 是否有任务正在运行
238
+ * @returns {boolean}
239
+ */
240
+ isRunning(): boolean;
241
+ /**
242
+ * 获取当前执行的任务ID
243
+ * @returns {string|null} 当前任务ID
244
+ */
245
+ getCurrentTaskId(): string | null;
246
+ /**
247
+ * 返回一个Promise,当任务队列中的所有任务都完成时resolve
248
+ * @returns {Promise<void>} 当所有任务完成时resolve的Promise
249
+ */
250
+ whenAllTasksComplete(): Promise<void>;
251
+ }
252
+ import { Logger } from '@ad-execute-manager/helper';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 判断是否需要重试
3
+ * @param {object} param
4
+ * @param {object} param.apiError 广告错误信息
5
+ * @param {number} param.configuredAdTimeout 配置的广告超时时间
6
+ * @param {object} [param.errorRetryStrategy] 错误重试策略
7
+ * @param {boolean} [param.errorRetryStrategy.timeout] 是否需要重试超时错误
8
+ * @param {boolean} [param.errorRetryStrategy.background] 是否需要重试后台错误
9
+ * @returns
10
+ */
11
+ export function needRetryAdError({ apiError, configuredAdTimeout, errorRetryStrategy }: {
12
+ apiError: object;
13
+ configuredAdTimeout: number;
14
+ errorRetryStrategy?: {
15
+ timeout?: boolean;
16
+ background?: boolean;
17
+ };
18
+ }): boolean;
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,t)=>{for(var r in t)__webpack_require__.o(t,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{AdExecuteManager:()=>src_AdExecuteManager});const helper_namespaceObject=require("@ad-execute-manager/helper");function isTimeoutError(e,t){return(null==e?void 0:e.errMsg)&&"string"==typeof e.errMsg&&e.errMsg.startsWith("ad_show_timeout: normal")&&(null==e?void 0:e.timeout)==t}function isBackgroundError(e){return(null==e?void 0:e.errMsg)&&"string"==typeof e.errMsg&&(e.errMsg.startsWith("app in background is not support show ad")||e.errMsg.startsWith("other controller is presented"))}function needRetryAdError({apiError:e,configuredAdTimeout:t,errorRetryStrategy:r}){return!!((null==r?void 0:r.timeout)&&isTimeoutError(e,t)||(null==r?void 0:r.background)&&isBackgroundError(e))}const compose=e=>e.map(e=>(t,r)=>async i=>await e(Object.assign({},t,i),r)).reduce((e,t)=>(r,i)=>e(r,t(r,i))),src_compose=compose;class AdExecuteManager{static _instance=null;_taskStack=[];_currentBatchTasks=[];_isRunning=!1;_currentTask=null;_isForeground=!0;_appShowListener=null;_appHideListener=null;_enableVisibilityListener=!1;_maxRetryCount=1;_retryQueue=[];_errorRetryStrategy={timeout:!0,background:!0};constructor(e){this.logger=new helper_namespaceObject.Logger({prefix:"AdExecuteManager",enabled:!!(null==e?void 0:e.log)}),(null==e?void 0:e.errorRetryStrategy)&&(this._errorRetryStrategy=Object.assign({},this._errorRetryStrategy,null==e?void 0:e.errorRetryStrategy)),this._enableVisibilityListener=null==e?void 0:e.enableVisibilityListener,this._maxRetryCount=(null==e?void 0:e.maxRetryCount)??1,this._enableVisibilityListener&&this._initVisibilityListener()}initialize(e){return this}static getInstance(e){return AdExecuteManager._instance||(AdExecuteManager._instance=new AdExecuteManager(e)),AdExecuteManager._instance}static build(e){return AdExecuteManager._instance||(AdExecuteManager._instance=new AdExecuteManager(e)),AdExecuteManager._instance}static new(e){return new AdExecuteManager(e)}static getSafeInstance(){return AdExecuteManager._instance?AdExecuteManager._instance:(this.logger.error("AdExecuteManager实例不存在"),null)}_initVisibilityListener(){!("u"<typeof tt)&&tt.onAppShow&&tt.onAppHide?(this._appShowListener=()=>{this._isForeground=!0,this._handleAppShow()},this._appHideListener=()=>{this._isForeground=!1,this._handleAppHide()},tt.onAppShow(this._appShowListener),tt.onAppHide(this._appHideListener),this.logger.info("前后台监听器已初始化")):this.logger.warn("tt API不可用,无法监听前后台状态")}_handleAppHide(){if(this._isRunning&&this._currentBatchTasks.length>0){let e=this._currentBatchTasks.filter(e=>{var t;return e.id!==(null==(t=this._currentTask)?void 0:t.id)&&!e._isResolved&&!e._isRejected});e.length>0&&(this.logger.info(`将 ${e.length} 个未执行的任务放回任务栈,任务ID: ${e.map(e=>e.id).join(",")}`),this._taskStack=[...e,...this._taskStack],this._currentBatchTasks=this._currentBatchTasks.filter(t=>!e.some(e=>e.id===t.id)))}}_handleAppShow(){this._retryQueue.length>0&&(this.logger.info(`应用进入前台:优先执行重试队列,任务数: ${this._retryQueue.length}, 任务ID: ${this._retryQueue.map(e=>e.id).join(",")}`),this._taskStack=[...this._retryQueue,...this._taskStack],this._retryQueue=[]),this._taskStack.length>0&&!this._isRunning&&(this.logger.info(`应用进入前台:恢复待执行任务数: ${this._taskStack.length}, 任务ID: ${this._taskStack.map(e=>e.id).join(",")}`),this._compose())}destroyVisibilityListener(){this._appShowListener&&"u">typeof tt&&tt.offAppShow&&(tt.offAppShow(this._appShowListener),this._appShowListener=null),this._appHideListener&&"u">typeof tt&&tt.offAppHide&&(tt.offAppHide(this._appHideListener),this._appHideListener=null),this.logger.info("前后台监听器已销毁")}enableVisibilityListener(){this._enableVisibilityListener||(this._enableVisibilityListener=!0,this._initVisibilityListener()),this.logger.info("前后台监听器已启用")}disableVisibilityListener(){this._enableVisibilityListener&&(this._enableVisibilityListener=!1,this.destroyVisibilityListener()),this.logger.info("前后台监听器已禁用")}isVisibilityListenerEnabled(){return this._enableVisibilityListener}addTask(e,t){return e&&"function"==typeof e.ad?new Promise((r,i)=>{let s={adInstance:e,options:t.options??{},callbackCollection:t.collection??{},resolve:r,reject:i,id:`ad_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,_isResolved:!1,_isRejected:!1,_retryCount:0,_retryMsg:"ok"};this._taskStack.push(s),this._isRunning||this._compose()}):(this.logger.error("无效的广告实例 请正确实现.ad方法"),Promise.reject(Error("无效的广告实例")))}_compose(){if(0===this._taskStack.length){this._isRunning=!1,this._currentTask=null;return}if(!this._isForeground){this.logger.info(`应用处于后台,暂停任务执行,任务ID: ${this._taskStack.map(e=>e.id).join(",")}`),this._isRunning=!1;return}this._isRunning=!0;let e=[...this._taskStack];this._taskStack=[],this._currentBatchTasks=e;let t=e.map(e=>async(t,r)=>{let{adInstance:i,options:s,callbackCollection:n,resolve:a,id:o,_retryCount:c,_retryMsg:l}=e,_={count:c,retry:c>0,message:l};if(e._isResolved||e._isRejected)return void await r(t);this._currentTask=e;try{let c=async e=>{await r(Object.assign({},t,e))},l=await i.initialize(s).ad({options:s,collection:n,recovered:_},c),h=Object.assign({id:o,recovered:_},l);if(this.logger.info(`任务执行成功,成功信息:${JSON.stringify(h)}`),needRetryAdError({apiError:null==h?void 0:h.apiError,configuredAdTimeout:i._adTimeoutTime,errorRetryStrategy:this._errorRetryStrategy})&&!this._isForeground&&e._retryCount<this._maxRetryCount){var u;e._retryCount++,e._retryMsg=(null==h||null==(u=h.apiError)?void 0:u.errMsg)||"unknown";let t=`任务 ${o} 在后台失败,加入重试队列 (即将第 ${e._retryCount} 次重试, 最大重试次数 ${this._maxRetryCount}, 重试原因: ${e._retryMsg})`;this.logger.warn(t),h.recovered.retry=!0,h.recovered.count=e._retryCount,h.recovered.message=e._retryMsg,e._isResolved=!1,e._isRejected=!1,this._retryQueue.push(e)}else e._isResolved=!0;a(h),null==i||i.record(h)}catch(n){let s=Object.assign({id:o,apiError:n,recovered:_});this.logger.error(`任务执行失败, 继续下一个任务,错误信息:${JSON.stringify(s)}`),e._isRejected=!0;try{null==i||i.clear()}catch(e){this.logger.error("clear error: ",JSON.stringify(Object.assign({id:o,apiError:e})))}a(s),null==i||i.record(s),await r(t)}}),r={roundTasks:t.length};compose(t)(r,async e=>{this.logger.info("本轮活动队列已经清空",e),this._taskStack.length>0?Promise.resolve().then(()=>{this._compose()}):(this._isRunning=!1,this._currentTask=null,this._currentBatchTasks=[])})()}clearTasks(){if(this._currentTask){if(this._currentTask._isResolved||this._currentTask._isRejected){this._currentTask=null;return}try{this._currentTask.callbackCollection&&this._currentTask.callbackCollection.onCancel&&this._currentTask.callbackCollection.onCancel(),this._currentTask._isResolved=!0,this._currentTask.resolve()}catch(e){this.logger.error("clear current task error: ",e)}this._currentTask=null}this._currentBatchTasks.length>0&&(this._currentBatchTasks.forEach(e=>{if(e!==this._currentTask&&!e._isResolved&&!e._isRejected)try{e.callbackCollection&&e.callbackCollection.onCancel&&e.callbackCollection.onCancel(),e._isResolved=!0,e.resolve()}catch(e){this.logger.error("clear current batch task error: ",e)}}),this._currentBatchTasks=[]),this._taskStack.forEach(e=>{if(!e._isResolved&&!e._isRejected&&(e._isResolved=!0,e.resolve(),e.callbackCollection&&e.callbackCollection.onCancel))try{e.callbackCollection.onCancel()}catch(e){this.logger.error("clear task error: ",e)}}),this._retryQueue.forEach(e=>{if(!e._isResolved&&!e._isRejected&&(e._isResolved=!0,e.resolve(),e.callbackCollection&&e.callbackCollection.onCancel))try{e.callbackCollection.onCancel()}catch(e){this.logger.error("clear retry task error: ",e)}}),this._taskStack=[],this._retryQueue=[],this._isRunning=!1}getTaskCount(){return this._taskStack.filter(e=>!e._isResolved&&!e._isRejected).length+this._currentBatchTasks.filter(e=>e!==this._currentTask&&!e._isResolved&&!e._isRejected).length+(!this._currentTask||this._currentTask._isResolved||this._currentTask._isRejected?0:1)+this._retryQueue.filter(e=>!e._isResolved&&!e._isRejected).length}isRunning(){return this._isRunning}getCurrentTaskId(){var e;return(null==(e=this._currentTask)?void 0:e.id)||null}whenAllTasksComplete(){return new Promise(e=>{let t=null,r=()=>{let i=0===this._taskStack.length,s=!this._isRunning,n=null===this._currentTask,a=0===this._currentBatchTasks.length||this._currentBatchTasks.every(e=>e._isResolved||e._isRejected),o=0===this._retryQueue.length;i&&s&&n&&a&&o?(t&&clearTimeout(t),e()):t=setTimeout(r,500)};r()})}}const src_AdExecuteManager=AdExecuteManager;for(var __rspack_i in exports.AdExecuteManager=__webpack_exports__.AdExecuteManager,__webpack_exports__)-1===["AdExecuteManager"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
package/dist/index.d.ts CHANGED
@@ -1,15 +1 @@
1
- export default AdExecuteManager;
2
- import AdExecuteManager from './core/AdExecuteManager';
3
- import RewardAdFather from './ad/RewardAdFather';
4
- import { SerializableError } from './helper/SerializableError';
5
- import { Logger } from './helper/Logger';
6
- import Storage from './helper/Storage';
7
- import { CountRecorder } from './helper/CountRecorder';
8
- import RewardAdGlobalRecorder from './helper/RewardAdGlobalRecorder';
9
- import RewardAdSceneTriggerManager from './helper/RewardAdSceneTriggerManager';
10
- import AdAnalyticsJS from './helper/AdAnalyticsJS';
11
- import RewardAdNovel from './ad/RewardAdNovel';
12
- import InterstitialAdFather from './ad/InterstitialAdFather';
13
- import InterstitialAdNovel from './ad/InterstitialAdNovel';
14
- import PubSub from './helper/PubSub';
15
- export { AdExecuteManager, RewardAdFather, SerializableError, Logger, Storage, CountRecorder, RewardAdGlobalRecorder, RewardAdSceneTriggerManager, AdAnalyticsJS, RewardAdNovel, InterstitialAdFather, InterstitialAdNovel, PubSub };
1
+ export { default as AdExecuteManager } from "./AdExecuteManager.js";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- let e=e=>e.map(e=>(t,s)=>async r=>await e(Object.assign({},t,r),s)).reduce((e,t)=>(s,r)=>e(s,t(s,r)));class t{constructor(e={}){let{prefix:t="Logger",level:s="log",enabled:r=!0}=e;this.prefix=t,this.enabled=r,this.levels={error:0,warn:1,info:2,log:3,debug:4},this.currentLevel=this.levels[s]||this.levels.log}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}setLevel(e){Object.prototype.hasOwnProperty.call(this.levels,e)&&(this.currentLevel=this.levels[e])}formatMessage(e,t){let s=new Date,r=`${s.getFullYear()}/${s.getMonth()+1}/${s.getDate()} ${s.getHours()}:${s.getMinutes()}:${s.getSeconds()}`;return`[${r}] [${e.toUpperCase()}] [${this.prefix}] ${t}`}_log(e,t,...s){if(!this.enabled)return;let r=this.levels[e];if(void 0!==r&&this.currentLevel>=r){let r=this.formatMessage(e,t);switch(e){case"error":console.error(r,...s);break;case"warn":console.warn(r,...s);break;case"info":console.info(r,...s);break;case"log":default:console.log(r,...s);break;case"debug":console.debug(r,...s)}}}error(e,...t){this._log("error",e,...t)}warn(e,...t){this._log("warn",e,...t)}info(e,...t){this._log("info",e,...t)}log(e,...t){this._log("log",e,...t)}debug(e,...t){this._log("debug",e,...t)}}class s{static _instance=null;_taskStack=[];_currentBatchTasks=[];_isRunning=!1;_currentTask=null;constructor(e){if(this.logger=new t({prefix:"AdExecuteManager",enabled:!!e?.log}),s._instance)return s._instance;s._instance=this}static getInstance(e){return s._instance||(s._instance=new s(e)),s._instance}static getSafeInstance(){return s._instance?s._instance:(this.logger.error("AdExecuteManager实例不存在"),null)}addTask(e,t){return e&&"function"==typeof e.ad?new Promise((s,r)=>{let i={adInstance:e,options:t.options??{},callbackCollection:t.collection??{},resolve:s,reject:r,id:`ad_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,_isResolved:!1,_isRejected:!1};this._taskStack.push(i),this._isRunning||this._compose()}):(this.logger.error("无效的广告实例 请正确实现.ad方法"),Promise.reject(Error("无效的广告实例")))}_compose(){if(0===this._taskStack.length){this._isRunning=!1,this._currentTask=null;return}this._isRunning=!0;let t=[...this._taskStack];this._taskStack=[],this._currentBatchTasks=t;let s=t.map(e=>async(t,s)=>{let{adInstance:r,options:i,callbackCollection:n,resolve:a,id:c}=e;if(e._isResolved||e._isRejected)return void await s(t);this._currentTask=e;try{let l=async e=>{await s(Object.assign({},t,e))},o=await r.initialize(i).ad({options:i,collection:n},l),h=Object.assign({id:c},o);this.logger.info(`任务执行成功,成功信息:${JSON.stringify(h)}`),e._isResolved=!0,a(h),r?.record(h)}catch(n){let i=Object.assign({id:c,apiError:n});this.logger.error(`任务执行失败, 继续下一个任务,错误信息:${JSON.stringify(i)}`),e._isRejected=!0;try{r?.clear()}catch(e){this.logger.error("clear error: ",JSON.stringify(Object.assign({id:c,apiError:e})))}a(i),r?.record(i),await s(t)}}),r={roundTasks:s.length};e(s)(r,async e=>{this.logger.info("本轮活动队列已经清空",e),this._taskStack.length>0?Promise.resolve().then(()=>{this._compose()}):(this._isRunning=!1,this._currentTask=null,this._currentBatchTasks=[])})()}clearTasks(){if(this._currentTask){if(this._currentTask._isResolved||this._currentTask._isRejected){this._currentTask=null;return}try{this._currentTask.callbackCollection&&this._currentTask.callbackCollection.onCancel&&this._currentTask.callbackCollection.onCancel(),this._currentTask._isResolved=!0,this._currentTask.resolve()}catch(e){this.logger.error("clear current task error: ",e)}this._currentTask=null}this._currentBatchTasks.length>0&&(this._currentBatchTasks.forEach(e=>{if(e!==this._currentTask&&!e._isResolved&&!e._isRejected)try{e.callbackCollection&&e.callbackCollection.onCancel&&e.callbackCollection.onCancel(),e._isResolved=!0,e.resolve()}catch(e){this.logger.error("clear current batch task error: ",e)}}),this._currentBatchTasks=[]),this._taskStack.forEach(e=>{if(!e._isResolved&&!e._isRejected&&(e._isResolved=!0,e.resolve(),e.callbackCollection&&e.callbackCollection.onCancel))try{e.callbackCollection.onCancel()}catch(e){this.logger.error("clear task error: ",e)}}),this._taskStack=[],this._isRunning=!1}getTaskCount(){return this._taskStack.filter(e=>!e._isResolved&&!e._isRejected).length+this._currentBatchTasks.filter(e=>e!==this._currentTask&&!e._isResolved&&!e._isRejected).length+(!this._currentTask||this._currentTask._isResolved||this._currentTask._isRejected?0:1)}isRunning(){return this._isRunning}getCurrentTaskId(){return this._currentTask?.id||null}whenAllTasksComplete(){return new Promise(e=>{let t=null,s=()=>{let r=0===this._taskStack.length,i=!this._isRunning,n=null===this._currentTask,a=0===this._currentBatchTasks.length||this._currentBatchTasks.every(e=>e._isResolved||e._isRejected);r&&i&&n&&a?(t&&clearTimeout(t),e()):t=setTimeout(s,500)};s()})}}let r=s;export{r as AdExecuteManager,e as compose};
1
+ import{Logger as e}from"@ad-execute-manager/helper";class t{static _instance=null;_taskStack=[];_currentBatchTasks=[];_isRunning=!1;_currentTask=null;_isForeground=!0;_appShowListener=null;_appHideListener=null;_enableVisibilityListener=!1;_maxRetryCount=1;_retryQueue=[];_errorRetryStrategy={timeout:!0,background:!0};constructor(t){this.logger=new e({prefix:"AdExecuteManager",enabled:!!(null==t?void 0:t.log)}),(null==t?void 0:t.errorRetryStrategy)&&(this._errorRetryStrategy=Object.assign({},this._errorRetryStrategy,null==t?void 0:t.errorRetryStrategy)),this._enableVisibilityListener=null==t?void 0:t.enableVisibilityListener,this._maxRetryCount=(null==t?void 0:t.maxRetryCount)??1,this._enableVisibilityListener&&this._initVisibilityListener()}initialize(e){return this}static getInstance(e){return t._instance||(t._instance=new t(e)),t._instance}static build(e){return t._instance||(t._instance=new t(e)),t._instance}static new(e){return new t(e)}static getSafeInstance(){return t._instance?t._instance:(this.logger.error("AdExecuteManager实例不存在"),null)}_initVisibilityListener(){!("u"<typeof tt)&&tt.onAppShow&&tt.onAppHide?(this._appShowListener=()=>{this._isForeground=!0,this._handleAppShow()},this._appHideListener=()=>{this._isForeground=!1,this._handleAppHide()},tt.onAppShow(this._appShowListener),tt.onAppHide(this._appHideListener),this.logger.info("前后台监听器已初始化")):this.logger.warn("tt API不可用,无法监听前后台状态")}_handleAppHide(){if(this._isRunning&&this._currentBatchTasks.length>0){let e=this._currentBatchTasks.filter(e=>{var t;return e.id!==(null==(t=this._currentTask)?void 0:t.id)&&!e._isResolved&&!e._isRejected});e.length>0&&(this.logger.info(`将 ${e.length} 个未执行的任务放回任务栈,任务ID: ${e.map(e=>e.id).join(",")}`),this._taskStack=[...e,...this._taskStack],this._currentBatchTasks=this._currentBatchTasks.filter(t=>!e.some(e=>e.id===t.id)))}}_handleAppShow(){this._retryQueue.length>0&&(this.logger.info(`应用进入前台:优先执行重试队列,任务数: ${this._retryQueue.length}, 任务ID: ${this._retryQueue.map(e=>e.id).join(",")}`),this._taskStack=[...this._retryQueue,...this._taskStack],this._retryQueue=[]),this._taskStack.length>0&&!this._isRunning&&(this.logger.info(`应用进入前台:恢复待执行任务数: ${this._taskStack.length}, 任务ID: ${this._taskStack.map(e=>e.id).join(",")}`),this._compose())}destroyVisibilityListener(){this._appShowListener&&"u">typeof tt&&tt.offAppShow&&(tt.offAppShow(this._appShowListener),this._appShowListener=null),this._appHideListener&&"u">typeof tt&&tt.offAppHide&&(tt.offAppHide(this._appHideListener),this._appHideListener=null),this.logger.info("前后台监听器已销毁")}enableVisibilityListener(){this._enableVisibilityListener||(this._enableVisibilityListener=!0,this._initVisibilityListener()),this.logger.info("前后台监听器已启用")}disableVisibilityListener(){this._enableVisibilityListener&&(this._enableVisibilityListener=!1,this.destroyVisibilityListener()),this.logger.info("前后台监听器已禁用")}isVisibilityListenerEnabled(){return this._enableVisibilityListener}addTask(e,t){return e&&"function"==typeof e.ad?new Promise((i,r)=>{let s={adInstance:e,options:t.options??{},callbackCollection:t.collection??{},resolve:i,reject:r,id:`ad_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,_isResolved:!1,_isRejected:!1,_retryCount:0,_retryMsg:"ok"};this._taskStack.push(s),this._isRunning||this._compose()}):(this.logger.error("无效的广告实例 请正确实现.ad方法"),Promise.reject(Error("无效的广告实例")))}_compose(){if(0===this._taskStack.length){this._isRunning=!1,this._currentTask=null;return}if(!this._isForeground){this.logger.info(`应用处于后台,暂停任务执行,任务ID: ${this._taskStack.map(e=>e.id).join(",")}`),this._isRunning=!1;return}this._isRunning=!0;let e=[...this._taskStack];this._taskStack=[],this._currentBatchTasks=e;let t=e.map(e=>async(t,i)=>{let{adInstance:r,options:s,callbackCollection:n,resolve:a,id:l,_retryCount:o,_retryMsg:c}=e,h={count:o,retry:o>0,message:c};if(e._isResolved||e._isRejected)return void await i(t);this._currentTask=e;try{let o=async e=>{await i(Object.assign({},t,e))},c=await r.initialize(s).ad({options:s,collection:n,recovered:h},o),u=Object.assign({id:l,recovered:h},c);if(this.logger.info(`任务执行成功,成功信息:${JSON.stringify(u)}`),function({apiError:e,configuredAdTimeout:t,errorRetryStrategy:i}){var r;return!!((null==i?void 0:i.timeout)&&(null==e?void 0:e.errMsg)&&"string"==typeof e.errMsg&&e.errMsg.startsWith("ad_show_timeout: normal")&&(null==e?void 0:e.timeout)==t)||!!((null==i?void 0:i.background)&&(null==(r=e)?void 0:r.errMsg)&&"string"==typeof r.errMsg&&(r.errMsg.startsWith("app in background is not support show ad")||r.errMsg.startsWith("other controller is presented")))}({apiError:null==u?void 0:u.apiError,configuredAdTimeout:r._adTimeoutTime,errorRetryStrategy:this._errorRetryStrategy})&&!this._isForeground&&e._retryCount<this._maxRetryCount){var _;e._retryCount++,e._retryMsg=(null==u||null==(_=u.apiError)?void 0:_.errMsg)||"unknown";let t=`任务 ${l} 在后台失败,加入重试队列 (即将第 ${e._retryCount} 次重试, 最大重试次数 ${this._maxRetryCount}, 重试原因: ${e._retryMsg})`;this.logger.warn(t),u.recovered.retry=!0,u.recovered.count=e._retryCount,u.recovered.message=e._retryMsg,e._isResolved=!1,e._isRejected=!1,this._retryQueue.push(e)}else e._isResolved=!0;a(u),null==r||r.record(u)}catch(n){let s=Object.assign({id:l,apiError:n,recovered:h});this.logger.error(`任务执行失败, 继续下一个任务,错误信息:${JSON.stringify(s)}`),e._isRejected=!0;try{null==r||r.clear()}catch(e){this.logger.error("clear error: ",JSON.stringify(Object.assign({id:l,apiError:e})))}a(s),null==r||r.record(s),await i(t)}}),i={roundTasks:t.length};t.map(e=>(t,i)=>async r=>await e(Object.assign({},t,r),i)).reduce((e,t)=>(i,r)=>e(i,t(i,r)))(i,async e=>{this.logger.info("本轮活动队列已经清空",e),this._taskStack.length>0?Promise.resolve().then(()=>{this._compose()}):(this._isRunning=!1,this._currentTask=null,this._currentBatchTasks=[])})()}clearTasks(){if(this._currentTask){if(this._currentTask._isResolved||this._currentTask._isRejected){this._currentTask=null;return}try{this._currentTask.callbackCollection&&this._currentTask.callbackCollection.onCancel&&this._currentTask.callbackCollection.onCancel(),this._currentTask._isResolved=!0,this._currentTask.resolve()}catch(e){this.logger.error("clear current task error: ",e)}this._currentTask=null}this._currentBatchTasks.length>0&&(this._currentBatchTasks.forEach(e=>{if(e!==this._currentTask&&!e._isResolved&&!e._isRejected)try{e.callbackCollection&&e.callbackCollection.onCancel&&e.callbackCollection.onCancel(),e._isResolved=!0,e.resolve()}catch(e){this.logger.error("clear current batch task error: ",e)}}),this._currentBatchTasks=[]),this._taskStack.forEach(e=>{if(!e._isResolved&&!e._isRejected&&(e._isResolved=!0,e.resolve(),e.callbackCollection&&e.callbackCollection.onCancel))try{e.callbackCollection.onCancel()}catch(e){this.logger.error("clear task error: ",e)}}),this._retryQueue.forEach(e=>{if(!e._isResolved&&!e._isRejected&&(e._isResolved=!0,e.resolve(),e.callbackCollection&&e.callbackCollection.onCancel))try{e.callbackCollection.onCancel()}catch(e){this.logger.error("clear retry task error: ",e)}}),this._taskStack=[],this._retryQueue=[],this._isRunning=!1}getTaskCount(){return this._taskStack.filter(e=>!e._isResolved&&!e._isRejected).length+this._currentBatchTasks.filter(e=>e!==this._currentTask&&!e._isResolved&&!e._isRejected).length+(!this._currentTask||this._currentTask._isResolved||this._currentTask._isRejected?0:1)+this._retryQueue.filter(e=>!e._isResolved&&!e._isRejected).length}isRunning(){return this._isRunning}getCurrentTaskId(){var e;return(null==(e=this._currentTask)?void 0:e.id)||null}whenAllTasksComplete(){return new Promise(e=>{let t=null,i=()=>{let r=0===this._taskStack.length,s=!this._isRunning,n=null===this._currentTask,a=0===this._currentBatchTasks.length||this._currentBatchTasks.every(e=>e._isResolved||e._isRejected),l=0===this._retryQueue.length;r&&s&&n&&a&&l?(t&&clearTimeout(t),e()):t=setTimeout(i,500)};i()})}}let i=t;export{i as AdExecuteManager};
@@ -5,12 +5,14 @@ export type IRewordAdConfig = {
5
5
  adUnitId: string;
6
6
  /**
7
7
  * 是否开启进度提醒。开启时广告文案为「再看 n 个xxx」,关闭时为「再看 1 个xxx」。其中 n 表示用户当前还需额外观看广告的次数
8
- * 再得广告的奖励文案,用户每看完一个广告都会展示,multiton 为 true 时必填。
9
- * 文案完整内容为「再看 1 个xxx」,其中 xxx 是 multitonRewardMsg 配置的文案内容,最大长度为 7,
10
- * 文案内容根据 multitonRewardMsg 的配置按顺序展示。若 multitonRewardMsg 长度小于 multitonRewardTimes ,
11
- * 则后续的激励再得广告文案取 multitonRewardMsg 数组最后一个。
12
8
  */
13
9
  progressTip?: boolean | undefined;
10
+ /**
11
+ * - 再得广告的奖励文案,用户每看完一个广告都会展示,multiton 为 true 时必填。
12
+ * - 文案完整内容为「再看 1 个xxx」,其中 xxx 是 multitonRewardMsg 配置的文案内容,最大长度为 7,
13
+ * - 文案内容根据 multitonRewardMsg 的配置按顺序展示。若 multitonRewardMsg 长度小于 multitonRewardTimes ,
14
+ * - 则后续的激励再得广告文案取 multitonRewardMsg 数组最后一个。
15
+ */
14
16
  multitonRewardMsg?: Array<string> | undefined;
15
17
  /**
16
18
  * 是否开启激励再得广告
@@ -28,14 +30,14 @@ export type IRewordAdConfig = {
28
30
  * 广告超时时间 单位ms - 自用参数,非tt API要求的参数
29
31
  */
30
32
  timeout?: number | undefined;
31
- /**
32
- * 奖励金额 - 自用参数 ,非tt API要求的参数
33
- */
34
- log?: boolean | undefined;
35
33
  /**
36
34
  * 重试次数 - 自用参数 ,非tt API要求的参数
37
35
  */
38
36
  retry: number;
37
+ /**
38
+ * 是否绑定永远的错误事件 - 自用参数,非tt API要求的参数
39
+ */
40
+ foreverErrorBind?: boolean | undefined;
39
41
  };
40
42
  export type InterstitialAdConfig = {
41
43
  /**
@@ -99,6 +101,20 @@ export type CallbackCollection = {
99
101
  */
100
102
  prelude?: (ctx?: unknown) => void;
101
103
  };
104
+ export type RecoveredInfo = {
105
+ /**
106
+ * 恢复重试次数
107
+ */
108
+ count: number;
109
+ /**
110
+ * 是否恢复重试
111
+ */
112
+ retry: boolean;
113
+ /**
114
+ * 恢复重试原因
115
+ */
116
+ message: string;
117
+ };
102
118
  export type ICallbackArgs = {
103
119
  /**
104
120
  * 广告执行场景
@@ -151,6 +167,10 @@ export type IConstructArgs = {
151
167
  * 初始化标识
152
168
  */
153
169
  sign: string;
170
+ /**
171
+ * 是否开启日志
172
+ */
173
+ log?: boolean | undefined;
154
174
  /**
155
175
  * 重试次数
156
176
  */
package/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "@ad-execute-manager/core",
3
- "version": "2.0.0-alpha.2",
3
+ "version": "2.0.0-alpha.4",
4
+ "description": "Core functionality for ad execution management including AdExecuteManager, utility functions, and middleware composition.",
4
5
  "type": "module",
5
- "main": "./dist/index.cjs",
6
- "module": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
11
6
  "exports": {
12
7
  ".": {
13
8
  "import": "./dist/index.js",
@@ -15,7 +10,60 @@
15
10
  "types": "./dist/index.d.ts"
16
11
  }
17
12
  },
18
- "author": "singcl",
13
+ "main": "./dist/index.cjs",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "author": {
19
+ "name": "singcl",
20
+ "email": "iambabyer@gmail.com",
21
+ "url": "https://github.com/singcl"
22
+ },
19
23
  "license": "MIT",
20
- "homepage": "https://npmjs.com/package/@ad-execute-manager/core"
24
+ "homepage": "https://npmjs.com/package/@singcl/core",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/singcl/ad-execute-manager.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/singcl/ad-execute-manager/issues"
31
+ },
32
+ "engines": {
33
+ "node": ">=14.0.0"
34
+ },
35
+ "keywords": [
36
+ "core",
37
+ "ad-manager",
38
+ "ad-execution",
39
+ "middleware",
40
+ "compose",
41
+ "utilities",
42
+ "javascript",
43
+ "nodejs"
44
+ ],
45
+ "scripts": {
46
+ "build": "rslib build && tsc",
47
+ "dev": "rslib build --watch",
48
+ "format": "prettier --write .",
49
+ "lint": "eslint .",
50
+ "test": "rstest",
51
+ "prepublishOnly": "npm run build"
52
+ },
53
+
54
+ "dependencies": {
55
+ "@ad-execute-manager/helper": "^2.0.0-alpha.2"
56
+ },
57
+ "devDependencies": {
58
+ "@babel/eslint-parser": "^7.28.5",
59
+ "@babel/preset-env": "^7.28.5",
60
+ "@eslint/js": "^9.39.1",
61
+ "@rslib/core": "^0.18.5",
62
+ "@rstest/core": "^0.7.2",
63
+ "eslint": "^9.39.2",
64
+ "eslint-plugin-import": "^2.32.0",
65
+ "globals": "^16.5.0",
66
+ "prettier": "^3.7.3",
67
+ "typescript": "^5.9.3"
68
+ }
21
69
  }