Whatsapp unlink WIP #1

This commit is contained in:
Darren Clarke 2025-11-09 11:12:04 +01:00
parent 12b72a727c
commit 48165db6a2
9 changed files with 120 additions and 3 deletions

View file

@ -0,0 +1 @@
export { relinkBot as POST } from "@link-stack/bridge-ui";

View file

@ -286,8 +286,30 @@ export default class WhatsappService extends Service {
} }
async unverify(botID: string): Promise<void> { async unverify(botID: string): Promise<void> {
// 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); 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( async register(

View file

@ -182,7 +182,7 @@ const sendSignalMessageTask = async ({
{ {
number, number,
recipients: [finalTo], recipients: [finalTo],
message: message.substring(0, 50) + "...", messageLength: message?.length,
hasQuoteParams: !!(quoteMessage && quoteAuthor && quoteTimestamp), hasQuoteParams: !!(quoteMessage && quoteAuthor && quoteTimestamp),
}, },
"Message data being sent", "Message data being sent",
@ -197,7 +197,7 @@ const sendSignalMessageTask = async ({
logger.debug( logger.debug(
{ {
quoteAuthor, quoteAuthor,
quoteMessage: quoteMessage.substring(0, 50) + "...", quoteMessageLength: quoteMessage?.length,
quoteTimestamp, quoteTimestamp,
}, },
"Including quote in message", "Including quote in message",

View file

@ -0,0 +1 @@
export { relinkBot as POST } from "@link-stack/bridge-ui";

View file

@ -34,6 +34,8 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
const { almostBlack } = colors; const { almostBlack } = colors;
const { bodyLarge } = typography; const { bodyLarge } = typography;
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const [showRelinkConfirmation, setShowRelinkConfirmation] = useState(false);
const [isRelinking, setIsRelinking] = useState(false);
const continueDeleteAction = async () => { const continueDeleteAction = async () => {
await deleteAction?.(id); await deleteAction?.(id);
@ -41,6 +43,23 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
router.push(`${getBasePath()}${entity}`); 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 ( return (
<> <>
<Dialog <Dialog
@ -57,6 +76,15 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
onClick={() => setShowDeleteConfirmation(true)} onClick={() => setShowDeleteConfirmation(true)}
/> />
</Grid> </Grid>
{service === "whatsapp" && (
<Grid item>
<Button
text="Relink"
kind="secondary"
onClick={() => setShowRelinkConfirmation(true)}
/>
</Grid>
)}
<Grid item> <Grid item>
<Button <Button
text="Edit" text="Edit"
@ -129,6 +157,35 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
Are you sure you want to delete this record? Are you sure you want to delete this record?
</Box> </Box>
</Dialog> </Dialog>
<Dialog
open={showRelinkConfirmation}
size="xs"
title="Relink WhatsApp Connection?"
buttons={
<Grid container justifyContent="space-between">
<Grid item>
<Button
text="Cancel"
kind="secondary"
onClick={() => setShowRelinkConfirmation(false)}
disabled={isRelinking}
/>
</Grid>
<Grid item>
<Button
text={isRelinking ? "Relinking..." : "Relink"}
kind="primary"
onClick={continueRelinkAction}
disabled={isRelinking}
/>
</Grid>
</Grid>
}
>
<Box sx={{ ...bodyLarge, color: almostBlack }}>
This will disconnect the current WhatsApp link and generate a new QR code. You will need to scan the new QR code to reconnect. Continue?
</Box>
</Dialog>
</> </>
); );
}; };

View file

@ -10,4 +10,5 @@ export {
sendMessage, sendMessage,
receiveMessage, receiveMessage,
handleWebhook, handleWebhook,
relinkBot,
} from "./lib/routing"; } from "./lib/routing";

View file

@ -23,3 +23,8 @@ export const handleWebhook = async (
req: NextRequest, req: NextRequest,
params: ServiceParams, params: ServiceParams,
): Promise<NextResponse> => (await getService(params))?.handleWebhook(req); ): Promise<NextResponse> => (await getService(params))?.handleWebhook(req);
export const relinkBot = async (
_req: NextRequest,
params: ServiceParams,
): Promise<NextResponse> => (await getService(params))?.relink(params);

View file

@ -122,4 +122,8 @@ export class Service {
async handleWebhook(_req: NextRequest): Promise<NextResponse> { async handleWebhook(_req: NextRequest): Promise<NextResponse> {
return NextResponse.error() as any; return NextResponse.error() as any;
} }
async relink({ params: _params }: ServiceParams): Promise<NextResponse> {
return NextResponse.error() as any;
}
} }

View file

@ -31,4 +31,30 @@ export class Whatsapp extends Service {
return NextResponse.json(json); 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." });
}
} }