2024-06-05 15:12:48 +02:00
import { db , getWorkerUtils } from "@link-stack/bridge-common" ;
2025-06-10 14:02:21 +02:00
import * as signalApi from "@link-stack/signal-api" ;
const { Configuration , GroupsApi } = signalApi ;
2024-04-30 13:13:49 +02:00
interface ReceiveSignalMessageTaskOptions {
2024-06-05 15:12:48 +02:00
token : string ;
2024-07-18 11:08:01 +02:00
to : string ;
from : string ;
messageId : string ;
sentAt : string ;
2024-06-05 15:12:48 +02:00
message : string ;
2024-07-18 11:08:01 +02:00
attachment? : string ;
filename? : string ;
mimeType? : string ;
2025-06-10 14:02:21 +02:00
isGroup? : boolean ;
2024-04-30 13:13:49 +02:00
}
const receiveSignalMessageTask = async ( {
2024-06-05 15:12:48 +02:00
token ,
2024-07-18 11:08:01 +02:00
to ,
from ,
messageId ,
sentAt ,
2024-04-30 13:13:49 +02:00
message ,
2024-07-18 11:08:01 +02:00
attachment ,
filename ,
mimeType ,
2025-06-10 14:02:21 +02:00
isGroup ,
2024-06-05 15:12:48 +02:00
} : ReceiveSignalMessageTaskOptions ) : Promise < void > = > {
2025-07-07 20:02:54 +02:00
console . log ( ` [receive-signal-message] Processing incoming message: ` , {
messageId ,
from ,
to ,
isGroup ,
hasMessage : ! ! message ,
hasAttachment : ! ! attachment ,
token ,
} ) ;
2024-06-05 15:12:48 +02:00
const worker = await getWorkerUtils ( ) ;
const row = await db
. selectFrom ( "SignalBot" )
. selectAll ( )
. where ( "id" , "=" , token )
. executeTakeFirstOrThrow ( ) ;
const backendId = row . id ;
2025-06-10 14:02:21 +02:00
let finalTo = to ;
2025-07-07 20:02:54 +02:00
let createdInternalId : string | undefined ;
2025-06-10 14:02:21 +02:00
// Check if auto-group creation is enabled and this is NOT already a group message
const enableAutoGroups = process . env . BRIDGE_SIGNAL_AUTO_GROUPS === "true" ;
2025-07-08 18:03:01 +02:00
2025-07-07 20:02:54 +02:00
console . log ( ` [receive-signal-message] Auto-groups config: ` , {
enableAutoGroups ,
isGroup ,
shouldCreateGroup : enableAutoGroups && ! isGroup && from && to ,
} ) ;
2025-06-10 14:02:21 +02:00
2025-07-07 20:02:54 +02:00
// If this is already a group message and auto-groups is enabled,
2025-07-08 18:03:01 +02:00
// use group provided in 'to'
if ( enableAutoGroups && isGroup && to ) {
// Signal sends the internal ID (base64) in group messages
// We should NOT add "group." prefix - that's for sending messages, not receiving
console . log (
` [receive-signal-message] Message is from existing group with internal ID ` ,
) ;
finalTo = to ;
2025-07-07 20:02:54 +02:00
} else if ( enableAutoGroups && ! isGroup && from && to ) {
2025-06-10 14:02:21 +02:00
try {
const config = new Configuration ( {
basePath : process.env.BRIDGE_SIGNAL_URL ,
} ) ;
const groupsClient = new GroupsApi ( config ) ;
2025-07-08 18:03:01 +02:00
// Always create a new group for direct messages to the helpdesk
// This ensures each conversation gets its own group/ticket
console . log (
` [receive-signal-message] Creating new group for user ${ from } ` ,
) ;
// Include timestamp to make each group unique
const timestamp = new Date ( )
. toISOString ( )
. replace ( /[:.]/g , "-" )
. substring ( 0 , 19 ) ;
const groupName = ` Support: ${ from } ( ${ timestamp } ) ` ;
2025-06-10 14:02:21 +02:00
// Create new group for this conversation
const createGroupResponse = await groupsClient . v1GroupsNumberPost ( {
number : row . phoneNumber ,
data : {
name : groupName ,
members : [ from ] ,
description : "Private support conversation" ,
} ,
} ) ;
2025-07-08 18:03:01 +02:00
console . log (
` [receive-signal-message] Full group creation response from Signal API: ` ,
JSON . stringify ( createGroupResponse , null , 2 ) ,
) ;
2025-06-10 14:02:21 +02:00
if ( createGroupResponse . id ) {
2025-07-08 18:03:01 +02:00
// The createGroupResponse.id already contains the full group identifier (group.BASE64)
finalTo = createGroupResponse . id ;
// Fetch the group details to get the actual internalId
// The base64 part of the ID is NOT the same as the internalId!
try {
console . log (
` [receive-signal-message] Fetching group details to get internalId... ` ,
2025-07-07 20:02:54 +02:00
) ;
2025-07-08 18:03:01 +02:00
const groups = await groupsClient . v1GroupsNumberGet ( {
number : row . phoneNumber ,
} ) ;
2025-07-07 20:02:54 +02:00
console . log (
2025-07-08 18:03:01 +02:00
` [receive-signal-message] All groups for bot: ` ,
JSON . stringify ( groups . slice ( 0 , 3 ) , null , 2 ) , // Show first 3 to avoid too much output
2025-07-07 20:02:54 +02:00
) ;
2025-07-08 18:03:01 +02:00
const createdGroup = groups . find ( ( g ) = > g . id === finalTo ) ;
if ( createdGroup ) {
console . log (
` [receive-signal-message] Found created group details: ` ,
JSON . stringify ( createdGroup , null , 2 ) ,
) ;
}
if ( createdGroup && createdGroup . internalId ) {
createdInternalId = createdGroup . internalId ;
console . log (
` [receive-signal-message] Got actual internalId: ${ createdInternalId } ` ,
) ;
} else {
// Fallback: extract base64 part from ID
if ( finalTo . startsWith ( "group." ) ) {
createdInternalId = finalTo . substring ( 6 ) ;
2025-07-07 20:02:54 +02:00
}
2025-07-08 18:03:01 +02:00
}
} catch ( fetchError ) {
console . log (
` [receive-signal-message] Could not fetch group details, using ID base64 part ` ,
) ;
// Fallback: extract base64 part from ID
if ( finalTo . startsWith ( "group." ) ) {
createdInternalId = finalTo . substring ( 6 ) ;
}
2025-07-07 20:02:54 +02:00
}
2025-07-08 18:03:01 +02:00
console . log ( ` [receive-signal-message] Group created successfully: ` , {
fullGroupId : finalTo ,
internalId : createdInternalId ,
} ) ;
console . log ( ` [receive-signal-message] Created new Signal group: ` , {
groupId : finalTo ,
internalId : createdInternalId ,
groupName ,
forPhoneNumber : from ,
botNumber : row.phoneNumber ,
response : createGroupResponse ,
} ) ;
}
// Now handle notifications and message forwarding for both new and existing groups
if ( finalTo && finalTo . startsWith ( "group." ) ) {
2025-07-07 20:02:54 +02:00
// Forward the user's initial message to the group using quote feature
try {
2025-07-08 18:03:01 +02:00
console . log (
` [receive-signal-message] Forwarding initial message to group using quote feature ` ,
) ;
2025-07-07 20:02:54 +02:00
const attributionMessage = ` Message from ${ from } : \ n" ${ message } " \ n \ n--- \ nSupport team: Your request has been received. An agent will respond shortly. ` ;
2025-07-08 18:03:01 +02:00
2025-07-07 20:02:54 +02:00
await worker . addJob ( "signal/send-signal-message" , {
2025-07-08 18:03:01 +02:00
token : row.token ,
to : finalTo ,
2025-07-07 20:02:54 +02:00
message : attributionMessage ,
2025-07-08 18:03:01 +02:00
conversationId : null ,
2025-07-07 20:02:54 +02:00
quoteMessage : message ,
quoteAuthor : from ,
2025-07-08 18:03:01 +02:00
quoteTimestamp : Date.parse ( sentAt ) ,
2025-07-07 20:02:54 +02:00
} ) ;
2025-07-08 18:03:01 +02:00
console . log (
` [receive-signal-message] Successfully forwarded initial message to group ${ finalTo } ` ,
) ;
2025-07-07 20:02:54 +02:00
} catch ( forwardError ) {
2025-07-08 18:03:01 +02:00
console . error (
"[receive-signal-message] Error forwarding message to group:" ,
forwardError ,
) ;
}
// Send a response to the original DM informing about the group
try {
console . log (
` [receive-signal-message] Sending group notification to original DM ` ,
) ;
const dmNotification = ` Hello! A private support group has been created for your conversation. \ n \ nGroup name: ${ groupName } \ n \ nPlease look for the new group in your Signal app to continue the conversation. Our support team will respond there shortly. \ n \ nThank you for contacting support! ` ;
await worker . addJob ( "signal/send-signal-message" , {
token : row.token ,
to : from ,
message : dmNotification ,
conversationId : null ,
} ) ;
console . log (
` [receive-signal-message] Successfully sent group notification to user DM ` ,
) ;
} catch ( dmError ) {
console . error (
"[receive-signal-message] Error sending DM notification:" ,
dmError ,
) ;
2025-07-07 20:02:54 +02:00
}
}
} catch ( error : any ) {
// Check if error is because group already exists
2025-07-08 18:03:01 +02:00
const errorMessage =
error ? . response ? . data ? . error || error ? . message || error ;
const isAlreadyExists =
errorMessage ? . toString ( ) . toLowerCase ( ) . includes ( "already" ) ||
errorMessage ? . toString ( ) . toLowerCase ( ) . includes ( "exists" ) ;
2025-07-07 20:02:54 +02:00
if ( isAlreadyExists ) {
2025-07-08 18:03:01 +02:00
console . log (
` [receive-signal-message] Group might already exist for ${ from } , continuing with original recipient ` ,
) ;
2025-07-07 20:02:54 +02:00
} else {
console . error ( "[receive-signal-message] Error creating Signal group:" , {
error : errorMessage ,
from ,
to ,
botNumber : row.phoneNumber ,
} ) ;
2025-06-10 14:02:21 +02:00
}
}
}
2024-06-05 15:12:48 +02:00
const payload = {
2025-07-08 18:03:01 +02:00
to : finalTo ,
2024-07-18 11:08:01 +02:00
from ,
message_id : messageId ,
sent_at : sentAt ,
2024-06-05 15:12:48 +02:00
message ,
2024-07-18 11:08:01 +02:00
attachment ,
filename ,
mime_type : mimeType ,
2025-07-08 18:03:01 +02:00
is_group : finalTo.startsWith ( "group" ) ,
2024-06-05 15:12:48 +02:00
} ;
await worker . addJob ( "common/notify-webhooks" , { backendId , payload } ) ;
} ;
2024-04-30 13:13:49 +02:00
export default receiveSignalMessageTask ;