diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..243a2ee --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,117 @@ +# cordova-outsystems-firebase-cloud-messaging Architecture + +> **Repository:** cordova-outsystems-firebase-cloud-messaging +> **Runtime Environment:** Mobile Applications (iOS/Android native code embedded in hybrid apps) +> **Last Updated:** 2026-03-16 + +## Overview + +This is a Cordova/Capacitor plugin that provides Firebase Cloud Messaging (FCM) capabilities to hybrid mobile applications. The plugin runs as native code (Swift on iOS, Kotlin on Android) within the host mobile application, bridging JavaScript calls from the WebView to native Firebase SDK functionality. + +## Architecture Diagram + +```mermaid +graph TB + %% This repository + ThisRepo["cordova-outsystems-firebase-cloud-messaging
Runs on: Mobile Device (iOS/Android)
Acts as: Cordova Plugin Bridge"] + + %% External services + FCM[Firebase Cloud Messaging
EXTERNAL] + APNs[Apple Push Notification Service
EXTERNAL] + LocalDB[(Local SQLite/Room Database
EXTERNAL)] + Azure[OutSystems Azure Artifact Repository
EXTERNAL] + CocoaPods[CocoaPods CDN
EXTERNAL] + + %% Communication flows + ThisRepo -->|HTTP/gRPC
Synchronous| FCM + ThisRepo -->|Native API
Synchronous| APNs + ThisRepo -->|SQL queries
Synchronous| LocalDB + + %% Build-time dependencies + ThisRepo -.->|Fetch dependencies
Build-time only| Azure + ThisRepo -.->|Fetch FirebaseMessaging pod
Build-time only| CocoaPods + + %% Styling + classDef thisRepo fill:#e0f2f1,stroke:#00796b,stroke-width:3px + classDef external fill:#ffe1e1,stroke:#d32f2f,stroke-width:2px,stroke-dasharray: 5 5 + classDef database fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px + + class ThisRepo thisRepo + class FCM,APNs,Azure,CocoaPods external + class LocalDB database +``` + +## External Integrations + +| External Service | Communication Type | Purpose | +|------------------|-------------------|---------| +| Firebase Cloud Messaging (FCM) | Sync (HTTP/gRPC) | Send/receive push notifications, manage device tokens, subscribe to topics | +| Apple Push Notification Service (APNs) | Sync (Native iOS API) | iOS-specific push notification delivery and APNs token management | +| Local SQLite/Room Database | Sync (SQL) | Store pending notifications locally for retrieval when app is offline or in background | +| OutSystems Azure Artifact Repository | Build-time (Maven) | Fetch proprietary Android AAR libraries (osfirebasemessaging-android, oslocalnotifications-android) | +| CocoaPods CDN | Build-time (CocoaPods) | Fetch Firebase iOS SDK (FirebaseMessaging 10.29.0) | + +## Architectural Tenets + +### T1. Platform-Specific Implementation with Shared Interface Contract + +The plugin maintains separate native implementations for iOS and Android but exposes an identical JavaScript API surface. Each platform implements the same set of methods defined in the JavaScript bridge, ensuring host applications can use the plugin uniformly across platforms while delegating to platform-appropriate FCM SDKs. + +**Evidence:** +- `www/OSFirebaseCloudMessaging.js` - Defines common JavaScript API with methods like `getToken`, `subscribe`, `registerDevice` +- `src/ios/OSFirebaseCloudMessaging.swift` (in `OSFirebaseCloudMessaging` class) - Swift implementation delegates to `FirebaseMessagingController` from iOS framework +- `src/android/com/outsystems/firebase/cloudmessaging/OSFirebaseCloudMessaging.kt` (in `OSFirebaseCloudMessaging` class) - Kotlin implementation delegates to `FirebaseMessagingController` from Android AAR +- Both platforms implement identical method signatures (`getToken`, `subscribe`, `unsubscribe`, `registerDevice`) despite using different underlying SDKs + +### T2. Abstraction Through Proprietary Native Libraries + +Business logic and Firebase SDK interaction are isolated in proprietary native libraries (`OSFirebaseMessagingLib` for iOS, `osfirebasemessaging-android` for Android), not in this plugin's code. This plugin acts as a thin bridge layer that translates Cordova calls to library calls. The proprietary libraries are maintained separately and distributed as compiled artifacts. + +**Evidence:** +- `plugin.xml` - References `OSFirebaseMessagingLib.xcframework` (lines 43-44) and `OSLocalNotificationsLib.xcframework` as embedded frameworks +- `src/android/com/outsystems/firebase/cloudmessaging/build.gradle` - Imports `com.github.outsystems:osfirebasemessaging-android:1.3.1@aar` from Azure repository +- `src/ios/OSFirebaseCloudMessaging.swift` (in `pluginInitialize`) - Instantiates `FirebaseMessagingController()` from external framework +- `src/android/com/outsystems/firebase/cloudmessaging/OSFirebaseCloudMessaging.kt` (in `initialize`) - Creates `FirebaseMessagingController` and `FirebaseNotificationManager` from imported AAR + +### T3. Event-Driven Callback Pattern with Queue Buffering + +The plugin uses an event listener system on the JavaScript side to handle asynchronous FCM events (notification clicks, token refresh). Events triggered before the Cordova device is ready are queued and replayed once the WebView is fully initialized, ensuring no events are lost during app startup. + +**Evidence:** +- `www/OSFirebaseCloudMessaging.js` (in `on` and `un` functions) - Implements listener registration/deregistration pattern +- `www/OSFirebaseCloudMessaging.js` (in `fireQueuedEvents`) - Calls native `ready` action to flush queued events +- `src/ios/OSFirebaseCloudMessaging.swift` (in `ready` and `trigger` methods) - Maintains `eventQueue` array and `deviceReady` flag +- `src/android/com/outsystems/firebase/cloudmessaging/OSFirebaseCloudMessaging.kt` (in `ready` and `callbackNotifyApp`) - Uses `eventQueue: MutableList` to buffer JavaScript calls + +### T4. Build-Time Hooks for Platform-Specific Configuration + +The plugin relies on Cordova/Capacitor build hooks to inject platform-specific configurations that cannot be expressed in static plugin descriptors. Hooks copy notification channel metadata, unzip custom sound files, modify AppDelegate/build.gradle files, and adapt to different build environments (Cordova vs. Capacitor). + +**Evidence:** +- `plugin.xml` - Declares hooks: `after_prepare` hooks for `unzipSound.js`, `cleanUp.js`, `androidCopyChannelInfo.js`, `iOSCopyPreferences.js` +- `hooks/android/androidCopyChannelInfo.js` - Reads notification channel preferences from `config.xml` and writes to Android strings resource +- `hooks/unzipSound.js` - Extracts `sounds.zip` to platform-specific resource directories during build +- `build-actions/capacitor_hooks_update_after.js` (in `updateAppDelegate` function) - Injects Firebase delegate calls into iOS AppDelegate for Capacitor apps +- `package.json` - Defines `capacitor:update:after` script to handle Capacitor-specific build modifications + +### T5. Permission Abstraction with Version-Aware Logic + +The plugin abstracts runtime permission handling across different Android API levels, treating pre-Tiramisu (API 33) and post-Tiramisu behavior uniformly. iOS permission handling is always asynchronous. This allows consuming apps to call `registerDevice` without platform-specific conditional logic. + +**Evidence:** +- `src/android/com/outsystems/firebase/cloudmessaging/OSFirebaseCloudMessaging.kt` (in `registerDevice`) - Checks `Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU` to determine if POST_NOTIFICATIONS permission is required +- `src/android/com/outsystems/firebase/cloudmessaging/OSFirebaseCloudMessaging.kt` (in `registerDevice`) - Uses `MutableSharedFlow` to convert Cordova permission callback to suspend function +- `src/android/com/outsystems/firebase/cloudmessaging/OSFCMPermissionEvents.kt` - Sealed class models permission result states (`Granted`, `NotGranted`) +- `src/ios/OSFirebaseCloudMessaging.swift` (in `registerDevice`) - Always calls `requestAuthorisation()` and checks result asynchronously + +## Current Phase Constraints + +### Capacitor Migration Strategy + +The plugin currently supports both Cordova and Capacitor build systems using conditional logic. Cordova apps use method swizzling in AppDelegate, while Capacitor apps require hook-injected code modifications. + +**Evidence:** +- `src/ios/AppDelegate+OSFirebaseCloudMessaging.m` - Uses runtime method swizzling but returns early if Capacitor bridge is detected +- `build-actions/capacitor_hooks_update_after.js` - Injects AppDelegate modifications specifically for Capacitor apps + +**Expires when:** Cordova support is officially deprecated and all consuming apps migrate to Capacitor. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f473d6..4b8e3f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,31 +6,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The changes documented here do not include those from the original repository. -### [2.5.6] +## [2.6.2] + +### Chores + +- chore(koda): initialize Koda (#133) + +## [2.6.1] + +### Chores + +- chore: update dependency to OSFirebaseMessagingLib-Android (#139) + +## [2.6.0] + +### Features + +- (ios) Support for Swift Package Manager (https://outsystemsrd.atlassian.net/browse/RMET-5137) + +### Fixes + +- (android) Avoid overwriting other plugins' `ext.postBuildExtras` Gradle configuration (https://outsystemsrd.atlassian.net/browse/RMET-5211) + +## [2.5.6] ### Fixes - (android) Handle errors in `RegisterDevice` instead of hanging indefinitely (https://outsystemsrd.atlassian.net/browse/RMET-4953) -### [2.5.5] +## [2.5.5] ### Fixes - (android) Remove unnecessary dependencies to `oscore` and `oscordova` (https://outsystemsrd.atlassian.net/browse/RMET-4900) -### [2.5.4] +## [2.5.4] ### Fixes - (android) Fix capacitor builds by removing kapt dependency (https://outsystemsrd.atlassian.net/browse/RMET-4862) -### [2.5.3] +## [2.5.3] ### Fixes - (android) Fixes missing strings.xml on target project (https://outsystemsrd.atlassian.net/browse/RMET-4774) -### [2.5.2] +## [2.5.2] ### Fixes diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f13f358 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,156 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is a Cordova/Capacitor plugin that bridges Firebase Cloud Messaging (FCM) to hybrid mobile applications. The plugin consists of native code (Swift for iOS, Kotlin for Android) with a JavaScript API layer exposed to the WebView. + +See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed architectural tenets, external integrations, and the Cordova/Capacitor migration strategy. + +See [CONTRIBUTING.md](./CONTRIBUTING.md) for development workflow, branch naming conventions, commit format, and PR process. + +## Directory Structure + +``` +src/ios/ - Swift/Objective-C bridge code + OSFirebaseCloudMessaging.swift - Main iOS plugin class + OSFCMEventExtensions.swift - Event handling utilities + AppDelegate+OSFirebaseCloudMessaging.{h,m} - Method swizzling for Cordova + frameworks/ - Proprietary xcframeworks (OSFirebaseMessagingLib, OSLocalNotificationsLib) + +src/android/ - Kotlin bridge code + com/outsystems/firebase/cloudmessaging/ + OSFirebaseCloudMessaging.kt - Main Android plugin class + OSFCMPermissionEvents.kt - Permission result states + build.gradle - Android dependencies (references osfirebasemessaging-android AAR) + +www/ - JavaScript API exposed to WebView + OSFirebaseCloudMessaging.js - Cordova exec bridge + +hooks/ - Cordova build hooks (run on after_prepare) + unzipSound.js - Extract sound resources + cleanUp.js - Remove temporary files + android/androidCopyChannelInfo.js - Copy notification channel config to strings.xml + ios/iOSCopyPreferences.js - Copy iOS preferences to plist + +build-actions/ - Capacitor/ODC support (replaces hooks for Capacitor apps) + updateCloudMessagingConfigs.yaml - Build actions for channel config and entitlements + capacitor_hooks_update_after.js - AppDelegate injection for Capacitor + README.md - Build actions usage documentation + +plugin.xml - Cordova plugin manifest (dependencies, hooks, platform configs) +``` + +## Testing and Development + +This is a plugin, not a standalone application. To test changes: + +### For Cordova Projects +```bash +# In your test Cordova app +cordova plugin add /absolute/path/to/cordova-outsystems-firebase-cloud-messaging +cordova build android +cordova build ios +``` + +### For Capacitor Projects +```bash +# In your test Capacitor app +npm install /absolute/path/to/cordova-outsystems-firebase-cloud-messaging +npx cap sync +``` + +The `capacitor:update:after` hook runs automatically during `cap sync` to inject AppDelegate modifications. + +### Manually Trigger Capacitor Hook +```bash +npm run capacitor:update:after +``` + +## Plugin API Surface + +The JavaScript API in `www/OSFirebaseCloudMessaging.js` exposes these methods: + +- `getToken()` - Get FCM device token +- `getAPNsToken()` - Get APNs token (iOS only) +- `subscribe(topic)` / `unsubscribe(topic)` - Manage topic subscriptions +- `registerDevice()` / `unregisterDevice()` - Handle push permission and registration +- `getPendingNotifications(clearFromDatabase)` - Retrieve queued notifications +- `clearNotifications()` - Clear notification center +- `setBadge(badge)` / `getBadge()` - Badge management +- `sendLocalNotification(...)` - Trigger local notification +- `setDeliveryMetricsExportToBigQuery(enable)` - Configure BigQuery export +- `on(event, callback)` / `un(event, callback)` - Event listener registration + +All methods use the Cordova exec bridge. Events are queued until device ready (see [ARCHITECTURE.md](./ARCHITECTURE.md) T3: Event-Driven Callback Pattern). + +## Key Dependencies + +### iOS (specified in plugin.xml) +- `FirebaseMessaging` pod version 10.29.0 (from CocoaPods CDN) +- `OSFirebaseMessagingLib.xcframework` (proprietary, embedded in repo) +- `OSLocalNotificationsLib.xcframework` (proprietary, embedded in repo) + +### Android (specified in src/android/.../build.gradle) +- `com.github.outsystems:osfirebasemessaging-android:1.3.1@aar` (from OutSystems Azure) +- `com.github.outsystems:oslocalnotifications-android` (from OutSystems Azure) + +The proprietary libraries contain the actual FCM SDK integration logic. This plugin acts as a thin bridge. + +## Important Context + +### Cordova vs Capacitor Build Systems + +The plugin supports both build systems using conditional logic: + +- **Cordova apps** use method swizzling in `AppDelegate+OSFirebaseCloudMessaging.m` and standard Cordova hooks +- **Capacitor apps** skip swizzling (detected via bridge check) and use `build-actions/capacitor_hooks_update_after.js` to inject AppDelegate code + +See [ARCHITECTURE.md](./ARCHITECTURE.md) "Current Phase Constraints: Capacitor Migration Strategy" for details. + +### Build Hooks Behavior + +Hooks in `hooks/` directory run during `cordova prepare` or `cordova build`: +- `unzipSound.js` - Extracts `sounds.zip` to platform resource directories +- `androidCopyChannelInfo.js` - Reads notification channel preferences from `config.xml` and writes to Android `strings.xml` +- `iOSCopyPreferences.js` - Copies iOS preferences from `config.xml` to `.plist` files + +For Capacitor apps, equivalent functionality is in `build-actions/updateCloudMessagingConfigs.yaml` (used by ODC Plugin Manager). + +### Permission Handling + +- **Android**: Pre-Tiramisu (API < 33) does not require POST_NOTIFICATIONS permission; Tiramisu+ does +- **iOS**: Always requires asynchronous permission request via `requestAuthorisation()` + +The `registerDevice()` method abstracts this logic so consuming apps don't need platform-specific code (see [ARCHITECTURE.md](./ARCHITECTURE.md) T5: Permission Abstraction). + +### Event Queue Pattern + +Events triggered before Cordova's `deviceready` are buffered in native code (`eventQueue` on both platforms) and replayed when the JavaScript calls the `ready()` action. This ensures no notifications are lost during app startup (see [ARCHITECTURE.md](./ARCHITECTURE.md) T3). + +## Common Tasks + +### Update iOS Firebase Dependency +Edit `plugin.xml` line 54 to change the FirebaseMessaging pod version. + +### Update Android Proprietary Library Version +Edit `src/android/com/outsystems/firebase/cloudmessaging/build.gradle` to change the AAR version. + +### Add New JavaScript API Method +1. Add method to `www/OSFirebaseCloudMessaging.js` using `exec()` +2. Implement in `src/ios/OSFirebaseCloudMessaging.swift` (method name must match) +3. Implement in `src/android/.../OSFirebaseCloudMessaging.kt` (method name must match) + +### Debug Hook Execution +Hooks write to console during build. Check Cordova build output for hook logs. For Capacitor, check `build-actions/capacitor_hooks_update_after.js` console output during `cap sync`. + +## Version and Release Management + +When releasing a new version: +1. Update `version` in `package.json` +2. Update `version` attribute in `plugin.xml` root `` element +3. Update `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com/) format +4. Commit with: `chore(release): raise to version X.Y.Z` + +See [CONTRIBUTING.md](./CONTRIBUTING.md) "Versioning" section for details. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cc31b73 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,212 @@ +# Contributing to cordova-outsystems-firebase-cloud-messaging + +## Overview + +This is a Cordova plugin for Firebase Cloud Messaging with support for both iOS and Android platforms. The plugin also supports Capacitor through build actions. + +## Development Setup + +### Prerequisites + +- Node.js and npm +- For iOS development: + - Xcode + - CocoaPods + - Swift 5 compiler +- For Android development: + - Android Studio + - Gradle + - Kotlin support + +### Installation + +1. Clone the repository +2. Install Node.js dependencies: + ```bash + npm install + ``` + +## Project Structure + +- `src/ios/` - iOS native code (Swift, Objective-C) +- `src/android/` - Android native code (Kotlin) +- `www/` - JavaScript interface layer +- `hooks/` - Cordova hooks for build-time configuration +- `build-actions/` - Capacitor build actions (ODC support) +- `plugin.xml` - Cordova plugin configuration + +## Development Workflow + +### Branch Naming + +Follow this pattern: `/RMET-/` + +Examples: +- `feat/RMET-1234/add-notification-actions` +- `fix/RMET-5678/handle-registration-errors` +- `chore/RMET-9012/update-dependencies` + +Check existing branches with: +```bash +git branch -r +``` + +### Commit Format + +Use conventional commits with platform scopes: + +``` +(): + +[optional body] +``` + +**Types:** `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `style`, `perf`, `build`, `ci`, `revert` + +**Scopes:** `android`, `ios`, `release`, or omit for cross-platform changes + +**Examples:** +``` +feat(ios): add support for notification badges +fix(android): handle errors in RegisterDevice +chore: update changelog +``` + +View recent commit patterns: +```bash +git log --oneline -20 +``` + +### Making Changes + +1. Create a feature branch from `main` +2. Make your changes following the commit format +3. Update `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com/) format +4. Update version in `package.json` and `plugin.xml` if releasing + +## Building and Testing + +### Testing in a Cordova Project + +Since this is a plugin, test by integrating into a Cordova project: + +```bash +# In your test Cordova project +cordova plugin add /path/to/cordova-outsystems-firebase-cloud-messaging + +# Build for Android +cordova build android + +# Build for iOS +cordova build ios +``` + +### Testing in a Capacitor Project + +For Capacitor/ODC testing: + +```bash +# In your test Capacitor project +npm install /path/to/cordova-outsystems-firebase-cloud-messaging + +# Sync with native projects +npx cap sync +``` + +The `capacitor:update:after` hook runs automatically: +```bash +npm run capacitor:update:after +``` + +### Hooks and Build Actions + +Hooks execute automatically during Cordova builds: +- `unzipSound.js` - Extracts sound resources +- `cleanUp.js` - Cleans temporary files +- `android/androidCopyChannelInfo.js` - Copies Android channel configuration +- `ios/iOSCopyPreferences.js` - Copies iOS preferences + +Build actions (`build-actions/updateCloudMessagingConfigs.yaml`) provide equivalent functionality for Capacitor apps. See `build-actions/README.md` for details. + +## Code Standards + +### iOS Code + +- Swift 5 syntax +- Follow existing patterns in `src/ios/` +- Use the `OSFirebaseMessagingLib.xcframework` for messaging functionality +- Update CocoaPods dependencies in `plugin.xml` when needed + +### Android Code + +- Kotlin (official code style as per `plugin.xml`) +- AndroidX libraries required +- Follow existing patterns in `src/android/` +- Use the `osfirebasemessaging-android` AAR library for messaging functionality +- Keep Gradle dependencies in `src/android/com/outsystems/firebase/cloudmessaging/build.gradle` + +### JavaScript Code + +- Follow existing patterns in `www/OSFirebaseCloudMessaging.js` +- Maintain backward compatibility with existing API + +## Pull Request Process + +### Before Submitting + +1. Ensure your branch follows the naming convention +2. Verify commit messages follow conventional commit format +3. Update `CHANGELOG.md` with your changes +4. Test on both platforms if applicable +5. Update documentation if adding/changing features + +### PR Title Format + +Must follow: `RMET-XXXX ` + +Example: `RMET-4953 Handle errors in RegisterDevice` + +### PR Checklist + +Use the provided template (`pull_request_template.md`). Ensure: + +- [ ] PR title includes ticket reference (`RMET-XXXX`) +- [ ] Code follows project style +- [ ] `CHANGELOG.md` updated correctly +- [ ] Documentation updated if needed +- [ ] Type of change specified (fix/feature/refactor/breaking) +- [ ] Affected platforms marked (Android/iOS/JavaScript) +- [ ] Testing details provided + +### Review Process + +- PRs require review from `@OutSystems/rd-mobile-ecosystem` (see `CODEOWNERS`) +- Address review feedback with new commits +- Once approved, the team will merge + +## Versioning + +This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +When releasing: +1. Update version in `package.json` +2. Update version in `plugin.xml` (both `version` attribute and `<plugin>` tag) +3. Update `CHANGELOG.md` with version header and release date +4. Create a release commit: `chore(release): raise to version X.Y.Z` + +## Useful Commands + +| Command | Description | +|---------|-------------| +| `npm install` | Install Node.js dependencies | +| `git log --oneline -20` | View recent commits for format patterns | +| `git branch -r` | View remote branches for naming patterns | +| `cordova plugin add <path>` | Test plugin in Cordova project | +| `npx cap sync` | Test plugin in Capacitor project | + +## Resources + +- [Cordova Plugin Development Guide](https://cordova.apache.org/docs/en/latest/guide/hybrid/plugins/) +- [Keep a Changelog](https://keepachangelog.com/) +- [Semantic Versioning](https://semver.org/) +- [Conventional Commits](https://www.conventionalcommits.org/) diff --git a/package.json b/package.json index 7b3401b..9519724 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.outsystems.firebase.cloudmessaging", - "version": "2.5.6", + "version": "2.6.2", "description": "Outsystems plugin for Firebase Cloud Messaging", "keywords": [ "ecosystem:cordova", diff --git a/plugin.xml b/plugin.xml index 94e6628..b48e747 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<plugin id="com.outsystems.firebase.cloudmessaging" version="2.5.6" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android"> +<plugin id="com.outsystems.firebase.cloudmessaging" version="2.6.2" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android"> <name>OSFirebaseCloudMessaging</name> <description>Outsystems plugin for Firebase Cloud Messaging</description>