-
Notifications
You must be signed in to change notification settings - Fork 9
chore(koda): initialize Koda #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<br/>Runs on: Mobile Device (iOS/Android)<br/>Acts as: Cordova Plugin Bridge"] | ||
|
|
||
| %% External services | ||
| FCM[Firebase Cloud Messaging<br/>EXTERNAL] | ||
| APNs[Apple Push Notification Service<br/>EXTERNAL] | ||
| LocalDB[(Local SQLite/Room Database<br/>EXTERNAL)] | ||
| Azure[OutSystems Azure Artifact Repository<br/>EXTERNAL] | ||
| CocoaPods[CocoaPods CDN<br/>EXTERNAL] | ||
|
|
||
| %% Communication flows | ||
| ThisRepo -->|HTTP/gRPC<br/>Synchronous| FCM | ||
| ThisRepo -->|Native API<br/>Synchronous| APNs | ||
| ThisRepo -->|SQL queries<br/>Synchronous| LocalDB | ||
|
|
||
| %% Build-time dependencies | ||
| ThisRepo -.->|Fetch dependencies<br/>Build-time only| Azure | ||
| ThisRepo -.->|Fetch FirebaseMessaging pod<br/>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 | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe have a line like in https://github.com/OutSystems/cordova-outsystems-secure-sqlite-bundle/pull/48/changes#diff-8f6366fd8e7a5fa30f7f05879999195b7cf5fa1e3d157822eb37f0e91d226cfd - For the OutSystems Plugin that consumes this plugin, which in turn can be used in Mobile Apps? But I guess that's not really a service per-se. Maybe MABS? But that is kind of exterior and out of control from this repo. |
||
| |------------------|-------------------|---------| | ||
| | 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docs mention proprietary here a few times, and I'm not sure if that's really the case. They're not open source I guess, but that doesn't make them proprietary, as they can be technically used in other projects (although we don't support it)? Thinking if |
||
|
|
||
| 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<String>` 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. | ||
|
Comment on lines
+107
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean the evidence is true, but I debate whether or not this is a constraint. It's just in difference between how Cordova and Capacitor apps are structured. And for a regular Capacitor app, it would me a matter of documenting what is necessary in And since deprecating Cordova support entirely has no date in sight, it's not like we'll be getting rid of this soon. So either we can leave this section here but change it to Capacitor vs Cordova, or remove it altogether. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless claude is going to create their own apps when testing (unlikely unless explicitly requested), the way to go here is to manually test using OutSystems apps, particularly, the Firebase Sample App, which is publicly available on Forge. Maybe it's simpler to just state that instead? |
||
|
|
||
| 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on the other comment I made for "Current Phase Constraints: Capacitor Migration Strategy", this may need updates. |
||
|
|
||
| ### 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) | ||
|
Comment on lines
+140
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| ### 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 `<plugin>` 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. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a Cordova Plugin, that is mean for use in OutSystems mobile apps, with either Cordova or Capacitor framework, believe that should be clarified.