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
38 changes: 38 additions & 0 deletions .claude/conventions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
conventions:
- id: adapter-structure
description: >
Each network adapter lives under Adapters/{Network}/IS{Network}Adapter/.
All adapters extend ISBaseAdapter. Each adapter has format-specific
delegate classes and a constants header. Per-adapter Podfile for
CocoaPods dependency management.
applies_to: "Adapters/**/*.{h,m}"

- id: naming-conventions
description: >
Adapter class: IS{Network}Adapter. Delegate classes:
IS{Network}{Format}Delegate (e.g., ISVungleRewardedVideoDelegate).
Constants: IS{Network}Constants.h. All code is Objective-C following
Apple naming conventions.
applies_to: "Adapters/**/*.{h,m}"

- id: ad-format-support
description: >
All adapters support Banner, Interstitial, and Rewarded Video.
Native Ads supported only by the Google adapter. Each format has
its own delegate class handling callbacks independently.
applies_to: "Adapters/**/*.m"

- id: version-management
description: >
Adapter versions are managed per network. Version numbers and
SDK dependencies are declared in the adapter's Podfile and source.
Constants header typically contains version string.
applies_to: "Adapters/**/*"

- id: xcframework-build
description: >
The build_XCFramework.sh script produces xcframework bundles
combining device and simulator architectures. This is the primary
distribution format for adapter binaries. Scripts use xcodebuild
archive and -create-xcframework.
applies_to: "**/*.sh"
22 changes: 22 additions & 0 deletions .claude/knowledge/adrs/001-is-base-adapter-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ADR-001: ISBaseAdapter Inheritance Pattern

## Status
Accepted

## Context
Unity LevelPlay (ironSource) mediates ads from 11 network SDKs on iOS. Each adapter must handle SDK initialization, ad format support declaration, and lifecycle management. A consistent base provides shared functionality.

## Decision
All network adapters extend `ISBaseAdapter` provided by the ironSource SDK:
- `ISBaseAdapter` provides shared initialization, configuration, and format registration
- Each adapter class (`IS{Network}Adapter`) overrides methods for supported ad formats
- Adapter declares supported formats (Banner, Interstitial, Rewarded Video) via base class methods
- Format-specific behavior is delegated to dedicated delegate classes

## Consequences
- Consistent adapter structure across all 11 networks
- ISBaseAdapter handles common mediation plumbing
- Adapters focus on network-specific SDK integration
- Base class updates from ironSource SDK affect all adapters
- Adapter must conform to base class lifecycle contract
- Adding native ads requires base class support (currently Google-only)
25 changes: 25 additions & 0 deletions .claude/knowledge/adrs/002-xcframework-build-scripts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ADR-002: Automated XCFramework Build Scripts

## Status
Accepted

## Context
iOS adapters must support multiple architectures: arm64 for physical devices, x86_64 and arm64 for simulators (Intel and Apple Silicon Macs). Apple's xcframework format is required to bundle multiple architecture slices without conflicts.

## Decision
Each adapter includes or shares a `build_XCFramework.sh` script that:
1. Cleans previous build artifacts
2. Archives for iOS device (arm64) using `xcodebuild archive`
3. Archives for iOS simulator (x86_64, arm64) using `xcodebuild archive`
4. Combines archives using `xcodebuild -create-xcframework`
5. Outputs the final `.xcframework` bundle

Scripts are designed to be run from the adapter directory or project root.

## Consequences
- Reproducible xcframework builds via a single script invocation
- Supports both Intel and Apple Silicon Mac simulators
- Scripts must be updated when Xcode build settings change
- xcframework is the standard Apple multi-architecture distribution format
- Enables binary distribution alongside CocoaPods source distribution
- Build scripts serve as documentation of the build process
23 changes: 23 additions & 0 deletions .claude/knowledge/adrs/003-delegate-per-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# ADR-003: Format-Specific Delegate Classes

## Status
Accepted

## Context
Each ad format (Banner, Interstitial, Rewarded Video) has its own set of delegate callbacks from the network SDK. Implementing all delegates in the main adapter class would create large, complex files. Different formats have different lifecycle events (e.g., rewarded has reward callback, banner has resize).

