diff --git a/package.json b/package.json index f7af9632a9e172237ec2ab98a27f181fe0654f93..c25b1873fc7e28fe05bb2399ac26db9a685f42b8 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "node": ">=10.0.0" }, "devDependencies": { - "@dcloudio/types": "^2.2.16", + "@dcloudio/types": "^2.2.18", "@jest/types": "^27.0.2", "@microsoft/api-extractor": "^7.13.2", "@rollup/plugin-alias": "^3.1.1", diff --git a/packages/uni-api/src/index.ts b/packages/uni-api/src/index.ts index 4d058a6203ca7bd6d4d3a992608d1c783ca21f5b..f32a721a4c70f6c8066feb12d9e5d8346ffa3981 100644 --- a/packages/uni-api/src/index.ts +++ b/packages/uni-api/src/index.ts @@ -83,6 +83,13 @@ export * from './protocols/plugin/getProvider' export * from './protocols/plugin/oauth' export * from './protocols/plugin/share' export * from './protocols/plugin/requestPayment' + +// ad +export * from './protocols/ad/rewardedVideoAd' +export * from './protocols/ad/fullScreenVideoAd' +export * from './protocols/ad/interstitialAd' +export * from './protocols/ad/interactiveAd' + // helpers export { defineOnApi, diff --git a/packages/uni-api/src/protocols/ad/fullScreenVideoAd.ts b/packages/uni-api/src/protocols/ad/fullScreenVideoAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..843da88fd80057945ac387f9bc7f5b62db14d711 --- /dev/null +++ b/packages/uni-api/src/protocols/ad/fullScreenVideoAd.ts @@ -0,0 +1,15 @@ +export const API_CREATE_FULL_SCREEN_VIDEO_AD = 'createFullScreenVideoAd' +export type API_TYPE_CREATE_FULL_SCREEN_VIDEO_AD = + typeof uni.createFullScreenVideoAd + +export const CreateFullScreenVideoAdOptions: ApiOptions = + { + formatArgs: { + adpid: '', + }, + } + +export const CreateFullScreenVideoAdProtocol: ApiProtocol = + { + adpid: String, + } diff --git a/packages/uni-api/src/protocols/ad/interactiveAd.ts b/packages/uni-api/src/protocols/ad/interactiveAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..51736aa07c42fba2f6a3025adfb251b6aebca251 --- /dev/null +++ b/packages/uni-api/src/protocols/ad/interactiveAd.ts @@ -0,0 +1,32 @@ +export const API_CREATE_INTERACTIVE_AD = 'createInteractiveAd' +export type API_TYPE_CREATE_INTERACTIVE_AD = typeof uni.createInteractiveAd + +export const CreateInteractiveAdOptions: ApiOptions = + { + formatArgs: { + adpid(value, params) { + if (!value) { + return 'adpid should not be empty.' + } + if (value) params.adpid = value + }, + provider(value, params) { + if (!value) { + return 'provider should not be empty.' + } + if (value) params.provider = value + }, + }, + } + +export const CreateInteractiveAdProtocol: ApiProtocol = + { + adpid: { + type: String, + required: true, + }, + provider: { + type: String, + required: true, + }, + } diff --git a/packages/uni-api/src/protocols/ad/interstitialAd.ts b/packages/uni-api/src/protocols/ad/interstitialAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ea4ab924aeffa51c1efe0c5546f63985691bf08 --- /dev/null +++ b/packages/uni-api/src/protocols/ad/interstitialAd.ts @@ -0,0 +1,16 @@ +export const API_CREATE_INTERSTITIAL_AD = 'createInterstitialAd' +export type API_TYPE_CREATE_INTERSTITIAL_AD = typeof uni.createInterstitialAd + +export const CreateInterstitialAdOptions: ApiOptions = + { + formatArgs: { + adpid: '', + adUnitId: '', + }, + } + +export const CreateInterstitialAdProtocol: ApiProtocol = + { + adpid: String, + adUnitId: String, + } diff --git a/packages/uni-api/src/protocols/ad/rewardedVideoAd.ts b/packages/uni-api/src/protocols/ad/rewardedVideoAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..aaf8807cfd61ce639ef2bb2e58ee0189e519fc5b --- /dev/null +++ b/packages/uni-api/src/protocols/ad/rewardedVideoAd.ts @@ -0,0 +1,16 @@ +export const API_CREATE_REWARDED_VIDEO_AD = 'createRewardedVideoAd' +export type API_TYPE_CREATE_REWARDED_VIDEO_AD = typeof uni.createRewardedVideoAd + +export const CreateRewardedVideoAdOptions: ApiOptions = + { + formatArgs: { + adpid: '', + adUnitId: '', + }, + } + +export const CreateRewardedVideoAdProtocol: ApiProtocol = + { + adpid: String, + adUnitId: String, + } diff --git a/packages/uni-app-plus/src/service/api/ad/adBase.ts b/packages/uni-app-plus/src/service/api/ad/adBase.ts new file mode 100644 index 0000000000000000000000000000000000000000..1179dd54cb3d931d4e9071c982a24b3752512db3 --- /dev/null +++ b/packages/uni-app-plus/src/service/api/ad/adBase.ts @@ -0,0 +1,194 @@ +const EventType = { + load: 'load', + close: 'close', + error: 'error', + adClicked: 'adClicked', +} + +type PlusError = { + code: Number + message: String +} + +class AdEventHandler { + private _callbacks: Record> + + constructor() { + this._callbacks = {} + } + + onLoad(callback: (result: any) => void) { + this._addEventListener(EventType.load, callback) + } + + onClose(callback: (result: any) => void) { + this._addEventListener(EventType.close, callback) + } + + onError(callback: (result: any) => void) { + this._addEventListener(EventType.error, callback) + } + + offLoad(callback: (result: any) => void) { + this._removeEventListener(EventType.load, callback) + } + + offClose(callback: (result: any) => void) { + this._removeEventListener(EventType.close, callback) + } + + offError(callback: (result: any) => void) { + this._removeEventListener(EventType.error, callback) + } + + _addEventListener(type: string, callback: Function) { + if (typeof callback !== 'function') { + return + } + this._callbacks[type].push(callback) + } + + _removeEventListener(type: string, callback: Function) { + const arrayFunction: Array = this._callbacks[type] + const index = arrayFunction.indexOf(callback) + if (index > -1) { + arrayFunction.splice(index, 1) + } + } + + _dispatchEvent(name: string, data: object) { + this._callbacks[name].forEach((callback) => { + callback(data || {}) + }) + } +} + +class AdBase extends AdEventHandler { + private _isLoaded: boolean = false + + private _isLoading: boolean = false + + private _preload: boolean = true + + private _loadPromiseResolve: Function | null = null + private _loadPromiseReject: Function | null = null + private _showPromiseResolve: Function | null = null + private _showPromiseReject: Function | null = null + + private _adInstance: any + + constructor(adInstance: any, options: any) { + super() + + this._preload = options.preload !== undefined ? options.preload : false + + const ad = (this._adInstance = adInstance) + + ad.onLoad(() => { + this._isLoaded = true + this._isLoading = false + + if (this._loadPromiseResolve != null) { + this._loadPromiseResolve() + this._loadPromiseResolve = null + } + if (this._showPromiseResolve != null) { + this._showPromiseResolve() + this._showPromiseResolve = null + this._showAd() + } + + this._dispatchEvent(EventType.load, {}) + }) + ad.onClose((e: any) => { + this._isLoaded = false + this._isLoading = false + this._dispatchEvent(EventType.close, e) + + if (this._preload === true) { + this._loadAd() + } + }) + ad.onError((e: PlusError) => { + this._isLoading = false + + const data = { + code: e.code, + errMsg: e.message, + } + + this._dispatchEvent(EventType.error, data) + + const error = new Error(JSON.stringify(data)) + + if (this._loadPromiseReject != null) { + this._loadPromiseReject(error) + this._loadPromiseReject = null + } + + if (this._showPromiseReject != null) { + this._showPromiseReject(error) + this._showPromiseReject = null + } + }) + ad.onAdClicked && + ad.onAdClicked(() => { + this._dispatchEvent(EventType.adClicked, {}) + }) + } + + getProvider() { + return this._adInstance.getProvider() + } + + load(): Promise { + return new Promise((resolve, reject) => { + this._loadPromiseResolve = resolve + this._loadPromiseReject = reject + + if (this._isLoading) { + return + } + + if (this._isLoaded) { + resolve('') + } else { + this._loadAd() + } + }) + } + + show(): Promise { + return new Promise((resolve, reject) => { + this._showPromiseResolve = resolve + this._showPromiseReject = reject + + if (this._isLoading) { + return + } + + if (this._isLoaded) { + this._showAd() + resolve('') + } else { + this._loadAd() + } + }) + } + + destroy() { + this._adInstance.destroy() + } + + _loadAd() { + this._isLoaded = false + this._isLoading = true + this._adInstance.load() + } + + _showAd() { + this._adInstance.show() + } +} + +export { AdBase, AdEventHandler, EventType } diff --git a/packages/uni-app-plus/src/service/api/ad/fullScreenVideoAd.ts b/packages/uni-app-plus/src/service/api/ad/fullScreenVideoAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa4951205ad53a6bea77d0d3072c5b9fcf19ae67 --- /dev/null +++ b/packages/uni-app-plus/src/service/api/ad/fullScreenVideoAd.ts @@ -0,0 +1,29 @@ +import { + defineSyncApi, + API_CREATE_FULL_SCREEN_VIDEO_AD, + API_TYPE_CREATE_FULL_SCREEN_VIDEO_AD, + CreateFullScreenVideoAdOptions, + CreateFullScreenVideoAdProtocol, +} from '@dcloudio/uni-api' + +import { AdBase } from './adBase' + +class FullScreenVideoAd + extends AdBase + implements UniApp.FullScreenVideoAdContext +{ + constructor(options: any) { + super(plus.ad.createFullScreenVideoAd(options), options) + } +} + +export const createFullScreenVideoAd = ( + defineSyncApi( + API_CREATE_FULL_SCREEN_VIDEO_AD, + (options) => { + return new FullScreenVideoAd(options) + }, + CreateFullScreenVideoAdProtocol, + CreateFullScreenVideoAdOptions + ) +) diff --git a/packages/uni-app-plus/src/service/api/ad/interactiveAd.ts b/packages/uni-app-plus/src/service/api/ad/interactiveAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..c361927204ebbc81e3426b39cd8c9d1c6cbeb0d7 --- /dev/null +++ b/packages/uni-app-plus/src/service/api/ad/interactiveAd.ts @@ -0,0 +1,270 @@ +import { + defineSyncApi, + API_CREATE_INTERACTIVE_AD, + API_TYPE_CREATE_INTERACTIVE_AD, + CreateInteractiveAdOptions, + CreateInteractiveAdProtocol, +} from '@dcloudio/uni-api' + +import { requireNativePlugin } from '../base' + +import { AdEventHandler, EventType } from './adBase' + +const sdkCache: Record = {} +const sdkQueue: Record> = {} + +function initSDK(options: any) { + const provider = options.provider + if (!sdkCache[provider]) { + sdkCache[provider] = {} + } + if (typeof sdkCache[provider].plugin === 'object') { + options.success(sdkCache[provider].plugin) + return + } + + if (!sdkQueue[provider]) { + sdkQueue[provider] = [] + } + sdkQueue[provider].push(options) + + if (sdkCache[provider].status === true) { + options.__plugin = sdkCache[provider].plugin + return + } + sdkCache[provider].status = true + + const plugin = requireNativePlugin(provider) + if (!plugin || !plugin.initSDK) { + sdkQueue[provider].forEach((item) => { + item.fail({ + code: -1, + message: 'provider [' + provider + '] invalid', + }) + }) + sdkQueue[provider].length = 0 + sdkCache[provider].status = false + return + } + + // TODO + sdkCache[provider].plugin = plugin + options.__plugin = plugin + plugin.initSDK((res: any) => { + const isSuccess = res.code === 1 || res.code === '1' + if (isSuccess) { + sdkCache[provider].plugin = plugin + } else { + sdkCache[provider].status = false + } + + sdkQueue[provider].forEach((item) => { + if (isSuccess) { + item.success(item.__plugin) + } else { + item.fail(res) + } + }) + sdkQueue[provider].length = 0 + }) +} + +class InteractiveAd + extends AdEventHandler + implements UniApp.InteractiveAdContext +{ + private _adpid: string = '' + private _provider: string = '' + private _userData: any = null + + private _isLoaded: boolean = false + private _isLoading: boolean = false + private _loadPromiseResolve: Function | null = null + private _loadPromiseReject: Function | null = null + private _showPromiseResolve: Function | null = null + private _showPromiseReject: Function | null = null + + private _adInstance: any = null + private _adError: any = '' + + constructor(options: any) { + super() + + this._adpid = options.adpid + this._provider = options.provider + this._userData = options.userData + + setTimeout(() => { + this._init() + }) + } + + _init() { + this._adError = '' + initSDK({ + provider: this._provider, + success: (res: any) => { + this._adInstance = res + if (this._userData) { + this.bindUserData(this._userData) + } + this._loadAd() + }, + fail: (err: any) => { + this._adError = err + if (this._loadPromiseReject != null) { + this._loadPromiseReject(this._createError(err)) + this._loadPromiseReject = null + } + this._dispatchEvent(EventType.error, err) + }, + }) + } + + getProvider() { + return this._provider + } + + load(): Promise { + return new Promise((resolve, reject) => { + this._loadPromiseResolve = resolve + this._loadPromiseReject = reject + if (this._isLoading) { + return + } + + if (this._adError) { + this._init() + return + } + + if (this._isLoaded) { + resolve('') + } else { + this._loadAd() + } + }) + } + + show(): Promise { + return new Promise((resolve, reject) => { + this._showPromiseResolve = resolve + this._showPromiseReject = reject + + if (this._isLoading) { + return + } + + if (this._adError) { + this._init() + return + } + + if (this._isLoaded) { + this._showAd() + resolve('') + } else { + this._loadAd() + } + }) + } + + reportExposure(): void { + if (this._adInstance !== null) { + this._adInstance.reportExposure() + } + } + + bindUserData(data: any) { + if (this._adInstance !== null) { + this._adInstance.bindUserData(data) + } + } + + destroy() { + if (this._adInstance !== null && this._adInstance.destroy) { + this._adInstance.destroy({ + adpid: this._adpid, + }) + } + } + + _loadAd() { + if (this._adInstance !== null) { + if (this._isLoading === true) { + return + } + this._isLoading = true + + this._adInstance.loadData( + { + adpid: this._adpid, + }, + (res: any) => { + this._isLoaded = true + this._isLoading = false + + if (this._loadPromiseResolve != null) { + this._loadPromiseResolve() + this._loadPromiseResolve = null + } + if (this._showPromiseResolve != null) { + this._showPromiseResolve() + this._showPromiseResolve = null + this._showAd() + } + + this._dispatchEvent(EventType.load, res) + }, + (err: any) => { + this._isLoading = false + + if (this._showPromiseReject != null) { + this._showPromiseReject(this._createError(err)) + this._showPromiseReject = null + } + + this._dispatchEvent(EventType.error, err) + } + ) + } + } + + _showAd() { + if (this._adInstance !== null && this._isLoaded === true) { + this._adInstance.show( + { + adpid: this._adpid, + }, + () => { + this._isLoaded = false + }, + (err: any) => { + this._isLoaded = false + + if (this._showPromiseReject != null) { + this._showPromiseReject(this._createError(err)) + this._showPromiseReject = null + } + + this._dispatchEvent(EventType.error, err) + } + ) + } + } + + _createError(err: any): Error { + return new Error(JSON.stringify(err)) + } +} + +export const createInteractiveAd = ( + defineSyncApi( + API_CREATE_INTERACTIVE_AD, + (options) => { + return new InteractiveAd(options) + }, + CreateInteractiveAdProtocol, + CreateInteractiveAdOptions + ) +) diff --git a/packages/uni-app-plus/src/service/api/ad/interstitialAd.ts b/packages/uni-app-plus/src/service/api/ad/interstitialAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..e203a10cdb8d5a6969728f8e833f889adea155a2 --- /dev/null +++ b/packages/uni-app-plus/src/service/api/ad/interstitialAd.ts @@ -0,0 +1,28 @@ +import { + defineSyncApi, + API_CREATE_INTERSTITIAL_AD, + API_TYPE_CREATE_INTERSTITIAL_AD, + CreateInterstitialAdOptions, + CreateInterstitialAdProtocol, +} from '@dcloudio/uni-api' + +import { AdBase } from './adBase' + +class InterstitialAd extends AdBase implements UniApp.InterstitialAdContext { + constructor(options: any) { + super(plus.ad.createInterstitialAd(options), options) + + this._loadAd() + } +} + +export const createInterstitialAd = ( + defineSyncApi( + API_CREATE_INTERSTITIAL_AD, + (options) => { + return new InterstitialAd(options) + }, + CreateInterstitialAdProtocol, + CreateInterstitialAdOptions + ) +) diff --git a/packages/uni-app-plus/src/service/api/ad/rewardedVideoAd.ts b/packages/uni-app-plus/src/service/api/ad/rewardedVideoAd.ts new file mode 100644 index 0000000000000000000000000000000000000000..bf182d720652a5ec2092cec652090ba67cf7d760 --- /dev/null +++ b/packages/uni-app-plus/src/service/api/ad/rewardedVideoAd.ts @@ -0,0 +1,28 @@ +import { + defineSyncApi, + API_CREATE_REWARDED_VIDEO_AD, + API_TYPE_CREATE_REWARDED_VIDEO_AD, + CreateRewardedVideoAdOptions, + CreateRewardedVideoAdProtocol, +} from '@dcloudio/uni-api' + +import { AdBase } from './adBase' + +class RewardedVideoAd extends AdBase implements UniApp.RewardedVideoAdContext { + constructor(options: any) { + super(plus.ad.createRewardedVideoAd(options), options) + + this._loadAd() + } +} + +export const createRewardedVideoAd = ( + defineSyncApi( + API_CREATE_REWARDED_VIDEO_AD, + (options) => { + return new RewardedVideoAd(options) + }, + CreateRewardedVideoAdProtocol, + CreateRewardedVideoAdOptions + ) +) diff --git a/packages/uni-app-plus/src/service/api/index.ts b/packages/uni-app-plus/src/service/api/index.ts index 67ab13893288a2439709f948a3576142bcb4d208..625843738449c62f4d25cf2a2313850904b4bc21 100644 --- a/packages/uni-app-plus/src/service/api/index.ts +++ b/packages/uni-app-plus/src/service/api/index.ts @@ -39,3 +39,8 @@ export * from './plugin/getProvider' export * from './plugin/oauth' export * from './plugin/share' export * from './plugin/requestPayment' + +export * from './ad/rewardedVideoAd' +export * from './ad/fullScreenVideoAd' +export * from './ad/interstitialAd' +export * from './ad/interactiveAd'