From 48165db6a217dddda998261a49bc38cd2a629403 Mon Sep 17 00:00:00 2001 From: Darren Clarke Date: Sun, 9 Nov 2025 11:12:04 +0100 Subject: [PATCH] Whatsapp unlink WIP #1 --- .../[service]/bots/[token]/relink/route.ts | 1 + apps/bridge-whatsapp/src/service.ts | 24 +++++++- .../tasks/signal/send-signal-message.ts | 4 +- .../[service]/bots/[token]/relink/route.ts | 1 + packages/bridge-ui/components/Detail.tsx | 57 +++++++++++++++++++ packages/bridge-ui/index.ts | 1 + packages/bridge-ui/lib/routing.ts | 5 ++ packages/bridge-ui/lib/service.ts | 4 ++ packages/bridge-ui/lib/whatsapp.ts | 26 +++++++++ 9 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 apps/bridge-frontend/app/api/[service]/bots/[token]/relink/route.ts create mode 100644 apps/link/app/api/[service]/bots/[token]/relink/route.ts diff --git a/apps/bridge-frontend/app/api/[service]/bots/[token]/relink/route.ts b/apps/bridge-frontend/app/api/[service]/bots/[token]/relink/route.ts new file mode 100644 index 0000000..a9789c0 --- /dev/null +++ b/apps/bridge-frontend/app/api/[service]/bots/[token]/relink/route.ts @@ -0,0 +1 @@ +export { relinkBot as POST } from "@link-stack/bridge-ui"; diff --git a/apps/bridge-whatsapp/src/service.ts b/apps/bridge-whatsapp/src/service.ts index 1708fa9..60ba170 100644 --- a/apps/bridge-whatsapp/src/service.ts +++ b/apps/bridge-whatsapp/src/service.ts @@ -286,8 +286,30 @@ export default class WhatsappService extends Service { } async unverify(botID: string): Promise { + // Step 1: Close and remove the active connection if it exists + const connection = this.connections[botID]; + if (connection?.socket) { + try { + // Properly close the WebSocket connection + await connection.socket.logout(); + } catch (error) { + logger.warn({ botID, error }, "Error during logout, forcing disconnect"); + try { + connection.socket.end(undefined); + } catch (endError) { + logger.warn({ botID, endError }, "Error ending socket connection"); + } + } + } + + // Step 2: Remove from in-memory connections + delete this.connections[botID]; + + // Step 3: Remove the bot directory (auth state, QR code, verified marker) const botDirectory = this.getBotDirectory(botID); - fs.rmSync(botDirectory, { recursive: true, force: true }); + if (fs.existsSync(botDirectory)) { + fs.rmSync(botDirectory, { recursive: true, force: true }); + } } async register( diff --git a/apps/bridge-worker/tasks/signal/send-signal-message.ts b/apps/bridge-worker/tasks/signal/send-signal-message.ts index 5b00bbc..d6dda1e 100644 --- a/apps/bridge-worker/tasks/signal/send-signal-message.ts +++ b/apps/bridge-worker/tasks/signal/send-signal-message.ts @@ -182,7 +182,7 @@ const sendSignalMessageTask = async ({ { number, recipients: [finalTo], - message: message.substring(0, 50) + "...", + messageLength: message?.length, hasQuoteParams: !!(quoteMessage && quoteAuthor && quoteTimestamp), }, "Message data being sent", @@ -197,7 +197,7 @@ const sendSignalMessageTask = async ({ logger.debug( { quoteAuthor, - quoteMessage: quoteMessage.substring(0, 50) + "...", + quoteMessageLength: quoteMessage?.length, quoteTimestamp, }, "Including quote in message", diff --git a/apps/link/app/api/[service]/bots/[token]/relink/route.ts b/apps/link/app/api/[service]/bots/[token]/relink/route.ts new file mode 100644 index 0000000..a9789c0 --- /dev/null +++ b/apps/link/app/api/[service]/bots/[token]/relink/route.ts @@ -0,0 +1 @@ +export { relinkBot as POST } from "@link-stack/bridge-ui"; diff --git a/packages/bridge-ui/components/Detail.tsx b/packages/bridge-ui/components/Detail.tsx index 8cb731e..69f97e9 100644 --- a/packages/bridge-ui/components/Detail.tsx +++ b/packages/bridge-ui/components/Detail.tsx @@ -34,6 +34,8 @@ export const Detail: FC = ({ service, row }) => { const { almostBlack } = colors; const { bodyLarge } = typography; const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); + const [showRelinkConfirmation, setShowRelinkConfirmation] = useState(false); + const [isRelinking, setIsRelinking] = useState(false); const continueDeleteAction = async () => { await deleteAction?.(id); @@ -41,6 +43,23 @@ export const Detail: FC = ({ service, row }) => { router.push(`${getBasePath()}${entity}`); }; + const continueRelinkAction = async () => { + setIsRelinking(true); + try { + const response = await fetch(`/link/api/${entity}/bots/${token}/relink`, { + method: "POST", + }); + if (response.ok) { + setShowRelinkConfirmation(false); + router.refresh(); + } + } catch (error) { + console.error("Relink failed:", error); + } finally { + setIsRelinking(false); + } + }; + return ( <> = ({ service, row }) => { onClick={() => setShowDeleteConfirmation(true)} /> + {service === "whatsapp" && ( + + + + + ); }; diff --git a/packages/bridge-ui/index.ts b/packages/bridge-ui/index.ts index 1735861..48d4b7c 100644 --- a/packages/bridge-ui/index.ts +++ b/packages/bridge-ui/index.ts @@ -10,4 +10,5 @@ export { sendMessage, receiveMessage, handleWebhook, + relinkBot, } from "./lib/routing"; diff --git a/packages/bridge-ui/lib/routing.ts b/packages/bridge-ui/lib/routing.ts index 17e685b..320a7b9 100644 --- a/packages/bridge-ui/lib/routing.ts +++ b/packages/bridge-ui/lib/routing.ts @@ -23,3 +23,8 @@ export const handleWebhook = async ( req: NextRequest, params: ServiceParams, ): Promise => (await getService(params))?.handleWebhook(req); + +export const relinkBot = async ( + _req: NextRequest, + params: ServiceParams, +): Promise => (await getService(params))?.relink(params); diff --git a/packages/bridge-ui/lib/service.ts b/packages/bridge-ui/lib/service.ts index 2a95ae9..603f996 100644 --- a/packages/bridge-ui/lib/service.ts +++ b/packages/bridge-ui/lib/service.ts @@ -122,4 +122,8 @@ export class Service { async handleWebhook(_req: NextRequest): Promise { return NextResponse.error() as any; } + + async relink({ params: _params }: ServiceParams): Promise { + return NextResponse.error() as any; + } } diff --git a/packages/bridge-ui/lib/whatsapp.ts b/packages/bridge-ui/lib/whatsapp.ts index e4d0596..6dea58c 100644 --- a/packages/bridge-ui/lib/whatsapp.ts +++ b/packages/bridge-ui/lib/whatsapp.ts @@ -31,4 +31,30 @@ export class Whatsapp extends Service { return NextResponse.json(json); } + + async relink({ params }: ServiceParams) { + const { token } = await params; + const row = await db + .selectFrom("WhatsappBot") + .selectAll() + .where("token", "=", token as string) + .executeTakeFirstOrThrow(); + const id = row.id; + + // Step 1: Call unverify to remove the bot directory and disconnect + const unverifyUrl = `${process.env.BRIDGE_WHATSAPP_URL}/api/bots/${id}/unverify`; + await fetch(unverifyUrl, { method: "POST" }); + + // Step 2: Reset verified flag in database + await db + .updateTable("WhatsappBot") + .set({ verified: false }) + .where("id", "=", id) + .execute(); + + // Step 3: Revalidate the path to refresh the UI + revalidatePath(`/whatsapp/${id}`); + + return NextResponse.json({ success: true, message: "WhatsApp connection reset. Please scan the new QR code." }); + } }