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
14 changes: 7 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
import PackageDescription

let package = Package(
name: "OneSignalCapacitorPlugin",
name: "OnesignalCapacitorPlugin",
platforms: [.iOS(.v14)],
products: [
.library(
name: "OneSignalCapacitorPlugin",
targets: ["OneSignalCapacitorPlugin"]
name: "OnesignalCapacitorPlugin",
targets: ["OnesignalCapacitorPlugin"]
)
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"),
.package(url: "https://github.com/nicklama/onesignal-xcframework-spm", from: "5.0.0")
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", "7.0.0"..<"9.0.0"),
.package(url: "https://github.com/OneSignal/OneSignal-XCFramework", from: "5.0.0")
],
targets: [
.target(
name: "OneSignalCapacitorPlugin",
name: "OnesignalCapacitorPlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(name: "OneSignalFramework", package: "onesignal-xcframework-spm")
.product(name: "OneSignalFramework", package: "OneSignal-XCFramework")
],
path: "ios/Sources/OneSignalCapacitorPlugin"
)
Expand Down
35 changes: 17 additions & 18 deletions examples/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,6 @@ All OneSignal SDK interactions and state are managed through a single `useOneSig

`HomeScreen` calls `useOneSignal()` and passes state/callbacks down to section components as props.

### Repository Pattern

`OneSignalRepository` (`src/repositories/OneSignalRepository.ts`) wraps all OneSignal SDK calls with `Capacitor.isNativePlatform()` guards, providing a safe abstraction that no-ops on web. It also delegates notification sending and user fetching to `OneSignalApiService`.

### Services

- `OneSignalApiService` (`src/services/OneSignalApiService.ts`) — Singleton REST client using `fetch` to send notifications via the OneSignal API and fetch user data
Expand Down Expand Up @@ -338,7 +334,7 @@ Under the hood, `addEventListener` wraps Capacitor's `this._plugin.addListener(n
The `useOneSignal()` hook centralizes all SDK state and actions:

- **Reactive state** via `useState`: app ID, consent settings, external user ID, push subscription ID, push enabled, notification permission, IAM paused, location shared, aliases, emails, SMS numbers, tags, triggers, loading state
- **Refs** via `useRef`: mount tracking (`mountedRef`), request sequencing (`requestSequenceRef`) to discard stale API responses
- **Refs** via `useRef`: request sequencing (`requestSequenceRef`) to discard stale API responses
- **Effects** via `useEffect`: one-time SDK init + listener registration with full cleanup
- **Memoized callbacks** via `useCallback`: `fetchUserDataFromApi` for API-driven state refresh

Expand Down Expand Up @@ -494,19 +490,17 @@ The iOS Xcode project includes extension targets:
- `ios/App/OneSignalNotificationServiceExtension/NotificationService.swift` — forwards to `OneSignalExtension` for rich notification support
- `ios/App/OneSignalNotificationServiceExtension/Info.plist` — extension point `com.apple.usernotifications.service`

### Podfile
### Swift Package Manager

The Podfile includes the NSE target:
The demo uses Swift Package Manager (SPM) instead of CocoaPods. Capacitor manages the App target's plugin dependencies through `ios/App/CapApp-SPM/Package.swift`. The extension targets reference the OneSignal XCFramework Swift package directly:

```ruby
target 'App' do
capacitor_pods
end
- App target → `CapApp-SPM` local package (Capacitor + plugin products, regenerated by `cap sync`)
- `OneSignalNotificationServiceExtension` → `OneSignalExtension` product from `https://github.com/OneSignal/OneSignal-XCFramework`
- `OneSignalWidgetExtension` → `OneSignalFramework` product from `https://github.com/OneSignal/OneSignal-XCFramework` (transitively brings in `OneSignalLiveActivities` for Live Activity widgets)

target 'OneSignalNotificationServiceExtension' do
pod 'OneSignalXCFramework', '>= 5.0.0', '< 6.0'
end
```
`ios/debug.xcconfig` is wired up as the App target's Debug base configuration so Capacitor's debug-only behaviors stay enabled.

To migrate an existing CocoaPods-based project to SPM, run `bunx cap spm-migration-assistant` and then add the local `CapApp-SPM` package and any extension dependencies through Xcode's Package Dependencies tab.

---

Expand All @@ -525,7 +519,7 @@ end
- `handleApplicationNotifications: false` in `capacitor.config.ts` to let OneSignal handle notifications
- Capacitor setup with push notification entitlement and app groups
- NSE target in Xcode project
- Podspec named `OnesignalCapacitorPlugin` to match Capacitor's derived pod name (Capacitor's `fixName` converts `onesignal-capacitor-plugin` → `OnesignalCapacitorPlugin`)
- SPM product named `OnesignalCapacitorPlugin` to match Capacitor's derived package name (Capacitor's `fixName` converts `onesignal-capacitor-plugin` → `OnesignalCapacitorPlugin`)

### Custom Notification Sound

Expand Down Expand Up @@ -620,7 +614,13 @@ examples/
├── OneSignalNotificationServiceExtension/ # NSE target
│ ├── NotificationService.swift
│ └── Info.plist
└── Podfile
├── OneSignalWidget/ # Widget + Live Activity target
│ ├── OneSignalWidgetBundle.swift
│ ├── OneSignalWidgetLiveActivity.swift
│ └── Info.plist
└── CapApp-SPM/ # Capacitor-managed SPM package
├── Package.swift # Auto-generated by `cap sync`
└── Sources/CapApp-SPM/CapApp-SPM.swift
```

---
Expand All @@ -629,7 +629,6 @@ examples/

- **TypeScript strict mode** on all source files, avoiding `any` and type assertions
- **Central `useOneSignal` hook** manages all SDK state and actions; section components are pure props-driven
- **Repository pattern** wraps SDK calls with `Capacitor.isNativePlatform()` guards
- **Service layer** separates REST API calls, persistence, and logging from UI
- **React + Ionic React** for component-based UI with React Router navigation
- **Separate section components** per feature area for maintainability
Expand Down
3 changes: 3 additions & 0 deletions examples/demo/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VITE_ONESIGNAL_APP_ID=your_onesignal_app_id
VITE_ONESIGNAL_API_KEY=your_rest_api_key
VITE_E2E_MODE=false
Comment on lines +1 to +3

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do these get referenced anywhere?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Its for e2e and local testing

101 changes: 101 additions & 0 deletions examples/demo/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore

# Built application files
*.apk
*.aar
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/

# Android Profiling
*.hprof

# Cordova plugins for Capacitor
capacitor-cordova-android-plugins

# Copied web assets
app/src/main/assets/public

# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
2 changes: 2 additions & 0 deletions examples/demo/android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep
56 changes: 56 additions & 0 deletions examples/demo/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
plugins {
id("com.android.application")
}

android {
namespace = "com.onesignal.example"
compileSdk = rootProject.extra["compileSdkVersion"] as Int
defaultConfig {
applicationId = "com.onesignal.example"
minSdk = rootProject.extra["minSdkVersion"] as Int
targetSdk = rootProject.extra["targetSdkVersion"] as Int
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
androidResources {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
}

repositories {
flatDir {
dirs("../capacitor-cordova-android-plugins/src/main/libs", "libs")
}
}

dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(libs.androidx.appcompat)
implementation(libs.androidx.coordinatorlayout)
implementation(libs.androidx.core.splashscreen)
implementation(project(":capacitor-android"))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.espresso.core)
implementation(project(":capacitor-cordova-android-plugins"))
}

apply(from = "capacitor.build.gradle")

try {
val servicesJSON = file("google-services.json")
if (servicesJSON.readText().isNotEmpty()) {
apply(plugin = "com.google.gms.google-services")
}
} catch (e: Exception) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
21 changes: 21 additions & 0 deletions examples/demo/android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
37 changes: 37 additions & 0 deletions examples/demo/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
</application>

<!-- Permissions -->

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
Loading
Loading