diff --git a/.gitignore b/.gitignore index 9b270af..1f5a509 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ apps/bridge-worker/scripts/* ENVIRONMENT_VARIABLES_MIGRATION.md local-scripts/* docs/ +packages/zammad-addon-bridge/test/ diff --git a/apps/bridge-frontend/package.json b/apps/bridge-frontend/package.json index 9f7f34c..6065502 100644 --- a/apps/bridge-frontend/package.json +++ b/apps/bridge-frontend/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/bridge-frontend", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "type": "module", "scripts": { "dev": "next dev", diff --git a/apps/bridge-migrations/package.json b/apps/bridge-migrations/package.json index 4c7a25e..a868cfb 100644 --- a/apps/bridge-migrations/package.json +++ b/apps/bridge-migrations/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/bridge-migrations", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "type": "module", "scripts": { "migrate:up:all": "tsx migrate.ts up:all", diff --git a/apps/bridge-whatsapp/package.json b/apps/bridge-whatsapp/package.json index 0aa64e8..9a2dc3d 100644 --- a/apps/bridge-whatsapp/package.json +++ b/apps/bridge-whatsapp/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/bridge-whatsapp", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "type": "module", "main": "build/main/index.js", "author": "Darren Clarke ", diff --git a/apps/bridge-worker/package.json b/apps/bridge-worker/package.json index f55f734..b6f0129 100644 --- a/apps/bridge-worker/package.json +++ b/apps/bridge-worker/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/bridge-worker", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "type": "module", "main": "build/main/index.js", "author": "Darren Clarke ", diff --git a/apps/bridge-worker/tasks/signal/send-signal-message.ts b/apps/bridge-worker/tasks/signal/send-signal-message.ts index 3235be2..901a7a1 100644 --- a/apps/bridge-worker/tasks/signal/send-signal-message.ts +++ b/apps/bridge-worker/tasks/signal/send-signal-message.ts @@ -64,13 +64,14 @@ const sendSignalMessageTask = async ({ let groupCreated = false; try { - // Check if 'to' is a group ID (UUID format, group.base64 format, or base64) vs phone number - const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( - to, - ); + // Check if 'to' is a group ID (group.base64 format or base64 internal ID) vs individual recipient + // Signal group IDs are 32 bytes = 44 chars base64 (or 43 without padding) + // Signal user UUIDs (ACIs) are 36 chars with hyphens: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // Phone numbers start with +, usernames with u:, PNIs with PNI: const isGroupPrefix = to.startsWith("group."); - const isBase64 = /^[A-Za-z0-9+/]+=*$/.test(to) && to.length > 20; // Base64 internal_id - const isGroupId = isUUID || isGroupPrefix || isBase64; + const isBase64GroupId = + /^[A-Za-z0-9+/]+=*$/.test(to) && to.length >= 43 && to.length <= 44; + const isGroupId = isGroupPrefix || isBase64GroupId; const enableAutoGroups = process.env.BRIDGE_SIGNAL_AUTO_GROUPS === "true"; logger.debug( diff --git a/apps/link/package.json b/apps/link/package.json index c712cac..88377dd 100644 --- a/apps/link/package.json +++ b/apps/link/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/link", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "type": "module", "scripts": { "dev": "next dev -H 0.0.0.0", diff --git a/package.json b/package.json index 6ef88f0..1ab613e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "Link from the Center for Digital Resilience", "scripts": { "dev": "dotenv -- turbo dev", diff --git a/packages/bridge-common/package.json b/packages/bridge-common/package.json index ac9fcca..071af5e 100644 --- a/packages/bridge-common/package.json +++ b/packages/bridge-common/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/bridge-common", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "main": "build/main/index.js", "type": "module", "author": "Darren Clarke ", diff --git a/packages/bridge-ui/package.json b/packages/bridge-ui/package.json index 019d5cf..3fba241 100644 --- a/packages/bridge-ui/package.json +++ b/packages/bridge-ui/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/bridge-ui", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "scripts": { "build": "tsc -p tsconfig.json" }, diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index fad4ed4..d8ec904 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/eslint-config", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "amigo's eslint config", "main": "index.js", "author": "Abel Luck ", diff --git a/packages/jest-config/package.json b/packages/jest-config/package.json index f66e6bf..86e0db3 100644 --- a/packages/jest-config/package.json +++ b/packages/jest-config/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/jest-config", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "", "main": "index.js", "author": "Abel Luck ", diff --git a/packages/logger/package.json b/packages/logger/package.json index 835c95d..fafb1b4 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/logger", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "Shared logging utility for Link Stack monorepo", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/packages/signal-api/package.json b/packages/signal-api/package.json index 02f3f8e..731d965 100644 --- a/packages/signal-api/package.json +++ b/packages/signal-api/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/signal-api", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "type": "module", "main": "build/index.js", "exports": { diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index 300f7dc..4c846eb 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/typescript-config", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "Shared TypeScript config", "license": "AGPL-3.0-or-later", "author": "Abel Luck ", diff --git a/packages/ui/package.json b/packages/ui/package.json index 4b2f24a..1e90c0b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/ui", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "", "scripts": { "build": "tsc -p tsconfig.json" diff --git a/packages/zammad-addon-bridge/package.json b/packages/zammad-addon-bridge/package.json index e7b33c1..2c83e3a 100644 --- a/packages/zammad-addon-bridge/package.json +++ b/packages/zammad-addon-bridge/package.json @@ -1,7 +1,7 @@ { "name": "@link-stack/zammad-addon-bridge", "displayName": "Bridge", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "An addon that adds CDR Bridge channels to Zammad.", "scripts": { "build": "node '../zammad-addon-common/dist/build.js'", diff --git a/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_signal_controller.rb b/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_signal_controller.rb index bd22b09..d467a13 100644 --- a/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_signal_controller.rb +++ b/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_signal_controller.rb @@ -165,7 +165,7 @@ class ChannelsCdrSignalController < ApplicationController # Lookup customer with fallback chain: # 1. Phone number in phone/mobile fields (preferred) - # 2. Signal user ID in signal_user_id field + # 2. Signal user ID in signal_uid field # 3. User ID in phone/mobile fields (legacy - we used to store UUIDs there) customer = nil if sender_phone_number.present? @@ -173,7 +173,7 @@ class ChannelsCdrSignalController < ApplicationController customer ||= User.find_by(mobile: sender_phone_number) end if customer.nil? && sender_user_id.present? - customer = User.find_by(signal_user_id: sender_user_id) + customer = User.find_by(signal_uid: sender_user_id) # Legacy fallback: user ID might be stored in phone field customer ||= User.find_by(phone: sender_user_id) customer ||= User.find_by(mobile: sender_user_id) @@ -187,7 +187,7 @@ class ChannelsCdrSignalController < ApplicationController email: '', password: '', phone: sender_phone_number.presence || sender_user_id, - signal_user_id: sender_user_id, + signal_uid: sender_user_id, note: 'CDR Signal', active: true, role_ids: role_ids, @@ -196,9 +196,9 @@ class ChannelsCdrSignalController < ApplicationController ) end - # Update signal_user_id if we have it and customer doesn't - if sender_user_id.present? && customer.signal_user_id.blank? - customer.update(signal_user_id: sender_user_id) + # Update signal_uid if we have it and customer doesn't + if sender_user_id.present? && customer.signal_uid.blank? + customer.update(signal_uid: sender_user_id) end # Update phone if we have it and customer only has user_id in phone field if sender_phone_number.present? && customer.phone == sender_user_id @@ -282,7 +282,8 @@ class ChannelsCdrSignalController < ApplicationController ticket.state = Ticket::State.find_by(default_follow_up: true) if ticket.state_id != new_state.id else # Set up chat_id based on whether this is a group message - chat_id = is_group_message ? receiver_phone_number : (sender_phone_number.presence || sender_user_id) + # For direct messages, prefer UUID (more stable than phone numbers which can change) + chat_id = is_group_message ? receiver_phone_number : (sender_user_id.presence || sender_phone_number) # Build preferences with group_id included if needed cdr_signal_prefs = { diff --git a/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_whatsapp_controller.rb b/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_whatsapp_controller.rb index bc91676..b273533 100644 --- a/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_whatsapp_controller.rb +++ b/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_whatsapp_controller.rb @@ -150,7 +150,7 @@ class ChannelsCdrWhatsappController < ApplicationController # Lookup customer with fallback chain: # 1. Phone number in phone/mobile fields (preferred) - # 2. WhatsApp user ID in whatsapp_user_id field + # 2. WhatsApp user ID in whatsapp_uid field # 3. User ID in phone/mobile fields (legacy - we used to store LIDs there) customer = nil if sender_phone_number.present? @@ -158,7 +158,7 @@ class ChannelsCdrWhatsappController < ApplicationController customer ||= User.find_by(mobile: sender_phone_number) end if customer.nil? && sender_user_id.present? - customer = User.find_by(whatsapp_user_id: sender_user_id) + customer = User.find_by(whatsapp_uid: sender_user_id) # Legacy fallback: user ID might be stored in phone field customer ||= User.find_by(phone: sender_user_id) customer ||= User.find_by(mobile: sender_user_id) @@ -172,7 +172,7 @@ class ChannelsCdrWhatsappController < ApplicationController email: '', password: '', phone: sender_phone_number.presence || sender_user_id, - whatsapp_user_id: sender_user_id, + whatsapp_uid: sender_user_id, note: 'CDR Whatsapp', active: true, role_ids: role_ids, @@ -181,9 +181,9 @@ class ChannelsCdrWhatsappController < ApplicationController ) end - # Update whatsapp_user_id if we have it and customer doesn't - if sender_user_id.present? && customer.whatsapp_user_id.blank? - customer.update(whatsapp_user_id: sender_user_id) + # Update whatsapp_uid if we have it and customer doesn't + if sender_user_id.present? && customer.whatsapp_uid.blank? + customer.update(whatsapp_uid: sender_user_id) end # Update phone if we have it and customer only has user_id in phone field if sender_phone_number.present? && customer.phone == sender_user_id diff --git a/packages/zammad-addon-bridge/src/db/addon/bridge/20260115000001_add_messaging_user_ids.rb b/packages/zammad-addon-bridge/src/db/addon/bridge/20260115000001_add_messaging_user_ids.rb index 817f9c2..2e247eb 100644 --- a/packages/zammad-addon-bridge/src/db/addon/bridge/20260115000001_add_messaging_user_ids.rb +++ b/packages/zammad-addon-bridge/src/db/addon/bridge/20260115000001_add_messaging_user_ids.rb @@ -1,25 +1,123 @@ -# frozen_string_literal: true - class AddMessagingUserIds < ActiveRecord::Migration[5.2] def self.up - # Add WhatsApp user ID field (LID - Linked ID in Baileys 7+) - unless column_exists?(:users, :whatsapp_user_id) - add_column :users, :whatsapp_user_id, :string, limit: 50 - add_index :users, :whatsapp_user_id + # Add WhatsApp UID column + unless column_exists?(:users, :whatsapp_uid) + add_column :users, :whatsapp_uid, :string, limit: 50 + add_index :users, :whatsapp_uid end + User.reset_column_information - # Add Signal user ID field (UUID) - unless column_exists?(:users, :signal_user_id) - add_column :users, :signal_user_id, :string, limit: 50 - add_index :users, :signal_user_id + # Add Signal UID column + unless column_exists?(:users, :signal_uid) + add_column :users, :signal_uid, :string, limit: 50 + add_index :users, :signal_uid end + User.reset_column_information + + # Register WhatsApp UID with ObjectManager for UI + # Column name: whatsapp_uid, Display name: "WhatsApp User ID" + ObjectManager::Attribute.add( + force: true, + object: 'User', + name: 'whatsapp_uid', + display: 'WhatsApp User ID', + data_type: 'input', + data_option: { + type: 'text', + maxlength: 50, + null: true, + item_class: 'formGroup--halfSize', + }, + editable: false, + active: true, + screens: { + signup: {}, + invite_agent: {}, + invite_customer: {}, + edit: { + '-all-' => { + null: true, + }, + }, + create: { + '-all-' => { + null: true, + }, + }, + view: { + '-all-' => { + shown: true, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 710, + created_by_id: 1, + updated_by_id: 1, + ) + + # Register Signal UID with ObjectManager for UI + # Column name: signal_uid, Display name: "Signal User ID" + ObjectManager::Attribute.add( + force: true, + object: 'User', + name: 'signal_uid', + display: 'Signal User ID', + data_type: 'input', + data_option: { + type: 'text', + maxlength: 50, + null: true, + item_class: 'formGroup--halfSize', + }, + editable: false, + active: true, + screens: { + signup: {}, + invite_agent: {}, + invite_customer: {}, + edit: { + '-all-' => { + null: true, + }, + }, + create: { + '-all-' => { + null: true, + }, + }, + view: { + '-all-' => { + shown: true, + }, + }, + }, + to_create: false, + to_migrate: false, + to_delete: false, + position: 720, + created_by_id: 1, + updated_by_id: 1, + ) end def self.down - remove_index :users, :whatsapp_user_id if index_exists?(:users, :whatsapp_user_id) - remove_column :users, :whatsapp_user_id if column_exists?(:users, :whatsapp_user_id) + ObjectManager::Attribute.remove( + object: 'User', + name: 'whatsapp_uid', + ) - remove_index :users, :signal_user_id if index_exists?(:users, :signal_user_id) - remove_column :users, :signal_user_id if column_exists?(:users, :signal_user_id) + ObjectManager::Attribute.remove( + object: 'User', + name: 'signal_uid', + ) + + remove_index :users, :whatsapp_uid if index_exists?(:users, :whatsapp_uid) + remove_column :users, :whatsapp_uid if column_exists?(:users, :whatsapp_uid) + + remove_index :users, :signal_uid if index_exists?(:users, :signal_uid) + remove_column :users, :signal_uid if column_exists?(:users, :signal_uid) end end diff --git a/packages/zammad-addon-common/package.json b/packages/zammad-addon-common/package.json index 281214c..f63468d 100644 --- a/packages/zammad-addon-common/package.json +++ b/packages/zammad-addon-common/package.json @@ -1,6 +1,6 @@ { "name": "@link-stack/zammad-addon-common", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "", "bin": { "zpm-build": "./dist/build.js", diff --git a/packages/zammad-addon-hardening/package.json b/packages/zammad-addon-hardening/package.json index 5404381..d5122b8 100644 --- a/packages/zammad-addon-hardening/package.json +++ b/packages/zammad-addon-hardening/package.json @@ -1,7 +1,7 @@ { "name": "@link-stack/zammad-addon-hardening", "displayName": "Hardening", - "version": "3.4.0-beta.5", + "version": "3.4.0-beta.6", "description": "A Zammad addon that hardens a Zammad instance according to CDR's needs.", "scripts": { "build": "node '../zammad-addon-common/dist/build.js'",