capacitor-messenger-notifications 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MessengerNotifications.podspec +18 -0
- package/Package.swift +26 -0
- package/README.md +152 -0
- package/android/build.gradle +62 -0
- package/android/src/main/AndroidManifest.xml +19 -0
- package/android/src/main/java/com/codecraft_studio/messenger/notifications/EncryptedMessageNotifier.java +68 -0
- package/android/src/main/java/com/codecraft_studio/messenger/notifications/FcmFetchManager.java +37 -0
- package/android/src/main/java/com/codecraft_studio/messenger/notifications/MessengerNotificationsPlugin.java +92 -0
- package/android/src/main/java/com/codecraft_studio/messenger/notifications/NativeCrypto.java +43 -0
- package/android/src/main/java/com/codecraft_studio/messenger/notifications/NotificationDismissReceiver.java +20 -0
- package/android/src/main/java/com/codecraft_studio/messenger/notifications/NotificationHelper.java +618 -0
- package/android/src/main/java/com/codecraft_studio/messenger/notifications/PersistentSocketService.java +213 -0
- package/dist/esm/definitions.d.ts +48 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +25 -0
- package/dist/esm/web.js +42 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +56 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +59 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/MessengerNotificationsPlugin/MessengerNotificationsPlugin.swift +76 -0
- package/ios/Sources/MessengerNotificationsPlugin/NativeCrypto.swift +22 -0
- package/ios/Sources/MessengerNotificationsPlugin/NotificationHelper.swift +58 -0
- package/ios/Sources/MessengerNotificationsPlugin/SafeStorageStore.swift +28 -0
- package/ios/Sources/MessengerNotificationsPlugin/TemporarySocketSessionManager.swift +186 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CodeCraft Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'MessengerNotifications'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '13.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.dependency 'Socket.io-client-swift', '~> 15.0'
|
|
17
|
+
s.swift_version = '5.1'
|
|
18
|
+
end
|
package/Package.swift
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "MessengerNotifications",
|
|
6
|
+
platforms: [.iOS(.v13)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "MessengerNotifications",
|
|
10
|
+
targets: ["MessengerNotificationsPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.1.0"),
|
|
14
|
+
.package(url: "https://github.com/socketio/socket.io-client-swift.git", from: "16.1.0")
|
|
15
|
+
],
|
|
16
|
+
targets: [
|
|
17
|
+
.target(
|
|
18
|
+
name: "MessengerNotificationsPlugin",
|
|
19
|
+
dependencies: [
|
|
20
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
21
|
+
.product(name: "Cordova", package: "capacitor-swift-pm"),
|
|
22
|
+
.product(name: "SocketIO", package: "socket.io-client-swift")
|
|
23
|
+
],
|
|
24
|
+
path: "ios/Sources/MessengerNotificationsPlugin")
|
|
25
|
+
]
|
|
26
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# capacitor-messenger-notifications
|
|
2
|
+
|
|
3
|
+
[](https://github.com/imuhammadnadeem/capacitor-messenger-notifications/blob/main/LICENSE)
|
|
4
|
+
|
|
5
|
+
Capacitor plugin for managing messenger-style notifications with WebSocket support on Android and iOS. Built for end-to-end encrypted chat applications.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [Requirements](#requirements)
|
|
11
|
+
- [Install](#install)
|
|
12
|
+
- [Usage](#usage)
|
|
13
|
+
- [JavaScript Examples](#javascript-examples)
|
|
14
|
+
- [Native Integration (Android/iOS)](#native-integration-androidios)
|
|
15
|
+
- [Configuration](#configuration)
|
|
16
|
+
- [Platform Implementation](#platform-implementation)
|
|
17
|
+
- [Setup by Platform](#setup-by-platform)
|
|
18
|
+
- [API](#api)
|
|
19
|
+
- [Troubleshooting](#troubleshooting)
|
|
20
|
+
- [Development](#development)
|
|
21
|
+
- [Contributing](#contributing)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **Native Notification Grouping**: Automatically groups messages by room ID using `MessagingStyle` (Android) and `threadIdentifier` (iOS).
|
|
27
|
+
- **Persistent WebSocket (Android)**: Foreground service that stays alive to receive messages even when the app is killed.
|
|
28
|
+
- **Background Fetch (iOS)**: Automatically wakes up to fetch unread messages when a silent push is received.
|
|
29
|
+
- **End-to-End Encryption Ready**: Designed to handle encrypted payloads and decrypt them on-device before showing notifications.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- **Capacitor**: ^6.0.0 || ^7.0.0 || ^8.0.0
|
|
34
|
+
- **Node.js**: 18.x or higher
|
|
35
|
+
- **iOS**: 13.0 or higher
|
|
36
|
+
- **Android**: API level 22 (Android 5.1) or higher
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install capacitor-messenger-notifications
|
|
42
|
+
npx cap sync
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### JavaScript Examples
|
|
48
|
+
|
|
49
|
+
#### Start Persistent Socket (Android)
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { MessengerNotifications } from 'capacitor-messenger-notifications';
|
|
53
|
+
|
|
54
|
+
// Starts a foreground service on Android to maintain a heartbeat connection
|
|
55
|
+
await MessengerNotifications.startPersistentSocket({
|
|
56
|
+
url: 'wss://your-chat-server.com',
|
|
57
|
+
token: 'YOUR_AUTH_TOKEN'
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Show a Manual Notification
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
await MessengerNotifications.showNotification({
|
|
65
|
+
title: 'John Doe',
|
|
66
|
+
body: 'Hey, how are you?',
|
|
67
|
+
roomId: 101,
|
|
68
|
+
roomName: 'General Chat',
|
|
69
|
+
messageId: 'uuid-12345',
|
|
70
|
+
timestamp: Date.now()
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Native Integration (Android/iOS)
|
|
75
|
+
|
|
76
|
+
This plugin is often triggered from native background tasks (FCM / Silent Push).
|
|
77
|
+
|
|
78
|
+
- **Android**: Use `EncryptedMessageNotifier.notifyFromSocketPayload(context, data)` from your background services.
|
|
79
|
+
- **iOS**: Use `TemporarySocketSessionManager.shared.fetchAndNotify(...)` inside `didReceiveRemoteNotification`.
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
Most configurations are handled dynamically via the API, but you can define defaults in **`capacitor.config.json`**:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"plugins": {
|
|
88
|
+
"MessengerNotifications": {
|
|
89
|
+
"defaultSocketUrl": "wss://your-default-server.com",
|
|
90
|
+
"notificationChannelId": "chat_messages",
|
|
91
|
+
"notificationChannelName": "Messenger Notifications"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Platform Implementation
|
|
98
|
+
|
|
99
|
+
| Platform | Implementation |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| Android | Foreground Service + NotificationManager with `MessagingStyle`. |
|
|
102
|
+
| iOS | `UNNotificationContent` with `threadIdentifier` + SocketIO Task. |
|
|
103
|
+
|
|
104
|
+
## Setup by Platform
|
|
105
|
+
|
|
106
|
+
> For full host‑app steps (Android & iOS), see `HOST_APP_SETUP.md`.
|
|
107
|
+
|
|
108
|
+
### Android
|
|
109
|
+
|
|
110
|
+
- **AndroidManifest.xml**: Add `FOREGROUND_SERVICE` and `POST_NOTIFICATIONS` permissions.
|
|
111
|
+
- **ProGuard**: Add `-keep` rules for the plugin and Socket.IO.
|
|
112
|
+
|
|
113
|
+
### iOS
|
|
114
|
+
|
|
115
|
+
- **Capabilities**: Enable **Push Notifications** and **Background Modes** (Background fetch, Remote notifications).
|
|
116
|
+
|
|
117
|
+
## API
|
|
118
|
+
|
|
119
|
+
| Method | Description |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `startPersistentSocket(options)` | Starts the background socket service (Android only). |
|
|
122
|
+
| `stopPersistentSocket()` | Stops the background socket service. |
|
|
123
|
+
| `showNotification(options)` | Manually triggers a native grouped notification. |
|
|
124
|
+
| `clearRoomNotification(options)` | Clears all notifications for a specific room. |
|
|
125
|
+
| `getPendingRoomId()` | Returns the roomId if the app was launched from a notification. |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Troubleshooting
|
|
130
|
+
|
|
131
|
+
### Android: Service is killed
|
|
132
|
+
|
|
133
|
+
Ensure you have added `android:foregroundServiceType="dataSync"` to the service declaration in `AndroidManifest.xml` as required by Android 14+.
|
|
134
|
+
|
|
135
|
+
### iOS: Notifications aren't grouping
|
|
136
|
+
|
|
137
|
+
Ensure `threadIdentifier` is correctly set in the push payload or that you are passing the same `roomId` to `showNotification`.
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
- **Build**: `npm run build`
|
|
142
|
+
- **Lint**: `npm run lint`
|
|
143
|
+
- **Format**: `npm run fmt`
|
|
144
|
+
- **Verify**: `npm run verify`
|
|
145
|
+
|
|
146
|
+
## Contributing
|
|
147
|
+
|
|
148
|
+
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
|
|
3
|
+
androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.12.0'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
buildscript {
|
|
7
|
+
repositories {
|
|
8
|
+
google()
|
|
9
|
+
mavenCentral()
|
|
10
|
+
}
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath 'com.android.tools.build:gradle:8.2.1'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
apply plugin: 'com.android.library'
|
|
17
|
+
|
|
18
|
+
android {
|
|
19
|
+
namespace "com.codecraft_studio.messenger.notifications"
|
|
20
|
+
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
|
21
|
+
defaultConfig {
|
|
22
|
+
minSdk project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
|
|
23
|
+
targetSdk project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
|
|
24
|
+
versionCode 1
|
|
25
|
+
versionName "1.0"
|
|
26
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
27
|
+
}
|
|
28
|
+
buildTypes {
|
|
29
|
+
release {
|
|
30
|
+
minifyEnabled false
|
|
31
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
lintOptions {
|
|
35
|
+
abortOnError false
|
|
36
|
+
}
|
|
37
|
+
compileOptions {
|
|
38
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
39
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
repositories {
|
|
44
|
+
google()
|
|
45
|
+
mavenCentral()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dependencies {
|
|
49
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
50
|
+
implementation project(':capacitor-android')
|
|
51
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
52
|
+
implementation "androidx.core:core:$androidxCoreVersion"
|
|
53
|
+
|
|
54
|
+
// Socket.IO client for Android
|
|
55
|
+
implementation ('io.socket:socket.io-client:2.1.0') {
|
|
56
|
+
exclude group: 'org.json', module: 'json'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
testImplementation 'junit:junit:4.13.2'
|
|
60
|
+
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
|
61
|
+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
|
62
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<application>
|
|
3
|
+
<service
|
|
4
|
+
android:name="com.codecraft_studio.messenger.notifications.PersistentSocketService"
|
|
5
|
+
android:enabled="true"
|
|
6
|
+
android:exported="false"
|
|
7
|
+
android:foregroundServiceType="dataSync" />
|
|
8
|
+
|
|
9
|
+
<receiver
|
|
10
|
+
android:name="com.codecraft_studio.messenger.notifications.NotificationDismissReceiver"
|
|
11
|
+
android:enabled="true"
|
|
12
|
+
android:exported="false" />
|
|
13
|
+
</application>
|
|
14
|
+
|
|
15
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
16
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
17
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
|
18
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
19
|
+
</manifest>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.codecraft_studio.messenger.notifications;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
import java.util.Map;
|
|
6
|
+
import org.json.JSONObject;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handles incoming push/socket payloads, decrypts them, and triggers notifications.
|
|
10
|
+
*/
|
|
11
|
+
public class EncryptedMessageNotifier {
|
|
12
|
+
|
|
13
|
+
private static final String TAG = "MessageNotifier";
|
|
14
|
+
|
|
15
|
+
public static boolean notifyFromPushData(Context context, Map<String, String> data) {
|
|
16
|
+
if (data == null || data.isEmpty()) return false;
|
|
17
|
+
try {
|
|
18
|
+
int roomId = Integer.parseInt(data.get("roomId") != null ? data.get("roomId") : data.get("room_id"));
|
|
19
|
+
String senderName = data.get("senderName") != null ? data.get("senderName") : data.get("title");
|
|
20
|
+
String body = data.get("body") != null ? data.get("body") : data.get("message");
|
|
21
|
+
String messageId = data.get("messageId") != null ? data.get("messageId") : data.get("id");
|
|
22
|
+
long timestamp = System.currentTimeMillis();
|
|
23
|
+
|
|
24
|
+
NotificationHelper.showRoomNotification(context, senderName, body, roomId, messageId, timestamp);
|
|
25
|
+
return true;
|
|
26
|
+
} catch (Exception e) {
|
|
27
|
+
Log.e(TAG, "Error processing push data", e);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static boolean notifyFromSocketPayload(Context context, Object payload) {
|
|
33
|
+
if (!(payload instanceof JSONObject)) return false;
|
|
34
|
+
JSONObject obj = (JSONObject) payload;
|
|
35
|
+
try {
|
|
36
|
+
int roomId = obj.optInt("room_id", obj.optInt("roomId"));
|
|
37
|
+
if (roomId <= 0) return false;
|
|
38
|
+
|
|
39
|
+
String encryptedMsg = obj.optString("encrypted_message", obj.optString("encryptedMessage"));
|
|
40
|
+
String encryptedUser = obj.optString("encrypted_username", obj.optString("encryptedUsername"));
|
|
41
|
+
String encryptedRoom = obj.optString("encrypted_room_name", obj.optString("encryptedRoomName"));
|
|
42
|
+
|
|
43
|
+
String senderName = encryptedUser.isEmpty()
|
|
44
|
+
? "New Message"
|
|
45
|
+
: NativeCrypto.decryptUserData(obj.optInt("sender_id"), encryptedUser).text;
|
|
46
|
+
String messageBody = encryptedMsg.isEmpty() ? "New encrypted message" : NativeCrypto.decryptRoomData(roomId, encryptedMsg).text;
|
|
47
|
+
String roomName = encryptedRoom.isEmpty() ? null : NativeCrypto.decryptRoomData(roomId, encryptedRoom).text;
|
|
48
|
+
|
|
49
|
+
String messageId = obj.optString("id", obj.optString("messageId"));
|
|
50
|
+
long timestamp = obj.optLong("timestamp", System.currentTimeMillis());
|
|
51
|
+
|
|
52
|
+
NotificationHelper.showRoomNotification(context, senderName, messageBody, roomId, roomName, messageId, timestamp, false);
|
|
53
|
+
return true;
|
|
54
|
+
} catch (Exception e) {
|
|
55
|
+
Log.e(TAG, "Error processing socket payload", e);
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public static boolean notifyFromSyncMessagesResponse(Context context, Object arg) {
|
|
61
|
+
// For sync response, we just treat each message as a socket payload.
|
|
62
|
+
return notifyFromSocketPayload(context, arg);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public static void notifyFromUnreadApiRecord(Context context, JSONObject item) {
|
|
66
|
+
notifyFromSocketPayload(context, item);
|
|
67
|
+
}
|
|
68
|
+
}
|
package/android/src/main/java/com/codecraft_studio/messenger/notifications/FcmFetchManager.java
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.codecraft_studio.messenger.notifications;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import java.util.Map;
|
|
5
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tracks when a notification was last shown per room ID.
|
|
9
|
+
* Used to avoid duplicate notifications.
|
|
10
|
+
*/
|
|
11
|
+
public final class FcmFetchManager {
|
|
12
|
+
|
|
13
|
+
private static final String TAG = "FcmFetchManager";
|
|
14
|
+
private static final Map<Integer, Long> lastRoomNotificationMs = new ConcurrentHashMap<>();
|
|
15
|
+
private static final long NOTIFICATION_GRACE_MS = 10_000L;
|
|
16
|
+
private static volatile long lastAnyNotificationMs = 0L;
|
|
17
|
+
|
|
18
|
+
private FcmFetchManager() {}
|
|
19
|
+
|
|
20
|
+
public static void markNotificationShown(int roomId) {
|
|
21
|
+
long now = System.currentTimeMillis();
|
|
22
|
+
lastRoomNotificationMs.put(roomId, now);
|
|
23
|
+
lastAnyNotificationMs = now;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static boolean wasNotificationShownRecently(int roomId) {
|
|
27
|
+
Long last = lastRoomNotificationMs.get(roomId);
|
|
28
|
+
if (last == null) return false;
|
|
29
|
+
return (System.currentTimeMillis() - last) < NOTIFICATION_GRACE_MS;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static boolean wasAnyNotificationShownRecently(long windowMs) {
|
|
33
|
+
long last = lastAnyNotificationMs;
|
|
34
|
+
if (last <= 0L) return false;
|
|
35
|
+
return (System.currentTimeMillis() - last) < Math.max(0L, windowMs);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package com.codecraft_studio.messenger.notifications;
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
import com.getcapacitor.JSObject;
|
|
6
|
+
import com.getcapacitor.Plugin;
|
|
7
|
+
import com.getcapacitor.PluginCall;
|
|
8
|
+
import com.getcapacitor.PluginMethod;
|
|
9
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
10
|
+
|
|
11
|
+
@CapacitorPlugin(name = "MessengerNotifications")
|
|
12
|
+
public class MessengerNotificationsPlugin extends Plugin {
|
|
13
|
+
|
|
14
|
+
private static final String TAG = "MessengerNotifications";
|
|
15
|
+
|
|
16
|
+
@Override
|
|
17
|
+
public void load() {
|
|
18
|
+
Log.i(TAG, "Plugin loaded");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@PluginMethod
|
|
22
|
+
public void showNotification(PluginCall call) {
|
|
23
|
+
String title = call.getString("title", "New Message");
|
|
24
|
+
String body = call.getString("body", "You have a new message");
|
|
25
|
+
int roomId = call.getInt("roomId", 0);
|
|
26
|
+
String messageId = call.getString("messageId");
|
|
27
|
+
long timestamp = call.getLong("timestamp", 0L);
|
|
28
|
+
String roomName = call.getString("roomName");
|
|
29
|
+
|
|
30
|
+
Log.i(TAG, "showNotification() title=" + title + " roomId=" + roomId + " messageId=" + messageId);
|
|
31
|
+
|
|
32
|
+
NotificationHelper.showRoomNotification(getContext(), title, body, roomId, roomName, messageId, timestamp, false);
|
|
33
|
+
call.resolve();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@PluginMethod
|
|
37
|
+
public void clearRoomNotification(PluginCall call) {
|
|
38
|
+
int roomId = call.getInt("roomId", 0);
|
|
39
|
+
if (roomId <= 0) {
|
|
40
|
+
call.resolve();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
Log.i(TAG, "clearRoomNotification() roomId=" + roomId);
|
|
45
|
+
NotificationHelper.clearRoomHistory(getContext(), roomId, true);
|
|
46
|
+
call.resolve();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@PluginMethod
|
|
50
|
+
public void getPendingRoomId(PluginCall call) {
|
|
51
|
+
JSObject result = new JSObject();
|
|
52
|
+
// Since we don't have MainActivity.pendingRoomId in a plugin,
|
|
53
|
+
// we might want to store it in SharedPreferences or through a static field in the plugin.
|
|
54
|
+
// For now, let's assume we store it in a static field here.
|
|
55
|
+
Integer roomId = NotificationHelper.getPendingRoomId();
|
|
56
|
+
if (roomId != null) {
|
|
57
|
+
result.put("roomId", roomId);
|
|
58
|
+
NotificationHelper.consumePendingRoomId();
|
|
59
|
+
} else {
|
|
60
|
+
result.put("roomId", JSObject.NULL);
|
|
61
|
+
}
|
|
62
|
+
call.resolve(result);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@PluginMethod
|
|
66
|
+
public void startPersistentSocket(PluginCall call) {
|
|
67
|
+
String url = call.getString("url");
|
|
68
|
+
String token = call.getString("token");
|
|
69
|
+
|
|
70
|
+
if (url == null || token == null) {
|
|
71
|
+
call.reject("Missing url or token");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Store these for the service to use (e.g. in SharedPreferences)
|
|
76
|
+
getContext()
|
|
77
|
+
.getSharedPreferences("messenger_plugin_prefs", android.content.Context.MODE_PRIVATE)
|
|
78
|
+
.edit()
|
|
79
|
+
.putString("socket_url", url)
|
|
80
|
+
.putString("auth_token", token)
|
|
81
|
+
.apply();
|
|
82
|
+
|
|
83
|
+
PersistentSocketService.start(getContext());
|
|
84
|
+
call.resolve();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@PluginMethod
|
|
88
|
+
public void stopPersistentSocket(PluginCall call) {
|
|
89
|
+
PersistentSocketService.stop(getContext());
|
|
90
|
+
call.resolve();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package com.codecraft_studio.messenger.notifications;
|
|
2
|
+
|
|
3
|
+
import android.util.Base64;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
import androidx.annotation.NonNull;
|
|
6
|
+
import java.nio.charset.StandardCharsets;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Native crypto logic picked from ChatE2EE.
|
|
10
|
+
* In a real plugin, this would interface with a security library or the app's keys.
|
|
11
|
+
*/
|
|
12
|
+
public class NativeCrypto {
|
|
13
|
+
|
|
14
|
+
private static final String TAG = "NativeCrypto";
|
|
15
|
+
|
|
16
|
+
public static class DecryptResult {
|
|
17
|
+
|
|
18
|
+
public final String text;
|
|
19
|
+
|
|
20
|
+
public DecryptResult(String text) {
|
|
21
|
+
this.text = text;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Placeholder decryption logic.
|
|
27
|
+
* In the real app, this uses Lipsum or similar.
|
|
28
|
+
* For the plugin, we assume the host app might provide keys or we just return the ciphertext
|
|
29
|
+
* if we can't decrypt it yet.
|
|
30
|
+
*/
|
|
31
|
+
public static DecryptResult decryptRoomData(int roomId, @NonNull String encryptedJSON) {
|
|
32
|
+
// This is a placeholder.
|
|
33
|
+
// In the original app, it calls a native method or a complex JS bridge.
|
|
34
|
+
// For the plugin, we'll try to decrypt if we have the keys, otherwise return as-is.
|
|
35
|
+
Log.d(TAG, "decryptRoomData() roomId=" + roomId);
|
|
36
|
+
return new DecryptResult(encryptedJSON); // Placeholder
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static DecryptResult decryptUserData(int userId, @NonNull String encryptedJSON) {
|
|
40
|
+
Log.d(TAG, "decryptUserData() userId=" + userId);
|
|
41
|
+
return new DecryptResult(encryptedJSON); // Placeholder
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.codecraft_studio.messenger.notifications;
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
public class NotificationDismissReceiver extends BroadcastReceiver {
|
|
9
|
+
|
|
10
|
+
private static final String TAG = "NotificationDismiss";
|
|
11
|
+
|
|
12
|
+
@Override
|
|
13
|
+
public void onReceive(Context context, Intent intent) {
|
|
14
|
+
int roomId = intent.getIntExtra(NotificationHelper.EXTRA_ROOM_ID, 0);
|
|
15
|
+
Log.d(TAG, "Notification dismissed for roomId: " + roomId);
|
|
16
|
+
if (roomId > 0) {
|
|
17
|
+
NotificationHelper.onNotificationDismissed(context, roomId);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|