Generating embedded webchat
To simplify integration with your website, the CLI includes theorchestrate channels webchat embed
command. This command takes the name of an agent and produces a script tag that you can place in the <head></head>
section of your page for the currently active environment.
BASH
Copy
Ask AI
orchestrate channels webchat embed --agent-name=test_agent1
OUTPUT
Copy
Ask AI
<script>
window.wxOConfiguration = {
orchestrationID: "your-orgID_orchestrationID", // Adds control over chat display mode (e.g., fullscreen)
hostURL: "https://dl.watson-orchestrate.ibm.com", // or region-specific host
rootElementID: "root",
showLauncher: false,
deploymentPlatform: "ibmcloud", // Required for IBM Cloud embed, can be skipped for other embed scenarios
crn: "your-org-crn", // Required for IBM Cloud embed, can be skipped for other embed scenarios. For more information, see https://www.ibm.com/docs/en/watsonx/watson-orchestrate/base?topic=experience-faqs#how-can-i-know-my-crn-id
chatOptions: {
agentId: "your-agent-id",
agentEnvironmentId: "your-agent-env-id",
},
layout: {
form: "fullscreen-overlay", // Options: float | custom | fullscreen-overlay - if not specified float is default
showOrchestrateHeader: true, // Optional: shows top agent header bar
width: "600px", // Optional: honored when form is float only
height: "600px", // Optional: honored when form is float only
},
};
setTimeout(function () {
const script = document.createElement("script");
script.src = `${window.wxOConfiguration.hostURL}/wxochat/wxoLoader.js?embed=true`;
script.addEventListener("load", function () {
wxoLoader.init();
});
document.head.appendChild(script);
}, 0);
</script>
Embedded chat security
By default, security is enabled, but not configured for the embedded chat. This means:- The embedded chat will not function until security is properly configured
- You must configure both IBM and client key pairs for the chat to work
- Alternatively, you can explicitly disable security to allow anonymous access
Security Architecture
The embedded chat uses RSA public-key cryptography to secure communication. The configuration involves two key pairs:-
IBM Key Pair
- Generated by: watsonx Orchestrate service.
- Public key: Shared with your application. Your application uses this key to encrypt the
user_payload
section of the JWT sent to watsonx Orchestrate. - Private key: Stored securely by watsonx Orchestrate.cUsed to decrypt the
user_payload
section of the JWT.
-
Client Key Pair
- Generated by: You (or a security configuration tool).
- Public key: Shared with watsonx Orchestrate. Used to verify that JWTs originate from your application.
- Private key: Remains with you and must be stored securely. Used to sign JWTs sent to watsonx Orchestrate.
-
JWT Authentication
- When security is enabled, your application must:
- Generate a JWT signed with your private key (Client Key Pair).
- Include the JWT in all requests to the Embedded Chat API. watsonx Orchestrate validates the token using your public key.
- When security is enabled, your application must:
- All requests to the Embedded Chat API must include a valid JWT token
- The token must be signed with your private key
- The watsonx Orchestrate service validates the token using your public key
- This prevents unauthorized access to your watsonx Orchestrate instance
- Requests to the Embedded Chat API do not require authentication
- Anyone with access to your web application where chat is Embedded can access your watsonx Orchestrate instance. In addition, your Watson Orchestrate instance allows anonymous authentication to a limited set of Apis, which is required to get your embed chat to work for anonymous users.
- This option should only be used for specific use cases where anonymous chat access is required.
- Ensure your watsonx Orchestrate instance in this case, does not provide access to sensitive data or access to tools configured with functional credentials that access sensitive data.
Enabling security
1
Prerequisites
- IBM watsonx Orchestrate instance
- API Key with administrative privileges
- Service Instance URL from your watsonx Orchestrate instance
- On macOS and Linux: OpenSSL installed on your system (for key generation)
- Python Installed on your system (for key extraction from APIs)
2
Get the `wxO-embed-chat-security-tool.sh`
Copy the following automated script to configure security:
wxO-embed-chat-security-tool.sh
Copy
Ask AI
#!/bin/bash
# IBM watsonx Orchestrate - Embedded Chat Security Configuration Tool (Universal Version)
# This script works on both Windows (PowerShell) and Unix-based systems (Bash)
# Detect OS and execute appropriate script
if [ -n "$BASH_VERSION" ]; then
# Running in Bash (Unix/Linux/Mac)
echo "Detected Bash environment. Running Unix/Linux/Mac version..."
# Parse command line arguments
VERBOSE=false
for arg in "$@"; do
case $arg in
-v|--verbose)
VERBOSE=true
shift
;;
esac
done
# Text formatting for Bash
BOLD='\033[1m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Debug function that only prints in verbose mode
debug_print() {
if [ "$VERBOSE" = true ]; then
echo -e "${BLUE}DEBUG: $1${NC}"
fi
}
# Create output directory
OUTPUT_DIR="wxo_security_config"
echo -e "${BLUE}Creating output directory: $OUTPUT_DIR${NC}"
# Check if directory exists
if [ -d "$OUTPUT_DIR" ]; then
echo -e "${GREEN}Output directory already exists.${NC}"
else
# Try to create the directory
mkdir -p "$OUTPUT_DIR" 2>/dev/null
# Check if creation was successful
if [ -d "$OUTPUT_DIR" ]; then
echo -e "${GREEN}Output directory created successfully.${NC}"
else
echo -e "${RED}ERROR: Failed to create output directory '$OUTPUT_DIR'.${NC}"
echo -e "${YELLOW}Please check permissions or create the directory manually:${NC}"
echo -e " mkdir -p $OUTPUT_DIR"
echo -e "${YELLOW}Then run this script again.${NC}"
exit 1
fi
fi
# Verify directory is writable
if [ -w "$OUTPUT_DIR" ]; then
echo -e "${GREEN}Output directory is writable.${NC}"
else
echo -e "${RED}ERROR: Output directory '$OUTPUT_DIR' is not writable.${NC}"
echo -e "${YELLOW}Please check permissions:${NC}"
echo -e " chmod 755 $OUTPUT_DIR"
echo -e "${YELLOW}Then run this script again.${NC}"
exit 1
fi
# Display welcome message
echo -e "${BOLD}Welcome to the IBM watsonx Orchestrate Embedded Chat Security Configuration Tool${NC}\n"
echo -e "This tool will guide you through configuring security for your embedded chat integration.\n"
echo -e "${YELLOW}IMPORTANT: By default, security is enabled but not configured, which means Embed Chat will not function until properly configured.${NC}\n"
# Function to check and create output directory
check_output_directory() {
# Check if directory exists
if [ ! -d "$OUTPUT_DIR" ]; then
echo -e "${YELLOW}Output directory '$OUTPUT_DIR' does not exist. Creating it now...${NC}"
# Try to create the directory
mkdir -p "$OUTPUT_DIR" 2>/dev/null
# Check if creation was successful
if [ ! -d "$OUTPUT_DIR" ]; then
echo -e "${RED}ERROR: Failed to create output directory '$OUTPUT_DIR'.${NC}"
echo -e "${YELLOW}Please check permissions or create the directory manually:${NC}"
echo -e " mkdir -p $OUTPUT_DIR"
return 1
fi
fi
# Verify directory is writable
if [ ! -w "$OUTPUT_DIR" ]; then
echo -e "${RED}ERROR: Output directory '$OUTPUT_DIR' is not writable.${NC}"
echo -e "${YELLOW}Please check permissions:${NC}"
echo -e " chmod 755 $OUTPUT_DIR"
return 1
fi
return 0
}
# Function to get user input with validation
get_input() {
local prompt="$1"
local var_name="$2"
local is_secret="$3"
local value=""
while [ -z "$value" ]; do
if [ "$is_secret" = true ]; then
read -sp "$prompt: " value
echo
else
read -p "$prompt: " value
fi
if [ -z "$value" ]; then
echo -e "${YELLOW}This field cannot be empty. Please try again.${NC}"
fi
done
eval $var_name=\$value
}
# Function to display help for finding instance ID and API URL
show_instance_id_help() {
echo -e "\n${BOLD}How to Find Your Instance ID and API URL:${NC}"
echo -e "1. Log in to your watsonx Orchestrate instance"
echo -e "2. Click on the profile icon in the top right corner"
echo -e "3. Select \"Settings\" from the dropdown menu"
echo -e "4. Navigate to the \"API Details\" tab"
echo -e "5. Find the \"Service instance URL\" field, which looks like:"
echo -e " ${BLUE}https://api.us-south.watson-orchestrate.ibm.com/instances/20250807-1007-4445-5049-459a42144389${NC}"
echo -e "6. Your API URL is the base URL: ${BLUE}https://api.us-south.watson-orchestrate.ibm.com${NC}"
echo -e "7. Your Instance ID is the UUID after \"/instances/\": ${BLUE}20250807-1007-4445-5049-459a42144389${NC}"
echo -e "\nYour API Key can also be found in the same API Details tab."
echo -e "Press Enter to continue..."
read
}
# Function to select environment
select_environment() {
# Default to Production environment
ENVIRONMENT="PROD"
IAMURL="https://iam.platform.saas.ibm.com"
echo -e "\n${BOLD}Using Production environment by default for initial setup.${NC}"
echo -e "${BLUE}The tool will automatically try other environments if needed.${NC}"
echo "IAM URL: $IAMURL"
}
# Function to select a different environment if needed
select_different_environment() {
echo -e "\n${BOLD}Select your environment:${NC}"
echo "1) Development"
echo "2) Test"
echo "3) Production"
local selection
while true; do
read -p "Enter your choice (1-3): " selection
case $selection in
1) ENVIRONMENT="DEV"; IAMURL="https://iam.platform.dev.saas.ibm.com"; break;;
2) ENVIRONMENT="TEST"; IAMURL="https://iam.platform.test.saas.ibm.com"; break;;
3) ENVIRONMENT="PROD"; IAMURL="https://iam.platform.saas.ibm.com"; break;;
*) echo -e "${YELLOW}Invalid selection. Please enter 1, 2, or 3.${NC}";;
esac
done
echo -e "${GREEN}Selected environment: $ENVIRONMENT${NC}"
echo "IAM URL: $IAMURL"
}
# Function to parse Service instance URL and extract API URL and instance ID
parse_service_instance_url() {
local service_url="$1"
# Check if the URL matches the expected pattern
if [[ $service_url =~ ^(https?://[^/]+)/instances/([a-zA-Z0-9-]+)$ ]]; then
API_URL="${BASH_REMATCH[1]}"
WXO_INSTANCE_ID="${BASH_REMATCH[2]}"
# Check if this is an IBM Cloud instance
if [[ $API_URL == *".cloud.ibm.com"* ]]; then
IS_IBM_CLOUD=true
echo -e "${BLUE}Detected IBM Cloud instance. Will use API key directly for authentication.${NC}"
else
IS_IBM_CLOUD=false
fi
return 0
else
return 1
fi
}
# Function to get API URL and instance ID
get_service_details() {
echo -e "\n${BOLD}Enter your Service instance URL:${NC}"
echo -e "${BLUE}You can find this URL in the Settings page under API Details tab.${NC}"
echo -e "${BLUE}Example: https://api.us-south.watson-orchestrate.ibm.com/instances/12345-67890-abcde${NC}"
echo -e "${BLUE}Common API regions include:${NC}"
echo -e "${BLUE}- api.us-south.watson-orchestrate.ibm.com (US South/Dallas)${NC}"
echo -e "${BLUE}- api.eu-de.watson-orchestrate.ibm.com (EU DE/Frankfurt)${NC}"
echo -e "${BLUE}- api.dl.watson-orchestrate.ibm.com (Dallas)${NC}"
local service_url
while true; do
read -p "Enter your Service instance URL: " service_url
if [[ -z "$service_url" ]]; then
echo -e "${YELLOW}This field cannot be empty. Please try again.${NC}"
continue
fi
if parse_service_instance_url "$service_url"; then
echo -e "${GREEN}Successfully parsed Service instance URL.${NC}"
echo -e "API URL: ${BOLD}$API_URL${NC}"
echo -e "Instance ID: ${BOLD}$WXO_INSTANCE_ID${NC}"
return 0
else
echo -e "${YELLOW}Invalid Service instance URL format. It should be like:${NC}"
echo -e "${YELLOW}https://api.us-south.watson-orchestrate.ibm.com/instances/12345-67890-abcde${NC}"
read -p "Would you like to enter the API URL and Instance ID separately? (yes/no): " separate_input
if [[ "$separate_input" == "yes" ]]; then
get_api_url_separately
get_instance_id_separately
return 0
fi
fi
done
}
# Function to get API URL separately
get_api_url_separately() {
echo -e "\n${BOLD}Enter your API URL:${NC}"
echo -e "${BLUE}It's the base part of your Service instance URL (before /instances/).${NC}"
echo -e "${BLUE}Example: https://api.us-south.watson-orchestrate.ibm.com${NC}"
get_input "Enter your API URL" API_URL false
echo -e "${GREEN}API URL: $API_URL${NC}"
}
# Function to get instance ID separately
get_instance_id_separately() {
echo -e "\n${BOLD}Enter your Orchestrate instance ID:${NC}"
echo -e "${BLUE}This is the UUID after /instances/ in your Service instance URL.${NC}"
echo -e "${BLUE}Example: 12345-67890-abcde${NC}"
get_input "Enter your Orchestrate instance ID" WXO_INSTANCE_ID false
}
# Function to obtain IAM token
obtain_iam_token() {
# Check output directory before saving token
check_output_directory || exit 1
echo -e "\n${BOLD}Step 1: Obtaining IAM Token${NC}"
get_input "Enter your IBM watsonx Orchestrate API Key" WXO_API_KEY true
# Keep track of which environments have been tried
local tried_prod=false
local tried_dev=false
local tried_test=false
local token_obtained=false
# Try with the default environment first
echo -e "\nTrying with ${BOLD}$ENVIRONMENT${NC} environment..."
echo "IAM URL: $IAMURL"
TOKEN_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$IAMURL/siusermgr/api/1.0/apikeys/token" \
--header "accept: application/json" \
--header "content-type: application/json" \
--data "{\"apikey\": \"$WXO_API_KEY\"}" 2>&1)
if [ $? -eq 0 ]; then
WXO_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"token":"[^"]*' | cut -d'"' -f4)
if [ -n "$WXO_TOKEN" ]; then
token_obtained=true
echo -e "${GREEN}Successfully obtained IAM token with $ENVIRONMENT environment.${NC}"
# Store token in memory only, don't save to file
echo -e "${GREEN}Successfully obtained IAM token.${NC}"
fi
fi
# Mark the current environment as tried
if [ "$ENVIRONMENT" = "PROD" ]; then
tried_prod=true
elif [ "$ENVIRONMENT" = "DEV" ]; then
tried_dev=true
elif [ "$ENVIRONMENT" = "TEST" ]; then
tried_test=true
fi
# If token was not obtained, try other environments
while [ "$token_obtained" = false ]; do
echo -e "${YELLOW}Failed to obtain token with $ENVIRONMENT environment.${NC}"
echo "$TOKEN_RESPONSE"
echo -e "\n${YELLOW}This could be due to:${NC}"
echo -e "1. Incorrect API key"
echo -e "2. Using an API key from a different watsonx Orchestrate environment"
# Check if all environments have been tried
if [ "$tried_prod" = true ] && [ "$tried_dev" = true ] && [ "$tried_test" = true ]; then
echo -e "\n${RED}Failed to obtain IAM token after trying all environments (PROD, DEV, TEST).${NC}"
echo -e "${RED}This likely indicates an incorrect API key. Please verify your API key and try again.${NC}"
exit 1
fi
echo -e "\n${BOLD}Would you like to try a different environment?${NC}"
echo "1) Development $([ "$tried_dev" = true ] && echo "[Already tried]")"
echo "2) Test $([ "$tried_test" = true ] && echo "[Already tried]")"
echo "3) Production $([ "$tried_prod" = true ] && echo "[Already tried]")"
echo "4) Exit"
local selection
read -p "Enter your choice (1-4): " selection
case $selection in
1)
if [ "$tried_dev" = true ]; then
echo -e "${YELLOW}You've already tried the Development environment.${NC}"
continue
fi
ENVIRONMENT="DEV"
IAMURL="https://iam.platform.dev.saas.ibm.com"
tried_dev=true
;;
2)
if [ "$tried_test" = true ]; then
echo -e "${YELLOW}You've already tried the Test environment.${NC}"
continue
fi
ENVIRONMENT="TEST"
IAMURL="https://iam.platform.test.saas.ibm.com"
tried_test=true
;;
3)
if [ "$tried_prod" = true ]; then
echo -e "${YELLOW}You've already tried the Production environment.${NC}"
continue
fi
ENVIRONMENT="PROD"
IAMURL="https://iam.platform.saas.ibm.com"
tried_prod=true
;;
4)
echo -e "${BLUE}Exiting the configuration tool.${NC}"
exit 0
;;
*)
echo -e "${YELLOW}Invalid selection. Please enter 1, 2, 3, or 4.${NC}"
continue
;;
esac
echo -e "\nTrying with ${BOLD}$ENVIRONMENT${NC} environment..."
echo "IAM URL: $IAMURL"
TOKEN_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$IAMURL/siusermgr/api/1.0/apikeys/token" \
--header "accept: application/json" \
--header "content-type: application/json" \
--data "{\"apikey\": \"$WXO_API_KEY\"}" 2>&1)
if [ $? -eq 0 ]; then
WXO_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"token":"[^"]*' | cut -d'"' -f4)
if [ -n "$WXO_TOKEN" ]; then
token_obtained=true
echo -e "${GREEN}Successfully obtained IAM token with $ENVIRONMENT environment.${NC}"
# Store token in memory only, don't save to file
echo -e "${GREEN}Successfully obtained IAM token.${NC}"
fi
fi
done
}
# Function to get current configuration
get_current_config() {
# Check output directory before saving configuration
check_output_directory || exit 1
echo -e "\n${BOLD}Getting current embed security configuration...${NC}"
# Use different authentication header based on instance type
if [ "$IS_IBM_CLOUD" = true ]; then
CONFIG_RESPONSE=$(curl --fail -sS \
--request GET \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "IAM-API_KEY: $WXO_API_KEY" \
--header "accept: application/json" 2>&1)
else
CONFIG_RESPONSE=$(curl --fail -sS \
--request GET \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "Authorization: Bearer $WXO_TOKEN" \
--header "accept: application/json" 2>&1)
fi
if [ $? -ne 0 ]; then
echo -e "${YELLOW}Could not retrieve current configuration:${NC}"
# Display error message
echo -e "${YELLOW}Could not retrieve current configuration: $CONFIG_RESPONSE${NC}"
echo -e "${YELLOW}This may be normal if security has not been configured yet.${NC}"
echo -e "${YELLOW}In this state, security is enabled by default but Embed Chat will not function until properly configured.${NC}"
IS_SECURITY_ENABLED="unknown"
else
# Don't save configuration to file
IS_SECURITY_ENABLED=$(echo $CONFIG_RESPONSE | grep -o '"is_security_enabled":[^,}]*' | cut -d':' -f2 | tr -d ' ')
echo -e "Current security status: ${BOLD}$([ "$IS_SECURITY_ENABLED" = "true" ] && echo "ENABLED" || echo "DISABLED")${NC}"
if [ "$IS_SECURITY_ENABLED" = "true" ]; then
HAS_PUBLIC_KEY=$(echo $CONFIG_RESPONSE | grep -o '"public_key"' | wc -l)
HAS_CLIENT_PUBLIC_KEY=$(echo $CONFIG_RESPONSE | grep -o '"client_public_key"' | wc -l)
if [ "$HAS_PUBLIC_KEY" -eq 0 ] || [ "$HAS_CLIENT_PUBLIC_KEY" -eq 0 ]; then
echo -e "${YELLOW}WARNING: Security is enabled but configuration is incomplete. Embed Chat will not function properly.${NC}"
else
echo -e "${GREEN}Security is properly configured with both IBM and client public keys.${NC}"
fi
fi
fi
}
# Function to generate IBM public key
generate_ibm_key() {
# Check output directory before saving keys
check_output_directory || exit 1
echo -e "\n${BOLD}Step 2: Generating IBM Public Key${NC}"
echo "Requesting new IBM key pair..."
# Use different authentication header based on instance type
if [ "$IS_IBM_CLOUD" = true ]; then
IBM_KEY_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/generate-key-pair" \
--header "IAM-API_KEY: $WXO_API_KEY" \
--header "accept: application/json" 2>&1)
else
IBM_KEY_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/generate-key-pair" \
--header "Authorization: Bearer $WXO_TOKEN" \
--header "accept: application/json" 2>&1)
fi
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to generate IBM key pair:${NC}"
echo "$IBM_KEY_RESPONSE"
exit 1
fi
# Extract the public key from the response
echo -e "${BLUE}Extracting and saving IBM public key...${NC}"
# Extract the key directly using Python one-liner
IBM_PUBLIC_KEY=$(echo "$IBM_KEY_RESPONSE" | python3 -c '
import re, sys
content = sys.stdin.read()
match = re.search(r"\"public_key\":\"(-----BEGIN PUBLIC KEY-----.*?-----END PUBLIC KEY-----)", content, re.DOTALL)
if match: print(match.group(1))
')
# Check if extraction was successful
if [ -n "$IBM_PUBLIC_KEY" ]; then
# Save the key to files
echo "$IBM_PUBLIC_KEY" > "$OUTPUT_DIR/ibm_public_key.pem"
cat "$OUTPUT_DIR/ibm_public_key.pem" | awk '{printf "%s\\n", $0}' > "$OUTPUT_DIR/ibm_public_key.txt"
else
echo -e "${YELLOW}Python extraction failed, trying direct extraction...${NC}"
# Try direct extraction of PEM format
IBM_PUBLIC_KEY=$(echo "$IBM_KEY_RESPONSE" | grep -o "\"public_key\":\"[^\"]*\"" | sed 's/"public_key":"//g' | sed 's/"$//g' | sed 's/\\n/\n/g')
if [ -n "$IBM_PUBLIC_KEY" ]; then
# Save the key to files
echo "$IBM_PUBLIC_KEY" > "$OUTPUT_DIR/ibm_public_key.pem"
cat "$OUTPUT_DIR/ibm_public_key.pem" | awk '{printf "%s\\n", $0}' > "$OUTPUT_DIR/ibm_public_key.txt"
else
echo -e "${RED}Failed to extract IBM public key.${NC}"
exit 1
fi
fi
# Get the key for further use
IBM_PUBLIC_KEY=$(cat "$OUTPUT_DIR/ibm_public_key.txt")
# Final check
if [ -z "$IBM_PUBLIC_KEY" ] || [ ${#IBM_PUBLIC_KEY} -lt 100 ]; then
echo -e "${RED}Failed to extract public key from response.${NC}"
if [ "$VERBOSE" = true ]; then
echo "$IBM_KEY_RESPONSE"
else
echo -e "${YELLOW}Run with -v option for more debugging information.${NC}"
fi
exit 1
fi
local save_error=false
if [ "$save_error" = false ]; then
echo -e "${GREEN}Successfully generated and saved IBM public key.${NC}"
else
echo -e "${YELLOW}Please check if the directory exists and is writable.${NC}"
fi
}
# Function to generate client key pair
generate_client_keys() {
echo -e "\n${BOLD}Step 3: Generating Client Key Pair${NC}"
echo "Generating RSA 4096-bit key pair..."
# Check if output directory exists and is writable
if [ ! -d "$OUTPUT_DIR" ] || [ ! -w "$OUTPUT_DIR" ]; then
echo -e "${RED}ERROR: Output directory '$OUTPUT_DIR' does not exist or is not writable.${NC}"
echo -e "${YELLOW}Please check if the directory exists and has proper permissions.${NC}"
exit 1
fi
# Generate private key with error handling
openssl genrsa -out "$OUTPUT_DIR/client_private_key.pem" 4096 2>/dev/null
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to generate client private key.${NC}"
echo -e "${YELLOW}Please check if OpenSSL is installed and the directory is writable.${NC}"
exit 1
fi
# Extract public key with error handling
openssl rsa -in "$OUTPUT_DIR/client_private_key.pem" -pubout -out "$OUTPUT_DIR/client_public_key.pem" 2>/dev/null
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to extract client public key.${NC}"
echo -e "${YELLOW}Please check if the private key was generated correctly.${NC}"
exit 1
fi
# Convert client public key to format needed for API
echo -e "${BLUE}Converting client public key to format needed for API...${NC}"
# Format the key with awk for API consumption - directly from the PEM file
CLIENT_PUBLIC_KEY=$(cat "$OUTPUT_DIR/client_public_key.pem" | awk '{printf "%s\\n", $0}')
# Check if the key seems too short
if [ ${#CLIENT_PUBLIC_KEY} -lt 100 ]; then
echo -e "${YELLOW}Warning: Client public key seems too short, trying alternative method${NC}"
# Alternative method: Direct extraction with sed
CLIENT_PUBLIC_KEY=$(sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' "$OUTPUT_DIR/client_public_key.pem" | awk '{printf "%s\\n", $0}')
fi
# Save the processed key
echo "$CLIENT_PUBLIC_KEY" > "$OUTPUT_DIR/client_public_key.txt"
# Debug information
local key_length=${#CLIENT_PUBLIC_KEY}
local txt_size=$(wc -c < "$OUTPUT_DIR/client_public_key.txt")
echo -e "${BLUE}Debug: Client public key length is $key_length bytes${NC}"
echo -e "${BLUE}Debug: client_public_key.txt size is $txt_size bytes${NC}"
if [ "$txt_size" -lt 100 ]; then
echo -e "${YELLOW}Warning: Client public key text file seems too small ($txt_size bytes).${NC}"
echo -e "${YELLOW}This might cause issues when configuring security.${NC}"
else
echo -e "${GREEN}Successfully generated client key pair.${NC}"
echo -e "Client keys saved to ${BOLD}$OUTPUT_DIR/client_private_key.pem${NC} and ${BOLD}$OUTPUT_DIR/client_public_key.pem${NC}"
echo -e "Client public key (text format) saved to ${BOLD}$OUTPUT_DIR/client_public_key.txt${NC}"
fi
}
# Function to enable security
enable_security() {
echo -e "\n${BOLD}Step 4: Enabling Security with Custom Keys${NC}"
echo "Configuring security with IBM and client public keys..."
# Create the JSON payload
local payload="{
\"public_key\": \"$IBM_PUBLIC_KEY\",
\"client_public_key\": \"$CLIENT_PUBLIC_KEY\",
\"is_security_enabled\": true
}"
# No need to save the payload
# Use different authentication header based on instance type
if [ "$IS_IBM_CLOUD" = true ]; then
ENABLE_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "IAM-API_KEY: $WXO_API_KEY" \
--header "Content-Type: application/json" \
--data "$payload" 2>&1)
else
ENABLE_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "Authorization: Bearer $WXO_TOKEN" \
--header "Content-Type: application/json" \
--data "$payload" 2>&1)
fi
# Check for errors
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to enable security:${NC}"
# No need to save the error response
# Check for specific error codes
if [[ "$ENABLE_RESPONSE" == *"422"* ]]; then
echo -e "${YELLOW}Received 422 error - This typically indicates an issue with the key format:${NC}"
echo -e "1. The public keys may not be properly formatted"
echo -e "2. The keys may be too short or corrupted"
echo -e "3. There might be special characters causing issues"
# Show key diagnostics
echo -e "\n${BLUE}Key diagnostics:${NC}"
echo -e "IBM public key length: ${#IBM_PUBLIC_KEY} bytes"
echo -e "Client public key length: ${#CLIENT_PUBLIC_KEY} bytes"
if [ "$VERBOSE" = true ]; then
echo -e "\n${YELLOW}Try running with -v option and check the generated files in $OUTPUT_DIR${NC}"
echo -e "${YELLOW}Specifically, examine ibm_public_key.pem and client_public_key.pem${NC}"
else
echo -e "\n${YELLOW}Run with -v option for more debugging information.${NC}"
fi
else
echo "$ENABLE_RESPONSE"
fi
exit 1
fi
echo -e "${GREEN}Successfully enabled security with custom keys.${NC}"
echo -e "${GREEN}Your Embed Chat will now function properly with security enabled.${NC}"
}
# Function to disable security
disable_security() {
echo -e "\n${BOLD}Disabling Security and Allowing Anonymous Access${NC}"
echo -e "${RED}WARNING: This will allow anonymous access to your embedded chat.${NC}"
echo -e "${YELLOW}Only do this if your use case specifically requires anonymous access${NC}"
echo -e "${YELLOW}and the data and team tools in your instance are appropriate for anonymous access.${NC}"
read -p "Are you sure you want to disable security and allow anonymous access? (yes/no): " confirmation
if [[ "$confirmation" == "yes" ]]; then
# Continue with disabling security
:
elif [[ "$confirmation" == "no" ]]; then
echo "Operation cancelled."
return 1
else
echo -e "${YELLOW}Unexpected input received. Operation cancelled.${NC}"
return 1
fi
echo "Disabling security and clearing key pairs..."
# Create the JSON payload
local payload='{
"public_key": "",
"client_public_key": "",
"is_security_enabled": false
}'
# No need to save the payload
# Use different authentication header based on instance type
if [ "$IS_IBM_CLOUD" = true ]; then
DISABLE_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "IAM-API_KEY: $WXO_API_KEY" \
--header "Content-Type: application/json" \
--data "$payload" 2>&1)
else
DISABLE_RESPONSE=$(curl --fail -sS \
--request POST \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "Authorization: Bearer $WXO_TOKEN" \
--header "Content-Type: application/json" \
--data "$payload" 2>&1)
fi
# Check for errors
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to disable security:${NC}"
# No need to save the error response
echo "$DISABLE_RESPONSE"
if [ "$VERBOSE" = false ]; then
echo -e "${YELLOW}Run with -v option for more debugging information.${NC}"
fi
exit 1
fi
# No need to save the successful response
echo -e "${YELLOW}Security has been disabled and key pairs cleared. Your embedded chat now allows anonymous access.${NC}"
}
# Function to verify configuration
verify_configuration() {
# Check output directory before saving configuration
check_output_directory || exit 1
echo -e "\n${BOLD}Verifying Configuration${NC}"
echo "Checking current security settings..."
# Use different authentication header based on instance type
if [ "$IS_IBM_CLOUD" = true ]; then
VERIFY_RESPONSE=$(curl --fail -sS \
--request GET \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "IAM-API_KEY: $WXO_API_KEY" \
--header "accept: application/json" 2>&1)
else
VERIFY_RESPONSE=$(curl --fail -sS \
--request GET \
--url "$API_URL/instances/$WXO_INSTANCE_ID/v1/embed/secure/config" \
--header "Authorization: Bearer $WXO_TOKEN" \
--header "accept: application/json" 2>&1)
fi
if [ $? -ne 0 ]; then
echo -e "${RED}Failed to verify configuration:${NC}"
# No need to save the error response
echo "$VERIFY_RESPONSE"
if [ "$VERBOSE" = false ]; then
echo -e "${YELLOW}Run with -v option for more debugging information.${NC}"
fi
return 1
fi
# Don't save final configuration to file
FINAL_STATUS=$(echo $VERIFY_RESPONSE | grep -o '"is_security_enabled":[^,}]*' | cut -d':' -f2 | tr -d ' ')
echo -e "Security is now: ${BOLD}$([ "$FINAL_STATUS" = "true" ] && echo "${GREEN}ENABLED${NC}" || echo "${YELLOW}DISABLED (Anonymous Access)${NC}")${NC}"
if [ "$FINAL_STATUS" = "true" ]; then
HAS_PUBLIC_KEY=$(echo $VERIFY_RESPONSE | grep -o '"public_key"' | wc -l)
HAS_CLIENT_PUBLIC_KEY=$(echo $VERIFY_RESPONSE | grep -o '"client_public_key"' | wc -l)
if [ "$HAS_PUBLIC_KEY" -eq 0 ] || [ "$HAS_CLIENT_PUBLIC_KEY" -eq 0 ]; then
echo -e "${YELLOW}WARNING: Security is enabled but configuration is incomplete. Embed Chat will not function properly.${NC}"
else
echo -e "${GREEN}Security is properly configured with both IBM and client public keys.${NC}"
echo -e "${GREEN}Your Embed Chat will function properly with security enabled.${NC}"
fi
else
echo -e "${YELLOW}Your Embed Chat is configured for anonymous access.${NC}"
fi
echo -e "Configuration completed successfully."
}
# Function to display the main menu and handle user actions
display_main_menu() {
action=""
while true; do
# Always display the menu options at the start of each loop iteration
echo -e "\n${BOLD}Select an action:${NC}"
echo "1) Configure security with custom keys (Recommended)"
echo "2) Disable security and allow anonymous access (Only for specific use cases)"
echo "3) View current configuration only"
echo "4) Exit"
read -p "Enter your choice (1-4): " action
case $action in
1)
generate_ibm_key
generate_client_keys
enable_security
verify_configuration
show_configuration_summary "1"
return
;;
2)
disable_security
if [[ $? -ne 0 ]]; then
# If disable_security returned non-zero (cancelled), continue the loop
# The menu will be displayed again at the start of the next iteration
continue
fi
verify_configuration
show_configuration_summary "2"
return
;;
3)
echo -e "${BLUE}Viewing current configuration only. No changes made.${NC}"
verify_configuration
show_configuration_summary "3"
return
;;
4)
echo -e "${BLUE}Exiting the configuration tool.${NC}"
exit 0
;;
*) echo -e "${YELLOW}Invalid selection. Please enter 1, 2, 3, or 4.${NC}";;
esac
done
}
# Function to show configuration summary
show_configuration_summary() {
# Check if output directory exists before referencing files
check_output_directory || echo -e "${YELLOW}Warning: Output directory not found. Configuration files may not be accessible.${NC}"
local action="$1"
echo -e "\n${BOLD}Configuration Summary${NC}"
echo -e "Key files are saved in the ${BOLD}$OUTPUT_DIR${NC} directory:"
if [ "$action" = "1" ]; then
echo -e "- IBM public key: ${BOLD}ibm_public_key.pem${NC} and ${BOLD}ibm_public_key.txt${NC}"
echo -e "- Client private key: ${BOLD}client_private_key.pem${NC}"
echo -e "- Client public key: ${BOLD}client_public_key.pem${NC} and ${BOLD}client_public_key.txt${NC}"
fi
echo -e "\n${GREEN}Configuration process completed.${NC}"
# Ask if user wants to return to action menu or exit
echo -e "\n${BOLD}Would you like to:${NC}"
echo "1) Return to action menu"
echo "2) Exit"
local next_action
while true; do
read -p "Enter your choice (1-2): " next_action
case $next_action in
1) return ;;
2)
echo -e "${BLUE}Exiting the configuration tool.${NC}"
exit 0
;;
*) echo -e "${YELLOW}Invalid selection. Please enter 1 or 2.${NC}";;
esac
done
}
# Main execution flow for Bash
echo -e "${BOLD}Do you need help finding your Service instance URL?${NC} (y/n): "
read need_help
if [[ $need_help == "y" || $need_help == "Y" ]]; then
show_instance_id_help
fi
select_environment
get_service_details
# For IBM Cloud instances, we don't need to obtain an IAM token
if [ "$IS_IBM_CLOUD" = true ]; then
echo -e "\n${BOLD}Step 1: Getting API Key${NC}"
get_input "Enter your IBM watsonx Orchestrate API Key" WXO_API_KEY true
echo -e "${GREEN}API Key received. Will use it directly for authentication.${NC}"
else
obtain_iam_token
fi
get_current_config
# Main menu loop
while true; do
display_main_menu
done
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ -n "$WINDIR" ]]; then
# Running on Windows
echo "Detected Windows environment. Running Windows version..."
# PowerShell script starts here
powershell.exe -ExecutionPolicy Bypass -Command "
# Parse command line arguments
param (
[switch]\$Verbose = \$false
)
# Debug function that only prints in verbose mode
function Debug-Print {
param (
[string]\$Message
)
if (\$Verbose) {
Write-Host \"DEBUG: \$Message\" -ForegroundColor Blue
}
}
# Create output directory
\$OUTPUT_DIR = \"wxo_security_config\"
Write-Host \"Creating output directory: \$OUTPUT_DIR\" -ForegroundColor Blue
# Check if directory exists
if (Test-Path -Path \$OUTPUT_DIR -PathType Container) {
Write-Host \"Output directory already exists.\" -ForegroundColor Green
} else {
# Try to create the directory
try {
New-Item -Path \$OUTPUT_DIR -ItemType Directory -ErrorAction Stop | Out-Null
Write-Host \"Output directory created successfully.\" -ForegroundColor Green
} catch {
Write-Host \"ERROR: Failed to create output directory '\$OUTPUT_DIR'.\" -ForegroundColor Red
Write-Host \"Please check permissions or create the directory manually:\" -ForegroundColor Yellow
Write-Host \" New-Item -Path \$OUTPUT_DIR -ItemType Directory\"
Write-Host \"Then run this script again.\" -ForegroundColor Yellow
exit 1
}
}
# Verify directory is writable
try {
\$testFile = Join-Path -Path \$OUTPUT_DIR -ChildPath \"test_write.tmp\"
[System.IO.File]::WriteAllText(\$testFile, \"test\")
Remove-Item -Path \$testFile -Force
Write-Host \"Output directory is writable.\" -ForegroundColor Green
} catch {
Write-Host \"ERROR: Output directory '\$OUTPUT_DIR' is not writable.\" -ForegroundColor Red
Write-Host \"Please check permissions.\" -ForegroundColor Yellow
exit 1
}
# Display welcome message
Write-Host \"Welcome to the IBM watsonx Orchestrate Embedded Chat Security Configuration Tool\" -ForegroundColor White
Write-Host \"\"
Write-Host \"This tool will guide you through configuring security for your embedded chat integration.\"
Write-Host \"\"
Write-Host \"IMPORTANT: By default, security is enabled but not configured, which means Embed Chat will not function until properly configured.\" -ForegroundColor Yellow
Write-Host \"\"
# Function to check and create output directory
function Check-OutputDirectory {
# Check if directory exists
if (-not (Test-Path -Path \$OUTPUT_DIR -PathType Container)) {
Write-Host \"Output directory '\$OUTPUT_DIR' does not exist. Creating it now...\" -ForegroundColor Yellow
# Try to create the directory
try {
New-Item -Path \$OUTPUT_DIR -ItemType Directory -ErrorAction Stop | Out-Null
} catch {
Write-Host \"ERROR: Failed to create output directory '\$OUTPUT_DIR'.\" -ForegroundColor Red
Write-Host \"Please check permissions or create the directory manually:\" -ForegroundColor Yellow
Write-Host \" New-Item -Path \$OUTPUT_DIR -ItemType Directory\"
return \$false
}
}
# Verify directory is writable
try {
\$testFile = Join-Path -Path \$OUTPUT_DIR -ChildPath \"test_write.tmp\"
[System.IO.File]::WriteAllText(\$testFile, \"test\")
Remove-Item -Path \$testFile -Force
} catch {
Write-Host \"ERROR: Output directory '\$OUTPUT_DIR' is not writable.\" -ForegroundColor Red
Write-Host \"Please check permissions.\" -ForegroundColor Yellow
return \$false
}
return \$true
}
# Function to get user input with validation
function Get-UserInput {
param (
[string]\$Prompt,
[string]\$VarName,
[bool]\$IsSecret = \$false
)
\$value = \"\"
while ([string]::IsNullOrEmpty(\$value)) {
if (\$IsSecret) {
\$secureString = Read-Host -Prompt \$Prompt -AsSecureString
\$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR(\$secureString)
\$value = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(\$bstr)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR(\$bstr)
} else {
\$value = Read-Host -Prompt \$Prompt
}
if ([string]::IsNullOrEmpty(\$value)) {
Write-Host \"This field cannot be empty. Please try again.\" -ForegroundColor Yellow
}
}
# Return the value
return \$value
}
# Function to display help for finding instance ID and API URL
function Show-InstanceIdHelp {
Write-Host \"\"
Write-Host \"How to Find Your Instance ID and API URL:\" -ForegroundColor White
Write-Host \"1. Log in to your watsonx Orchestrate instance\"
Write-Host \"2. Click on the profile icon in the top right corner\"
Write-Host \"3. Select 'Settings' from the dropdown menu\"
Write-Host \"4. Navigate to the 'API Details' tab\"
Write-Host \"5. Find the 'Service instance URL' field, which looks like:\"
Write-Host \" https://api.us-south.watson-orchestrate.ibm.com/instances/20250807-1007-4445-5049-459a42144389\" -ForegroundColor Blue
Write-Host \"6. Your API URL is the base URL: https://api.us-south.watson-orchestrate.ibm.com\" -ForegroundColor Blue
Write-Host \"7. Your Instance ID is the UUID after '/instances/': 20250807-1007-4445-5049-459a42144389\" -ForegroundColor Blue
Write-Host \"\"
Write-Host \"Your API Key can also be found in the same API Details tab.\"
Write-Host \"Press Enter to continue...\"
Read-Host | Out-Null
}
# Main execution flow for PowerShell
Write-Host \"Do you need help finding your Service instance URL? (y/n): \" -ForegroundColor White -NoNewline
\$needHelp = Read-Host
if (\$needHelp -eq \"y\" -or \$needHelp -eq \"Y\") {
Show-InstanceIdHelp
}
# Function to generate client key pair using PowerShell's cryptography capabilities
function New-ClientKeys {
Write-Host \"\"
Write-Host \"Step 3: Generating Client Key Pair\" -ForegroundColor White
Write-Host \"Generating RSA 4096-bit key pair...\"
# Check if output directory exists and is writable
if (-not (Test-Path -Path \$OUTPUT_DIR -PathType Container) -or -not (Test-Path -Path \$OUTPUT_DIR -PathType Container -IsValid)) {
Write-Host \"ERROR: Output directory '\$OUTPUT_DIR' does not exist or is not writable.\" -ForegroundColor Red
Write-Host \"Please check if the directory exists and has proper permissions.\" -ForegroundColor Yellow
exit 1
}
try {
# Load required .NET classes
Add-Type -AssemblyName System.Security
# Create RSA provider with 4096 bit key
\$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider(4096)
# Get the private key in PKCS#1 format
\$privateKeyBytes = \$rsa.ExportRSAPrivateKey()
\$privateKeyPem = @()
\$privateKeyPem += \"-----BEGIN RSA PRIVATE KEY-----\"
\$privateKeyPem += [Convert]::ToBase64String(\$privateKeyBytes, [System.Base64FormattingOptions]::InsertLineBreaks)
\$privateKeyPem += \"-----END RSA PRIVATE KEY-----\"
\$privateKeyText = \$privateKeyPem -join \"\`n\"
# Get the public key in X.509 format
\$publicKeyBytes = \$rsa.ExportRSAPublicKey()
\$publicKeyPem = @()
\$publicKeyPem += \"-----BEGIN PUBLIC KEY-----\"
\$publicKeyPem += [Convert]::ToBase64String(\$publicKeyBytes, [System.Base64FormattingOptions]::InsertLineBreaks)
\$publicKeyPem += \"-----END PUBLIC KEY-----\"
\$publicKeyText = \$publicKeyPem -join \"\`n\"
# Save the keys to files
Set-Content -Path \"\$OUTPUT_DIR/client_private_key.pem\" -Value \$privateKeyText
Set-Content -Path \"\$OUTPUT_DIR/client_public_key.pem\" -Value \$publicKeyText
# Format the public key for API consumption
Write-Host \"Converting client public key to format needed for API...\" -ForegroundColor Blue
\$script:CLIENT_PUBLIC_KEY = \$publicKeyText -replace \"\`n\", \"\\n\"
# Save the processed key
Set-Content -Path \"\$OUTPUT_DIR/client_public_key.txt\" -Value \$CLIENT_PUBLIC_KEY
# Debug information
\$keyLength = \$CLIENT_PUBLIC_KEY.Length
\$txtSize = (Get-Item -Path \"\$OUTPUT_DIR/client_public_key.txt\").Length
Write-Host \"Debug: Client public key length is \$keyLength bytes\" -ForegroundColor Blue
Write-Host \"Debug: client_public_key.txt size is \$txtSize bytes\" -ForegroundColor Blue
if (\$txtSize -lt 100) {
Write-Host \"Warning: Client public key text file seems too small (\$txtSize bytes).\" -ForegroundColor Yellow
Write-Host \"This might cause issues when configuring security.\" -ForegroundColor Yellow
} else {
Write-Host \"Successfully generated client key pair.\" -ForegroundColor Green
Write-Host \"Client keys saved to \$OUTPUT_DIR/client_private_key.pem and \$OUTPUT_DIR/client_public_key.pem\" -ForegroundColor White
Write-Host \"Client public key (text format) saved to \$OUTPUT_DIR/client_public_key.txt\" -ForegroundColor White
}
} catch {
Write-Host \"ERROR: Failed to generate client key pair: \$(\$_.Exception.Message)\" -ForegroundColor Red
Write-Host \"This might be due to missing .NET Framework features or insufficient permissions.\" -ForegroundColor Yellow
exit 1
}
}
# Function to enable security
function Enable-Security {
Write-Host \"\"
Write-Host \"Step 4: Enabling Security with Custom Keys\" -ForegroundColor White
Write-Host \"Configuring security with IBM and client public keys...\"
# Create the JSON payload
\$payload = @{
\"public_key\" = \$IBM_PUBLIC_KEY
\"client_public_key\" = \$CLIENT_PUBLIC_KEY
\"is_security_enabled\" = \$true
} | ConvertTo-Json
try {
\$headers = @{
\"Content-Type\" = \"application/json\"
}
# Use different authentication header based on instance type
if (\$IS_IBM_CLOUD) {
\$headers[\"IAM-API_KEY\"] = \$WXO_API_KEY
} else {
\$headers[\"Authorization\"] = \"Bearer \$WXO_TOKEN\"
}
\$enableResponse = Invoke-RestMethod -Uri \"\$API_URL/instances/\$WXO_INSTANCE_ID/v1/embed/secure/config\" -Method Post -Headers \$headers -Body \$payload -ErrorAction Stop
Write-Host \"Successfully enabled security with custom keys.\" -ForegroundColor Green
Write-Host \"Your Embed Chat will now function properly with security enabled.\" -ForegroundColor Green
} catch {
Write-Host \"Failed to enable security:\" -ForegroundColor Red
# Check for specific error codes
if (\$_.Exception.Response.StatusCode -eq 422) {
Write-Host \"Received 422 error - This typically indicates an issue with the key format:\" -ForegroundColor Yellow
Write-Host \"1. The public keys may not be properly formatted\"
Write-Host \"2. The keys may be too short or corrupted\"
Write-Host \"3. There might be special characters causing issues\"
# Show key diagnostics
Write-Host \"\"
Write-Host \"Key diagnostics:\" -ForegroundColor Blue
Write-Host \"IBM public key length: \$(\$IBM_PUBLIC_KEY.Length) bytes\"
Write-Host \"Client public key length: \$(\$CLIENT_PUBLIC_KEY.Length) bytes\"
if (\$Verbose) {
Write-Host \"\"
Write-Host \"Try running with -v option and check the generated files in \$OUTPUT_DIR\" -ForegroundColor Yellow
Write-Host \"Specifically, examine ibm_public_key.pem and client_public_key.pem\" -ForegroundColor Yellow
} else {
Write-Host \"\"
Write-Host \"Run with -v option for more debugging information.\" -ForegroundColor Yellow
}
} else {
Write-Host \$_.Exception.Message
}
exit 1
}
}
# Function to disable security
function Disable-Security {
Write-Host \"\"
Write-Host \"Disabling Security and Allowing Anonymous Access\" -ForegroundColor White
Write-Host \"WARNING: This will allow anonymous access to your embedded chat.\" -ForegroundColor Red
Write-Host \"Only do this if your use case specifically requires anonymous access\" -ForegroundColor Yellow
Write-Host \"and the data and team tools in your instance are appropriate for anonymous access.\" -ForegroundColor Yellow
\$confirmation = Read-Host -Prompt \"Are you sure you want to disable security and allow anonymous access? (yes/no)\"
if (\$confirmation -eq \"yes\") {
# Continue with disabling security
} elseif (\$confirmation -eq \"no\") {
Write-Host \"Operation cancelled.\"
return \$false
} else {
Write-Host \"Unexpected input received. Operation cancelled.\" -ForegroundColor Yellow
return \$false
}
Write-Host \"Disabling security and clearing key pairs...\"
# Create the JSON payload
\$payload = @{
\"public_key\" = \"\"
\"client_public_key\" = \"\"
\"is_security_enabled\" = \$false
} | ConvertTo-Json
try {
\$headers = @{
\"Content-Type\" = \"application/json\"
}
# Use different authentication header based on instance type
if (\$IS_IBM_CLOUD) {
\$headers[\"IAM-API_KEY\"] = \$WXO_API_KEY
} else {
\$headers[\"Authorization\"] = \"Bearer \$WXO_TOKEN\"
}
\$disableResponse = Invoke-RestMethod -Uri \"\$API_URL/instances/\$WXO_INSTANCE_ID/v1/embed/secure/config\" -Method Post -Headers \$headers -Body \$payload -ErrorAction Stop
Write-Host \"Security has been disabled and key pairs cleared. Your embedded chat now allows anonymous access.\" -ForegroundColor Yellow
return \$true
} catch {
Write-Host \"Failed to disable security:\" -ForegroundColor Red
Write-Host \$_.Exception.Message
if (-not \$Verbose) {
Write-Host \"Run with -v option for more debugging information.\" -ForegroundColor Yellow
}
return \$false
}
}
# Function to verify configuration
function Test-Configuration {
# Check output directory before saving configuration
if (-not (Check-OutputDirectory)) { exit 1 }
Write-Host \"\"
Write-Host \"Verifying Configuration\" -ForegroundColor White
Write-Host \"Checking current security settings...\"
try {
\$headers = @{
\"accept\" = \"application/json\"
}
# Use different authentication header based on instance type
if (\$IS_IBM_CLOUD) {
\$headers[\"IAM-API_KEY\"] = \$WXO_API_KEY
} else {
\$headers[\"Authorization\"] = \"Bearer \$WXO_TOKEN\"
}
\$verifyResponse = Invoke-RestMethod -Uri \"\$API_URL/instances/\$WXO_INSTANCE_ID/v1/embed/secure/config\" -Method Get -Headers \$headers -ErrorAction Stop
\$finalStatus = \$verifyResponse.is_security_enabled
if (\$finalStatus) {
Write-Host \"Security is now: ENABLED\" -ForegroundColor Green
} else {
Write-Host \"Security is now: DISABLED (Anonymous Access)\" -ForegroundColor Yellow
}
if (\$finalStatus) {
\$hasPublicKey = -not [string]::IsNullOrEmpty(\$verifyResponse.public_key)
\$hasClientPublicKey = -not [string]::IsNullOrEmpty(\$verifyResponse.client_public_key)
if (-not \$hasPublicKey -or -not \$hasClientPublicKey) {
Write-Host \"WARNING: Security is enabled but configuration is incomplete. Embed Chat will not function properly.\" -ForegroundColor Yellow
} else {
Write-Host \"Security is properly configured with both IBM and client public keys.\" -ForegroundColor Green
Write-Host \"Your Embed Chat will function properly with security enabled.\" -ForegroundColor Green
}
} else {
Write-Host \"Your Embed Chat is configured for anonymous access.\" -ForegroundColor Yellow
}
Write-Host \"Configuration completed successfully.\"
return \$true
} catch {
Write-Host \"Failed to verify configuration:\" -ForegroundColor Red
Write-Host \$_.Exception.Message
if (-not \$Verbose) {
Write-Host \"Run with -v option for more debugging information.\" -ForegroundColor Yellow
}
return \$false
}
}
# Function to display the main menu and handle user actions
function Show-MainMenu {
\$action = \"\"
while (\$true) {
# Always display the menu options at the start of each loop iteration
Write-Host \"\"
Write-Host \"Select an action:\" -ForegroundColor White
Write-Host \"1) Configure security with custom keys (Recommended)\"
Write-Host \"2) Disable security and allow anonymous access (Only for specific use cases)\"
Write-Host \"3) View current configuration only\"
Write-Host \"4) Exit\"
\$action = Read-Host -Prompt \"Enter your choice (1-4)\"
switch (\$action) {
\"1\" {
New-IbmKey
New-ClientKeys
Enable-Security
Test-Configuration
Show-ConfigurationSummary -Action \"1\"
return
}
\"2\" {
\$result = Disable-Security
if (-not \$result) {
# If disable_security returned false (cancelled), continue the loop
# The menu will be displayed again at the start of the next iteration
continue
}
Test-Configuration
Show-ConfigurationSummary -Action \"2\"
return
}
\"3\" {
Write-Host \"Viewing current configuration only. No changes made.\" -ForegroundColor Blue
Test-Configuration
Show-ConfigurationSummary -Action \"3\"
return
}
\"4\" {
Write-Host \"Exiting the configuration tool.\" -ForegroundColor Blue
exit 0
}
default {
Write-Host \"Invalid selection. Please enter 1, 2, 3, or 4.\" -ForegroundColor Yellow
}
}
}
}
# Function to show configuration summary
function Show-ConfigurationSummary {
param (
[string]\$Action
)
# Check if output directory exists before referencing files
if (-not (Check-OutputDirectory)) {
Write-Host \"Warning: Output directory not found. Configuration files may not be accessible.\" -ForegroundColor Yellow
}
Write-Host \"\"
Write-Host \"Configuration Summary\" -ForegroundColor White
Write-Host \"Key files are saved in the \$OUTPUT_DIR directory:\"
if (\$Action -eq \"1\") {
Write-Host \"- IBM public key: ibm_public_key.pem and ibm_public_key.txt\" -ForegroundColor White
Write-Host \"- Client private key: client_private_key.pem\" -ForegroundColor White
Write-Host \"- Client public key: client_public_key.pem and client_public_key.txt\" -ForegroundColor White
}
Write-Host \"\"
Write-Host \"Configuration process completed.\" -ForegroundColor Green
# Ask if user wants to return to action menu or exit
Write-Host \"\"
Write-Host \"Would you like to:\" -ForegroundColor White
Write-Host \"1) Return to action menu\"
Write-Host \"2) Exit\"
while (\$true) {
\$nextAction = Read-Host -Prompt \"Enter your choice (1-2)\"
switch (\$nextAction) {
\"1\" { return }
\"2\" {
Write-Host \"Exiting the configuration tool.\" -ForegroundColor Blue
exit 0
}
default {
Write-Host \"Invalid selection. Please enter 1 or 2.\" -ForegroundColor Yellow
}
}
}
}
# Main execution flow for PowerShell
Write-Host \"Do you need help finding your Service instance URL? (y/n): \" -ForegroundColor White -NoNewline
\$needHelp = Read-Host
if (\$needHelp -eq \"y\" -or \$needHelp -eq \"Y\") {
Show-InstanceIdHelp
}
Select-Environment
Get-ServiceDetails
# For IBM Cloud instances, we don't need to obtain an IAM token
if (\$IS_IBM_CLOUD) {
Write-Host \"\"
Write-Host \"Step 1: Getting API Key\" -ForegroundColor White
\$script:WXO_API_KEY = Get-UserInput -Prompt \"Enter your IBM watsonx Orchestrate API Key\" -VarName \"WXO_API_KEY\" -IsSecret \$true
Write-Host \"API Key received. Will use it directly for authentication.\" -ForegroundColor Green
} else {
Get-IamToken
}
Get-CurrentConfig
# Main menu loop
while (\$true) {
Show-MainMenu
}
"
else
# Unknown OS
echo "Error: Unable to detect operating system."
echo "This script supports Unix/Linux/Mac (Bash) and Windows (PowerShell)."
exit 1
fi
3
Change the script's permissions
On Unix-based systems (macOS and Linux), change the permissions to run the script:
Copy
Ask AI
chmod +x wxO-embed-chat-security-tool.sh
4
Run the script
Run the script and follow the instructions to enable or disable security:
Copy
Ask AI
./wxO-embed-chat-security-tool.sh
- The tool generates an IBM key pair via the API
- The tool generates a client key pair using OpenSSL
- Both public keys are configured in the service
- Security is enabled
wxo_security_config
directory:
ibm_public_key.pem
: IBM’s public key in PEM formatibm_public_key.txt
: IBM’s public key in single-line formatclient_private_key.pem
: Your private key (keep it secure!)client_public_key.pem
: Your public key in PEM formatclient_public_key.txt
: Your public key in single-line format
Context variables for embedded webchat
To use context variables in embedded webchat, include them inside the JWT token. You can add context variables to a JWT token using a JavaScript script. The following script shows how to include context variables inside a JWT token:[createJWT]
Copy
Ask AI
const fs = require('fs');
const RSA = require('node-rsa');
const crypto = require('crypto');
const jwtLib = require('jsonwebtoken');
const express = require('express');
const path = require('path');
const { v4: uuid } = require('uuid');
const router = express.Router();
// This is your private key that you will keep on your server. This is used to sign the jwt. You will paste your public
// key into the appropriate field on the Security tab of the web chat settings page.
// This public key is used to validate the signature on the jwt.
const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, '../keys/private.key'));
//The code below will use this key to encrypt the user payload inside the JWT.
const PUBLIC_KEY = fs.readFileSync(path.join(__dirname, '../keys/public.pub'));
// A time period of 45 days in milliseconds.
const TIME_45_DAYS = 1000 * 60 * 60 * 24 * 45;
/**
* Generates a signed JWT. The JWT used here will always be assigned a user ID using the given anonymous user ID. If
* the user is authenticated and we have session info, then info about the user will also be added to the JWT.
* Always use the anonymous user ID even if the user is authenticated because changing the user ID in the middle of
* a session is not allowed.
*/
function createJWTString(anonymousUserID, sessionInfo,context) {
// This is the content of the JWT. You would normally look up the user information from a user profile.
const jwtContent = {
// This is the subject of the JWT which will be the ID of the user.
//
// This user ID will be available under integrations.channel.private.user.id in dialog and
// system_integrations.channel.private.user.id in actions.
sub: anonymousUserID,
// This object is optional and contains any data you wish to include as part of the JWT. This data will be
// encrypted using the public key so it will not be visible to your users.
user_payload: {
custom_message: 'Encrypted message',
name: 'Anonymous',
},
context
};
// If the user is authenticated, then add the user's real info to the JWT.
if (sessionInfo) {
jwtContent.user_payload.name = sessionInfo.userName;
jwtContent.user_payload.custom_user_id = sessionInfo.customUserID;
}
const dataString = JSON.stringify(jwtContent.user_payload);
// Encrypt the data
const encryptedBuffer = crypto.publicEncrypt(
{
key: PUBLIC_KEY,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256' // Specify OAEP padding with SHA256
},
Buffer.from(dataString, 'utf-8')
);
// Convert encrypted data to base64
jwtContent.user_payload = encryptedBuffer.toString('base64');
console.log(jwtContent.user_payload)
// Now sign the jwt content to make the actual jwt. We are giving this a very short expiration time (10 seconds)
// to demonstrate the web chat capability of fetching a new token when it expires. In a production environment,
// you would likely want to set this to a much higher value or leave it out entirely.
const jwtString = jwtLib.sign(jwtContent, PRIVATE_KEY, {
algorithm: 'RS256',
expiresIn: '10000000s',
});
return jwtString;
}
/**
* Gets or sets the anonymous user ID cookie. This will also ensure that an existing cookie is updated with a new 45
* day expiration time.
*/
function getOrSetAnonymousID(request, response) {
let anonymousID = request.cookies['ANONYMOUS-USER-ID'];
if (!anonymousID) {
// If we don't already have an anonymous user ID, then create one. Normally you would want to use a full UUID,
// but for the sake of this example we are going to shorten it to just five characters to make them easier to read.
anonymousID = `anon-${uuid().substr(0, 5)}`;
}
// Here we set the value of the cookie and give it an expiration date of 45 days. We do this even if we already
// have an ID to make sure that we update the expiration date to a new 45 days.
response.cookie('ANONYMOUS-USER-ID', anonymousID, {
expires: new Date(Date.now() + TIME_45_DAYS),
httpOnly: true,
});
return anonymousID;
}
/**
* Returns the session info for an authenticated user.
*/
function getSessionInfo(request) {
// Normally the cookie would contain a session token that we would use to look up the user's info from something
// like a database. But for the sake of simplicity in this example the session cookie directly contains the user's
// info.
const sessionInfo = request.cookies.SESSION_INFO;
if (sessionInfo) {
return JSON.parse(sessionInfo);
}
return null;
}
/**
* Handles the createJWT request.
*/
function createJWT(request, response) {
const anonymousUserID = getOrSetAnonymousID(request, response);
const sessionInfo = getSessionInfo(request);
const context = {
dev_id: 23424,
dev_name: "Name",
is_active: true
}
response.send(createJWTString(anonymousUserID, sessionInfo,context));
}
router.get('/', createJWT);
module.exports = router;
[JavaScript]
Copy
Ask AI
<script>
function getUserId() {
let embed_user_id = sessionStorage.embed_user_id;
if (!embed_user_id) {
embed_user_id = Math.trunc(Math.random() * 1000000);
sessionStorage.embed_user_id = embed_user_id;
}
return embed_user_id;
}
function preSendHandler(event) {
if (event?.message?.content) {
event.message.content = event.message.content.toUpperCase();
}
}
function sendHandler(event) {
console.log('send event', event);
}
function feedbackHandler(event) {
console.log('feedback', event);
}
function preReceiveHandler(event) {
event?.content?.map((element) => {
element.type = 'date';
});
}
function receiveHandler(event) {
console.log('received event', event);
}
function userDefinedResponseHandler(event) {
console.log('userDefinedResponse event', event);
event.hostElement.innerHTML = `
<cds-code-snippet>
node -v Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis,
veritatis voluptate id incidunt molestiae officia possimus, quasi itaque
alias, architecto hic, dicta fugit? Debitis delectus quidem explicabo vitae
laboriosam!
</cds-code-snippet>
<br><br>
<div style="background-color:orange;color:white;padding:10px;">
<p>${event.contentItem?.template || '[No message content]'}</p>
</div>`;
}
function onChatLoad(instance) {
instance.on('chatstarted', (instance) => {
window.wxoChatInstance = instance;
});
instance.on('pre:send', preSendHandler);
instance.on('send', sendHandler);
instance.on('pre:receive', preReceiveHandler);
instance.on('receive', receiveHandler);
instance.on('feedback', feedbackHandler);
instance.on('userDefinedResponse', userDefinedResponseHandler);
}
async function getIdentityToken() {
// This will make a call to your server to request a new JWT.
const result = await fetch(
"http://localhost:3003/createJWT?user_id=" + getUserId()
);
window.wxOConfiguration.token = await result.text();
}
window.wxOConfiguration = {
orchestrationID: "20250430-0912-2925-309a-35c6bef54760_20250430-0949-0287-00f2-33dc583100c9",
hostURL: "https://dl.watson-orchestrate.ibm.com",
rootElementID: "root",
chatOptions: {
agentId: "852431a8-32dd-4925-8cc3-9ea3d3162726",
agentEnvironmentId: "5d769a04-9445-4768-a687-710d6e9a24cf",
},
style: {
headerColor: '#b8b890',
userMessageBackgroundColor: '#ffa31a',
primaryColor: '#33ff3c',
},
showLauncher: false,
layout: {
form: 'fullscreen-overlay',
showOrchestrateHeader: true,
}
};
setTimeout(function () {
const script = document.createElement('script');
script.src = `${window.wxOConfiguration.hostURL}/wxochat/wxoLoader.js?embed=true`;
script.addEventListener('load', function () {
wxoLoader.init();
});
document.head.appendChild(script);
}, 0);
getIdentityToken().then(() => {
const script = document.createElement("script");
script.src = `${window.wxOConfiguration.hostUrl}/wxochat/wxoLoader.js?embed=true`;
script.addEventListener("load", function () {
wxoLoader.init();
});
document.head.appendChild(script);
});
</script>
Customizing embedded webchat
Customizing styles
You can customize embedded web chats to create a unique chat interface that better fits your webpage. To apply custom styles, add astyle
component inside the window.wxOConfiguration
object in your web chat script. In this component, you can configure the following elements:
Parameter | Type | Description |
---|---|---|
headerColor | string | Set a six-digit hex code that defines the chat header color. |
userMessageBackgroundColor | string | Set a six-digit hex code that defines the user message bubble color. |
primaryColor | string | Set a six-digit hex code that defines the interactive elements color. |
style
component inside window.wxOConfiguration
:
JavaScript
Copy
Ask AI
<script>
window.wxOConfiguration = {
orchestrationID: "my-tenant-id",
hostURL: "my-host-url",
rootElementID: "root",
showLauncher: false,
chatOptions: {
agentId: "test_agent1",
agentEnvironmentId: "my-agent-draft-env-id"
},
style: {
headerColor: '#000000',
userMessageBackgroundColor: '#000000',
primaryColor: '#000000'
},
};
setTimeout(function() {
const script = document.createElement('script');
script.src = `${window.wxOConfiguration.hostURL}/wxochat/wxoLoader.js?embed=true`;
script.addEventListener('load', () => wxoLoader.init());
document.head.appendChild(script);
}, 0);
</script>
Customizing layout
The watsonx Orchestrate embed supports a flexiblelayout
object to control how and where the chat UI appears.
Parameter | Type | Default | Description | |
---|---|---|---|---|
rootElementID | string | — | (fullscreen-overlay only) ID of the container node to mount chat into. | |
showLauncher | boolean | true | (fullscreen-overlay only) Show the bubble launcher (true) or render chat immediately (false). | |
layout.form | string | float | Defines the layout form of your web chat. Use fullscreen-overlay to display the web chat in fullscreen mode. No additional parameters are required.Use float to display the web chat as a floating window. Also configure:
custom to define a custom layout. Also configure the customElement parameter with your custom element. | |
layout.width | string | — | (float only) Popup width (e.g. ‘350px’, ‘30rem’). | |
layout.height | string | — | (float only) Popup height (e.g. ‘500px’, ‘40rem’). | |
layout.showOrchestrateHeader | boolean | true | Render the standard header bar (true) or hide it | (false). |
layout.customElement | HTMLElement | — | element reference to render into. |
[JavaScript]
Copy
Ask AI
<script>
window.wxOConfiguration = {
orchestrationID: "my-tenant-id",
hostURL: "my-host-url",
rootElementID: "root", // fullscreen-overlay only
showLauncher: false, // fullscreen-overlay only, false = direct render, true = launcher bubble
chatOptions: {
agentId: "12345_test_agent1", // required
agentEnvironmentId: "my-agent-env-id" // required
},
layout: {
form: 'float', // 'fullscreen-overlay' | 'float' | 'custom'
width: '600px', // float only
height: '600px', // float only
showOrchestrateHeader: true, // hide header if false
customElement: hostElement // custom only
}
};
setTimeout(function() {
const script = document.createElement('script');
script.src = `${window.wxOConfiguration.hostURL}/wxochat/wxoLoader.js?embed=true`;
script.addEventListener('load', () => wxoLoader.init());
document.head.appendChild(script);
}, 0);
</script>
[JavaScript]
Copy
Ask AI
<script>
window.wxOConfiguration = {
orchestrationID: "my-tenant-id",
hostURL: "my-host-url",
rootElementID: "root",
showLauncher: false,
chatOptions: {
agentId: "test_agent1",
agentEnvironmentId: "my-agent-draft-env-id"
},
layout:{
form: 'fullscreen-overlay',
showOrchestrateHeader: true,
}
};
setTimeout(function() {
const script = document.createElement('script');
script.src = `${window.wxOConfiguration.hostURL}/wxochat/wxoLoader.js?embed=true`;
script.addEventListener('load', () => wxoLoader.init());
document.head.appendChild(script);
}, 0);
</script>
Enabling thumbs-up and thumbs-down
In the embedded chat, you need to manually enable thumbs-up and thumbs-down feedback usingpre:receive
handlers. First, subscribe to the pre:receive
event to inject feedback options. Then, handle submitted feedback through the feedback
event.
The following script shows how to configure feedback in the embedded chat:
[JavaScript]
Copy
Ask AI
<script>
function feedbackHandler(event) {
console.log('feedback', event);
}
function preReceiveHandler(event) {
console.log('pre-receive event', event);
const lastItem = event?.content?.[event.content.length - 1];
if (lastItem) {
lastItem.message_options = {
feedback: {
is_on: true,
show_positive_details: false,
show_negative_details: true,
// Note, these positive details are not used as long as show_positive_details is false.
positive_options: {
categories: ['Funny', 'Helpful', 'Correct'],
disclaimer: "Provide content that can be shared publicly.",
},
negative_options: {
categories: ['Inaccurate', 'Incomplete', 'Too long', 'Irrelevant', 'Other'],
disclaimer: "Provide content that can be shared publicly.",
},
},
};
}
}
function onChatLoad(instance) {
instance.on('pre:receive', preReceiveHandler);
instance.on('feedback', feedbackHandler);
}
window.wxOConfiguration = {
...
};
setTimeout(function () {
...
}, 0);
</script>
Events reference
Embedded webchat supports a variety of events that allow you to trigger specific actions or customize behavior. The following tables list all supported events, grouped by category.Customization Events
Event name | Description |
---|---|
userDefinedResponse | Triggered when a response contains an unrecognized or user_defined response type. |
Message Events
Event name | Description |
---|---|
pre:send | Triggered before the webchat sends a message to the assistant. |
send | Triggered after the webchat sends a message to the assistant. |
pre:receive | Triggered before the webchat receives a response from the assistant. |
receive | Triggered after the webchat receives a response from the assistant. |
pre:restartConversation | Triggered before the conversation restarts. Useful for alerting the user that the chat will reset, allowing them to complete any ongoing actions (e.g., finishing a tool call). |
restartConversation | Triggered after the conversation restarts, before a new session begins. Useful for displaying specific UI elements when a new session starts. |
View Events
Event name | Description |
---|---|
view:pre:change | Triggered before the view state changes. |
view:change | Triggered after the view state changes. |
pre:threadLoaded | Triggered when a user navigates to a chat thread in full-screen embedded chat. Useful for displaying custom responses or UI elements. |
Security Events
Event name | Description |
---|---|
identityTokenExpired | Triggered when security is enabled and the JWT token expires. |
authTokenNeeded | Triggered when the embedded chat requires a refreshed or updated authentication token. |
Miscellaneous Events
Event name | Description |
---|---|
chat:ready | Triggered when the webchat is fully loaded and ready to receive user input. Useful for displaying a welcome message or initializing UI components. |
Events example
The following example shows how to configure events in the embedded webchat:[JavaScript]
Copy
Ask AI
<script>
function preSendHandler(event, instance) {
console.log('pre:send event', event);
if (event?.message?.message?.content) {
event.message.message.content = event.message.message?.content.toUpperCase();
}
}
function sendHandler(event, instance) {
console.log('send event', event);
}
function feedbackHandler(event, instance) {
console.log('feedback', event);
}
function preReceiveHandler(event, instance) {
console.log('pre-receive event', event);
event?.message?.content?.map((element) => {
if (element?.text?.includes('assistant')) {
element.text = element.text.replace('assistant', 'Agent');
}
element.type = 'user_defined';
});
const lastItem = event?.message?.content?.[event.message?.content.length - 1];
if (lastItem) {
lastItem.message_options = {
feedback: {
is_on: true,
show_positive_details: false,
show_negative_details: true,
positive_options: {
categories: ['Funny', 'Helpful', 'Correct'],
disclaimer: "Provide content that can be shared publicly.",
},
negative_options: {
categories: ['Inaccurate', 'Incomplete', 'Too long', 'Irrelevant', 'Other'],
disclaimer: "Provide content that can be shared publicly.",
},
},
};
}
}
function receiveHandler(event, instance) {
console.log('received event', event);
instance.off('pre:receive', preReceiveHandler);
instance.updateAuthToken("wrong-or-expired-token")
}
function userDefinedResponseHandler(event, instance) {
console.log('userDefinedResponse event', event);
event.hostElement.innerHTML = `
<cds-code-snippet>
node -v Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis,
veritatis voluptate id incidunt molestiae officia possimus, quasi itaque
alias, architecto hic, dicta fugit? Debitis delectus quidem explicabo vitae
laboriosam!
</cds-code-snippet>
<br><br>
<div style="background-color:orange;color:white;padding:10px;">
<p>${event.contentItem?.text || '[No message content]'}</p>
</div>`;
}
function preRestartConversationHandler(event, instance) {
console.log('pre:restartConversation event', event);
}
let calledRestartConversation = false;
function restartConversationHandler(event, instance) {
console.log('restartConversationHandler event', event);
if (!calledRestartConversation) {
setTimeout(() => {
instance.send('Hello from embedded webchat second time')
}, 3000);
calledRestartConversation = true;
}
}
function preThreadLoadedHandler(event, instance) {
console.log('pre:threadLoaded event', event);
event.messages[0].content[0].text = 'Modified prompt in thread history'
}
async function authTokenNeededHandler(event, instance) {
console.log('authTokenNeeded event', event);
event.authToken = "<Refreshed Token>"
}
function onChatLoad(instance) {
instance.on('chat:ready', (event, instance) => {
console.log('chat:ready', event);
});
instance.once('pre:send', preSendHandler);
instance.on('send', sendHandler);
instance.once('pre:receive', preReceiveHandler);
instance.on('receive', receiveHandler);
instance.on('feedback', feedbackHandler);
instance.on('userDefinedResponse', userDefinedResponseHandler);
instance.on('pre:restartConversation', preRestartConversationHandler);
instance.on('restartConversation', restartConversationHandler);
instance.on('pre:threadLoaded', preThreadLoadedHandler);
instance.on('authTokenNeeded', authTokenNeededHandler);
}
window.wxOConfiguration = {
clientVersion: 'latest',
orchestrationID: '<tenantId>',
hostUrl: 'http://localhost:3000',
showLauncher: true,
rootElementId: 'root',
chatOptions: {
agentId: '<agentId>',
agentEnvironmentId: '<agentEnvironmentId>',
onLoad: onChatLoad,
},
};
setTimeout(function () {
const script = document.createElement('script');
script.src = `${window.wxOConfiguration.hostUrl}/wxoLoader.js?embed=true`;
script.addEventListener('load', function () {
wxoLoader.init();
});
document.head.appendChild(script);
}, 0);
</script>