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.
Files changed (32) hide show
  1. package/LICENSE +21 -0
  2. package/MessengerNotifications.podspec +18 -0
  3. package/Package.swift +26 -0
  4. package/README.md +152 -0
  5. package/android/build.gradle +62 -0
  6. package/android/src/main/AndroidManifest.xml +19 -0
  7. package/android/src/main/java/com/codecraft_studio/messenger/notifications/EncryptedMessageNotifier.java +68 -0
  8. package/android/src/main/java/com/codecraft_studio/messenger/notifications/FcmFetchManager.java +37 -0
  9. package/android/src/main/java/com/codecraft_studio/messenger/notifications/MessengerNotificationsPlugin.java +92 -0
  10. package/android/src/main/java/com/codecraft_studio/messenger/notifications/NativeCrypto.java +43 -0
  11. package/android/src/main/java/com/codecraft_studio/messenger/notifications/NotificationDismissReceiver.java +20 -0
  12. package/android/src/main/java/com/codecraft_studio/messenger/notifications/NotificationHelper.java +618 -0
  13. package/android/src/main/java/com/codecraft_studio/messenger/notifications/PersistentSocketService.java +213 -0
  14. package/dist/esm/definitions.d.ts +48 -0
  15. package/dist/esm/definitions.js +2 -0
  16. package/dist/esm/definitions.js.map +1 -0
  17. package/dist/esm/index.d.ts +4 -0
  18. package/dist/esm/index.js +7 -0
  19. package/dist/esm/index.js.map +1 -0
  20. package/dist/esm/web.d.ts +25 -0
  21. package/dist/esm/web.js +42 -0
  22. package/dist/esm/web.js.map +1 -0
  23. package/dist/plugin.cjs.js +56 -0
  24. package/dist/plugin.cjs.js.map +1 -0
  25. package/dist/plugin.js +59 -0
  26. package/dist/plugin.js.map +1 -0
  27. package/ios/Sources/MessengerNotificationsPlugin/MessengerNotificationsPlugin.swift +76 -0
  28. package/ios/Sources/MessengerNotificationsPlugin/NativeCrypto.swift +22 -0
  29. package/ios/Sources/MessengerNotificationsPlugin/NotificationHelper.swift +58 -0
  30. package/ios/Sources/MessengerNotificationsPlugin/SafeStorageStore.swift +28 -0
  31. package/ios/Sources/MessengerNotificationsPlugin/TemporarySocketSessionManager.swift +186 -0
  32. package/package.json +77 -0
