diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index c19b382..8fa1185 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -85,7 +85,7 @@ jobs:
example-ios:
name: Build Example iOS App
- runs-on: macos-latest
+ runs-on: macos-15
defaults:
run:
working-directory: ./example
@@ -96,6 +96,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
+ - name: Select Xcode Version
+ run: sudo xcode-select -s /Applications/Xcode_16.4.app
- name: Install Cordova
run: npm install -g cordova
- name: Install Dependencies
@@ -103,7 +105,7 @@ jobs:
- name: Add iOS Platform
run: cordova platform add ios
- name: Build iOS
- run: cordova build ios
+ run: cordova build ios --verbose
pr-notify:
if: >
diff --git a/example/config.xml b/example/config.xml
index e152a99..62f763c 100644
--- a/example/config.xml
+++ b/example/config.xml
@@ -27,5 +27,9 @@
+
+
+
+
diff --git a/example/hooks/after_platform_add/020_run_pod_install.js b/example/hooks/after_platform_add/020_run_pod_install.js
new file mode 100755
index 0000000..2ceac06
--- /dev/null
+++ b/example/hooks/after_platform_add/020_run_pod_install.js
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+
+var shell = require('child_process');
+var path = require('path');
+var fs = require('fs');
+
+module.exports = function(context) {
+ // Skip if not iOS
+ if (context.opts.platforms.indexOf('ios') === -1) {
+ return;
+ }
+
+ console.log('Setting up iOS pods...');
+
+ // Get the paths
+ var iosPath = path.join(context.opts.projectRoot, 'platforms', 'ios');
+ var customPodfilePath = path.join(context.opts.projectRoot, 'platform_overrides', 'ios', 'Podfile');
+ var platformPodfilePath = path.join(iosPath, 'Podfile');
+
+ return new Promise((resolve, reject) => {
+ // Copy custom Podfile if it exists
+ if (fs.existsSync(customPodfilePath)) {
+ try {
+ fs.copyFileSync(customPodfilePath, platformPodfilePath);
+ console.log('Copied custom Podfile');
+ } catch (error) {
+ console.error('Error copying Podfile: ' + error);
+ reject(error);
+ return;
+ }
+ }
+
+ // Run pod install
+ shell.exec('pod install', { cwd: iosPath }, function(error, stdout, stderr) {
+ if (error) {
+ console.error('Error running pod install: ' + error);
+ console.error('stderr: ' + stderr);
+ reject(error);
+ return;
+ }
+ console.log('pod install output: ' + stdout);
+ resolve();
+ });
+ });
+};
diff --git a/example/hooks/after_platform_add/030_update_android_gradle.js b/example/hooks/after_platform_add/030_update_android_gradle.js
new file mode 100755
index 0000000..4f51a53
--- /dev/null
+++ b/example/hooks/after_platform_add/030_update_android_gradle.js
@@ -0,0 +1,32 @@
+#!/usr/bin/env node
+
+var fs = require('fs');
+var path = require('path');
+
+module.exports = function(context) {
+ // Skip if not Android
+ if (context.opts.platforms.indexOf('android') === -1) {
+ return;
+ }
+
+ console.log('Copying Android build-extras.gradle...');
+
+ // Source and destination paths
+ var srcFile = path.join(context.opts.projectRoot, 'platform_overrides', 'android', 'build-extras.gradle');
+ var destFile = path.join(context.opts.projectRoot, 'platforms', 'android', 'app', 'build-extras.gradle');
+
+ try {
+ // Ensure the source file exists
+ if (!fs.existsSync(srcFile)) {
+ console.error('build-extras.gradle not found in platform_overrides');
+ return;
+ }
+
+ // Copy the file
+ fs.copyFileSync(srcFile, destFile);
+ console.log('Successfully copied build-extras.gradle to platform directory');
+ } catch (error) {
+ console.error('Error copying build-extras.gradle:', error);
+ throw error;
+ }
+};
diff --git a/example/platform_overrides/android/build-extras.gradle b/example/platform_overrides/android/build-extras.gradle
new file mode 100644
index 0000000..a911668
--- /dev/null
+++ b/example/platform_overrides/android/build-extras.gradle
@@ -0,0 +1,3 @@
+dependencies {
+ implementation 'com.mparticle:android-rokt-kit:5.71.0'
+}
\ No newline at end of file
diff --git a/example/platform_overrides/ios/Podfile b/example/platform_overrides/ios/Podfile
new file mode 100644
index 0000000..b9f6e36
--- /dev/null
+++ b/example/platform_overrides/ios/Podfile
@@ -0,0 +1,6 @@
+platform :ios, '12.0'
+use_frameworks!
+
+target 'MParticleExample' do
+ pod 'mParticle-Rokt', '8.2.0'
+end
\ No newline at end of file
diff --git a/example/www/css/index.css b/example/www/css/index.css
index fbcd1c9..1387501 100644
--- a/example/www/css/index.css
+++ b/example/www/css/index.css
@@ -95,3 +95,21 @@ h1 {
animation:fade 3000ms infinite;
-webkit-animation:fade 3000ms infinite;
}
+
+.button {
+ background-color: #4CAF50;
+ border: none;
+ color: white;
+ padding: 15px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 4px 2px;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.button:hover {
+ background-color: #45a049;
+}
diff --git a/example/www/index.html b/example/www/index.html
index 955ad91..28b641b 100644
--- a/example/www/index.html
+++ b/example/www/index.html
@@ -25,6 +25,7 @@
Apache Cordova
Connecting to Device
Device is Ready
+
diff --git a/example/www/js/index.js b/example/www/js/index.js
index 60d1771..d46d873 100644
--- a/example/www/js/index.js
+++ b/example/www/js/index.js
@@ -1,3 +1,5 @@
+document.getElementById("selectPlacementsBtn").addEventListener('click', selectPlacements, false);
+
var app = {
// Application Constructor
initialize: function() {
@@ -32,7 +34,9 @@ var app = {
user.setUserAttribute(mparticle.UserAttributeType.FirstName, 'Cordova Test first name');
user.setUserAttributeArray(mparticle.UserAttributeType.FirstName, ['Cordova Test value 1', 'Cordova Test value 2']);
- user.setUserTag('Cordova testUser');
+
+ // TODO: Uncomment this when a new version of mparticle-apple-integration-rokt is released
+ // user.setUserTag('Cordova testUser');
user.removeUserAttribute(mparticle.UserAttributeType.FirstName);
user.getUserIdentities(function(userIdenitities) {
console.log('User userIdentities: ' + userIdenitities);
@@ -40,7 +44,7 @@ var app = {
});
var request = new mparticle.IdentityRequest();
- request.setEmail('cordova123@gmail.com');
+ request.setEmail('j.smith@example.com');
identity.login(request, function (userID) {
console.log('Login Success: ' + userID);
@@ -78,4 +82,31 @@ var app = {
}
};
+function selectPlacements() {
+ console.log('MParticleCordova Plugin Example: Selecting Placements');
+
+ var attributes = {
+ 'email': 'j.smith@example.com',
+ 'firstname': 'Jenny',
+ 'lastname': 'Smith',
+ 'billingzipcode': '90210',
+ 'confirmationref': '54321'
+ };
+
+ var config = {
+ colorMode: mparticle.RoktColorMode.SYSTEM,
+ cacheConfig: {
+ cacheDurationInSeconds: 5400,
+ cacheAttributes: {}
+ },
+ edgeToEdgeDisplay: true
+ };
+
+ mparticle.selectPlacements(
+ 'MSDKOverlayLayout',
+ attributes,
+ config
+ );
+}
+
app.initialize();
diff --git a/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java b/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java
index 15a607f..189a68a 100644
--- a/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java
+++ b/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java
@@ -14,6 +14,8 @@
import com.mparticle.consent.GDPRConsent;
import com.mparticle.identity.*;
import com.mparticle.internal.Logger;
+import com.mparticle.rokt.RoktConfig;
+import com.mparticle.rokt.CacheConfig;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
@@ -91,6 +93,9 @@ public boolean execute(String action, final JSONArray args, final CallbackContex
} else if (action.equals("removeCCPAConsentState")) {
removeCCPAConsentState(args);
return true;
+ } else if (action.equals("selectPlacements")) {
+ selectPlacements(args);
+ return true;
} else {
return false;
}
@@ -403,6 +408,40 @@ public void removeCCPAConsentState(final JSONArray state) throws JSONException {
}
}
+ public void selectPlacements(final JSONArray args) throws JSONException {
+ final String identifier = args.getString(0);
+ final JSONObject attributesMap = args.getJSONObject(1);
+ final JSONObject configMap = args.getJSONObject(2);
+
+
+ final Map attributes = ConvertStringMap(attributesMap);
+
+ final RoktConfig.ColorMode colorMode = RoktConfig.ColorMode.valueOf(configMap.getJSONObject("colorMode").getString("value"));
+
+ final JSONObject cacheConfigMap = configMap.getJSONObject("cacheConfig");
+ final CacheConfig cacheConfig = new CacheConfig(
+ cacheConfigMap.getLong("cacheDurationInSeconds"),
+ ConvertStringMap(cacheConfigMap.getJSONObject("cacheAttributes"))
+ );
+
+ final boolean edgeToEdgeDisplay = configMap.getBoolean("edgeToEdgeDisplay");
+
+ final RoktConfig roktConfig = new RoktConfig.Builder()
+ .colorMode(colorMode)
+ .cacheConfig(cacheConfig)
+ .edgeToEdgeDisplay(edgeToEdgeDisplay)
+ .build();
+
+ MParticle.getInstance().Rokt().selectPlacements(
+ identifier,
+ attributes,
+ null, // callbacks not used in Cordova
+ null, // embeddedViews not used in Cordova
+ null, // fontTypefaces not used in Cordova
+ roktConfig
+ );
+ }
+
private static IdentityApiRequest ConvertIdentityAPIRequest(JSONObject map) throws JSONException {
IdentityApiRequest.Builder identityRequest = IdentityApiRequest.withEmptyUser();
diff --git a/plugin/src/ios/CDVMParticle.m b/plugin/src/ios/CDVMParticle.m
index 6ef3e64..992a841 100644
--- a/plugin/src/ios/CDVMParticle.m
+++ b/plugin/src/ios/CDVMParticle.m
@@ -323,6 +323,40 @@ - (void)getUserIdentities:(CDVInvokedUrlCommand*)command {
}];
}
+- (void)selectPlacements:(CDVInvokedUrlCommand*)command {
+ [self.commandDelegate runInBackground:^{
+ NSString *identifier = [command.arguments objectAtIndex:0];
+ NSDictionary *attributes = [command.arguments objectAtIndex:1];
+ NSDictionary *configDict = [command.arguments objectAtIndex:2];
+
+ MPRoktConfig *config = [[MPRoktConfig alloc] init];
+
+ NSString *colorModeStr = configDict[@"colorMode"][@"value"];
+ if ([colorModeStr isEqualToString:@"LIGHT"]) {
+ config.colorMode = MPColorModeLight;
+ } else if ([colorModeStr isEqualToString:@"DARK"]) {
+ config.colorMode = MPColorModeDark;
+ } else {
+ config.colorMode = MPColorModeSystem;
+ }
+
+ NSDictionary *cacheConfig = configDict[@"cacheConfig"];
+ if (cacheConfig) {
+ config.cacheDuration = @([cacheConfig[@"cacheDurationInSeconds"] longLongValue]);
+ config.cacheAttributes = cacheConfig[@"cacheAttributes"];
+ }
+
+ [[MParticle sharedInstance].rokt selectPlacements:identifier
+ attributes:attributes
+ embeddedViews:nil
+ config:config
+ callbacks:nil];
+
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ }];
+}
+
typedef NS_ENUM(NSUInteger, MPCDVCommerceEventAction) {
MPCDVCommerceEventActionAddToCart = 1,
MPCDVCommerceEventActionRemoveFromCart,
diff --git a/plugin/www/mparticle.js b/plugin/www/mparticle.js
index 9ee86b8..b24a513 100644
--- a/plugin/www/mparticle.js
+++ b/plugin/www/mparticle.js
@@ -437,6 +437,48 @@ var mparticle = {
this.hardwareId = hardwareId
return this
}
+ },
+
+ RoktColorMode: {
+ LIGHT: 'LIGHT',
+ DARK: 'DARK',
+ SYSTEM: 'SYSTEM'
+ },
+
+ validateRoktColorMode: function (colorMode) {
+ var validColorModes = Object.values(this.RoktColorMode)
+ if (!validColorModes.includes(colorMode)) {
+ console.error('Invalid color mode value: ' + colorMode + '. Must be one of: ' + validColorModes.join(', '))
+ return this.RoktColorMode.SYSTEM
+ }
+ return colorMode
+ },
+
+ selectPlacements: function (identifier, attributes, config) {
+ var defaultConfig = {
+ colorMode: this.RoktColorMode.SYSTEM,
+ cacheConfig: {
+ cacheDurationInSeconds: 5400,
+ cacheAttributes: {}
+ },
+ edgeToEdgeDisplay: true
+ }
+
+ var requestedColorMode = (config && config.colorMode) || defaultConfig.colorMode
+ requestedColorMode = this.validateRoktColorMode(requestedColorMode)
+
+ var finalConfig = {
+ colorMode: {
+ value: requestedColorMode
+ },
+ cacheConfig: {
+ cacheDurationInSeconds: (config && config.cacheConfig && config.cacheConfig.cacheDurationInSeconds) || defaultConfig.cacheConfig.cacheDurationInSeconds,
+ cacheAttributes: (config && config.cacheConfig && config.cacheConfig.cacheAttributes) || defaultConfig.cacheConfig.cacheAttributes
+ },
+ edgeToEdgeDisplay: (config && config.edgeToEdgeDisplay !== undefined) ? config.edgeToEdgeDisplay : defaultConfig.edgeToEdgeDisplay
+ }
+
+ exec('selectPlacements', [identifier, attributes || {}, finalConfig])
}
}