From e2cee835e6cc5ccccd12045fe967810224702cea Mon Sep 17 00:00:00 2001 From: Ryan Lepinski Date: Fri, 24 Jan 2025 17:04:20 -0800 Subject: [PATCH] Update to SDK 19 (#620) * Update to SDK 19 * Update * Update xcode * Make it possible to disable the early headless js task --- .github/workflows/ci.yml | 2 +- android/gradle.properties | 8 +- .../urbanairship/reactnative/AirshipModule.kt | 272 +++++++----------- .../reactnative/ReactAutopilot.kt | 29 +- .../reactnative/ReactMessageView.kt | 126 ++++---- .../reactnative/ReactNotificationProvider.kt | 5 - .../AirshipExample.xcodeproj/project.pbxproj | 14 +- .../ExampleWidgetsLiveActivity.swift | 6 - example/ios/Podfile | 2 +- example/ios/Podfile.lock | 118 ++++---- ios/AirshipReactNative.swift | 105 +++++-- ios/MessageWebViewWrapper.swift | 12 +- ios/ProxyDataMigrator.swift | 1 + ios/RTNAirship.mm | 4 +- package.json | 2 +- react-native-airship.podspec | 5 +- 16 files changed, 369 insertions(+), 342 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3487b1eb..c670b2fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: run: gem install cocoapods -v '1.16.1' - name: Select Xcode version - run: sudo xcode-select -s '/Applications/Xcode_16.1.app/Contents/Developer' + run: sudo xcode-select -s '/Applications/Xcode_16.2.app/Contents/Developer' - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/android/gradle.properties b/android/gradle.properties index 28664f08..48395302 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,6 +1,6 @@ Airship_kotlinVersion=1.9.24 -Airship_minSdkVersion=21 -Airship_targetSdkVersion=34 -Airship_compileSdkVersion=34 +Airship_minSdkVersion=23 +Airship_targetSdkVersion=35 +Airship_compileSdkVersion=35 Airship_ndkversion=26.1.10909125 -Airship_airshipProxyVersion=11.2.1 \ No newline at end of file +Airship_airshipProxyVersion=12.1.0 \ No newline at end of file diff --git a/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt b/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt index 6254aa36..d846769d 100644 --- a/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt +++ b/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt @@ -65,10 +65,10 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : // Background events will create a headless JS task in ReactAutopilot since // initialized wont be called until we have a JS task. EventEmitter.shared().pendingEventListener - .filter { it.type.isForeground() } - .collect { - notifyPending() - } + .filter { it.type.isForeground() } + .collect { + notifyPending() + } } context.addLifecycleEventListener(object : LifecycleEventListener { @@ -94,14 +94,14 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun takeOff(config: ReadableMap?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.takeOff(Utils.convertMap(requireNotNull(config)).toJsonValue()) } } @ReactMethod override fun isFlying(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { this.proxy.isFlying() } } @@ -129,7 +129,7 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @SuppressLint("RestrictedApi") @ReactMethod override fun takePendingEvents(eventName: String?, isHeadlessJS: Boolean, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { val eventTypes = Utils.parseEventTypes(requireNotNull(eventName)) .filter { if (isHeadlessJS) { @@ -157,93 +157,91 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun channelEnableChannelCreation(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.enableChannelCreation() } } @ReactMethod override fun channelAddTag(tag: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.addTag(requireNotNull(tag)) } } @ReactMethod override fun channelRemoveTag(tag: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.removeTag(requireNotNull(tag)) } } @ReactMethod override fun channelEditTags(operations: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.editTags(Utils.convertArray(operations).toJsonValue()) } } @ReactMethod override fun channelGetTags(promise: Promise) { - promise.resolveResult { - JsonValue.wrapOpt(proxy.channel.getTags()) + promise.resolve(scope) { + proxy.channel.getTags() } } @ReactMethod override fun channelGetChannelId(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.getChannelId() } } @ReactMethod override fun channelGetSubscriptionLists(promise: Promise) { - promise.resolveDeferred { callback -> - proxy.channel.getSubscriptionLists().addResultCallback { - callback(JsonValue.wrapOpt(it), null) - } + promise.resolve(scope) { + proxy.channel.getSubscriptionLists() } } @ReactMethod override fun channelEditTagGroups(operations: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.editTagGroups(Utils.convertArray(operations).toJsonValue()) } } @ReactMethod override fun channelEditAttributes(operations: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.editAttributes(Utils.convertArray(operations).toJsonValue()) } } @ReactMethod override fun channelEditSubscriptionLists(operations: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.channel.editSubscriptionLists(Utils.convertArray(operations).toJsonValue()) } } @ReactMethod override fun pushSetUserNotificationsEnabled(enabled: Boolean, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.push.setUserNotificationsEnabled(enabled) } } @ReactMethod override fun pushIsUserNotificationsEnabled(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.push.isUserNotificationsEnabled() } } @ReactMethod override fun pushEnableUserNotifications(options: ReadableMap?, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { val args = options?.let { EnableUserNotificationsArgs.fromJson(Utils.convertMap(it).toJsonValue()) } @@ -253,22 +251,22 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun pushGetNotificationStatus(promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.push.getNotificationStatus() } } @ReactMethod override fun pushGetRegistrationToken(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.push.getRegistrationToken() } } @ReactMethod override fun pushGetActiveNotifications(promise: Promise) { - promise.resolveResult { - JsonValue.wrapOpt(proxy.push.getActiveNotifications()) + promise.resolve(scope) { + proxy.push.getActiveNotifications() } } @@ -289,42 +287,42 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : options: ReadableArray?, promise: Promise ) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun pushIosSetNotificationOptions(options: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun pushIosSetAutobadgeEnabled(enabled: Boolean, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun pushIosIsAutobadgeEnabled(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun pushIosSetBadgeNumber(badgeNumber: Double, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun pushIosGetBadgeNumber(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @@ -351,7 +349,7 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun pushAndroidIsNotificationChannelEnabled(channel: String?, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { proxy.push.isNotificationChannelEnabled(requireNotNull(channel)) } else { @@ -392,51 +390,49 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun contactIdentify(namedUser: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.contact.identify(namedUser) } } @ReactMethod override fun contactReset(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.contact.reset() } } @ReactMethod override fun contactNotifyRemoteLogin(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.contact.notifyRemoteLogin() } } @ReactMethod override fun contactGetNamedUserId(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.contact.getNamedUserId() } } @ReactMethod override fun contactGetSubscriptionLists(promise: Promise) { - promise.resolveDeferred { callback -> - proxy.contact.getSubscriptionLists().addResultCallback { - callback(JsonValue.wrapOpt(it), null) - } + promise.resolve(scope) { + proxy.contact.getSubscriptionLists() } } @ReactMethod override fun contactEditTagGroups(operations: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.contact.editTagGroups(Utils.convertArray(operations).toJsonValue()) } } @ReactMethod override fun contactEditAttributes(operations: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.contact.editAttributes(Utils.convertArray(operations).toJsonValue()) } } @@ -446,14 +442,14 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : operations: ReadableArray?, promise: Promise ) { - promise.resolveResult { + promise.resolve(scope) { proxy.contact.editSubscriptionLists(Utils.convertArray(operations).toJsonValue()) } } @ReactMethod override fun analyticsTrackScreen(screen: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.analytics.trackScreen(screen) } } @@ -464,14 +460,14 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : identifier: String?, promise: Promise ) { - promise.resolveResult { + promise.resolve(scope) { proxy.analytics.associateIdentifier(requireNotNull(key), identifier) } } @ReactMethod override fun addCustomEvent(event: ReadableMap?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.analytics.addEvent(Utils.convertMap(event).toJsonValue()) } } @@ -480,92 +476,90 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : override fun analyticsGetSessionId( promise: Promise ) { - promise.resolveResult { + promise.resolve(scope) { proxy.analytics.getSessionId() } } @ReactMethod override fun actionRun(action: ReadableMap, promise: Promise) { - promise.resolveDeferred { callback -> - proxy.actions.runAction(requireNotNull(action.getString("name")), Utils.convertDynamic(action.getDynamic("value"))) - .addResultCallback { actionResult -> - if (actionResult != null && actionResult.status == ActionResult.STATUS_COMPLETED) { - callback(actionResult.value, null) - } else { - callback(null, Exception("Action failed ${actionResult?.status}")) - } - } + promise.resolve(scope) { + val result = proxy.actions.runAction(requireNotNull(action.getString("name")), Utils.convertDynamic(action.getDynamic("value"))) + if (result.status == ActionResult.STATUS_COMPLETED) { + result.value + } else { + throw Exception("Action failed ${result.status}") + } } } @ReactMethod override fun privacyManagerSetEnabledFeatures(features: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.privacyManager.setEnabledFeatures( - Utils.convertArray(requireNotNull(features)) + Utils.convertArray(requireNotNull(features)) ) } } @ReactMethod override fun privacyManagerGetEnabledFeatures(promise: Promise) { - promise.resolveResult { - JsonValue.wrapOpt(proxy.privacyManager.getFeatureNames()) + promise.resolve(scope) { + proxy.privacyManager.getFeatureNames() } } @ReactMethod override fun privacyManagerEnableFeature(features: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.privacyManager.enableFeatures( - Utils.convertArray(requireNotNull(features)) + Utils.convertArray(requireNotNull(features)) ) } } @ReactMethod override fun privacyManagerDisableFeature(features: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.privacyManager.disableFeatures( - Utils.convertArray(requireNotNull(features)) + Utils.convertArray(requireNotNull(features)) ) } } @ReactMethod override fun privacyManagerIsFeatureEnabled(features: ReadableArray?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.privacyManager.isFeatureEnabled( - Utils.convertArray(requireNotNull(features)) + Utils.convertArray(requireNotNull(features)) ) } } @ReactMethod override fun inAppSetDisplayInterval(milliseconds: Double, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { this.proxy.inApp.setDisplayInterval(milliseconds.toLong()) } } @ReactMethod override fun inAppGetDisplayInterval(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { this.proxy.inApp.getDisplayInterval() } } @ReactMethod override fun inAppSetPaused(paused: Boolean, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.inApp.setPaused(paused) } } @ReactMethod override fun inAppIsPaused(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.inApp.isPaused() } } @@ -577,55 +571,51 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun messageCenterGetUnreadCount(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.messageCenter.getUnreadMessagesCount() } } @ReactMethod override fun messageCenterDismiss(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.messageCenter.dismiss() } } @ReactMethod override fun messageCenterDisplay(messageId: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.messageCenter.display(messageId) } } @ReactMethod override fun messageCenterGetMessages(promise: Promise) { - promise.resolveResult { - JsonValue.wrapOpt(proxy.messageCenter.getMessages()) + promise.resolve(scope) { + proxy.messageCenter.getMessages() } } @ReactMethod override fun messageCenterDeleteMessage(messageId: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.messageCenter.deleteMessage(requireNotNull(messageId)) } } @ReactMethod override fun messageCenterMarkMessageRead(messageId: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.messageCenter.markMessageRead(requireNotNull(messageId)) } } @ReactMethod override fun messageCenterRefresh(promise: Promise) { - promise.resolveDeferred { callback -> - proxy.messageCenter.refreshInbox().addResultCallback { - if (it == true) { - callback(null, null) - } else { - callback(null, Exception("Failed to refresh")) - } + promise.resolve(scope) { + if (!proxy.messageCenter.refreshInbox()) { + throw Exception("Failed to refresh") } } } @@ -637,28 +627,28 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun messageCenterShowMessageCenter(messageId: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.messageCenter.showMessageCenter(messageId) } } @ReactMethod override fun messageCenterShowMessageView(messageId: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.messageCenter.showMessageView(requireNotNull(messageId)) } } @ReactMethod override fun preferenceCenterDisplay(preferenceCenterId: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.preferenceCenter.displayPreferenceCenter(requireNotNull(preferenceCenterId)) } } @ReactMethod override fun preferenceCenterGetConfig(preferenceCenterId: String?, promise: Promise) { - promise.resolvePending { + promise.resolve(scope) { proxy.preferenceCenter.getPreferenceCenterConfig(requireNotNull(preferenceCenterId)) } } @@ -675,7 +665,7 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun localeSetLocaleOverride(localeIdentifier: String?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { if (localeIdentifier.isNullOrEmpty()) { proxy.locale.clearLocale() } else { @@ -686,28 +676,28 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun localeGetLocale(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.locale.getCurrentLocale() } } @ReactMethod override fun localeClearLocaleOverride(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { proxy.locale.clearLocale() } } @ReactMethod override fun featureFlagManagerFlag(flagName: String, useResultCache: Boolean, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.featureFlagManager.flag(flagName, useResultCache) } } @ReactMethod override fun featureFlagManagerTrackInteraction(flag: ReadableMap, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { val parsedFlag = FeatureFlagProxy(Utils.convertMap(flag).toJsonValue()) proxy.featureFlagManager.trackInteraction(parsedFlag) } @@ -715,14 +705,14 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun featureFlagManagerResultCacheGetFlag(flagName: String, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.featureFlagManager.resultCache.flag(flagName) } } @ReactMethod override fun featureFlagManagerResultCacheSetFlag(flag: ReadableMap, ttl: Double, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { val parsedFlag = FeatureFlagProxy(Utils.convertMap(flag).toJsonValue()) proxy.featureFlagManager.resultCache.cache(parsedFlag, ttl.milliseconds) } @@ -730,69 +720,65 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun featureFlagManagerResultCacheRemoveFlag(flagName: String, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.featureFlagManager.resultCache.removeCachedFlag(flagName) } } @ReactMethod override fun liveActivityListAll(promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun liveActivityList(request: ReadableMap?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun liveActivityStart(request: ReadableMap?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun liveActivityUpdate(request: ReadableMap?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun liveActivityEnd(request: ReadableMap?, promise: Promise) { - promise.resolveResult { + promise.resolve(scope) { throw IllegalStateException("Not supported on Android") } } @ReactMethod override fun liveUpdateListAll(promise: Promise) { - promise.resolveSuspending(scope) { - proxy.liveUpdateManager.listAll().let { - JsonValue.wrapOpt(it) - } + promise.resolve(scope) { + proxy.liveUpdateManager.listAll() } } @ReactMethod override fun liveUpdateList(request: ReadableMap?, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.liveUpdateManager.list( LiveUpdateRequest.List.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue()) - ).let { - JsonValue.wrapOpt(it) - } + ) } } @ReactMethod override fun liveUpdateStart(request: ReadableMap?, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.liveUpdateManager.start( LiveUpdateRequest.Start.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue()) ) @@ -801,7 +787,7 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun liveUpdateUpdate(request: ReadableMap?, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.liveUpdateManager.update( LiveUpdateRequest.Update.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue()) ) @@ -810,7 +796,7 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun liveUpdateEnd(request: ReadableMap?, promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.liveUpdateManager.end( LiveUpdateRequest.End.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue()) ) @@ -819,7 +805,7 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : @ReactMethod override fun liveUpdateClearAll(promise: Promise) { - promise.resolveSuspending(scope) { + promise.resolve(scope) { proxy.liveUpdateManager.clearAll() } } @@ -848,68 +834,26 @@ internal fun JsonSerializable.toReactType(): Any? { return Utils.convertJsonValue(toJsonValue()) } -internal fun Promise.resolveResult(function: () -> Any?) { - resolveDeferred { callback -> callback(function(), null) } -} -internal fun Promise.resolveSuspending(scope: CoroutineScope, function: suspend () -> Any?) { +internal fun Promise.resolve(scope: CoroutineScope, function: suspend () -> Any?) { scope.launch { try { when (val result = function()) { is Unit -> { - this@resolveSuspending.resolve(null) + this@resolve.resolve(null) } is JsonSerializable -> { - this@resolveSuspending.resolve(result.toReactType()) + this@resolve.resolve(result.toReactType()) } is Number -> { - this@resolveSuspending.resolve(result.toDouble()) + this@resolve.resolve(result.toDouble()) } else -> { - this@resolveSuspending.resolve(result) + this@resolve.resolve(JsonValue.wrapOpt(result).toReactType()) } } } catch (e: Exception) { - this@resolveSuspending.reject("AIRSHIP_ERROR", e) - } - } - -} - -internal fun Promise.resolveDeferred(function: ((T?, Exception?) -> Unit) -> Unit) { - try { - function { result, error -> - if (error != null) { - this.reject("AIRSHIP_ERROR", error) - } - try { - when (result) { - is Unit -> { - this.resolve(null) - } - is JsonSerializable -> { - this.resolve(result.toReactType()) - } - is Number -> { - this.resolve((result as Number).toDouble()) - } - else -> { - this.resolve(result) - } - } - } catch (e: Exception) { - this.reject("AIRSHIP_ERROR", e) - } - } - } catch (e: Exception) { - this.reject("AIRSHIP_ERROR", e) - } -} - -internal fun Promise.resolvePending(function: () -> PendingResult) { - resolveDeferred { callback -> - function().addResultCallback { - callback(it, null) + this@resolve.reject("AIRSHIP_ERROR", e) } } } diff --git a/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt b/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt index a6868e3a..9fbf557b 100644 --- a/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt +++ b/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt @@ -33,12 +33,17 @@ class ReactAutopilot : BaseAutopilot() { override fun onReady(context: Context, airship: UAirship) { ProxyLogger.info("Airship React Native version: %s, SDK version: %s", BuildConfig.AIRSHIP_MODULE_VERSION, UAirship.getVersion()) - scope.launch { - EventEmitter.shared().pendingEventListener + val allowHeadlessJsTaskBeforeModule = isHeadlessJSTaskEnabledOnStart(context) + ProxyLogger.debug("ALLOW_HEADLESS_JS_TASK_BEFORE_MODULE: $allowHeadlessJsTaskBeforeModule") + + if (allowHeadlessJsTaskBeforeModule) { + scope.launch { + EventEmitter.shared().pendingEventListener .filter { !it.type.isForeground() } .collect { AirshipHeadlessEventService.startService(context) } + } } scope.launch { @@ -86,7 +91,24 @@ class ReactAutopilot : BaseAutopilot() { return null } + private fun isHeadlessJSTaskEnabledOnStart(context: Context): Boolean { + val ai: ApplicationInfo + try { + ai = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA) + + if (ai.metaData == null) { + return true + } + } catch (e: PackageManager.NameNotFoundException) { + return true + } + + return ai.metaData.getBoolean(HEADLESS_JS_TASK_ON_START_MANIFEST_KEY, true) + } + + companion object { + const val HEADLESS_JS_TASK_ON_START_MANIFEST_KEY = "com.urbanairship.reactnative.ALLOW_HEADLESS_JS_TASK_BEFORE_MODULE" const val EXTENDER_MANIFEST_KEY = "com.urbanairship.reactnative.AIRSHIP_EXTENDER" } } @@ -97,4 +119,5 @@ internal class PendingEmbeddedUpdated(pending: List) : Even override val body: JsonMap = jsonMapOf( "pending" to pending.map { jsonMapOf( "embeddedId" to it.embeddedId ) } ) -} \ No newline at end of file +} + diff --git a/android/src/main/java/com/urbanairship/reactnative/ReactMessageView.kt b/android/src/main/java/com/urbanairship/reactnative/ReactMessageView.kt index 129e1da2..b3b3efaf 100644 --- a/android/src/main/java/com/urbanairship/reactnative/ReactMessageView.kt +++ b/android/src/main/java/com/urbanairship/reactnative/ReactMessageView.kt @@ -2,9 +2,8 @@ package com.urbanairship.reactnative +import android.annotation.SuppressLint import android.content.Context -import android.os.Handler -import android.os.Looper import android.webkit.WebView import android.webkit.WebViewClient import android.widget.FrameLayout @@ -12,37 +11,42 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.WritableMap -import com.facebook.react.uimanager.events.RCTEventEmitter -import com.urbanairship.Cancelable -import com.urbanairship.UAirship -import com.urbanairship.messagecenter.Inbox.FetchMessagesCallback +import com.urbanairship.android.framework.proxy.ui.MessageWebView +import com.urbanairship.android.framework.proxy.ui.MessageWebViewClient import com.urbanairship.messagecenter.Message import com.urbanairship.messagecenter.MessageCenter -import com.urbanairship.messagecenter.webkit.MessageWebView -import com.urbanairship.messagecenter.webkit.MessageWebViewClient - +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus + +@SuppressLint("RestrictedApi") class ReactMessageView(context: Context) : FrameLayout(context), LifecycleEventListener { private var message: Message? = null - private var fetchMessageRequest: Cancelable? = null private var webView: MessageWebView? = null - private val loadHandler = Handler(Looper.getMainLooper()) + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main.immediate) + SupervisorJob() + private var loadJob: Job? = null private val webViewClient: WebViewClient = object : MessageWebViewClient() { private var error: Int? = null - override fun onPageFinished(view: WebView?, url: String?) { + override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) message?.let { message -> error?.let { - notifyLoadError(message.messageId, ERROR_MESSAGE_LOAD_FAILED, false) + notifyLoadError(message.id, ERROR_MESSAGE_LOAD_FAILED, false) return } - message.markRead() - notifyLoadFinished(message.messageId) + MessageCenter.shared().inbox.markMessagesRead(message.id) + notifyLoadFinished(message.id) } } @@ -51,7 +55,7 @@ class ReactMessageView(context: Context) : FrameLayout(context), LifecycleEventL message?.let { message -> failingUrl?.let { - if (it == message.messageBodyUrl) { + if (it == message.bodyUrl) { error = errorCode } } @@ -60,12 +64,31 @@ class ReactMessageView(context: Context) : FrameLayout(context), LifecycleEventL public override fun onClose(webView: WebView) { message?.let { - notifyClose(it.messageId) + notifyClose(it.id) + } + } + } + + private suspend fun fetchMessage(messageId: String): FetchMessageResult { + var message = MessageCenter.shared().inbox.getMessage(messageId) + + if (message == null) { + if (!MessageCenter.shared().inbox.fetchMessages()) { + return FetchMessageResult.Error(ERROR_FAILED_TO_FETCH_MESSAGE, true) } + + message = MessageCenter.shared().inbox.getMessage(messageId) + } + + return if (message == null || message.isExpired) { + FetchMessageResult.Error(ERROR_MESSAGE_NOT_AVAILABLE, false) + } else { + FetchMessageResult.Success(message) } } fun loadMessage(messageId: String) { + loadJob?.cancel() var delayLoading = false if (webView == null) { @@ -75,50 +98,37 @@ class ReactMessageView(context: Context) : FrameLayout(context), LifecycleEventL delayLoading = true } - fetchMessageRequest?.let { - it.cancel() - } message = null - // Until ReactFeatureFlags.enableFabricPendingEventQueue is enabled by default, we need to avoid - // sending events when the view is unmounted because the events are discarded otherwise - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && delayLoading) { - loadHandler.postDelayed({ - startLoading(messageId) - }, 50) - } else { - startLoading(messageId) - } - } + loadJob = scope.launch { + // Until ReactFeatureFlags.enableFabricPendingEventQueue is enabled by default, we need to avoid + // sending events when the view is unmounted because the events are discarded otherwise + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && delayLoading) { + delay(50) + } - fun startLoading(messageId: String) { - notifyLoadStarted(messageId) + if (!isActive) { + return@launch + } - if (!(UAirship.isFlying() || UAirship.isTakingOff())) { - notifyLoadError(messageId, ERROR_MESSAGE_NOT_AVAILABLE, false) - return - } + notifyLoadStarted(messageId) - message = MessageCenter.shared().inbox.getMessage(messageId) + val messageResult = fetchMessage(messageId) - if (message == null) { - fetchMessageRequest = MessageCenter.shared().inbox.fetchMessages(FetchMessagesCallback { success -> - message = MessageCenter.shared().inbox.getMessage(messageId) - if (!success) { - notifyLoadError(messageId, ERROR_FAILED_TO_FETCH_MESSAGE, true) - return@FetchMessagesCallback - } else if (message == null || message!!.isExpired) { - notifyLoadError(messageId, ERROR_MESSAGE_NOT_AVAILABLE, false) - return@FetchMessagesCallback - } - webView?.loadMessage(message!!) - }) - } else { - if (message!!.isExpired) { - notifyLoadError(messageId, ERROR_MESSAGE_NOT_AVAILABLE, false) - return + if (!isActive) { + return@launch + } + + when (messageResult) { + is FetchMessageResult.Error -> { + notifyLoadError(messageId, messageResult.error, messageResult.isRetryable) + } + + is FetchMessageResult.Success -> { + this@ReactMessageView.message = messageResult.message + webView?.loadMessage(messageResult.message) + } } - webView?.loadMessage(message!!) } } @@ -155,10 +165,12 @@ class ReactMessageView(context: Context) : FrameLayout(context), LifecycleEventL override fun onHostResume() { webView?.onResume() + webView?.resumeTimers() } override fun onHostPause() { webView?.onPause() + webView?.pauseTimers() } override fun onHostDestroy() { @@ -166,7 +178,6 @@ class ReactMessageView(context: Context) : FrameLayout(context), LifecycleEventL } fun cleanup() { - webView?.setWebViewClient(null) webView?.destroy() webView = null } @@ -195,4 +206,9 @@ class ReactMessageView(context: Context) : FrameLayout(context), LifecycleEventL private const val ERROR_FAILED_TO_FETCH_MESSAGE = "FAILED_TO_FETCH_MESSAGE" private const val ERROR_MESSAGE_LOAD_FAILED = "MESSAGE_LOAD_FAILED" } +} + +internal sealed class FetchMessageResult { + data class Success(val message: Message) : FetchMessageResult() + data class Error(val error: String, val isRetryable: Boolean) : FetchMessageResult() } \ No newline at end of file diff --git a/android/src/main/java/com/urbanairship/reactnative/ReactNotificationProvider.kt b/android/src/main/java/com/urbanairship/reactnative/ReactNotificationProvider.kt index aaf6ab26..6cfe395d 100644 --- a/android/src/main/java/com/urbanairship/reactnative/ReactNotificationProvider.kt +++ b/android/src/main/java/com/urbanairship/reactnative/ReactNotificationProvider.kt @@ -3,13 +3,8 @@ package com.urbanairship.reactnative import android.content.Context -import com.facebook.react.module.annotations.ReactModule import com.urbanairship.AirshipConfigOptions import com.urbanairship.android.framework.proxy.BaseNotificationProvider -import com.urbanairship.push.notifications.NotificationArguments -import com.urbanairship.push.notifications.NotificationResult -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.runBlocking /** * React Native notification provider. diff --git a/example/ios/AirshipExample.xcodeproj/project.pbxproj b/example/ios/AirshipExample.xcodeproj/project.pbxproj index 53e5dea2..f4c17502 100644 --- a/example/ios/AirshipExample.xcodeproj/project.pbxproj +++ b/example/ios/AirshipExample.xcodeproj/project.pbxproj @@ -650,7 +650,6 @@ "$(inherited)", ); INFOPLIST_FILE = AirshipExampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -675,7 +674,6 @@ BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; INFOPLIST_FILE = AirshipExampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -704,7 +702,6 @@ DEVELOPMENT_TEAM = PGJV57GD94; ENABLE_BITCODE = NO; INFOPLIST_FILE = AirshipExample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -735,7 +732,6 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = PGJV57GD94; INFOPLIST_FILE = AirshipExample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -775,7 +771,6 @@ INFOPLIST_FILE = AirshipExampleNotificationServiceExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AirshipExampleNotificationServiceExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -818,7 +813,6 @@ INFOPLIST_FILE = AirshipExampleNotificationServiceExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AirshipExampleNotificationServiceExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -860,7 +854,7 @@ INFOPLIST_FILE = ExampleWidgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ExampleWidgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 18.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -905,7 +899,7 @@ INFOPLIST_FILE = ExampleWidgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ExampleWidgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 18.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -973,7 +967,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -1045,7 +1039,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", diff --git a/example/ios/ExampleWidgets/ExampleWidgetsLiveActivity.swift b/example/ios/ExampleWidgets/ExampleWidgetsLiveActivity.swift index acf1ecee..820abfc9 100644 --- a/example/ios/ExampleWidgets/ExampleWidgetsLiveActivity.swift +++ b/example/ios/ExampleWidgets/ExampleWidgetsLiveActivity.swift @@ -61,9 +61,3 @@ extension ExampleWidgetsAttributes.ContentState { } } -#Preview("Notification", as: .content, using: ExampleWidgetsAttributes.preview) { - ExampleWidgetsLiveActivity() -} contentStates: { - ExampleWidgetsAttributes.ContentState.smiley - ExampleWidgetsAttributes.ContentState.starEyes -} diff --git a/example/ios/Podfile b/example/ios/Podfile index 8918d455..e65d9a06 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -5,7 +5,7 @@ require Pod::Executable.execute_command('node', ['-p', {paths: [process.argv[1]]}, )', __dir__]).strip -platform :ios, 14 +platform :ios, 15 prepare_react_native_project! # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index c160fb40..491287a5 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,25 +1,25 @@ PODS: - - Airship (18.14.1): - - Airship/Automation (= 18.14.1) - - Airship/Basement (= 18.14.1) - - Airship/Core (= 18.14.1) - - Airship/FeatureFlags (= 18.14.1) - - Airship/MessageCenter (= 18.14.1) - - Airship/PreferenceCenter (= 18.14.1) - - Airship/Automation (18.14.1): + - Airship (19.0.0): + - Airship/Automation (= 19.0.0) + - Airship/Basement (= 19.0.0) + - Airship/Core (= 19.0.0) + - Airship/FeatureFlags (= 19.0.0) + - Airship/MessageCenter (= 19.0.0) + - Airship/PreferenceCenter (= 19.0.0) + - Airship/Automation (19.0.0): - Airship/Core - - Airship/Basement (18.14.1) - - Airship/Core (18.14.1): + - Airship/Basement (19.0.0) + - Airship/Core (19.0.0): - Airship/Basement - - Airship/FeatureFlags (18.14.1): + - Airship/FeatureFlags (19.0.0): - Airship/Core - - Airship/MessageCenter (18.14.1): + - Airship/MessageCenter (19.0.0): - Airship/Core - - Airship/PreferenceCenter (18.14.1): + - Airship/PreferenceCenter (19.0.0): - Airship/Core - - AirshipFrameworkProxy (11.2.1): - - Airship (= 18.14.1) - - AirshipServiceExtension (18.14.0) + - AirshipFrameworkProxy (12.1.0): + - Airship (= 19.0.0) + - AirshipServiceExtension (19.0.0) - boost (1.83.0) - DoubleConversion (1.1.6) - FBLazyVector (0.73.4) @@ -907,8 +907,8 @@ PODS: - React-Mapbuffer (0.73.4): - glog - React-debug - - react-native-airship (20.2.0): - - AirshipFrameworkProxy (= 11.2.1) + - react-native-airship (21.0.0): + - AirshipFrameworkProxy (= 12.1.0) - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1279,9 +1279,9 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Airship: f293470bde4a4cbd23ac73dbda986629d9ad9fdb - AirshipFrameworkProxy: 9332b3f7871f054375653e72428fbcf4a3a9f849 - AirshipServiceExtension: 1d6c2443724f6ea541eecb76ddbaab307809d561 + Airship: 0b26712fb551e96c3f9e790352059ff8fd3eb920 + AirshipFrameworkProxy: 0068e9108d41563f9127cbb4b736975d7c569825 + AirshipServiceExtension: c5b9fcf2720065f347f831799c0fdfd3cad5d971 boost: d3f49c53809116a5d38da093a8aa78bf551aed09 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 FBLazyVector: 84f6edbe225f38aebd9deaf1540a4160b1f087d7 @@ -1290,56 +1290,56 @@ SPEC CHECKSUMS: glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: b2669ce35fc4ac14f523b307aff8896799829fe2 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 + RCT-Folly: cd21f1661364f975ae76b3308167ad66b09f53f5 RCTRequired: ab7f915c15569f04a49669e573e6e319a53f9faa RCTTypeSafety: 63b97ced7b766865057e7154db0e81ce4ee6cf1e React: 1c87497e50fa40ba9c54e5ea5e53483a0f8eecc0 React-callinvoker: e3a52a9a93e3eb004d7282c26a4fb27003273fe6 - React-Codegen: 50c0f8f073e71b929b057b68bf31be604f1dccc8 - React-Core: d0ecde72894b792cb8922efaa0990199cbe85169 - React-CoreModules: 2ff1684dd517f0c441495d90a704d499f05e9d0a - React-cxxreact: d9be2fac926741052395da0a6d0bab8d71e2f297 + React-Codegen: accd8617d26d9e8192f2105e2b715f346bbd6888 + React-Core: e063354ccbed01b836af1e6e94b5cb8319097104 + React-CoreModules: db63015ed3e85382f6d8ec7f78b5136cfb345c18 + React-cxxreact: 7e569c1f7e4dda58ba17df36450c6d98df407b5b React-debug: 4678e73a37cb501d784e99ff0f219b4940362a3b - React-Fabric: 460ee9d4b8b9de3382504a711430bfead1d5be1e - React-FabricImage: d0a0631bc8ad9143f42bfccf9d3d533a144cc3d6 - React-graphics: f0d5040263a9649e2a70ebe27b3120c49411afef - React-hermes: b9ac2f7b0c1eeb206eb883583cab7a973d570a6e - React-ImageManager: 6c4bf9d5ed363ead7b5aaf820a3feab221b7063e - React-jserrorhandler: 6e7a7e187583e14dc7a0053a2bdd66c252ea3b21 - React-jsi: 380cd24dd81a705dd042c18989fb10b07182210c - React-jsiexecutor: 8ed7a18b9f119440efdcd424c8257dc7e18067e2 + React-Fabric: a6a9148a530e4b5e984f5f3373b07ee0a2e41f45 + React-FabricImage: 701972b5524a7a6a02710b55f0f6a82e94bd7da8 + React-graphics: 46697b8481d17ead6e346f2cbdeee443eff3fd18 + React-hermes: a29dacf053e80ebe20b680e30afe26580521e0c9 + React-ImageManager: 9ae8207447796390d7b78beffd7aec8dc28311c4 + React-jserrorhandler: e53f2eee7b67787ac8bfb6a709ac4eecdb57c2e9 + React-jsi: 6db2f81ad41fc50394a70af6e38dd23ac483ff79 + React-jsiexecutor: 951f2809bea7cab011dd1bbe06c631c75df4f673 React-jsinspector: 9ac353eccf6ab54d1e0a33862ba91221d1e88460 - React-logger: 0a57b68dd2aec7ff738195f081f0520724b35dab - React-Mapbuffer: 63913773ed7f96b814a2521e13e6d010282096ad - react-native-airship: a4111a9d2ecb4b12bc021698917be8eeded34793 - react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b + React-logger: 5295f5eac9d7624fe9a33a473442d8f4c1074197 + React-Mapbuffer: f7ba4d5459e546d741791a55664388e97b31df7c + react-native-airship: 378f1a9e37b79c1165f4d2d0e0acd0f5e0e97dc8 + react-native-safe-area-context: 435f4c13ac75ceed6135382ee77d57d1a5b5b2d6 React-nativeconfig: d7af5bae6da70fa15ce44f045621cf99ed24087c - React-NativeModulesApple: 0123905d5699853ac68519607555a9a4f5c7b3ac + React-NativeModulesApple: 7a561c2792b0a2b74aff6c58d2554dcf824372aa React-perflogger: 8a1e1af5733004bdd91258dcefbde21e0d1faccd React-RCTActionSheet: 64bbff3a3963664c2d0146f870fe8e0264aee4c4 - React-RCTAnimation: b698168a7269265a4694727196484342d695f0c1 - React-RCTAppDelegate: dcd8e955116eb1d1908dfaf08b4c970812e6a1e6 - React-RCTBlob: 47f8c3b2b4b7fa2c5f19c43f0b7f77f57fb9d953 - React-RCTFabric: 6067a32d683d0c2b84d444548bc15a263c64abed - React-RCTImage: ac0e77a44c290b20db783649b2b9cddc93e3eb99 - React-RCTLinking: e626fd2900913fe5d25922ea1be394b7aafa09c9 - React-RCTNetwork: d3114bce3977dafe8bd06421b29812f5a8527ba0 - React-RCTSettings: a53511f90d8df637a1a11ac729179a4d2f734481 - React-RCTText: f0176f5f5952f9a4a2c7354f5ae71f7c420aaf34 - React-RCTVibration: 8160223c6eda5b187079fec204f80eca8b8f3177 - React-rendererdebug: ed286b4da8648c27d6ed3ae1410d4b21ba890d5a + React-RCTAnimation: 548fdbd189275f721a4f3098da847a84e3e3339f + React-RCTAppDelegate: 7d6c7ca4a9d94c889533ad6730782f176ff4b43a + React-RCTBlob: 7bf2d87c667d5a570212d921583dca5e1156395c + React-RCTFabric: d3eee95226b91877334fefbbd4d48122f114506b + React-RCTImage: 8ad4a0f9e728ff81a229446a6a8de8657c133cc6 + React-RCTLinking: 12c1070797c9242b8ca2d171fc43588de47e442d + React-RCTNetwork: 1a59d000d0053f56f405d52521ae9df1b6108aa3 + React-RCTSettings: a3b91c385e2f3bc7aa2bae75b2f60c6fd0c9052e + React-RCTText: 78330d21c9d14d680f31a3a3866436b978a66a8c + React-RCTVibration: 632344d1fdb7f1d568d1c6715f2dfe270fff6e88 + React-rendererdebug: 740d4bbbbea219809a58cde58c8af0e39c787831 React-rncore: 43f133b89ac10c4b6ab43702a541dee1c292a3bf React-runtimeexecutor: e6ab6bb083dbdbdd489cff426ed0bce0652e6edf - React-runtimescheduler: ed48e5faac6751e66ee1261c4bd01643b436f112 - React-utils: 6e5ad394416482ae21831050928ae27348f83487 - ReactCommon: 840a955d37b7f3358554d819446bffcf624b2522 - RNCClipboard: 60fed4b71560d7bfe40e9d35dea9762b024da86d - RNGestureHandler: deda62b8339496ba721a45e0f3e2d7a319932cee - RNScreens: b582cb834dc4133307562e930e8fa914b8c04ef2 - RNVectorIcons: 210f910e834e3485af40693ad4615c1ec22fc02b + React-runtimescheduler: ceaeeb19e75912958625f72883d240640e8f40da + React-utils: 8439db27c3745f6de9d67eb61f5600c8d624274c + ReactCommon: cc18bd33c0fab03c67620da6a602dcc1704e66b4 + RNCClipboard: 4abb037e8fe3b98a952564c9e0474f91c492df6d + RNGestureHandler: 2a76de24abfbdfdc7a7a5fcb4b5e57563ada065e + RNScreens: 8d1521c6e375a79dbd4525efaf21704cc64ac0cf + RNVectorIcons: ff027ab4234109f91d261fbceae7f0bf95dcc479 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 -PODFILE CHECKSUM: 71939b1c0f451ff75f31df2f5e51d1c63f04150d +PODFILE CHECKSUM: 50322234ec14b347050b85e0e18246a45e155b4a -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/AirshipReactNative.swift b/ios/AirshipReactNative.swift index b33b39bb..f8041968 100644 --- a/ios/AirshipReactNative.swift +++ b/ios/AirshipReactNative.swift @@ -48,7 +48,7 @@ public class AirshipReactNative: NSObject { @objc public func setNotifier(_ notifier: ((String, [String: Any]) -> Void)?) { - self.serialQueue.enqueue { + self.serialQueue.enqueue { @MainActor in if let notifier = notifier { await self.eventNotifier.setNotifier { notifier(AirshipReactNative.pendingEventsEventName, [:]) @@ -135,7 +135,14 @@ public class AirshipReactNative: NSObject { return await AirshipProxyEventEmitter.shared.takePendingEvents( type: type - ).map { $0.body } + ).compactMap { + do { + return try $0.body.unwrapped() + } catch { + AirshipLogger.error("Failed to unwrap event body \($0) \(error)") + return nil + } + } } @@ -187,7 +194,13 @@ public extension AirshipReactNative { @objc func channelEditTags(json: Any) throws { - try AirshipProxy.shared.channel.editTags(json: json) + let operations = try JSONDecoder().decode( + [TagOperation].self, + from: try AirshipJSONUtils.data(json) + ) + try AirshipProxy.shared.channel.editTags( + operations: operations + ) } @objc @@ -197,27 +210,35 @@ public extension AirshipReactNative { @objc func channelGetTags() throws -> [String] { - return try AirshipProxy.shared.channel.getTags() + return try AirshipProxy.shared.channel.tags } @objc func channelGetSubscriptionLists() async throws -> [String] { - return try await AirshipProxy.shared.channel.getSubscriptionLists() + return try await AirshipProxy.shared.channel.fetchSubscriptionLists() } @objc func channelGetChannelIdOrEmpty() throws -> String { - return try AirshipProxy.shared.channel.getChannelId() ?? "" + return try AirshipProxy.shared.channel.channelID ?? "" } @objc func channelEditTagGroups(json: Any) throws { - try AirshipProxy.shared.channel.editTagGroups(json: json) + let operations = try JSONDecoder().decode( + [TagGroupOperation].self, + from: try AirshipJSONUtils.data(json) + ) + try AirshipProxy.shared.channel.editTagGroups(operations: operations) } @objc func channelEditAttributes(json: Any) throws { - try AirshipProxy.shared.channel.editAttributes(json: json) + let operations = try JSONDecoder().decode( + [AttributeOperation].self, + from: try AirshipJSONUtils.data(json) + ) + try AirshipProxy.shared.channel.editAttributes(operations: operations) } @objc @@ -254,11 +275,13 @@ public extension AirshipReactNative { } @objc + @MainActor func pushGetRegistrationTokenOrEmpty() throws -> String { return try AirshipProxy.shared.push.getRegistrationToken() ?? "" } @objc + @MainActor func pushSetNotificationOptions( names:[String] ) throws { @@ -266,6 +289,7 @@ public extension AirshipReactNative { } @objc + @MainActor func pushSetForegroundPresentationOptions( names:[String] ) throws { @@ -276,7 +300,7 @@ public extension AirshipReactNative { @objc func pushGetNotificationStatus() async throws -> [String: Any] { - return try await AirshipProxy.shared.push.getNotificationStatus() + return try await AirshipProxy.shared.push.notificationStatus.unwrapped() } @objc @@ -326,8 +350,8 @@ public extension AirshipReactNative { } @objc - func pushGetActiveNotifications() async -> [[String: Any]] { - return await AirshipProxy.shared.push.getActiveNotifications() + func pushGetActiveNotifications() async throws -> [[String: Any]] { + return try await AirshipProxy.shared.push.getActiveNotifications().map { try $0.unwrapped() } } } @@ -394,7 +418,7 @@ public extension AirshipReactNative { @objc func contactGetNamedUserIdOrEmtpy() async throws -> String { - return try await AirshipProxy.shared.contact.getNamedUser() ?? "" + return try await AirshipProxy.shared.contact.namedUserID ?? "" } @objc @@ -404,17 +428,29 @@ public extension AirshipReactNative { @objc func contactEditTagGroups(json: Any) throws { - try AirshipProxy.shared.contact.editTagGroups(json: json) + let operations = try JSONDecoder().decode( + [TagGroupOperation].self, + from: try AirshipJSONUtils.data(json) + ) + try AirshipProxy.shared.contact.editTagGroups(operations: operations) } @objc func contactEditAttributes(json: Any) throws { - try AirshipProxy.shared.contact.editAttributes(json: json) + let operations = try JSONDecoder().decode( + [AttributeOperation].self, + from: try AirshipJSONUtils.data(json) + ) + try AirshipProxy.shared.contact.editAttributes(operations: operations) } @objc func contactEditSubscriptionLists(json: Any) throws { - try AirshipProxy.shared.contact.editSubscriptionLists(json: json) + let operations = try JSONDecoder().decode( + [ScopedSubscriptionListOperation].self, + from: try AirshipJSONUtils.data(json) + ) + try AirshipProxy.shared.contact.editSubscriptionLists(operations: operations) } } @@ -438,7 +474,7 @@ public extension AirshipReactNative { @objc @MainActor func inAppSetDisplayInterval(milliseconds: Double) throws { - try AirshipProxy.shared.inApp.setDisplayInterval(Int(milliseconds)) + try AirshipProxy.shared.inApp.setDisplayInterval(milliseconds: Int(milliseconds)) } @objc @@ -470,7 +506,7 @@ public extension AirshipReactNative { @objc func localeGetLocale() throws -> String { - return try AirshipProxy.shared.locale.getCurrentLocale() + return try AirshipProxy.shared.locale.currentLocale } } @@ -479,12 +515,12 @@ public extension AirshipReactNative { public extension AirshipReactNative { @objc func messageCenterGetUnreadCount() async throws -> Double { - return try await Double(AirshipProxy.shared.messageCenter.getUnreadCount()) + return try await Double(AirshipProxy.shared.messageCenter.unreadCount) } @objc func messageCenterGetMessages() async throws -> Any { - let messages = try await AirshipProxy.shared.messageCenter.getMessages() + let messages = try await AirshipProxy.shared.messageCenter.messages return try AirshipJSON.wrap(messages).unWrap() as Any } @@ -528,6 +564,7 @@ public extension AirshipReactNative { } @objc + @MainActor func messageCenterSetAutoLaunchDefaultMessageCenter(autoLaunch: Bool) { AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter(autoLaunch) } @@ -537,6 +574,7 @@ public extension AirshipReactNative { @objc public extension AirshipReactNative { @objc + @MainActor func preferenceCenterDisplay(preferenceCenterId: String) throws { try AirshipProxy.shared.preferenceCenter.displayPreferenceCenter( preferenceCenterID: preferenceCenterId @@ -551,6 +589,7 @@ public extension AirshipReactNative { } @objc + @MainActor func preferenceCenterAutoLaunchDefaultPreferenceCenter( preferenceCenterId: String, autoLaunch: Bool @@ -691,9 +730,21 @@ extension AirshipReactNative: AirshipProxyDelegate { } public func loadDefaultConfig() -> AirshipConfig { - let config = AirshipConfig.default() - config.requireInitialRemoteConfigEnabled = true - return config + let path = Bundle.main.path( + forResource: "AirshipConfig", + ofType: "plist" + ) + + var config: AirshipConfig? + if let path = path, FileManager.default.fileExists(atPath: path) { + do { + config = try AirshipConfig.default() + } catch { + AirshipLogger.error("Failed to load config from plist: \(error)") + } + } + + return config ?? AirshipConfig() } public func onAirshipReady() { @@ -741,3 +792,13 @@ extension AirshipProxyEventType { return type } } + + +fileprivate extension Encodable { + func unwrapped() throws -> T { + guard let value = try AirshipJSON.wrap(self).unWrap() as? T else { + throw AirshipErrors.error("Failed to unwrap codable") + } + return value + } +} diff --git a/ios/MessageWebViewWrapper.swift b/ios/MessageWebViewWrapper.swift index da723a6b..1efab87c 100644 --- a/ios/MessageWebViewWrapper.swift +++ b/ios/MessageWebViewWrapper.swift @@ -48,7 +48,7 @@ public class MessageWebViewWrapper: NSObject { } -class _MessageWebViewWrapper: NSObject, UANavigationDelegate, NativeBridgeDelegate { +class _MessageWebViewWrapper: NSObject, AirshipWKNavigationDelegate, NativeBridgeDelegate { public weak var delegate: MessageWebViewWrapperDelegate? = nil @@ -69,7 +69,7 @@ class _MessageWebViewWrapper: NSObject, UANavigationDelegate, NativeBridgeDelega self.webView.configuration.dataDetectorTypes = .all if #available(iOS 16.4, *) { - self.webView.isInspectable = Airship.isFlying && Airship.config.isWebViewInspectionEnabled + self.webView.isInspectable = Airship.isFlying && Airship.config.airshipConfig.isWebViewInspectionEnabled } } @@ -103,7 +103,7 @@ class _MessageWebViewWrapper: NSObject, UANavigationDelegate, NativeBridgeDelega self.delegate?.onMessageLoadFailed(messageID: messageID) } - guard let message = message, let user = await MessageCenter.shared.inbox.user else { + guard let message = message, let user = await Airship.messageCenter.inbox.user else { if (!Task.isCancelled) { self.delegate?.onMessageGone(messageID: messageID) } @@ -115,7 +115,7 @@ class _MessageWebViewWrapper: NSObject, UANavigationDelegate, NativeBridgeDelega user: user ) self.nativeBridge.nativeBridgeExtensionDelegate = self.nativeBridgeExtension - let auth = await MessageCenter.shared.inbox.user?.basicAuthString + let auth = await Airship.messageCenter.inbox.user?.basicAuthString var request = URLRequest(url: message.bodyURL) request.timeoutInterval = 60 @@ -130,13 +130,13 @@ class _MessageWebViewWrapper: NSObject, UANavigationDelegate, NativeBridgeDelega } private func getMessage(messageID: String) async throws -> MessageCenterMessage? { - let message = await MessageCenter.shared.inbox.message(forID: messageID) + let message = await Airship.messageCenter.inbox.message(forID: messageID) if let message = message { return message } try await AirshipProxy.shared.messageCenter.refresh() - return await MessageCenter.shared.inbox.message(forID: messageID) + return await Airship.messageCenter.inbox.message(forID: messageID) } public func close() { diff --git a/ios/ProxyDataMigrator.swift b/ios/ProxyDataMigrator.swift index b4008b98..c1b9617b 100644 --- a/ios/ProxyDataMigrator.swift +++ b/ios/ProxyDataMigrator.swift @@ -27,6 +27,7 @@ struct ProxyDataMigrator { private let configKey = "com.urbanairship.react.airship_config" + @MainActor func migrateData(store: ProxyStore) { // Presentation options let optionsInt = defaults.object( diff --git a/ios/RTNAirship.mm b/ios/RTNAirship.mm index 87c8785e..3361c654 100644 --- a/ios/RTNAirship.mm +++ b/ios/RTNAirship.mm @@ -133,8 +133,8 @@ + (BOOL)requiresMainQueueSetup { RCT_REMAP_METHOD(pushGetActiveNotifications, pushGetActiveNotifications:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [AirshipReactNative.shared pushGetActiveNotificationsWithCompletionHandler:^(NSArray *> *result) { - resolve(result); + [AirshipReactNative.shared pushGetActiveNotificationsWithCompletionHandler:^(NSArray *> *result, NSError *error) { + [self handleResult:result error:error resolve:resolve reject:reject]; }]; } diff --git a/package.json b/package.json index fa0d28b5..b3faf969 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ua/react-native-airship", - "version": "20.2.0", + "version": "21.0.0", "description": "Airship plugin for React Native apps.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/react-native-airship.podspec b/react-native-airship.podspec index d941255c..25f28103 100644 --- a/react-native-airship.podspec +++ b/react-native-airship.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.license = package["license"] s.authors = package["author"] - s.platforms = { :ios => "14.0" } + s.platforms = { :ios => "15.0" } s.source = { :git => "https://github.com/urbanairship/react-native-module.git", :tag => "#{s.version}" } s.source_files = "ios/**/*.{h,m,mm,swift}" @@ -22,6 +22,5 @@ Pod::Spec.new do |s| s.dependency "React-Core" end - s.dependency "AirshipFrameworkProxy", "11.2.1" - + s.dependency "AirshipFrameworkProxy", "12.1.0" end