@@ -0,0 +1,213 @@
1
+ package com.codecraft_studio.messenger.notifications;
2
+
3
+ import android.app.Notification;
4
+ import android.app.NotificationChannel;
5
+ import android.app.NotificationManager;
6
+ import android.app.PendingIntent;
7
+ import android.app.Service;
8
+ import android.content.Context;
9
+ import android.content.Intent;
10
+ import android.content.SharedPreferences;
11
+ import android.os.Build;
12
+ import android.os.IBinder;
13
+ import android.text.TextUtils;
14
+ import android.util.Log;
15
+ import androidx.annotation.Nullable;
16
+ import androidx.core.app.NotificationCompat;
17
+ import io.socket.client.IO;
18
+ import io.socket.client.Socket;
19
+ import io.socket.engineio.client.transports.Polling;
20
+ import io.socket.engineio.client.transports.WebSocket;
21
+ import java.net.URISyntaxException;
22
+ import java.util.Arrays;
23
+ import java.util.HashMap;
24
+ import java.util.HashSet;
25
+ import java.util.Map;
26
+ import java.util.Set;
27
+ import org.json.JSONObject;
28
+
29
+ public class PersistentSocketService extends Service {
30
+
31
+ private static final String TAG = "PersistentSocketSvc";
32
+ private static final String CHANNEL_ID = "persistent_socket_channel";
33
+ private static final int NOTIFICATION_ID = 91005;
34
+
35
+ private static final Set<String> MESSAGE_EVENTS = new HashSet<>(
36
+ Arrays.asList("sync_messages_response", "sync:messages", "room:message_notification")
37
+ );
38
+
39
+ private Socket mSocket;
40
+ private SharedPreferences.OnSharedPreferenceChangeListener mPrefsListener;
41
+ private String mCurrentToken;
42
+
43
+ public static void start(Context context) {
44
+ Intent intent = new Intent(context, PersistentSocketService.class);
45
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
46
+ context.startForegroundService(intent);
47
+ } else {
48
+ context.startService(intent);
49
+ }
50
+ }
51
+
52
+ public static void stop(Context context) {
53
+ context.stopService(new Intent(context, PersistentSocketService.class));
54
+ }
55
+
56
+ @Override
57
+ public void onCreate() {
58
+ super.onCreate();
59
+ ensureChannel();
60
+ startForeground(NOTIFICATION_ID, buildNotification("Messaging Active"));
61
+ setupPrefsListener();
62
+ connectSocket();
63
+ }
64
+
65
+ @Override
66
+ public int onStartCommand(Intent intent, int flags, int startId) {
67
+ return START_STICKY;
68
+ }
69
+
70
+ @Override
71
+ public void onDestroy() {
72
+ super.onDestroy();
73
+ if (mPrefsListener != null) {
74
+ getSharedPreferences("messenger_plugin_prefs", MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(mPrefsListener);
75
+ }
76
+ disconnectSocket();
77
+ }
78
+
79
+ @Nullable
80
+ @Override
81
+ public IBinder onBind(Intent intent) {
82
+ return null;
83
+ }
84
+
85
+ private void setupPrefsListener() {
86
+ mPrefsListener = (prefs, key) -> {
87
+ if ("auth_token".equals(key)) {
88
+ String newToken = prefs.getString("auth_token", null);
89
+ if (!TextUtils.equals(newToken, mCurrentToken)) {
90
+ disconnectSocket();
91
+ connectSocket();
92
+ }
93
+ }
94
+ };
95
+ getSharedPreferences("messenger_plugin_prefs", MODE_PRIVATE).registerOnSharedPreferenceChangeListener(mPrefsListener);
96
+ }
97
+
98
+ private synchronized void connectSocket() {
99
+ if (mSocket != null && mSocket.connected()) return;
100
+
101
+ SharedPreferences prefs = getSharedPreferences("messenger_plugin_prefs", Context.MODE_PRIVATE);
102
+ mCurrentToken = prefs.getString("auth_token", null);
103
+ String socketUrl = prefs.getString("socket_url", null);
104
+
105
+ if (TextUtils.isEmpty(mCurrentToken) || TextUtils.isEmpty(socketUrl)) {
106
+ updateNotification("Waiting for credentials...");
107
+ return;
108
+ }
109
+
110
+ try {
111
+ IO.Options options = new IO.Options();
112
+ options.forceNew = true;
113
+ options.reconnection = true;
114
+ options.reconnectionDelay = 5000;
115
+ options.reconnectionDelayMax = 30000;
116
+ options.timeout = 20000;
117
+ options.transports = new String[] { WebSocket.NAME, Polling.NAME };
118
+
119
+ // Socket.IO v4.x auth mechanism
120
+ Map<String, String> auth = new HashMap<>();
121
+ auth.put("token", mCurrentToken);
122
+ options.auth = auth;
123
+
124
+ mSocket = IO.socket(socketUrl, options);
125
+
126
+ mSocket.on(Socket.EVENT_CONNECT, (args) -> {
127
+ updateNotification("Connected");
128
+ mSocket.emit("sync_messages");
129
+ });
130
+
131
+ mSocket.on(Socket.EVENT_DISCONNECT, (args) -> {
132
+ updateNotification("Reconnecting...");
133
+ });
134
+
135
+ mSocket.on(Socket.EVENT_CONNECT_ERROR, (args) -> {
136
+ updateNotification("Connection error, retrying...");
137
+ });
138
+
139
+ mSocket.onAnyIncoming((args) -> {
140
+ if (args != null && args.length > 0) {
141
+ String event = String.valueOf(args[0]);
142
+ if (MESSAGE_EVENTS.contains(event)) {
143
+ Object[] payloadArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new Object[0];
144
+ handleSocketMessage(event, payloadArgs);
145
+ }
146
+ }
147
+ });
148
+
149
+ mSocket.connect();
150
+ } catch (URISyntaxException e) {
151
+ Log.e(TAG, "Invalid socket URL", e);
152
+ }
153
+ }
154
+
155
+ private void handleSocketMessage(String event, Object[] args) {
156
+ if (args == null || args.length == 0) return;
157
+ boolean syncReceived = "sync_messages_response".equals(event);
158
+ for (Object arg : args) {
159
+ if (syncReceived) {
160
+ EncryptedMessageNotifier.notifyFromSyncMessagesResponse(this, arg);
161
+ } else {
162
+ EncryptedMessageNotifier.notifyFromSocketPayload(this, arg);
163
+ }
164
+ }
165
+ }
166
+
167
+ private void disconnectSocket() {
168
+ if (mSocket != null) {
169
+ mSocket.off();
170
+ mSocket.disconnect();
171
+ mSocket.close();
172
+ mSocket = null;
173
+ }
174
+ }
175
+
176
+ private void ensureChannel() {
177
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
178
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Messaging Connection", NotificationManager.IMPORTANCE_LOW);
179
+ channel.setDescription("Maintains messaging connection background");
180
+ NotificationManager manager = getSystemService(NotificationManager.class);
181
+ if (manager != null) manager.createNotificationChannel(channel);
182
+ }
183
+ }
184
+
185
+ private Notification buildNotification(String contentText) {
186
+ Intent launchIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
187
+ PendingIntent pendingIntent = null;
188
+ if (launchIntent != null) {
189
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
190
+ pendingIntent = PendingIntent.getActivity(
191
+ this,
192
+ 0,
193
+ launchIntent,
194
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
195
+ );
196
+ }
197
+
198
+ return new NotificationCompat.Builder(this, CHANNEL_ID)
199
+ .setSmallIcon(getApplicationInfo().icon)
200
+ .setContentTitle("Chat Messenger")
201
+ .setContentText(contentText)
202
+ .setOngoing(true)
203
+ .setCategory(NotificationCompat.CATEGORY_SERVICE)
204
+ .setOnlyAlertOnce(true)
205
+ .setContentIntent(pendingIntent)
206
+ .build();
207
+ }
208
+
209
+ private void updateNotification(String contentText) {
210
+ NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
211
+ if (manager != null) manager.notify(NOTIFICATION_ID, buildNotification(contentText));
212
+ }
213
+ }
@@ -0,0 +1,48 @@
1
+ import type { PermissionState } from '@capacitor/core';
2
+ export interface PermissionStatus {
3
+ notifications: PermissionState;
4
+ }
5
+ export interface MessengerNotificationsPlugin {
6
+ /**
7
+ * Shows a native notification, grouped by room.
8
+ */
9
+ showNotification(options: {
10
+ title: string;
11
+ body: string;
12
+ roomId: number;
13
+ messageId?: string;
14
+ timestamp?: number;
15
+ roomName?: string;
16
+ }): Promise<void>;
17
+ /**
18
+ * Clears notifications for a specific room.
19
+ */
20
+ clearRoomNotification(options: {
21
+ roomId: number;
22
+ }): Promise<void>;
23
+ /**
24
+ * Returns the roomId that triggered the app launch, if any.
25
+ */
26
+ getPendingRoomId(): Promise<{
27
+ roomId: number | null;
28
+ }>;
29
+ /**
30
+ * Starts a persistent background service (Android only) that maintains a socket connection.
31
+ */
32
+ startPersistentSocket(options: {
33
+ url: string;
34
+ token: string;
35
+ }): Promise<void>;
36
+ /**
37
+ * Stops the persistent background service.
38
+ */
39
+ stopPersistentSocket(): Promise<void>;
40
+ /**
41
+ * Check notification permissions.
42
+ */
43
+ checkPermissions(): Promise<PermissionStatus>;
44
+ /**
45
+ * Request notification permissions.
46
+ */
47
+ requestPermissions(): Promise<PermissionStatus>;
48
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState } from '@capacitor/core';\n\nexport interface PermissionStatus {\n notifications: PermissionState;\n}\n\nexport interface MessengerNotificationsPlugin {\n /**\n * Shows a native notification, grouped by room.\n */\n showNotification(options: {\n title: string;\n body: string;\n roomId: number;\n messageId?: string;\n timestamp?: number;\n roomName?: string;\n }): Promise<void>;\n\n /**\n * Clears notifications for a specific room.\n */\n clearRoomNotification(options: { roomId: number }): Promise<void>;\n\n /**\n * Returns the roomId that triggered the app launch, if any.\n */\n getPendingRoomId(): Promise<{ roomId: number | null }>;\n\n /**\n * Starts a persistent background service (Android only) that maintains a socket connection.\n */\n startPersistentSocket(options: { url: string; token: string }): Promise<void>;\n\n /**\n * Stops the persistent background service.\n */\n stopPersistentSocket(): Promise<void>;\n\n /**\n * Check notification permissions.\n */\n checkPermissions(): Promise<PermissionStatus>;\n\n /**\n * Request notification permissions.\n */\n requestPermissions(): Promise<PermissionStatus>;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { MessengerNotificationsPlugin } from './definitions';
2
+ declare const MessengerNotifications: MessengerNotificationsPlugin;
3
+ export * from './definitions';
4
+ export { MessengerNotifications };
@@ -0,0 +1,7 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+ const MessengerNotifications = registerPlugin('MessengerNotifications', {
3
+ web: () => import('./web').then((m) => new m.MessengerNotificationsWeb()),
4
+ });
5
+ export * from './definitions';
6
+ export { MessengerNotifications };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,sBAAsB,GAAG,cAAc,CAA+B,wBAAwB,EAAE;IACpG,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,yBAAyB,EAAE,CAAC;CAC1E,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,sBAAsB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { MessengerNotificationsPlugin } from './definitions';\n\nconst MessengerNotifications = registerPlugin<MessengerNotificationsPlugin>('MessengerNotifications', {\n web: () => import('./web').then((m) => new m.MessengerNotificationsWeb()),\n});\n\nexport * from './definitions';\nexport { MessengerNotifications };\n"]}
@@ -0,0 +1,25 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ import type { MessengerNotificationsPlugin, PermissionStatus } from './definitions';
3
+ export declare class MessengerNotificationsWeb extends WebPlugin implements MessengerNotificationsPlugin {
4
+ showNotification(options: {
5
+ title: string;
6
+ body: string;
7
+ roomId: number;
8
+ messageId?: string;
9
+ timestamp?: number;
10
+ roomName?: string;
11
+ }): Promise<void>;
12
+ clearRoomNotification(options: {
13
+ roomId: number;
14
+ }): Promise<void>;
15
+ getPendingRoomId(): Promise<{
16
+ roomId: number | null;
17
+ }>;
18
+ startPersistentSocket(options: {
19
+ url: string;
20
+ token: string;
21
+ }): Promise<void>;
22
+ stopPersistentSocket(): Promise<void>;
23
+ checkPermissions(): Promise<PermissionStatus>;
24
+ requestPermissions(): Promise<PermissionStatus>;
25
+ }
@@ -0,0 +1,42 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ export class MessengerNotificationsWeb extends WebPlugin {
3
+ async showNotification(options) {
4
+ console.log('[MessengerNotificationsWeb] showNotification:', options);
5
+ if ('Notification' in window) {
6
+ if (Notification.permission === 'granted') {
7
+ new Notification(options.title, {
8
+ body: options.body,
9
+ data: { roomId: options.roomId, messageId: options.messageId },
10
+ });
11
+ }
12
+ }
13
+ }
14
+ async clearRoomNotification(options) {
15
+ console.log('[MessengerNotificationsWeb] clearRoomNotification:', options);
16
+ }
17
+ async getPendingRoomId() {
18
+ return { roomId: null };
19
+ }
20
+ async startPersistentSocket(options) {
21
+ console.log('[MessengerNotificationsWeb] startPersistentSocket (not supported on web):', options);
22
+ }
23
+ async stopPersistentSocket() {
24
+ console.log('[MessengerNotificationsWeb] stopPersistentSocket (not supported on web)');
25
+ }
26
+ async checkPermissions() {
27
+ if ('Notification' in window) {
28
+ const state = Notification.permission === 'granted' ? 'granted' : Notification.permission === 'denied' ? 'denied' : 'prompt';
29
+ return { notifications: state };
30
+ }
31
+ return { notifications: 'denied' };
32
+ }
33
+ async requestPermissions() {
34
+ if ('Notification' in window) {
35
+ const permission = await Notification.requestPermission();
36
+ const state = permission === 'granted' ? 'granted' : permission === 'denied' ? 'denied' : 'prompt';
37
+ return { notifications: state };
38
+ }
39
+ return { notifications: 'denied' };
40
+ }
41
+ }
42
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,yBAA0B,SAAQ,SAAS;IACtD,KAAK,CAAC,gBAAgB,CAAC,OAOtB;QACC,OAAO,CAAC,GAAG,CAAC,+CAA+C,EAAE,OAAO,CAAC,CAAC;QACtE,IAAI,cAAc,IAAI,MAAM,EAAE,CAAC;YAC7B,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC1C,IAAI,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE;oBAC9B,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;iBAC/D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,OAA2B;QACrD,OAAO,CAAC,GAAG,CAAC,oDAAoD,EAAE,OAAO,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,OAAuC;QACjE,OAAO,CAAC,GAAG,CAAC,2EAA2E,EAAE,OAAO,CAAC,CAAC;IACpG,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;IACzF,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,IAAI,cAAc,IAAI,MAAM,EAAE,CAAC;YAC7B,MAAM,KAAK,GACT,YAAY,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;YACjH,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QAClC,CAAC;QACD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,IAAI,cAAc,IAAI,MAAM,EAAE,CAAC;YAC7B,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,iBAAiB,EAAE,CAAC;YAC1D,MAAM,KAAK,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;YACnG,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QAClC,CAAC;QACD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { MessengerNotificationsPlugin, PermissionStatus } from './definitions';\n\nexport class MessengerNotificationsWeb extends WebPlugin implements MessengerNotificationsPlugin {\n async showNotification(options: {\n title: string;\n body: string;\n roomId: number;\n messageId?: string;\n timestamp?: number;\n roomName?: string;\n }): Promise<void> {\n console.log('[MessengerNotificationsWeb] showNotification:', options);\n if ('Notification' in window) {\n if (Notification.permission === 'granted') {\n new Notification(options.title, {\n body: options.body,\n data: { roomId: options.roomId, messageId: options.messageId },\n });\n }\n }\n }\n\n async clearRoomNotification(options: { roomId: number }): Promise<void> {\n console.log('[MessengerNotificationsWeb] clearRoomNotification:', options);\n }\n\n async getPendingRoomId(): Promise<{ roomId: number | null }> {\n return { roomId: null };\n }\n\n async startPersistentSocket(options: { url: string; token: string }): Promise<void> {\n console.log('[MessengerNotificationsWeb] startPersistentSocket (not supported on web):', options);\n }\n\n async stopPersistentSocket(): Promise<void> {\n console.log('[MessengerNotificationsWeb] stopPersistentSocket (not supported on web)');\n }\n\n async checkPermissions(): Promise<PermissionStatus> {\n if ('Notification' in window) {\n const state =\n Notification.permission === 'granted' ? 'granted' : Notification.permission === 'denied' ? 'denied' : 'prompt';\n return { notifications: state };\n }\n return { notifications: 'denied' };\n }\n\n async requestPermissions(): Promise<PermissionStatus> {\n if ('Notification' in window) {\n const permission = await Notification.requestPermission();\n const state = permission === 'granted' ? 'granted' : permission === 'denied' ? 'denied' : 'prompt';\n return { notifications: state };\n }\n return { notifications: 'denied' };\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ const MessengerNotifications = core.registerPlugin('MessengerNotifications', {
6
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.MessengerNotificationsWeb()),
7
+ });
8
+
9
+ class MessengerNotificationsWeb extends core.WebPlugin {
10
+ async showNotification(options) {
11
+ console.log('[MessengerNotificationsWeb] showNotification:', options);
12
+ if ('Notification' in window) {
13
+ if (Notification.permission === 'granted') {
14
+ new Notification(options.title, {
15
+ body: options.body,
16
+ data: { roomId: options.roomId, messageId: options.messageId },
17
+ });
18
+ }
19
+ }
20
+ }
21
+ async clearRoomNotification(options) {
22
+ console.log('[MessengerNotificationsWeb] clearRoomNotification:', options);
23
+ }
24
+ async getPendingRoomId() {
25
+ return { roomId: null };
26
+ }
27
+ async startPersistentSocket(options) {
28
+ console.log('[MessengerNotificationsWeb] startPersistentSocket (not supported on web):', options);
29
+ }
30
+ async stopPersistentSocket() {
31
+ console.log('[MessengerNotificationsWeb] stopPersistentSocket (not supported on web)');
32
+ }
33
+ async checkPermissions() {
34
+ if ('Notification' in window) {
35
+ const state = Notification.permission === 'granted' ? 'granted' : Notification.permission === 'denied' ? 'denied' : 'prompt';
36
+ return { notifications: state };
37
+ }
38
+ return { notifications: 'denied' };
39
+ }
40
+ async requestPermissions() {
41
+ if ('Notification' in window) {
42
+ const permission = await Notification.requestPermission();
43
+ const state = permission === 'granted' ? 'granted' : permission === 'denied' ? 'denied' : 'prompt';
44
+ return { notifications: state };
45
+ }
46
+ return { notifications: 'denied' };
47
+ }
48
+ }
49
+
50
+ var web = /*#__PURE__*/Object.freeze({
51
+ __proto__: null,
52
+ MessengerNotificationsWeb: MessengerNotificationsWeb
53
+ });
54
+
55
+ exports.MessengerNotifications = MessengerNotifications;
56
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst MessengerNotifications = registerPlugin('MessengerNotifications', {\n web: () => import('./web').then((m) => new m.MessengerNotificationsWeb()),\n});\nexport * from './definitions';\nexport { MessengerNotifications };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class MessengerNotificationsWeb extends WebPlugin {\n async showNotification(options) {\n console.log('[MessengerNotificationsWeb] showNotification:', options);\n if ('Notification' in window) {\n if (Notification.permission === 'granted') {\n new Notification(options.title, {\n body: options.body,\n data: { roomId: options.roomId, messageId: options.messageId },\n });\n }\n }\n }\n async clearRoomNotification(options) {\n console.log('[MessengerNotificationsWeb] clearRoomNotification:', options);\n }\n async getPendingRoomId() {\n return { roomId: null };\n }\n async startPersistentSocket(options) {\n console.log('[MessengerNotificationsWeb] startPersistentSocket (not supported on web):', options);\n }\n async stopPersistentSocket() {\n console.log('[MessengerNotificationsWeb] stopPersistentSocket (not supported on web)');\n }\n async checkPermissions() {\n if ('Notification' in window) {\n const state = Notification.permission === 'granted' ? 'granted' : Notification.permission === 'denied' ? 'denied' : 'prompt';\n return { notifications: state };\n }\n return { notifications: 'denied' };\n }\n async requestPermissions() {\n if ('Notification' in window) {\n const permission = await Notification.requestPermission();\n const state = permission === 'granted' ? 'granted' : permission === 'denied' ? 'denied' : 'prompt';\n return { notifications: state };\n }\n return { notifications: 'denied' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,sBAAsB,GAAGA,mBAAc,CAAC,wBAAwB,EAAE;AACxE,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,yBAAyB,EAAE,CAAC;AAC7E,CAAC;;ACFM,MAAM,yBAAyB,SAASC,cAAS,CAAC;AACzD,IAAI,MAAM,gBAAgB,CAAC,OAAO,EAAE;AACpC,QAAQ,OAAO,CAAC,GAAG,CAAC,+CAA+C,EAAE,OAAO,CAAC;AAC7E,QAAQ,IAAI,cAAc,IAAI,MAAM,EAAE;AACtC,YAAY,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,EAAE;AACvD,gBAAgB,IAAI,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE;AAChD,oBAAoB,IAAI,EAAE,OAAO,CAAC,IAAI;AACtC,oBAAoB,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;AAClF,iBAAiB,CAAC;AAClB,YAAY;AACZ,QAAQ;AACR,IAAI;AACJ,IAAI,MAAM,qBAAqB,CAAC,OAAO,EAAE;AACzC,QAAQ,OAAO,CAAC,GAAG,CAAC,oDAAoD,EAAE,OAAO,CAAC;AAClF,IAAI;AACJ,IAAI,MAAM,gBAAgB,GAAG;AAC7B,QAAQ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;AAC/B,IAAI;AACJ,IAAI,MAAM,qBAAqB,CAAC,OAAO,EAAE;AACzC,QAAQ,OAAO,CAAC,GAAG,CAAC,2EAA2E,EAAE,OAAO,CAAC;AACzG,IAAI;AACJ,IAAI,MAAM,oBAAoB,GAAG;AACjC,QAAQ,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC;AAC9F,IAAI;AACJ,IAAI,MAAM,gBAAgB,GAAG;AAC7B,QAAQ,IAAI,cAAc,IAAI,MAAM,EAAE;AACtC,YAAY,MAAM,KAAK,GAAG,YAAY,CAAC,UAAU,KAAK,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC,UAAU,KAAK,QAAQ,GAAG,QAAQ,GAAG,QAAQ;AACxI,YAAY,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE;AAC3C,QAAQ;AACR,QAAQ,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE;AAC1C,IAAI;AACJ,IAAI,MAAM,kBAAkB,GAAG;AAC/B,QAAQ,IAAI,cAAc,IAAI,MAAM,EAAE;AACtC,YAAY,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,iBAAiB,EAAE;AACrE,YAAY,MAAM,KAAK,GAAG,UAAU,KAAK,SAAS,GAAG,SAAS,GAAG,UAAU,KAAK,QAAQ,GAAG,QAAQ,GAAG,QAAQ;AAC9G,YAAY,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE;AAC3C,QAAQ;AACR,QAAQ,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE;AAC1C,IAAI;AACJ;;;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,59 @@
1
+ var capacitorLEAN = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const MessengerNotifications = core.registerPlugin('MessengerNotifications', {
5
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.MessengerNotificationsWeb()),
6
+ });
7
+
8
+ class MessengerNotificationsWeb extends core.WebPlugin {
9
+ async showNotification(options) {
10
+ console.log('[MessengerNotificationsWeb] showNotification:', options);
11
+ if ('Notification' in window) {
12
+ if (Notification.permission === 'granted') {
13
+ new Notification(options.title, {
14
+ body: options.body,
15
+ data: { roomId: options.roomId, messageId: options.messageId },
16
+ });
17
+ }
18
+ }
19
+ }
20
+ async clearRoomNotification(options) {
21
+ console.log('[MessengerNotificationsWeb] clearRoomNotification:', options);
22
+ }
23
+ async getPendingRoomId() {
24
+ return { roomId: null };
25
+ }
26
+ async startPersistentSocket(options) {
27
+ console.log('[MessengerNotificationsWeb] startPersistentSocket (not supported on web):', options);
28
+ }
29
+ async stopPersistentSocket() {
30
+ console.log('[MessengerNotificationsWeb] stopPersistentSocket (not supported on web)');
31
+ }
32
+ async checkPermissions() {
33
+ if ('Notification' in window) {
34
+ const state = Notification.permission === 'granted' ? 'granted' : Notification.permission === 'denied' ? 'denied' : 'prompt';
35
+ return { notifications: state };
36
+ }
37
+ return { notifications: 'denied' };
38
+ }
39
+ async requestPermissions() {
40
+ if ('Notification' in window) {
41
+ const permission = await Notification.requestPermission();
42
+ const state = permission === 'granted' ? 'granted' : permission === 'denied' ? 'denied' : 'prompt';
43
+ return { notifications: state };
44
+ }
45
+ return { notifications: 'denied' };
46
+ }
47
+ }
48
+
49
+ var web = /*#__PURE__*/Object.freeze({
50
+ __proto__: null,
51
+ MessengerNotificationsWeb: MessengerNotificationsWeb
52
+ });
53
+
54
+ exports.MessengerNotifications = MessengerNotifications;
55
+
56
+ return exports;
57
+
58
+ })({}, capacitorExports);
59
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst MessengerNotifications = registerPlugin('MessengerNotifications', {\n web: () => import('./web').then((m) => new m.MessengerNotificationsWeb()),\n});\nexport * from './definitions';\nexport { MessengerNotifications };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class MessengerNotificationsWeb extends WebPlugin {\n async showNotification(options) {\n console.log('[MessengerNotificationsWeb] showNotification:', options);\n if ('Notification' in window) {\n if (Notification.permission === 'granted') {\n new Notification(options.title, {\n body: options.body,\n data: { roomId: options.roomId, messageId: options.messageId },\n });\n }\n }\n }\n async clearRoomNotification(options) {\n console.log('[MessengerNotificationsWeb] clearRoomNotification:', options);\n }\n async getPendingRoomId() {\n return { roomId: null };\n }\n async startPersistentSocket(options) {\n console.log('[MessengerNotificationsWeb] startPersistentSocket (not supported on web):', options);\n }\n async stopPersistentSocket() {\n console.log('[MessengerNotificationsWeb] stopPersistentSocket (not supported on web)');\n }\n async checkPermissions() {\n if ('Notification' in window) {\n const state = Notification.permission === 'granted' ? 'granted' : Notification.permission === 'denied' ? 'denied' : 'prompt';\n return { notifications: state };\n }\n return { notifications: 'denied' };\n }\n async requestPermissions() {\n if ('Notification' in window) {\n const permission = await Notification.requestPermission();\n const state = permission === 'granted' ? 'granted' : permission === 'denied' ? 'denied' : 'prompt';\n return { notifications: state };\n }\n return { notifications: 'denied' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,sBAAsB,GAAGA,mBAAc,CAAC,wBAAwB,EAAE;IACxE,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,yBAAyB,EAAE,CAAC;IAC7E,CAAC;;ICFM,MAAM,yBAAyB,SAASC,cAAS,CAAC;IACzD,IAAI,MAAM,gBAAgB,CAAC,OAAO,EAAE;IACpC,QAAQ,OAAO,CAAC,GAAG,CAAC,+CAA+C,EAAE,OAAO,CAAC;IAC7E,QAAQ,IAAI,cAAc,IAAI,MAAM,EAAE;IACtC,YAAY,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,EAAE;IACvD,gBAAgB,IAAI,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE;IAChD,oBAAoB,IAAI,EAAE,OAAO,CAAC,IAAI;IACtC,oBAAoB,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;IAClF,iBAAiB,CAAC;IAClB,YAAY;IACZ,QAAQ;IACR,IAAI;IACJ,IAAI,MAAM,qBAAqB,CAAC,OAAO,EAAE;IACzC,QAAQ,OAAO,CAAC,GAAG,CAAC,oDAAoD,EAAE,OAAO,CAAC;IAClF,IAAI;IACJ,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;IAC/B,IAAI;IACJ,IAAI,MAAM,qBAAqB,CAAC,OAAO,EAAE;IACzC,QAAQ,OAAO,CAAC,GAAG,CAAC,2EAA2E,EAAE,OAAO,CAAC;IACzG,IAAI;IACJ,IAAI,MAAM,oBAAoB,GAAG;IACjC,QAAQ,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC;IAC9F,IAAI;IACJ,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,IAAI,cAAc,IAAI,MAAM,EAAE;IACtC,YAAY,MAAM,KAAK,GAAG,YAAY,CAAC,UAAU,KAAK,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC,UAAU,KAAK,QAAQ,GAAG,QAAQ,GAAG,QAAQ;IACxI,YAAY,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE;IAC3C,QAAQ;IACR,QAAQ,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE;IAC1C,IAAI;IACJ,IAAI,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,IAAI,cAAc,IAAI,MAAM,EAAE;IACtC,YAAY,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,iBAAiB,EAAE;IACrE,YAAY,MAAM,KAAK,GAAG,UAAU,KAAK,SAAS,GAAG,SAAS,GAAG,UAAU,KAAK,QAAQ,GAAG,QAAQ,GAAG,QAAQ;IAC9G,YAAY,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE;IAC3C,QAAQ;IACR,QAAQ,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE;IAC1C,IAAI;IACJ;;;;;;;;;;;;;;;"}
@@ -0,0 +1,76 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc(MessengerNotificationsPlugin)
5
+ public class MessengerNotificationsPlugin: CAPPlugin, CAPBridgedPlugin {
6
+ public let identifier = "MessengerNotificationsPlugin"
7
+ public let jsName = "MessengerNotifications"
8
+ public let pluginMethods: [CAPPluginMethod] = [
9
+ CAPPluginMethod(name: "showNotification", returnType: CAPPluginReturnPromise),
10
+ CAPPluginMethod(name: "clearRoomNotification", returnType: CAPPluginReturnPromise),
11
+ CAPPluginMethod(name: "getPendingRoomId", returnType: CAPPluginReturnPromise),
12
+ CAPPluginMethod(name: "startPersistentSocket", returnType: CAPPluginReturnPromise),
13
+ CAPPluginMethod(name: "stopPersistentSocket", returnType: CAPPluginReturnPromise)
14
+ ]
15
+
16
+ private static var _pendingRoomId: Int? = nil
17
+ public static var pendingRoomId: Int? {
18
+ get { return _pendingRoomId }
19
+ set { _pendingRoomId = newValue }
20
+ }
21
+
22
+ @objc func showNotification(_ call: CAPPluginCall) {
23
+ let title = call.getString("title") ?? "New Message"
24
+ let body = call.getString("body") ?? "You have a new message"
25
+ let roomId = call.getInt("roomId") ?? 0
26
+ let messageId = call.getString("messageId")
27
+ let timestamp = call.getInt("timestamp") ?? 0
28
+ let roomName = call.getString("roomName")
29
+
30
+ NotificationHelper.showRoomNotification(
31
+ title: title,
32
+ body: body,
33
+ roomId: roomId,
34
+ messageId: messageId,
35
+ timestamp: Int64(timestamp),
36
+ isSync: false
37
+ )
38
+ call.resolve()
39
+ }
40
+
41
+ @objc func clearRoomNotification(_ call: CAPPluginCall) {
42
+ let roomId = call.getInt("roomId") ?? 0
43
+ if roomId > 0 {
44
+ NotificationHelper.clearRoomHistory(roomId: roomId)
45
+ }
46
+ call.resolve()
47
+ }
48
+
49
+ @objc func getPendingRoomId(_ call: CAPPluginCall) {
50
+ if let rid = MessengerNotificationsPlugin.pendingRoomId {
51
+ call.resolve(["roomId": rid])
52
+ MessengerNotificationsPlugin.pendingRoomId = nil
53
+ } else {
54
+ call.resolve(["roomId": NSNull()])
55
+ }
56
+ }
57
+
58
+ @objc func startPersistentSocket(_ call: CAPPluginCall) {
59
+ guard let url = call.getString("url"), let token = call.getString("token") else {
60
+ call.reject("Missing URL or token")
61
+ return
62
+ }
63
+
64
+ // On iOS, persistent sockets are usually handled differently (or not at all in same way as Android service)
65
+ // because of background limits. But for now we'll store them.
66
+ SafeStorageStore.set("socket_url", value: url)
67
+ SafeStorageStore.set("auth_token", value: token)
68
+
69
+ // We'll call complete for now.
70
+ call.resolve()
71
+ }
72
+
73
+ @objc func stopPersistentSocket(_ call: CAPPluginCall) {
74
+ call.resolve()
75
+ }
76
+ }
@@ -0,0 +1,22 @@
1
+ import Foundation
2
+
3
+ /**
4
+ * Native crypto logic picked from ChatE2EE-IOS.
5
+ */
6
+ public enum NativeCrypto {
7
+ public struct DecryptResult {
8
+ public let text: String
9
+ }
10
+
11
+ /**
12
+ * Placeholder decryption logic.
13
+ */
14
+ public static func decryptRoomData(roomId: Int, encryptedJSON: String) throws -> DecryptResult {
15
+ // Placeholder return the encrypted text
16
+ return DecryptResult(text: encryptedJSON)
17
+ }
18
+
19
+ public static func decryptUserData(userId: Int, encryptedJSON: String) throws -> DecryptResult {
20
+ return DecryptResult(text: encryptedJSON)
21
+ }
22
+ }
@@ -0,0 +1,58 @@
1
+ import Foundation
2
+ import UserNotifications
3
+ import os.log
4
+
5
+ enum NotificationHelper {
6
+ private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "com.codecraft_studio.messenger.notifications",
7
+ category: "Notifications")
8
+
9
+ private static let historyKey = "notification_history_map"
10
+ private static let dismissedUntilKey = "dismissed_until_by_room"
11
+ private static let maxMessagesPerRoom = 20
12
+
13
+ static func showRoomNotification(title: String,
14
+ body: String,
15
+ roomId: Int,
16
+ messageId: String? = nil,
17
+ timestamp: Int64 = 0,
18
+ isSync: Bool = false) {
19
+
20
+ let center = UNUserNotificationCenter.current()
21
+ let identifier = "\(roomId)"
22
+ let threadIdentifier = "room_\(roomId)"
23
+
24
+ // Add to history for summary logic
25
+ addToHistory(roomId: roomId, title: title, body: body, messageId: messageId, timestamp: timestamp)
26
+
27
+ let content = UNMutableNotificationContent()
28
+ content.title = title
29
+ content.body = body
30
+ content.sound = .default
31
+ content.userInfo = ["roomId": roomId, "messageId": messageId ?? ""]
32
+ content.threadIdentifier = threadIdentifier
33
+ content.categoryIdentifier = "CHAT_MESSAGE"
34
+
35
+ let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
36
+ center.add(request) { error in
37
+ if let error = error {
38
+ os_log("❌ Failed to add notification: %{public}@", log: log, type: .error, error.localizedDescription)
39
+ }
40
+ }
41
+ }
42
+
43
+ static func clearRoomHistory(roomId: Int) {
44
+ let center = UNUserNotificationCenter.current()
45
+ center.removeDeliveredNotifications(withIdentifiers: ["\(roomId)"])
46
+ // Also remove summary if any
47
+ clearHistory(roomId: roomId)
48
+ }
49
+
50
+ private static func addToHistory(roomId: Int, title: String, body: String, messageId: String?, timestamp: Int64) {
51
+ // Implementation for history if needed for summaries on iOS.
52
+ // On iOS, system handles grouping by threadIdentifier automatically if supported.
53
+ }
54
+
55
+ private static func clearHistory(roomId: Int) {
56
+ // Implementation
57
+ }
58
+ }