Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion OneSignalCapacitorPlugin.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
require 'json'

package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
onesignal_xcframework_version = '5.5.2'
onesignal_disable_location_env = ENV['ONESIGNAL_DISABLE_LOCATION'].to_s.strip.downcase
onesignal_disable_location = ['true', '1'].include?(onesignal_disable_location_env)

Pod::Spec.new do |s|
s.name = 'OnesignalCapacitorPlugin'
Expand All @@ -20,5 +23,10 @@ Pod::Spec.new do |s|
s.swift_version = '5.9'

s.dependency 'Capacitor'
s.dependency 'OneSignalXCFramework', '5.5.2'
if onesignal_disable_location
s.dependency 'OneSignalXCFramework/OneSignal', onesignal_xcframework_version
s.dependency 'OneSignalXCFramework/OneSignalInAppMessages', onesignal_xcframework_version
else
s.dependency 'OneSignalXCFramework', onesignal_xcframework_version
end
end
42 changes: 29 additions & 13 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
// swift-tools-version: 5.9

import PackageDescription
import Foundation

func oneSignalEnvFlag(_ name: String) -> Bool {
let value = Context.environment[name]?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
return value == "true" || value == "1"
}

let oneSignalDisableLocation = oneSignalEnvFlag("ONESIGNAL_DISABLE_LOCATION")

// InAppMessages, Location, and Extension are separate library products in
// OneSignal-XCFramework and must be linked explicitly under SPM. Location can
// be omitted for apps that do not use OneSignal.Location.
var oneSignalDependencies: [Target.Dependency] = [
.product(name: "OneSignalFramework", package: "OneSignal-XCFramework"),
.product(name: "OneSignalInAppMessages", package: "OneSignal-XCFramework"),
.product(name: "OneSignalExtension", package: "OneSignal-XCFramework"),
]

if !oneSignalDisableLocation {
oneSignalDependencies.append(.product(name: "OneSignalLocation", package: "OneSignal-XCFramework"))
}

var capacitorPluginDependencies: [Target.Dependency] = [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
]
capacitorPluginDependencies.append(contentsOf: oneSignalDependencies)
capacitorPluginDependencies.append("OSCapacitorLaunchOptions")

