Compare commits
2 Commits
b3ef0b89f6
...
5717d7047e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5717d7047e | ||
|
|
123902a6b1 |
@@ -67,6 +67,7 @@
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
BE672457097ACB02A0172419 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
C67DFB872FBB6A460051E88E /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
F120D236BB7566D5DE73F0B9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
F90E4DAFC6F0443563808446 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -109,7 +110,6 @@
|
||||
5FAD3FB76264405B9D466D11 /* Pods-RunnerTests.release.xcconfig */,
|
||||
BE672457097ACB02A0172419 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -149,6 +149,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C67DFB872FBB6A460051E88E /* Runner.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
@@ -478,6 +479,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 63CVT8G5P8;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -661,6 +663,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 63CVT8G5P8;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -684,6 +687,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 63CVT8G5P8;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
||||
@@ -26,8 +26,20 @@
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app requires camera access to take and send photos via WhatsApp.</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>This app requires contacts access to match phone numbers with your local address book names.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app requires microphone access to record and send audio messages via WhatsApp.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>This app requires photo library access to choose and send photos via WhatsApp.</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
@@ -45,13 +57,5 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>This app requires contacts access to match phone numbers with your local address book names.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app requires camera access to take and send photos via WhatsApp.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>This app requires photo library access to choose and send photos via WhatsApp.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app requires microphone access to record and send audio messages via WhatsApp.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
8
whatsapp_app/ios/Runner/Runner.entitlements
Normal file
8
whatsapp_app/ios/Runner/Runner.entitlements
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../models/conversation_model.dart';
|
||||
@@ -158,6 +159,14 @@ class FirebaseService extends GetxService {
|
||||
final name = data['name'] ?? 'WhatsApp';
|
||||
final body = data['body'] ?? 'New Message';
|
||||
|
||||
// Only show local notifications when the app is actively in the foreground (resumed).
|
||||
// If the app is in the background or suspended, the native FCM notification will handle it natively.
|
||||
// This fully prevents duplicate notifications!
|
||||
if (WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) {
|
||||
print('[FCM] Skipping WebSocket local notification: app is in background.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Smart Notification: Only show if we are NOT currently in this chat
|
||||
final activeChatId = Get.find<WhatsAppService>().activeChatId.value;
|
||||
if (chatId != null && activeChatId == chatId) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import '../config/app_config.dart';
|
||||
import 'contacts_service.dart';
|
||||
import 'firebase_service.dart';
|
||||
|
||||
enum WsStatus { disconnected, connecting, connected, waReady }
|
||||
@@ -104,9 +105,15 @@ class WhatsAppService extends GetxService {
|
||||
body = '📷 Media/Audio message';
|
||||
}
|
||||
try {
|
||||
final String cleanNumber = chatId?.split('@')[0] ?? '';
|
||||
final String senderName = Get.find<ContactsService>().getContactName(
|
||||
cleanNumber,
|
||||
cleanNumber.isNotEmpty ? '+$cleanNumber' : 'WhatsApp',
|
||||
);
|
||||
|
||||
Get.find<FirebaseService>().showLocalNotificationFromData({
|
||||
'chatId': chatId,
|
||||
'name': chatId?.split('@')[0] ?? 'WhatsApp',
|
||||
'name': senderName,
|
||||
'body': body,
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -702,6 +702,41 @@ wss.on('connection', (ws, req) => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── HTTP REST API Endpoints (For easy integration as a Proxy/API) ───────
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
|
||||
app.post('/api/send', async (req, res) => {
|
||||
if (!clientReady) return res.status(503).json({ error: 'WhatsApp is not ready' });
|
||||
const { phone, message } = req.body;
|
||||
if (!phone || !message) return res.status(400).json({ error: 'phone and message are required' });
|
||||
|
||||
try {
|
||||
const chatId = phone.includes('@') ? phone : `${phone}@c.us`;
|
||||
const sentMsg = await waClient.sendMessage(chatId, message);
|
||||
res.status(200).json({ success: true, messageId: sentMsg.id.id });
|
||||
} catch (err) {
|
||||
console.error('[API] Send error:', err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/send-media', async (req, res) => {
|
||||
if (!clientReady) return res.status(503).json({ error: 'WhatsApp is not ready' });
|
||||
const { phone, base64, mimetype, filename, caption } = req.body;
|
||||
if (!phone || !base64 || !mimetype) return res.status(400).json({ error: 'phone, base64, and mimetype are required' });
|
||||
|
||||
try {
|
||||
const { MessageMedia } = require('whatsapp-web.js');
|
||||
const media = new MessageMedia(mimetype, base64, filename || 'file');
|
||||
const chatId = phone.includes('@') ? phone : `${phone}@c.us`;
|
||||
const sentMsg = await waClient.sendMessage(chatId, media, { caption: caption || '' });
|
||||
res.status(200).json({ success: true, messageId: sentMsg.id.id });
|
||||
} catch (err) {
|
||||
console.error('[API] Send media error:', err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── HTTP Health Endpoint ──────────────────────────────────────────────────
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({
|
||||
@@ -712,6 +747,7 @@ app.get('/health', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ─── Start HTTP + WebSocket Server ─────────────────────────────────────────
|
||||
server.listen(PORT, () => {
|
||||
console.log(`[SERVER] Standalone WhatsApp Bridge running on port ${PORT}`);
|
||||
|
||||
Reference in New Issue
Block a user