## Decision
Each ad format has a dedicated delegate class:
- `IS{Network}BannerDelegate` — handles banner load, display, click, resize
- `IS{Network}InterstitialDelegate` — handles interstitial load, show, close, click
- `IS{Network}RewardedVideoDelegate` — handles rewarded load, show, close, click, reward

The main adapter class (`IS{Network}Adapter`) instantiates and coordinates delegates. A constants header (`IS{Network}Constants.h`) defines shared values.

## Consequences
- Each delegate class has focused, single-format responsibility
- Main adapter class stays manageable as an orchestrator
- Easier to add new format support by adding a new delegate class
- Constants shared across delegates are centralized
- More files per adapter (adapter + 3 delegates + constants = 5 files minimum)
- Delegates must communicate with the adapter for shared state (e.g., init status)
48 changes: 48 additions & 0 deletions .claude/knowledge/computed/adapter-class-index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# LevelPlay (ironSource) — Vungle iOS Adapter Class Index

## Adapter Entry Point

| Class | Superclass | File |
|-------|-----------|------|
| `ISVungleAdapter` | `ISBaseAdapter` | `ISVungleAdapter.m` |

**Initialization**: `initSDKWithAppKey:userId:` → calls `[VungleAds initWithAppId:]`
**Network Key**: `"Vungle"` (registered in ironSource mediation)

## Format Delegates

| Format | Delegate Class | Protocol |
|--------|---------------|----------|
| Interstitial | `ISVungleInterstitialDelegate` | `ISAdapterInterstitialDelegate` |
| Rewarded | `ISVungleRewardedVideoDelegate` | `ISAdapterRewardedVideoDelegate` |
| Banner | `ISVungleBannerDelegate` | `ISAdapterBannerDelegate` |

## Callback Mapping (Vungle → LevelPlay)

| Vungle Callback | LevelPlay Callback | Context |
|----------------|-------------------|---------|
| `interstitialAdDidLoad:` | `adDidLoad` | Interstitial loaded |
| `interstitialAdDidFailToLoad:withError:` | `adDidFailToLoadWithError:` | Load failure |
| `interstitialAdWillPresent:` | `adDidOpen` | Shown |
| `interstitialAdDidClick:` | `adDidClick` | Click |
| `interstitialAdDidClose:` | `adDidClose` | Dismissed |
| `rewardedAdDidLoad:` | `adDidLoad` | Rewarded loaded |
| `rewardedAdDidRewardUser:` | `adDidReceiveReward` | Reward granted |
| `rewardedAdDidClose:` | `adDidClose` | Dismissed |
| `bannerAdDidLoad:` | `adDidLoad` | Banner loaded |
| `bannerAdDidClick:` | `adDidClick` | Click |
| `bannerAdDidTrackImpression:` | `adDidShow` | Impression |

## Bidding Support

- Implements `ISBiddingDataDelegate` protocol
- `collectBiddingDataWithDelegate:` → calls `[VungleAds getBiddingToken]`
- Token returned via `successWithBiddingData:` delegate callback

## Key Patterns

1. **Delegate-per-format**: Each format has its own delegate class within the adapter
2. **Singleton initialization**: `VungleAds` initialized once, completion block notifies all pending loads
3. **Banner size mapping**: ironSource `ISBannerSize` → Vungle banner size (320×50, 728×90, 300×250)
4. **Waterfall + bidding**: Supports both traditional waterfall and in-app bidding modes
5. **Privacy**: GDPR consent via `[VunglePrivacySettings setGDPRStatus:]`, CCPA via `setCCPAStatus:`, COPPA via `setCOPPAStatus:`
16 changes: 16 additions & 0 deletions .claude/knowledge/computed/churn-leaders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Churn Leaders
# Auto-generated — do not edit manually

## Files With Most Lines Changed (last 90 days)

| Rank | File | Lines Added | Lines Removed | Net |
|------|------|------------|--------------|-----|
| - | `CLAUDE.md` | +39 | -0 | 39 |
| - | `.claude/conventions.yml` | +38 | -0 | 38 |
| - | `.claude/review-rules.yml` | +29 | -0 | 29 |
| - | `.claude/knowledge/adrs/002-xcframework-build-scripts.md` | +25 | -0 | 25 |
| - | `.claude/knowledge/adrs/003-delegate-per-format.md` | +23 | -0 | 23 |
| - | `.claude/knowledge/adrs/001-is-base-adapter-pattern.md` | +22 | -0 | 22 |

