diff --git a/CHANGELOG.md b/CHANGELOG.md index b5cb9c1..0d6055d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Capacitor Plugin Changelog +## Version 2.1.0 September 16, 2024 +Minor release that adds `notificationPermissionStatus` to the `PushNotificationStatus` object and a way to specify the fallback when requesting permissions and the permission is already denied. + +### Changes +- Updated Airship Android SDK to 18.3.0 +- Updated Airship iOS SDK to 18.9.1 +- Added `notificationPermissionStatus` to `PushNotificationStatus` +- Added options to `enableUserNotifications` to specify the `PromptPermissionFallback` when enabling user notifications + ## Version 2.0.1 July 16, 2024 Patch release that fixes a critical issue with iOS that prevents the Airship library from automatically initializing. Apps using 2.0.0 should update. diff --git a/Package.swift b/Package.swift index 4dbf925..ed359fd 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main"), - .package(url: "https://github.com/urbanairship/airship-mobile-framework-proxy.git", from: "7.0.0") + .package(url: "https://github.com/urbanairship/airship-mobile-framework-proxy.git", from: "8.0.0") ], targets: [ .target( diff --git a/UaCapacitorAirship.podspec b/UaCapacitorAirship.podspec index b8a0bb0..1ce1939 100644 --- a/UaCapacitorAirship.podspec +++ b/UaCapacitorAirship.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = '14.0' s.dependency 'Capacitor' s.swift_version = '5.1' - s.dependency "AirshipFrameworkProxy", "7.0.0" + s.dependency "AirshipFrameworkProxy", "8.0.0" s.default_subspecs = ["Bootloader", "Plugin"] diff --git a/android/build.gradle b/android/build.gradle index 6dc424b..6e3d1b8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ ext { - airshipProxyVersion = project.hasProperty('airshipProxyVersion') ? rootProject.ext.airshipProxyVersion : '7.0.0' + airshipProxyVersion = project.hasProperty('airshipProxyVersion') ? rootProject.ext.airshipProxyVersion : '8.0.0' } diff --git a/android/src/main/java/com/airship/capacitor/AirshipPlugin.kt b/android/src/main/java/com/airship/capacitor/AirshipPlugin.kt index 2e5a21c..2b3c41b 100644 --- a/android/src/main/java/com/airship/capacitor/AirshipPlugin.kt +++ b/android/src/main/java/com/airship/capacitor/AirshipPlugin.kt @@ -1,7 +1,6 @@ package com.airship.capacitor import android.os.Build -import com.getcapacitor.JSArray import com.getcapacitor.JSObject import com.getcapacitor.Plugin import com.getcapacitor.PluginCall @@ -14,6 +13,7 @@ import com.urbanairship.actions.ActionResult import com.urbanairship.android.framework.proxy.EventType import com.urbanairship.android.framework.proxy.events.EventEmitter import com.urbanairship.android.framework.proxy.proxies.AirshipProxy +import com.urbanairship.android.framework.proxy.proxies.EnableUserNotificationsArgs import com.urbanairship.android.framework.proxy.proxies.FeatureFlagProxy import com.urbanairship.json.JsonList import com.urbanairship.json.JsonMap @@ -67,7 +67,7 @@ class AirshipPlugin : Plugin() { } private fun notifyPendingEvents() { - EventType.values().forEach { eventType -> + EventType.entries.forEach { eventType -> EventEmitter.shared().processPending(listOf(eventType)) { event -> val name = EVENT_NAME_MAP[event.type] if (hasListeners(name)) { @@ -118,9 +118,17 @@ class AirshipPlugin : Plugin() { // Push "push#setUserNotificationsEnabled" -> call.resolveResult(method) { proxy.push.setUserNotificationsEnabled(arg.requireBoolean()) } - "push#enableUserNotifications" -> call.resolvePending(method) { proxy.push.enableUserPushNotifications() } + "push#enableUserNotifications" -> call.resolveSuspending(method) { + val options = if (arg.isNull) { + null + } else { + EnableUserNotificationsArgs.fromJson(arg) + } + proxy.push.enableUserPushNotifications(options) + } + "push#isUserNotificationsEnabled" -> call.resolveResult(method) { proxy.push.isUserNotificationsEnabled() } - "push#getNotificationStatus" -> call.resolveResult(method) { proxy.push.getNotificationStatus() } + "push#getNotificationStatus" -> call.resolveSuspending(method) { proxy.push.getNotificationStatus() } "push#getActiveNotifications" -> call.resolveResult(method) { if (Build.VERSION.SDK_INT >= 23) { proxy.push.getActiveNotifications() @@ -225,28 +233,14 @@ class AirshipPlugin : Plugin() { } // Feature Flag - "featureFlagManager#flag" -> call.resolveDeferred(method) { resolveCallback -> - scope.launch { - try { - val flag = proxy.featureFlagManager.flag(arg.requireString()) - resolveCallback(flag, null) - } catch (e: Exception) { - resolveCallback(null, e) - } - } + "featureFlagManager#flag" -> call.resolveSuspending(method) { + proxy.featureFlagManager.flag(arg.requireString()) } "featureFlagManager#trackInteraction" -> { - call.resolveDeferred(method) { resolveCallback -> - scope.launch { - try { - val featureFlagProxy = FeatureFlagProxy(arg) - proxy.featureFlagManager.trackInteraction(flag = featureFlagProxy) - resolveCallback(null, null) - } catch (e: Exception) { - resolveCallback(null, e) - } - } + call.resolveSuspending(method) { + val featureFlagProxy = FeatureFlagProxy(arg) + proxy.featureFlagManager.trackInteraction(flag = featureFlagProxy) } } @@ -262,14 +256,27 @@ internal fun PluginCall.resolveResult(method: String, function: () -> Any?) { resolveDeferred(method) { callback -> callback(function(), null) } } +internal suspend fun PluginCall.resolveSuspending(method: String, function: suspend () -> Any?) { + try { + when (val result = function()) { + is Unit -> { + this.resolve(JSObject()) + } + else -> { + this.resolve(jsonMapOf("value" to result).toJSObject()) + } + } + } catch (e: Exception) { + this.reject(method, e) + } +} + internal fun PluginCall.resolveDeferred(method: String, function: ((T?, Exception?) -> Unit) -> Unit) { try { function { result, error -> if (error != null) { this.reject(method, error) } else { - - try { when (result) { is Unit -> { diff --git a/example/package-lock.json b/example/package-lock.json index 20499a7..5d5d92a 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -23,7 +23,7 @@ }, "..": { "name": "@ua/capacitor-airship", - "version": "2.0.0", + "version": "2.1.0", "license": "Apache-2.0", "devDependencies": { "@capacitor/android": "^6.0.0", diff --git a/ios/Plugin/AirshipPlugin.swift b/ios/Plugin/AirshipPlugin.swift index d37eecc..2f3389f 100644 --- a/ios/Plugin/AirshipPlugin.swift +++ b/ios/Plugin/AirshipPlugin.swift @@ -203,7 +203,9 @@ public class AirshipPlugin: CAPPlugin, CAPBridgedPlugin { return nil case "push#enableUserNotifications": - return try await AirshipProxy.shared.push.enableUserPushNotifications() + return try await AirshipProxy.shared.push.enableUserPushNotifications( + args: try call.optionalCodableArg() + ) case "push#isUserNotificationsEnabled": return try AirshipProxy.shared.push.isUserNotificationsEnabled() @@ -475,7 +477,15 @@ public class AirshipPlugin: CAPPlugin, CAPBridgedPlugin { } extension CAPPluginCall { - func requireCodableArg() throws -> T { + func optionalCodableArg() throws -> T? { + guard let value = self.getValue("value") else { + return nil + } + + return try AirshipJSON.wrap(value).decode() + } + + func requireCodableArg() throws -> T { guard let value = self.getValue("value") else { throw AirshipErrors.error("Missing argument") } diff --git a/ios/Podfile b/ios/Podfile index 5310e7d..bdbb823 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -5,7 +5,7 @@ def capacitor_pods use_frameworks! pod 'Capacitor', :path => '../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' - pod 'AirshipFrameworkProxy', '6.3.0' + pod 'AirshipFrameworkProxy', '8.0.0' end target 'Plugin' do diff --git a/package-lock.json b/package-lock.json index 720cac0..4d0121e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ua/capacitor-airship", - "version": "2.0.1", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ua/capacitor-airship", - "version": "2.0.1", + "version": "2.1.0", "license": "Apache-2.0", "devDependencies": { "@capacitor/android": "^6.0.0", diff --git a/package.json b/package.json index 0d2d5ef..31356a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ua/capacitor-airship", - "version": "2.0.1", + "version": "2.1.0", "description": "Airship capacitor plugin", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", diff --git a/src/AirshipPush.ts b/src/AirshipPush.ts index 766be19..795acb9 100644 --- a/src/AirshipPush.ts +++ b/src/AirshipPush.ts @@ -11,6 +11,7 @@ import type { PushPayload, PushReceivedEvent, PushTokenReceivedEvent, + PromptPermissionFallback } from './types'; /** @@ -55,10 +56,13 @@ export class AirshipPush { /** * Enables user notifications. + * @param options Optional options. * @returns A promise with the permission result. */ - public enableUserNotifications(): Promise { - return this.plugin.perform('push#enableUserNotifications'); + public enableUserNotifications(options?: { + fallback?: PromptPermissionFallback + }): Promise { + return this.plugin.perform('push#enableUserNotifications', options); } /** diff --git a/src/types.ts b/src/types.ts index da51522..5379cec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,8 +36,8 @@ export interface PushReceivedEvent { pushPayload: PushPayload; /** - * Indicates whether the push was received when the application was in the background or foreground. - */ + * Indicates whether the push was received when the application was in the background or foreground. + */ isForeground: boolean; } @@ -123,6 +123,42 @@ export interface PushNotificationStatus { * is true but `isOptedIn` is false, that means push token was not able to be registered. */ isUserOptedIn: boolean; + + /** + * The notification permission status. + */ + notificationPermissionStatus: PermissionStatus; +} + +/** + * Enum of permission status. + */ +export enum PermissionStatus { + /** + * Permission is granted. + */ + Granted = 'granted', + + /** + * Permission is denied. + */ + Denied = 'denied', + + /** + * Permission has not yet been requested. + */ + NotDetermined = 'not_determined', +} + +/** + * Fallback when prompting for permission and the permission is + * already denied on iOS or is denied silently on Android. + */ +export enum PromptPermissionFallback { + /** + * Take the user to the system settings to enable the permission. + */ + SystemSettings = "systemSettings" } /** @@ -382,7 +418,6 @@ export namespace iOS { } } - export namespace Android { /** * Android notification config. @@ -407,7 +442,6 @@ export namespace Android { } } - /** * Airship config environment */ @@ -436,8 +470,8 @@ export interface ConfigEnvironment { * and redacting logs with string interpolation. `public` will log all configured log levels to the console * without redacting any of the log lines. */ - logPrivacyLevel: "private" | "public" - } + logPrivacyLevel?: 'private' | 'public'; + }; } /**