let package = Package(
name: "OnesignalCapacitorPlugin",
Expand Down Expand Up @@ -28,19 +56,7 @@ let package = Package(
),
.target(
name: "OnesignalCapacitorPlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
// InAppMessages and Location are separate library products in
// OneSignal-XCFramework and must be linked explicitly under SPM,
// otherwise their xcframeworks aren't loaded and the namespaces
// are silent no-ops at runtime.
.product(name: "OneSignalFramework", package: "OneSignal-XCFramework"),
.product(name: "OneSignalInAppMessages", package: "OneSignal-XCFramework"),
.product(name: "OneSignalLocation", package: "OneSignal-XCFramework"),
.product(name: "OneSignalExtension", package: "OneSignal-XCFramework"),
"OSCapacitorLaunchOptions"
],
dependencies: capacitorPluginDependencies,
path: "ios/Sources/OneSignalCapacitorPlugin"
)
]
Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,60 @@ bunx cap sync
npx cap sync
```

## Disabling OneSignal Location

If your app does not use `OneSignal.Location`, you can exclude the native OneSignal location module from iOS and Android builds.

Set `ONESIGNAL_DISABLE_LOCATION=true` in the environment before resolving or building native dependencies. The value is case-insensitive, and `1` is also accepted.

```bash
ONESIGNAL_DISABLE_LOCATION=true npx cap sync
```

In GitHub Actions, set it once at the job or step level so Swift Package Manager, CocoaPods, and Gradle builds inherit it:

```yaml
env:
ONESIGNAL_DISABLE_LOCATION: true
```

With the location module disabled, calls to `OneSignal.Location` are ignored on Android and `OneSignal.Location.isShared()` resolves `false`.

### Applying the change

The environment variable is read when native dependencies are resolved. If you change the variable in an existing project, clear the relevant cache and re-resolve in a shell where the variable is exported.

> [!IMPORTANT]
> When using Xcode or Android Studio, launch the IDE from a terminal that has `ONESIGNAL_DISABLE_LOCATION` exported. An IDE launched from the Dock/Finder does not inherit variables set only in your shell profile.

Swift Package Manager:

```bash
rm -rf ~/Library/Caches/org.swift.swiftpm ~/Library/Developer/Xcode/DerivedData/*
ONESIGNAL_DISABLE_LOCATION=true npx cap sync ios
```

In Xcode, you can instead use **File -> Packages -> Reset Package Caches** with the variable exported, then build.

CocoaPods:

```bash
cd ios/App
pod deintegrate
rm -rf Pods Podfile.lock
ONESIGNAL_DISABLE_LOCATION=true pod install
```

Android Gradle, which re-reads the variable on each configuration:

```bash
ONESIGNAL_DISABLE_LOCATION=true npx cap sync android
cd android
ONESIGNAL_DISABLE_LOCATION=true ./gradlew assembleDebug
```

On CI, key any DerivedData, SwiftPM, CocoaPods, or Gradle caches on the value of `ONESIGNAL_DISABLE_LOCATION` so a restored cache does not resurrect the location module.

## Usage

```ts
Expand Down
16 changes: 15 additions & 1 deletion android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,16 @@ fun propertyOrCatalog(propertyName: String, catalogKey: String): String =
fun intPropertyOrCatalog(propertyName: String, catalogKey: String): Int =
project.findProperty(propertyName)?.toString()?.toInt() ?: catalogVersion(catalogKey).toInt()

fun envFlag(envName: String): Boolean {
val value = System.getenv(envName)
val normalizedValue = value?.trim()
return normalizedValue.equals("true", ignoreCase = true) || normalizedValue == "1"
}

val junitVersion: String = propertyOrCatalog("junitVersion", "junit")
val androidxAppCompatVersion: String = propertyOrCatalog("androidxAppCompatVersion", "androidxAppCompat")
val oneSignalVersion: String = catalogVersion("onesignal")
val oneSignalDisableLocation: Boolean = envFlag("ONESIGNAL_DISABLE_LOCATION")

extra["junitVersion"] = junitVersion
extra["androidxAppCompatVersion"] = androidxAppCompatVersion
Expand Down Expand Up @@ -126,7 +134,13 @@ repositories {
dependencies {
"implementation"(project(":capacitor-android"))
"implementation"("androidx.appcompat:appcompat:$androidxAppCompatVersion")
"implementation"("com.onesignal:OneSignal:${catalogVersion("onesignal")}")
if (oneSignalDisableLocation) {
"implementation"("com.onesignal:core:$oneSignalVersion")
"implementation"("com.onesignal:notifications:$oneSignalVersion")
"implementation"("com.onesignal:in-app-messages:$oneSignalVersion")
} else {
"implementation"("com.onesignal:OneSignal:$oneSignalVersion")
}
"testImplementation"("junit:junit:$junitVersion")
"androidTestImplementation"("androidx.test.ext:junit:${catalogVersion("androidxTestJunit")}")
"androidTestImplementation"("androidx.test.espresso:espresso-core:${catalogVersion("androidxEspresso")}")
Expand Down

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each try/catch in this file also throws when the SDK isn't initialized yet, which can be confusing to debug. Consider a message like "OneSignal.Location call failed — the location module may not be included in this build" or detecting the module-missing case specifically.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.getcapacitor.PluginMethod
import com.getcapacitor.annotation.CapacitorPlugin
import com.onesignal.OneSignal
import com.onesignal.common.OneSignalWrapper
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.inAppMessages.IInAppMessageClickEvent
import com.onesignal.inAppMessages.IInAppMessageClickListener
import com.onesignal.inAppMessages.IInAppMessageDidDismissEvent
Expand Down Expand Up @@ -44,6 +45,8 @@ class OneSignalCapacitorPlugin : Plugin(),
// provisional (3), and ephemeral (4) states do not apply here.
private const val PERMISSION_DENIED = 1
private const val PERMISSION_AUTHORIZED = 2
private const val LOCATION_MODULE_NOT_AVAILABLE =
"OneSignal location module is not available. Add the location dependency to use OneSignal.Location."
}

private val notificationWillDisplayCache = mutableMapOf<String, INotificationWillDisplayEvent>()
Expand All @@ -56,6 +59,10 @@ class OneSignalCapacitorPlugin : Plugin(),
// call into the dead Capacitor bridge.
private val pluginScope = MainScope()

private fun logLocationModuleNotAvailable(throwable: Throwable) {
Logging.error(LOCATION_MODULE_NOT_AVAILABLE, throwable)
}

private val permissionObserver = object : IPermissionObserver {
override fun onNotificationPermissionChange(permission: Boolean) {
val ret = JSObject()
Expand Down Expand Up @@ -640,22 +647,35 @@ class OneSignalCapacitorPlugin : Plugin(),
@PluginMethod
fun requestLocationPermission(call: PluginCall) {
pluginScope.launch {
OneSignal.Location.requestPermission()
try {
OneSignal.Location.requestPermission()
} catch (t: Throwable) {
logLocationModuleNotAvailable(t)
}
Comment on lines +650 to +654

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the scope is cancelled while OneSignal.Location.requestPermission() is suspended, the new catch (t: Throwable):

  • catches the CancellationException instead of letting cancellation propagate,
  • logs a misleading "location module is not available" error,
  • then calls call.resolve() on the destroyed bridge — the exact crash path the previous fix removed.

Fix: rethrow cancellation before the broad catch:

try {
    OneSignal.Location.requestPermission()
} catch (e: CancellationException) {
    throw e
} catch (t: Throwable) {
    logLocationModuleNotAvailable(t)
}

call.resolve()
}
}

@PluginMethod
fun setLocationShared(call: PluginCall) {
val shared = call.getBoolean("shared") ?: false
OneSignal.Location.isShared = shared
try {
OneSignal.Location.isShared = shared
} catch (t: Throwable) {
logLocationModuleNotAvailable(t)
}
call.resolve()
}

@PluginMethod
fun isLocationShared(call: PluginCall) {
val ret = JSObject()
ret.put("shared", OneSignal.Location.isShared)
try {
ret.put("shared", OneSignal.Location.isShared)
} catch (t: Throwable) {
logLocationModuleNotAvailable(t)
ret.put("shared", false)
}
call.resolve(ret)
}

Expand Down
Loading