---
_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_
8 changes: 8 additions & 0 deletions .claude/knowledge/computed/co-change-clusters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Co-Change Clusters
# Auto-generated — do not edit manually

## Files That Frequently Change Together (last 90 days)


---
_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_
10 changes: 10 additions & 0 deletions .claude/knowledge/computed/contributor-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Contributor Summary
# Auto-generated — do not edit manually

## Top Contributors (last 90 days)

| Author | Commits | Files Touched |
|--------|---------|--------------|

---
_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_
24 changes: 24 additions & 0 deletions .claude/knowledge/computed/git-intelligence.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"generated": "2026-03-23T00:00:00Z",
"period_days": 180,
"since": "2025-09-24",
"recent_activity": {
"total_commits": 1,
"unique_authors": 1,
"fix_bug_commits": 0,
"first_commit_date": "2026-03-20",
"last_commit_date": "2026-03-20",
"commits_per_month": {
"2026-03": 1
}
},
"top_authors": [
{"author": "Mian Leow", "commits": 1}
],
"file_churn": [
{"file": "CLAUDE.md", "commits": 1}, {"file": ".claude/review-rules.yml", "commits": 1}, {"file": ".claude/knowledge/adrs/003-delegate-per-format.md", "commits": 1}, {"file": ".claude/knowledge/adrs/002-xcframework-build-scripts.md", "commits": 1}, {"file": ".claude/knowledge/adrs/001-is-base-adapter-pattern.md", "commits": 1}, {"file": ".claude/conventions.yml", "commits": 1}
],
"bug_hotspots": [

]
}
17 changes: 17 additions & 0 deletions .claude/knowledge/computed/hotspot-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Git Hotspot Map
# Auto-generated — do not edit manually
# Re-run: .claude/scripts/git-hotspots.sh

## Most Frequently Changed Files (last 90 days)

| Rank | File | Commits | Last Changed |
|------|------|---------|-------------|
| - | `CLAUDE.md` | 1 | 2026-03-20 |
| - | `.claude/review-rules.yml` | 1 | 2026-03-20 |
| - | `.claude/knowledge/adrs/003-delegate-per-format.md` | 1 | 2026-03-20 |
| - | `.claude/knowledge/adrs/002-xcframework-build-scripts.md` | 1 | 2026-03-20 |
| - | `.claude/knowledge/adrs/001-is-base-adapter-pattern.md` | 1 | 2026-03-20 |
| - | `.claude/conventions.yml` | 1 | 2026-03-20 |

---
_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_
8 changes: 8 additions & 0 deletions .claude/knowledge/computed/meta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Claude knowledge layer metadata
generated: 2026-03-23T00:00:00Z
source_commit: d55047cc768a3c85f5753067369c36d90834ab33
stale_if_older_than: 7d
artifacts:
git-intelligence.json: { bytes: 835, tokens: ~208 }
total_bytes: 835
total_tokens: ~208
29 changes: 29 additions & 0 deletions .claude/review-rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
review_rules:
- path: "Adapters/**/*.{h,m}"
rules:
- Adapter must extend ISBaseAdapter
- All three ad formats (Banner, Interstitial, Rewarded Video) must be supported
- Format-specific delegate classes must be separate files
- Constants must be defined in a dedicated IS{Network}Constants.h header
- Memory management must be correct (no retain cycles in delegate patterns)
- All callbacks must dispatch on the main thread

- path: "Adapters/**/*Delegate*.{h,m}"
rules:
- Each delegate class handles exactly one ad format
- Must implement all required delegate methods for the format
- Must forward events correctly to the ironSource mediation layer
- Must handle error cases and report them via appropriate callbacks

- path: "**/Podfile"
rules:
- Must specify the correct ironSource SDK dependency version
- Must specify the correct third-party network SDK version
- Platform version must be iOS 12.0 or higher

- path: "**/*.sh"
rules:
- xcframework build scripts must handle both device and simulator slices
- Must use xcodebuild archive and -create-xcframework
- Must clean previous build artifacts before rebuilding
- Output paths must be consistent and documented
1 change: 0 additions & 1 deletion Adapters/Vungle/ISVungleAdapter/ISVungleAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ static NSString * Githash = @"";
@import WebKit;

@interface ISVungleAdapter : ISBaseAdapter

