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]) } }