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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public enum SafeStorageStore {
|
|
4
|
+
private static let rootKey = "messenger_plugin_storage"
|
|
5
|
+
|
|
6
|
+
public static func getAll() -> [String: String] {
|
|
7
|
+
guard let dict = UserDefaults.standard.dictionary(forKey: rootKey) as? [String: String] else {
|
|
8
|
+
return [:]
|
|
9
|
+
}
|
|
10
|
+
return dict
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public static func get(_ key: String) -> String? {
|
|
14
|
+
return getAll()[key]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static func set(_ key: String, value: String?) {
|
|
18
|
+
var dict = getAll()
|
|
19
|
+
dict[key] = value
|
|
20
|
+
UserDefaults.standard.set(dict, forKey: rootKey)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public static func remove(_ key: String) {
|
|
24
|
+
var dict = getAll()
|
|
25
|
+
dict.removeValue(forKey: key)
|
|
26
|
+
UserDefaults.standard.set(dict, forKey: rootKey)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import SocketIO
|
|
3
|
+
import os.log
|
|
4
|
+
|
|
5
|
+
public enum TemporarySocketSessionManager {
|
|
6
|
+
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "com.codecraft_studio.messenger.notifications",
|
|
7
|
+
category: "Notifications")
|
|
8
|
+
private static let defaultIdleTimeout: TimeInterval = 15
|
|
9
|
+
private static let defaultConnectTimeout: TimeInterval = 20
|
|
10
|
+
private static let defaultMaxSession: TimeInterval = 45
|
|
11
|
+
|
|
12
|
+
private static let messageEvents: Set<String> = [
|
|
13
|
+
"sync_messages_response",
|
|
14
|
+
"sync:messages",
|
|
15
|
+
"room:message_notification"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
public static func runSession(payloadData: [String: Any]?, completion: @escaping (Bool) -> Void) {
|
|
19
|
+
let jwt = SafeStorageStore.get("auth_token") ?? ""
|
|
20
|
+
let socketFromPayload = payloadData?["socketUrl"] as? String
|
|
21
|
+
let socketFromPrefs = SafeStorageStore.get("socket_url")
|
|
22
|
+
let base = socketFromPayload ?? socketFromPrefs ?? "wss://4.rw"
|
|
23
|
+
|
|
24
|
+
if jwt.isEmpty {
|
|
25
|
+
completion(false)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let url = normalizeSocketBaseUrl(base)
|
|
30
|
+
guard let socketURL = URL(string: url) else {
|
|
31
|
+
completion(false)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let manager = SocketManager(
|
|
36
|
+
socketURL: socketURL,
|
|
37
|
+
config: [
|
|
38
|
+
.forceNew(true),
|
|
39
|
+
.reconnects(false),
|
|
40
|
+
.log(false),
|
|
41
|
+
.connectParams([
|
|
42
|
+
"token": jwt,
|
|
43
|
+
"auth[token]": jwt
|
|
44
|
+
]),
|
|
45
|
+
.compress
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
let socket = manager.defaultSocket
|
|
50
|
+
var finished = false
|
|
51
|
+
var messageReceived = false
|
|
52
|
+
var syncResponseReceived = false
|
|
53
|
+
|
|
54
|
+
let finish: () -> Void = {
|
|
55
|
+
if finished { return }
|
|
56
|
+
finished = true
|
|
57
|
+
socket.removeAllHandlers()
|
|
58
|
+
socket.disconnect()
|
|
59
|
+
completion(messageReceived)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var idleTimer: Timer?
|
|
63
|
+
func resetIdleTimer() {
|
|
64
|
+
idleTimer?.invalidate()
|
|
65
|
+
idleTimer = Timer.scheduledTimer(withTimeInterval: defaultIdleTimeout, repeats: false) { _ in
|
|
66
|
+
finish()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
socket.on(clientEvent: .connect) { _, _ in
|
|
71
|
+
resetIdleTimer()
|
|
72
|
+
if let payloadData = payloadData {
|
|
73
|
+
let roomId = (payloadData["roomId"] as? Int) ?? (payloadData["room_id"] as? Int) ?? 0
|
|
74
|
+
if roomId > 0 {
|
|
75
|
+
socket.emit("join_room", String(roomId))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
socket.emit("sync_messages")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
socket.onAny { event in
|
|
82
|
+
let name = event.event
|
|
83
|
+
let items = event.items ?? []
|
|
84
|
+
|
|
85
|
+
if name == "sync_messages_response" {
|
|
86
|
+
syncResponseReceived = true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if messageEvents.contains(name) {
|
|
90
|
+
if handleSocketArgs(event: name, args: items, syncReceived: syncResponseReceived) {
|
|
91
|
+
messageReceived = true
|
|
92
|
+
resetIdleTimer()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
socket.on(clientEvent: .disconnect) { _, _ in
|
|
98
|
+
finish()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
socket.connect(timeoutAfter: defaultConnectTimeout) {
|
|
102
|
+
finish()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + defaultMaxSession) {
|
|
106
|
+
if !finished { finish() }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private static func normalizeSocketBaseUrl(_ baseUrl: String) -> String {
|
|
111
|
+
var normalized = baseUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
112
|
+
if normalized.hasSuffix("/") { normalized = String(normalized.dropLast()) }
|
|
113
|
+
if normalized.hasSuffix("/api") { normalized = String(normalized.dropLast(4)) }
|
|
114
|
+
if !normalized.contains("://") { normalized = "https://\(normalized)" }
|
|
115
|
+
return normalized
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private static func handleSocketArgs(event: String, args: [Any], syncReceived: Bool) -> Bool {
|
|
119
|
+
guard !args.isEmpty else { return false }
|
|
120
|
+
var handled = false
|
|
121
|
+
for arg in args {
|
|
122
|
+
guard let dict = arg as? [String: Any] ?? (arg as? NSDictionary as? [String: Any]) else {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
if handleNormalizedRecord(dict, isSync: event == "sync_messages_response") {
|
|
126
|
+
handled = true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return handled
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private static func handleNormalizedRecord(_ payload: [String: Any], isSync: Bool) -> Bool {
|
|
133
|
+
guard let roomId = (payload["room_id"] as? Int) ?? (payload["roomId"] as? Int),
|
|
134
|
+
roomId > 0 else { return false }
|
|
135
|
+
|
|
136
|
+
let senderId = (payload["sender_id"] as? Int) ?? (payload["senderId"] as? Int) ?? 0
|
|
137
|
+
|
|
138
|
+
let encryptedMessage = payload["encrypted_message"] as? String
|
|
139
|
+
?? payload["encryptedMessage"] as? String
|
|
140
|
+
?? payload["ciphertext"] as? String
|
|
141
|
+
|
|
142
|
+
let encryptedUsername = payload["encrypted_username"] as? String
|
|
143
|
+
?? payload["encryptedUsername"] as? String
|
|
144
|
+
|
|
145
|
+
let encryptedRoomName = payload["encrypted_room_name"] as? String
|
|
146
|
+
?? payload["encryptedRoomName"] as? String
|
|
147
|
+
|
|
148
|
+
var roomName: String?
|
|
149
|
+
if let encRoom = encryptedRoomName {
|
|
150
|
+
roomName = try? NativeCrypto.decryptRoomData(roomId: roomId, encryptedJSON: encRoom).text
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
var username: String?
|
|
154
|
+
if let encUser = encryptedUsername {
|
|
155
|
+
username = try? NativeCrypto.decryptUserData(userId: senderId, encryptedJSON: encUser).text
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var messageText: String?
|
|
159
|
+
if let encMsg = encryptedMessage {
|
|
160
|
+
messageText = try? NativeCrypto.decryptRoomData(roomId: roomId, encryptedJSON: encMsg).text
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let finalTitle = username ?? roomName ?? "New message"
|
|
164
|
+
let finalBody = messageText ?? "New encrypted message"
|
|
165
|
+
|
|
166
|
+
let messageId = (payload["id"] as? String)
|
|
167
|
+
?? (payload["messageId"] as? String)
|
|
168
|
+
?? (payload["message_id"] as? String)
|
|
169
|
+
|
|
170
|
+
let timestampMs: Int64 = {
|
|
171
|
+
if let ts = payload["timestamp"] as? Int64 { return ts }
|
|
172
|
+
if let ts = payload["timestamp"] as? Int { return Int64(ts) }
|
|
173
|
+
return Int64(Date().timeIntervalSince1970 * 1000)
|
|
174
|
+
}()
|
|
175
|
+
|
|
176
|
+
NotificationHelper.showRoomNotification(
|
|
177
|
+
title: finalTitle,
|
|
178
|
+
body: finalBody,
|
|
179
|
+
roomId: roomId,
|
|
180
|
+
messageId: messageId,
|
|
181
|
+
timestamp: timestampMs,
|
|
182
|
+
isSync: isSync
|
|
183
|
+
)
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capacitor-messenger-notifications",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Capacitor plugin for managing messenger notifications using a web socket in Android and iOS.",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"Package.swift",
|
|
15
|
+
"MessengerNotifications.podspec"
|
|
16
|
+
],
|
|
17
|
+
"author": "Muhammad Nadeem",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/imuhammadnadeem/capacitor-messenger-notifications.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/imuhammadnadeem/capacitor-messenger-notifications/issues"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"capacitor",
|
|
28
|
+
"capacitor-plugin",
|
|
29
|
+
"messenger",
|
|
30
|
+
"notifications",
|
|
31
|
+
"socket.io",
|
|
32
|
+
"android",
|
|
33
|
+
"ios"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
37
|
+
"verify:ios": "xcodebuild -scheme MessengerNotifications -destination 'generic/platform=iOS' -configuration Debug build",
|
|
38
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
39
|
+
"verify:web": "npm run build",
|
|
40
|
+
"lint": "npm run eslint && npm run prettier",
|
|
41
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write",
|
|
42
|
+
"eslint": "eslint . --ext ts",
|
|
43
|
+
"prettier": "prettier \"{src,android/src}/**/*.{css,html,ts,js,java}\" --write --plugin=prettier-plugin-java",
|
|
44
|
+
"build": "npm run clean && tsc && rollup -c rollup.config.mjs",
|
|
45
|
+
"clean": "rimraf ./dist",
|
|
46
|
+
"watch": "tsc --watch",
|
|
47
|
+
"prepublishOnly": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@capacitor/android": "^6.0.0",
|
|
51
|
+
"@capacitor/core": "^6.0.0",
|
|
52
|
+
"@capacitor/ios": "^6.0.0",
|
|
53
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
54
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
55
|
+
"eslint": "^8.57.1",
|
|
56
|
+
"prettier": "^3.6.2",
|
|
57
|
+
"prettier-plugin-java": "^2.7.7",
|
|
58
|
+
"rimraf": "^6.1.0",
|
|
59
|
+
"rollup": "^4.53.2",
|
|
60
|
+
"typescript": "^5.5.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@capacitor/core": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
64
|
+
},
|
|
65
|
+
"prettier": "@ionic/prettier-config",
|
|
66
|
+
"eslintConfig": {
|
|
67
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
68
|
+
},
|
|
69
|
+
"capacitor": {
|
|
70
|
+
"ios": {
|
|
71
|
+
"src": "ios"
|
|
72
|
+
},
|
|
73
|
+
"android": {
|
|
74
|
+
"src": "android"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|