@end
52 changes: 21 additions & 31 deletions Adapters/Vungle/ISVungleAdapter/ISVungleAdapter.m
Original file line number Diff line number Diff line change
Expand Up @@ -639,13 +639,10 @@ - (void)loadBannerInternal:(NSString *)placementId
[self.bannerPlacementIdToSmashDelegate setObject:delegate
forKey:placementId];

// create banner container view
// create Vungle banner view
dispatch_async(dispatch_get_main_queue(), ^{
UIView *containerView = [[UIView alloc] initWithFrame:[self getBannerFrame:size]];

// initialize banner ad delegate
ISVungleBannerDelegate *bannerAdDelegate = [[ISVungleBannerDelegate alloc] initWithPlacementId:placementId
containerView:containerView
andDelegate:delegate];

[self.bannerPlacementIdToVungleAdDelegate setObject:bannerAdDelegate
Expand All @@ -654,17 +651,21 @@ - (void)loadBannerInternal:(NSString *)placementId
[self.bannerPlacementIdToAdSize setObject:size
forKey:placementId];

// calculate VungleAdSize
VungleAdSize *adSize = [self getBannerSize:size];

// create vungle banner ad
VungleBanner *vungleBannerAd = [[VungleBanner alloc] initWithPlacementId:placementId
size:[self getBannerSize:size]];

vungleBannerAd.delegate = bannerAdDelegate;
VungleBannerView *vungleBannerView = [[VungleBannerView alloc] initWithPlacementId:placementId
vungleAdSize:adSize];

// set delegate
vungleBannerView.delegate = bannerAdDelegate;

[self.bannerPlacementIdToAd setObject:vungleBannerAd
[self.bannerPlacementIdToAd setObject:vungleBannerView
forKey:placementId];

// load banner
[vungleBannerAd load:serverData];
[vungleBannerView load:serverData];
});
}

Expand Down Expand Up @@ -776,30 +777,19 @@ - (NSDictionary *)getBiddingDataWithPlacementId:(NSString *)placementId {
return @{@"token": returnedToken};
}

- (BannerSize)getBannerSize:(ISBannerSize *)size {
if ([size.sizeDescription isEqualToString:@"RECTANGLE"]) {
return BannerSizeMrec;
} else if ([size.sizeDescription isEqualToString:@"SMART"]) {
- (VungleAdSize *)getBannerSize:(ISBannerSize *)size {
if ([size.sizeDescription isEqualToString:kSizeCustom]) {
return [VungleAdSize VungleAdSizeFromCGSize:CGSizeMake(size.width, size.height)];
} else if ([size.sizeDescription isEqualToString:kSizeRectangle]) {
return [VungleAdSize VungleAdSizeMREC];
} else if ([size.sizeDescription isEqualToString:kSizeLeaderboard]) {
return [VungleAdSize VungleAdSizeLeaderboard];
} else if ([size.sizeDescription isEqualToString:kSizeSmart]) {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return BannerSizeLeaderboard;
return [VungleAdSize VungleAdSizeLeaderboard];
}
}

return BannerSizeRegular;
}

- (CGRect)getBannerFrame:(ISBannerSize *)size {
CGRect rect = CGRectMake(0, 0, 320, 50);

if ([size.sizeDescription isEqualToString:@"RECTANGLE"]) {
rect = CGRectMake(0, 0, 300, 250);
} else if ([size.sizeDescription isEqualToString:@"SMART"]) {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
rect = CGRectMake(0, 0, 728, 90);
}
}

return rect;
return [VungleAdSize VungleAdSizeBannerRegular];
}

@end
5 changes: 2 additions & 3 deletions Adapters/Vungle/ISVungleAdapter/ISVungleBannerDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@

NS_ASSUME_NONNULL_BEGIN

@interface ISVungleBannerDelegate : NSObject <VungleBannerDelegate>
@interface ISVungleBannerDelegate : NSObject <VungleBannerViewDelegate>

@property (nonatomic, strong) NSString *placementId;
@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, weak) id<ISBannerAdapterDelegate> delegate;
@property (nonatomic, assign) BOOL isAdloadSuccess;

- (instancetype)initWithPlacementId:(NSString *)placementId
containerView:(UIView *)containerView
andDelegate:(id<ISBannerAdapterDelegate>)delegate;

@end
Expand Down
Loading