Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 38 additions & 23 deletions android/src/main/java/com/adversport/rnaps/RNAPSAdLoaderModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class RNAPSAdLoaderModule extends ReactContextBaseJavaModule {
public static final String MODULE_NAME = "RNAPSAdLoaderModule";
public static final String AD_TYPE_BANNER = "banner";
public static final String AD_TYPE_INTERSTITIAL = "interstitial";
public static final String AD_TYPE_VIDEO = "video";
public static final String EVENT_SUCCESS = "onSuccess";
public static final String EVENT_FAILURE = "onFailure";

Expand Down Expand Up @@ -64,10 +65,12 @@ public void removeListeners(double count) {

private class AdCallback implements DTBAdCallback {
private final int loaderId;
private final boolean isVideo;
private Promise promise;

public AdCallback(int loaderId, Promise promise) {
public AdCallback(int loaderId, boolean isVideo, Promise promise) {
this.loaderId = loaderId;
this.isVideo = isVideo;
this.promise = promise;
}

Expand Down Expand Up @@ -143,29 +146,32 @@ public void onFailure(AdError adError) {

@Override
public void onSuccess(DTBAdResponse response) {
WritableMap responseMap = Arguments.createMap();
Map<String, List<String>> customParams = response.getDefaultDisplayAdsRequestCustomParams();

for (Map.Entry<String, List<String>> entry : customParams.entrySet()) {
List<String> values = entry.getValue();
StringBuilder valueBuilder = new StringBuilder();
for (int i = 0; i < values.size(); i++) {
valueBuilder.append(values.get(i));
if (i < values.size() - 1) {
valueBuilder.append(",");
}
}
responseMap.putString(entry.getKey(), valueBuilder.toString());
}

WritableMap payload = Arguments.createMap();
payload.putInt("loaderId", loaderId);
payload.putMap("response", responseMap);
payload.putMap("response", buildTargetingMap(response));

sendEvent(EVENT_SUCCESS, payload);
if (promise != null) {
// Créer une copie pour promise.resolve car responseMap est déjà utilisé
WritableMap responseCopy = Arguments.createMap();
// Fresh copy — the event payload above already consumed a WritableMap.
promise.resolve(buildTargetingMap(response));
promise = null;
}
}

// Banner/interstitial expose Map<String, List<String>> (comma-joined here);
// instream video exposes a flat Map<String, String> via a distinct SDK
// accessor — pick the right one so the amzn* video keywords aren't dropped.
private WritableMap buildTargetingMap(DTBAdResponse response) {
WritableMap map = Arguments.createMap();
if (isVideo) {
Map<String, String> videoParams = response.getDefaultVideoAdsRequestCustomParams();
if (videoParams != null) {
for (Map.Entry<String, String> entry : videoParams.entrySet()) {
map.putString(entry.getKey(), entry.getValue());
}
}
} else {
Map<String, List<String>> customParams = response.getDefaultDisplayAdsRequestCustomParams();
for (Map.Entry<String, List<String>> entry : customParams.entrySet()) {
List<String> values = entry.getValue();
StringBuilder valueBuilder = new StringBuilder();
Expand All @@ -175,11 +181,10 @@ public void onSuccess(DTBAdResponse response) {
valueBuilder.append(",");
}
}
responseCopy.putString(entry.getKey(), valueBuilder.toString());
map.putString(entry.getKey(), valueBuilder.toString());
}
promise.resolve(responseCopy);
promise = null;
}
return map;
}
}

Expand Down Expand Up @@ -224,6 +229,15 @@ public void loadAd(int loaderId, String adType, ReadableMap options, Promise pro
case AD_TYPE_INTERSTITIAL:
adSize = new DTBAdSize.DTBInterstitialAdSize(slotUUID);
break;
case AD_TYPE_VIDEO:
{
// Instream video bid request — player size mirrors the web adManager
// (assets/js/adManager.js playerSize [640, 480]).
int playerWidth = options.hasKey("playerWidth") ? options.getInt("playerWidth") : 640;
int playerHeight = options.hasKey("playerHeight") ? options.getInt("playerHeight") : 480;
adSize = new DTBAdSize.DTBVideo(playerWidth, playerHeight, slotUUID);
break;
}
default:
promise.reject("invalid_ad_type", "unsupported ad type: " + adType);
return;
Expand Down Expand Up @@ -254,6 +268,7 @@ public void loadAd(int loaderId, String adType, ReadableMap options, Promise pro
}

adLoaders.put(loaderId, adLoader);

// NOTE: options.contentUrl is intentionally ignored here.
//
// Amazon DSP asks for the public web URL of the content being viewed, but
Expand All @@ -266,7 +281,7 @@ public void loadAd(int loaderId, String adType, ReadableMap options, Promise pro
// bidding chain for callers that legitimately pass the option for iOS. The
// limitation has been raised with Amazon APS; revisit when they ship an API.

adLoader.loadAd(new AdCallback(loaderId, promise));
adLoader.loadAd(new AdCallback(loaderId, AD_TYPE_VIDEO.equals(adType), promise));
}

@ReactMethod
Expand Down
10 changes: 10 additions & 0 deletions ios/RNAPS/RNAPSAdLoaderModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Foundation
class RNAPSAdLoaderModule: RCTEventEmitter {
static let AD_TYPE_BANNER = "banner"
static let AD_TYPE_INTERSTITIAL = "interstitial"
static let AD_TYPE_VIDEO = "video"
static let EVENT_SUCCESS = "onSuccess"
static let EVENT_FAILURE = "onFailure"
static let ERROR_DOMAIN = "RNAPS"
Expand Down Expand Up @@ -171,6 +172,15 @@ class RNAPSAdLoaderModule: RCTEventEmitter {
case RNAPSAdLoaderModule.AD_TYPE_INTERSTITIAL:
adSize = DTBAdSize(interstitialAdSizeWithSlotUUID: slotUUID)
break
case RNAPSAdLoaderModule.AD_TYPE_VIDEO:
// Instream video bid request — player size mirrors the web adManager
// (assets/js/adManager.js playerSize [640, 480]). Same legacy DTB path the
// banner/interstitial cases use; DTBAdResponse.customTargeting() (in the
// shared onSuccess) returns the amzn* keywords for the video slot.
let playerWidth = options["playerWidth"] as? Int ?? 640
let playerHeight = options["playerHeight"] as? Int ?? 480
adSize = DTBAdSize(videoAdSizeWithPlayerWidth: playerWidth, height: playerHeight, andSlotUUID: slotUUID)
break
default:
// Original code just returned, no error reject
return
Expand Down
17 changes: 17 additions & 0 deletions src/AdLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import type { AdLoaderListener } from './types/AdLoaderListener';
import {
type AdLoaderOptions,
type BannerAdLoaderOptions,
type VideoAdLoaderOptions,
validateAdLoaderOptions,
validateBannerAdLoaderOptions,
validateVideoAdLoaderOptions,
} from './types/AdLoaderOptions';
import { AdType } from './types';

Expand Down Expand Up @@ -74,6 +76,21 @@ export class AdLoader {
return adLoader;
}

/**
* Create a video (instream) AdLoader instance.
*/
static createVideoAdLoader(adLoaderOptions: VideoAdLoaderOptions) {
try {
validateVideoAdLoaderOptions(adLoaderOptions);
} catch (e) {
if (e instanceof Error) {
throw new Error(`AdLoader.createVideoAdLoader(*) ${e.message}`);
}
}
const adLoader = new AdLoader(AdType.VIDEO, adLoaderOptions);
return adLoader;
}

/**
* Add a listener for the bid response. Supported events are:
* - `AdLoaderEvent.SUCCESS`
Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/AdLoader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,53 @@ describe('AdLoader', function () {
});
});

describe('createVideoAdLoader', function () {
it('throws if adLoaderOptions is invalid', function () {
expect(() =>
// @ts-ignore
AdLoader.createVideoAdLoader(123)
).toThrowError(
"AdLoader.createVideoAdLoader(*) 'adLoaderOptions' expected an object value"
);
});
it('throws if slotUUID is invalid', function () {
expect(() =>
// @ts-ignore
AdLoader.createVideoAdLoader({ slotUUID: 123 })
).toThrowError(
"AdLoader.createVideoAdLoader(*) 'adLoaderOptions.slotUUID' expected a string value"
);
});
it('throws if the player size is missing', function () {
expect(() =>
// @ts-ignore
AdLoader.createVideoAdLoader({ slotUUID: 'uuid' })
).toThrowError(
"AdLoader.createVideoAdLoader(*) 'adLoaderOptions.playerWidth' and 'adLoaderOptions.playerHeight' expected number values"
);
});
it('throws if the player size is not numeric', function () {
expect(() =>
AdLoader.createVideoAdLoader({
slotUUID: 'uuid',
// @ts-ignore
playerWidth: '640',
playerHeight: 480,
})
).toThrowError(
"AdLoader.createVideoAdLoader(*) 'adLoaderOptions.playerWidth' and 'adLoaderOptions.playerHeight' expected number values"
);
});
it('returns an AdLoader for a valid player size', function () {
const adLoader = AdLoader.createVideoAdLoader({
slotUUID: 'uuid',
playerWidth: 640,
playerHeight: 480,
});
expect(adLoader).toBeInstanceOf(AdLoader);
});
});

describe('contentUrl', function () {
it('throws if contentUrl is not a string', function () {
expect(() =>
Expand Down
26 changes: 26 additions & 0 deletions src/types/AdLoaderOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,29 @@ export function validateBannerAdLoaderOptions(
throw new Error("'adLoaderOptions.size' expected a valid size string");
}
}

export interface VideoAdLoaderOptions extends AdLoaderOptions {
/**
* The width of the video player. Required for video (instream) ad slots.
*/
playerWidth: number;

/**
* The height of the video player. Required for video (instream) ad slots.
*/
playerHeight: number;
}

export function validateVideoAdLoaderOptions(
adLoaderOptions: VideoAdLoaderOptions
) {
validateAdLoaderOptions(adLoaderOptions);
if (
typeof adLoaderOptions.playerWidth !== 'number' ||
typeof adLoaderOptions.playerHeight !== 'number'
) {
throw new Error(
"'adLoaderOptions.playerWidth' and 'adLoaderOptions.playerHeight' expected number values"
);
}
}
1 change: 1 addition & 0 deletions src/types/AdType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
export enum AdType {
BANNER = 'banner',
INTERSTITIAL = 'interstitial',
VIDEO = 'video',
}

export function isAdType(value: any): value is AdType {
Expand Down
6 changes: 5 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@

export { AdLoaderEvent } from './AdLoaderEvent';
export type { AdLoaderListener } from './AdLoaderListener';
export type { AdLoaderOptions, BannerAdLoaderOptions } from './AdLoaderOptions';
export type {
AdLoaderOptions,
BannerAdLoaderOptions,
VideoAdLoaderOptions,
} from './AdLoaderOptions';
export { AdNetwork } from './AdNetwork';
export type { AdNetworkInfo } from './AdNetworkInfo';
export { AdType } from './AdType';
Expand Down
Loading