Skip to content

Commit 6bff2c0

Browse files
committed
fix (integrations): whatsapp polling
1 parent 92284df commit 6bff2c0

4 files changed

Lines changed: 100 additions & 19 deletions

File tree

src/client/app/integrations/page.js

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const integrationColorIcons = {
8686

8787
const IconPlaceholder = IconSettingsCog
8888

89-
const PRO_ONLY_INTEGRATIONS = ["notion", "github", "slack", "discord", "trello"]
89+
const PRO_ONLY_INTEGRATIONS = ["notion", "github", "slack", "discord", "trello", "whatsapp"]
9090

9191
const proPlanFeatures = [
9292
{ name: "Text Chat", limit: "100 messages per day" },
@@ -174,9 +174,9 @@ const UpgradeToProModal = ({ isOpen, onClose }) => {
174174
</button>
175175
<button
176176
onClick={onClose}
177-
className="w-full py-2 px-5 rounded-lg hover:bg-neutral-800 text-sm font-medium text-neutral-400"
177+
className="w-full py-2 px-5 rounded-lg hover:bg-neutral-800 transition-colors"
178178
>
179-
Not now
179+
Cancel
180180
</button>
181181
</footer>
182182
</motion.div>
@@ -186,11 +186,54 @@ const UpgradeToProModal = ({ isOpen, onClose }) => {
186186
)
187187
}
188188

189+
const WhatsAppDisclaimerModal = ({ isOpen, onAgree, onClose }) => {
190+
// This modal doesn't need to know if it's open, the parent handles it.
191+
// But we keep the prop for clarity and potential internal logic.
192+
if (!isOpen) return null
193+
194+
return (
195+
<ModalDialog
196+
title={
197+
<div className="flex items-center gap-2">
198+
<IconBrandWhatsapp />
199+
<span>WhatsApp Disclaimer</span>
200+
</div>
201+
}
202+
description="Please review the following before connecting your WhatsApp account."
203+
onCancel={onClose}
204+
onConfirm={onAgree}
205+
confirmButtonText="Agree and Connect"
206+
extraContent={
207+
<div className="text-sm text-neutral-300 space-y-3 pt-2">
208+
<p>
209+
By proceeding, you acknowledge that you have read and
210+
agree to the{" "}
211+
<a
212+
href="https://www.whatsapp.com/legal/terms-of-service"
213+
target="_blank"
214+
rel="noopener noreferrer"
215+
className="text-blue-400 hover:underline"
216+
>
217+
WhatsApp Terms of Service by Meta
218+
</a>
219+
.
220+
</p>
221+
<p>
222+
Connecting this integration allows Sentient to act on
223+
your behalf to: read your messages, send messages, and
224+
manage your chats and contacts.
225+
</p>
226+
</div>
227+
}
228+
/>
229+
)
230+
}
231+
189232
const MANUAL_INTEGRATION_CONFIGS = {} // Manual integrations removed for Slack and Notion
190233

191234
const WhatsAppQRCodeModal = ({ onClose }) => {
192235
const [qrCode, setQrCode] = useState(null)
193-
const [status, setStatus] = useState("initiating") // initiating, scanning, working, error
236+
const [status, setStatus] = useState("initiating") // initiating, scanning, working,const WhatsAppQRCodeModal = ({ onClose }) => {
194237
const [error, setError] = useState("")
195238
const intervalRef = useRef(null)
196239

@@ -212,15 +255,15 @@ const WhatsAppQRCodeModal = ({ onClose }) => {
212255
}
213256
if (data.status === "WORKING") {
214257
setStatus("working")
215-
stopPolling()
216258
toast.success("WhatsApp connected successfully!")
259+
stopPolling()
217260
setTimeout(onClose, 1500)
218261
} else if (data.status === "FAILED") {
219262
setError("Connection failed. Please close this and try again.")
220263
setStatus("error")
221264
stopPolling()
222265
} else {
223-
setStatus("scanning") // Still waiting for scan
266+
setStatus("scanning")
224267
}
225268
} catch (err) {
226269
setError(err.message)
@@ -241,11 +284,13 @@ const WhatsAppQRCodeModal = ({ onClose }) => {
241284
if (!res.ok) {
242285
throw new Error(data.error || "Failed to get QR code")
243286
}
244-
// WAHA returns base64 image data in the 'data' field
245287
setQrCode(data.data)
246288
setStatus("scanning")
247-
// Start polling for status
248-
intervalRef.current = setInterval(pollStatus, 3000)
289+
290+
// Start polling for status if not already started
291+
if (!intervalRef.current) {
292+
intervalRef.current = setInterval(pollStatus, 3000)
293+
}
249294
} catch (err) {
250295
setError(err.message)
251296
setStatus("error")
@@ -254,9 +299,10 @@ const WhatsAppQRCodeModal = ({ onClose }) => {
254299

255300
useEffect(() => {
256301
initiateConnection()
257-
// Cleanup on unmount
258-
return () => stopPolling()
259-
}, [initiateConnection])
302+
return () => {
303+
stopPolling()
304+
}
305+
}, [initiateConnection, stopPolling])
260306

261307
return (
262308
<motion.div
@@ -751,6 +797,7 @@ const IntegrationsPage = () => {
751797
const [activeCategory, setActiveCategory] = useState("Most Popular")
752798
const [selectedIntegration, setSelectedIntegration] = useState(null)
753799
const [activeManualIntegration, setActiveManualIntegration] = useState(null)
800+
const [isWhatsAppDisclaimerOpen, setIsWhatsAppDisclaimerOpen] = useState(false)
754801
const [isWhatsAppQRModalOpen, setIsWhatsAppQRModalOpen] = useState(false)
755802
const [sparkleTrigger, setSparkleTrigger] = useState(0)
756803
const [privacyModalService, setPrivacyModalService] = useState(null)
@@ -1364,9 +1411,11 @@ const IntegrationsPage = () => {
13641411
onClick={async (e) => {
13651412
e.stopPropagation()
13661413
if (
1367-
integration.name === "whatsapp"
1414+
integration.name ===
1415+
"whatsapp"
13681416
) {
1369-
setIsWhatsAppQRModalOpen(true)
1417+
// Show disclaimer first
1418+
setIsWhatsAppDisclaimerOpen(true)
13701419
} else if (
13711420
integration.auth_type ===
13721421
"composio"
@@ -1407,6 +1456,24 @@ const IntegrationsPage = () => {
14071456
place="right-start"
14081457
style={{ zIndex: 9999 }}
14091458
/>
1459+
<AnimatePresence>
1460+
{isWhatsAppDisclaimerOpen && (
1461+
<div className="isolate z-[120]">
1462+
<WhatsAppDisclaimerModal
1463+
isOpen={isWhatsAppDisclaimerOpen}
1464+
onClose={() => setIsWhatsAppDisclaimerOpen(false)}
1465+
onAgree={() => {
1466+
setIsWhatsAppDisclaimerOpen(false)
1467+
setIsWhatsAppQRModalOpen(true)
1468+
}}
1469+
/>
1470+
</div>
1471+
)}
1472+
</AnimatePresence>
1473+
<UpgradeToProModal
1474+
isOpen={isUpgradeModalOpen}
1475+
onClose={() => setUpgradeModalOpen(false)}
1476+
/>
14101477
<UpgradeToProModal
14111478
isOpen={isUpgradeModalOpen}
14121479
onClose={() => setUpgradeModalOpen(false)}
@@ -1561,4 +1628,4 @@ const IntegrationsPage = () => {
15611628
)
15621629
}
15631630

1564-
export default IntegrationsPage
1631+
export default IntegrationsPage

src/server/main/integrations/routes.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
SLACK_CLIENT_SECRET, NOTION_CLIENT_ID, NOTION_CLIENT_SECRET,
2626
)
2727
from workers.tasks import execute_triggered_task
28-
from workers.proactive.utils import event_pre_filter
28+
from workers.proactive.utils import event_pre_filter # noqa: E402
2929
from main.plans import PRO_ONLY_INTEGRATIONS
3030
from .utils import waha_request_from_main
3131

@@ -474,8 +474,22 @@ async def composio_webhook(request: Request):
474474
# --- NEW WHATSAPP FULL CONTROL ROUTES ---
475475

476476
@router.post("/whatsapp/connect/initiate", summary="Start a WAHA session and get a QR code")
477-
async def initiate_whatsapp_connection(user_id: str = Depends(auth_helper.get_current_user_id)):
477+
async def initiate_whatsapp_connection(
478+
user_id_and_plan: Tuple[str, str] = Depends(auth_helper.get_current_user_id_and_plan)
479+
):
480+
user_id, plan = user_id_and_plan
481+
service_name = "whatsapp"
482+
service_config = INTEGRATIONS_CONFIG.get(service_name)
483+
484+
# --- Check Plan Limit ---
485+
if service_name in PRO_ONLY_INTEGRATIONS and plan == "free":
486+
raise HTTPException(
487+
status_code=status.HTTP_403_FORBIDDEN,
488+
detail=f"The {service_config.get('display_name', service_name)} integration is a Pro feature. Please upgrade your plan."
489+
)
490+
478491
try:
492+
479493
# Step 1: Start the session for the user
480494
# --- MODIFICATION START (v2) ---
481495
# The user_id from Auth0 (e.g., 'google-oauth2|123...') contains invalid characters ('|')

src/server/main/plans.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@
4444
]
4545

4646
PRO_ONLY_INTEGRATIONS = [
47-
"notion", "github", "slack", "discord", "trello"
47+
"notion", "github", "slack", "discord", "trello", "whatsapp"
4848
]

src/server/mcp_hub/whatsapp/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
async def send_message_to_self(ctx: Context, message: str) -> Dict[str, Any]:
3434
"""
3535
Sends a WhatsApp message FROM the Sentient system TO the user's configured notification number.
36-
This tool is for system alerts and notifications, not for sending messages to other people from the user's account.
36+
This tool is to be used only when the user says 'Send message to me', not for sending messages to other people from the user's account.
3737
"""
3838
logger.info(f"Executing tool: send_message_to_self")
3939
try:

0 commit comments

Comments
 (0)