@cxbuilder/flow-config 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../backend/FlowConfig.ts", "../../../backend/shared/logger.ts", "../../../backend/shared/respond.ts", "../../../backend/shared/snsClient.ts", "../../../backend/shared/getVar.ts", "../../../backend/shared/transformFlowConfig.ts"],
4
- "sourcesContent": ["import {\n APIGatewayProxyEvent,\n APIGatewayProxyStructuredResultV2,\n Context,\n} from 'aws-lambda';\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport {\n DynamoDBDocumentClient,\n GetCommand,\n ScanCommand,\n PutCommand,\n DeleteCommand,\n} from '@aws-sdk/lib-dynamodb';\nimport { logEvent } from './shared/logger';\nimport { FlowConfigEnv } from '../infrastructure/api/FlowConfig/FlowConfig.interface';\nimport { respondError, respondMessage, respondObject } from './shared/respond';\nimport { sendError } from './shared/snsClient';\nimport { transformFlowConfig } from './shared/transformFlowConfig';\n\nconst env = process.env as unknown as FlowConfigEnv;\nconst client = new DynamoDBClient();\nconst docClient = DynamoDBDocumentClient.from(client);\nimport { FlowConfig, FlowConfigList, FlowConfigSummary } from './shared/models';\nexport const handler = async (\n event: APIGatewayProxyEvent,\n context?: Context\n): Promise<APIGatewayProxyStructuredResultV2> => {\n logEvent(event, context);\n\n try {\n const method = event.httpMethod;\n const path = event.path;\n const pathParameters = event.pathParameters;\n\n // Extract user claims from Cognito authorizer\n const claims = event.requestContext.authorizer?.claims;\n\n if (!claims) {\n return respondObject(401, new Error('Unauthorized'));\n }\n\n // Route to appropriate handler\n if (method === 'GET' && path === '/api/flow-config') {\n return await listFlowConfigs(event, claims);\n } else if (method === 'GET' && pathParameters?.id) {\n return await getFlowConfig(pathParameters.id, claims);\n } else if (method === 'POST' && path === '/api/flow-config/preview') {\n return await previewFlowConfig(event, claims);\n } else if (method === 'POST' && pathParameters?.id) {\n return await saveFlowConfig(pathParameters.id, event, claims);\n } else if (method === 'DELETE' && pathParameters?.id) {\n return await deleteFlowConfig(pathParameters.id, claims);\n }\n\n return respondMessage(404, 'Not Found');\n } catch (error) {\n await sendError('Unhandled Error: api/flow-config', error as Error);\n return respondError(error);\n }\n};\n\nasync function listFlowConfigs(\n event: APIGatewayProxyEvent,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Get all flow configs from DynamoDB\n const scanCommand = new ScanCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n });\n\n const response = await docClient.send(scanCommand);\n const flowConfigs = response.Items || [];\n\n // Get query parameters for filtering\n const pattern = event.queryStringParameters?.pattern;\n\n // Filter by pattern if provided\n let filteredConfigs = flowConfigs;\n if (pattern) {\n filteredConfigs = flowConfigs.filter((config) =>\n config.id.startsWith(pattern)\n );\n }\n\n // Check permissions for each flow config\n const resultItems: FlowConfigSummary[] = [];\n\n for (const config of filteredConfigs) {\n // Check access level using user claims\n const accessLevel = await checkPermissions(claims, config.id, 'Read');\n if (accessLevel) {\n resultItems.push({\n id: config.id,\n description: config.description,\n accessLevel,\n });\n }\n }\n\n const result: FlowConfigList = { items: resultItems };\n return respondObject(200, result);\n } catch (error) {\n throw new Error(`Error listing flow configs: ${error}`);\n }\n}\n\nasync function getFlowConfig(\n flowConfigId: string,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Check permissions\n const accessLevel = await checkPermissions(claims, flowConfigId, 'Read');\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // Get flow config from DynamoDB\n const getCommand = new GetCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n const response = await docClient.send(getCommand);\n\n if (!response.Item) {\n return respondMessage(404, 'Flow Config not found');\n }\n\n return respondObject(200, response.Item as FlowConfig);\n } catch (error) {\n throw new Error(`Error getting flow config ${flowConfigId}: ${error}`);\n }\n}\n\nasync function saveFlowConfig(\n flowConfigId: string,\n event: APIGatewayProxyEvent,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n // Parse and validate request body first (outside try-catch)\n if (!event.body) {\n return respondMessage(400, 'Request body required');\n }\n\n let body: FlowConfig;\n try {\n body = JSON.parse(event.body) as FlowConfig;\n } catch (error) {\n return respondMessage(400, 'Invalid JSON in request body');\n }\n\n // Ensure ID in body matches path parameter\n body.id = flowConfigId;\n\n // Basic validation\n if (!body.description || !body.variables || !body.prompts) {\n return respondMessage(\n 400,\n 'Missing required fields: description, variables, prompts'\n );\n }\n\n // Validate variables are strings\n for (const [key, value] of Object.entries(body.variables)) {\n if (typeof value !== 'string') {\n return respondMessage(400, `Variable ${key} must be a string`);\n }\n }\n\n // Validate prompts structure\n for (const [promptName, promptData] of Object.entries(body.prompts)) {\n for (const [lang, langData] of Object.entries(promptData)) {\n if (!langData.voice) {\n return respondMessage(\n 400,\n `Prompt ${promptName} for language ${lang} must have a voice variant`\n );\n }\n }\n }\n\n // Check size constraints (approximate)\n const itemSize = JSON.stringify(body).length;\n if (itemSize > 380000) {\n // Leave some buffer for DynamoDB 400KB limit\n return respondMessage(413, 'Flow config exceeds maximum size limit');\n }\n\n try {\n // Check if flow config exists\n const getCommand = new GetCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n const response = await docClient.send(getCommand);\n const existingConfig = response.Item;\n\n // Determine required permission level\n const action = existingConfig ? 'Edit' : 'Create';\n\n // Check permissions\n const accessLevel = await checkPermissions(claims, flowConfigId, action);\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // Save to DynamoDB\n const putCommand = new PutCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Item: body,\n });\n\n await docClient.send(putCommand);\n\n const statusCode = existingConfig ? 200 : 201;\n return respondObject(statusCode, body);\n } catch (error) {\n throw new Error(`Error saving flow config ${flowConfigId}: ${error}`);\n }\n}\n\nasync function deleteFlowConfig(\n flowConfigId: string,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Check permissions\n const accessLevel = await checkPermissions(claims, flowConfigId, 'Delete');\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // Check if flow config exists\n const getCommand = new GetCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n const response = await docClient.send(getCommand);\n\n if (!response.Item) {\n return respondMessage(404, 'Flow Config not found');\n }\n\n // Delete from DynamoDB\n const deleteCommand = new DeleteCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n await docClient.send(deleteCommand);\n\n return respondMessage(204, '');\n } catch (error) {\n throw new Error(`Error deleting flow config ${flowConfigId}: ${error}`);\n }\n}\n\nasync function previewFlowConfig(\n event: APIGatewayProxyEvent,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Parse request body\n if (!event.body) {\n return respondMessage(400, 'Request body required');\n }\n\n let requestData;\n try {\n requestData = JSON.parse(event.body);\n } catch (error) {\n return respondMessage(400, 'Invalid JSON in request body');\n }\n\n // Extract and validate required fields\n const { flowConfig, lang: language, channel } = requestData;\n\n if (!flowConfig) {\n return respondMessage(400, 'flowConfig is required');\n }\n\n if (!language) {\n return respondMessage(400, 'lang is required');\n }\n\n if (!channel) {\n return respondMessage(400, 'channel is required');\n }\n\n // Validate parameters\n if (!/^[a-z]{2}-[A-Z]{2}$/.test(language)) {\n return respondMessage(\n 400,\n 'Invalid language code format. Expected format: en-US'\n );\n }\n\n if (!['voice', 'chat'].includes(channel)) {\n return respondMessage(400, 'Invalid channel. Must be \"voice\" or \"chat\"');\n }\n\n // Basic validation of flowConfig structure\n if (\n !flowConfig.id ||\n !flowConfig.description ||\n !flowConfig.variables ||\n !flowConfig.prompts\n ) {\n return respondMessage(\n 400,\n 'FlowConfig must have id, description, variables, and prompts'\n );\n }\n\n // Check permissions for the flow config ID\n const accessLevel = await checkPermissions(claims, flowConfig.id, 'Read');\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // Use shared transformation function directly\n const result = transformFlowConfig(flowConfig, {\n language,\n channel: channel as 'voice' | 'chat',\n });\n\n return respondObject(200, result);\n } catch (error) {\n throw new Error(`Error previewing flow config: ${error}`);\n }\n}\n\nasync function checkPermissions(\n _claims: Record<string, string>,\n flowConfigId: string,\n action: string\n): Promise<'Full' | 'Edit' | 'Read' | null> {\n try {\n return Promise.resolve('Full');\n } catch (error) {\n console.error(\n `Error checking permissions for ${flowConfigId}, action ${action}:`,\n error\n );\n return null;\n }\n}\n", "import { Logger } from '@aws-lambda-powertools/logger';\nimport { ConnectContactFlowEvent, Context } from 'aws-lambda';\n\nexport const logger = new Logger();\n\n/**\n * Call this method in your lambda handler to capture event details\n * @todo use logger.appendKeys to add attributes like ContactId\n */\nexport const logEvent = (event?: unknown, context?: Context) => {\n if (context) {\n logger.addContext(context);\n }\n if (event) {\n logger.logEventIfEnabled(event);\n }\n\n const ContactData = (event as ConnectContactFlowEvent)?.Details?.ContactData;\n if (ContactData) {\n const { InstanceARN, ContactId } = ContactData;\n logger.appendKeys({\n connectInstanceId: InstanceARN?.split('/').pop(),\n connectContactId: ContactId\n });\n }\n};\n", "// Utility functions for consistent API response\n\nimport { logger } from './logger';\n\nexport const respond = (statusCode: number, body: string) => ({\n statusCode,\n body,\n});\n\nexport const respondObject = (statusCode: number, obj?: object) =>\n respond(statusCode, JSON.stringify(obj));\n\nexport const respondMessage = (statusCode: number, message: string) =>\n respondObject(statusCode, { message });\n\n/**\n * Log error and respond with HTTP 500\n * Note: You can conditionally hide the message from the user based on environment\n */\nexport const respondError = (error: unknown) => {\n logger.error('Unhandled Server Error', error as Error);\n return respondMessage(500, (error as Error).message);\n};\n", "import { PublishCommand, SNSClient } from '@aws-sdk/client-sns';\nimport { logger } from './logger';\nimport { getVar } from './getVar';\n\nconst client = new SNSClient();\n\nconst ALERT_TOPIC_ARN = getVar('ALERT_TOPIC_ARN');\n\n/**\n * Send unhandled exceptions to admins so that the issue can be handled before a client complains\n */\nexport const sendError = async (\n subject: string,\n error: string | Error\n): Promise<void> => {\n try {\n await client.send(\n new PublishCommand({\n TopicArn: ALERT_TOPIC_ARN,\n Subject: subject,\n Message: typeof error === 'string' ? error : error.message,\n })\n );\n } catch (error) {\n logger.error('Error sending message to SNS', {\n error,\n sns: { subject, message: error },\n });\n }\n};\n", "/**\n * Get environment variable and throw a descriptive error if its undefined\n */\nexport function getVar(name: string, defaultValue?: string): string {\n const val = process.env[name] || defaultValue;\n if (!val) {\n throw new Error(`Environment variable \"${name}\" is not defined`);\n }\n return val;\n}\n", "import { logger } from './logger';\nimport { FlowConfig } from './models';\n\nexport interface TransformOptions {\n language: string;\n channel: 'voice' | 'chat';\n}\n\n/**\n * Transform a FlowConfig object into a Record<string, string> for Amazon Connect\n * This function is shared between the preview API and GetConfig lambda\n */\nexport function transformFlowConfig(\n config: FlowConfig,\n options: TransformOptions\n): Record<string, string> {\n const { language, channel } = options;\n\n logger.debug('Transforming flow config', {\n configId: config.id,\n language,\n channel,\n });\n\n // Extract variables\n const variables = config.variables || {};\n\n // Resolve prompts for the specified language and channel\n const prompts: Record<string, string> = {};\n const rawPrompts = config.prompts || {};\n\n for (const [promptName, promptData] of Object.entries(rawPrompts)) {\n if (language in promptData) {\n const langData = promptData[language];\n\n // Use channel-specific prompt, fallback to voice\n if (channel === 'chat' && langData.chat) {\n prompts[promptName] = langData.chat;\n } else if (langData.voice) {\n // For chat channel without chat content, strip SSML tags from voice content\n if (channel === 'chat') {\n prompts[promptName] = stripSSML(langData.voice);\n } else {\n prompts[promptName] = langData.voice;\n }\n }\n } else {\n logger.warn(`Language ${language} not found for prompt ${promptName}`, {\n configId: config.id,\n promptName,\n availableLanguages: Object.keys(promptData),\n });\n }\n }\n\n const result: Record<string, string> = {\n ...variables,\n ...prompts,\n };\n\n // Check response size (Amazon Connect has 32KB limit)\n const responseSize = JSON.stringify(result).length;\n if (responseSize > 30000) {\n // Leave some buffer\n logger.warn('Response size approaching Amazon Connect limit', {\n responseSize,\n configId: config.id,\n limit: 32768,\n });\n }\n\n logger.info('Successfully transformed FlowConfig', {\n configId: config.id,\n language,\n channel,\n variableCount: Object.keys(variables).length,\n promptCount: Object.keys(prompts).length,\n responseSize,\n });\n\n return result;\n}\n\n/**\n * Strip SSML tags from voice content for chat channel\n */\nfunction stripSSML(text: string): string {\n // Remove SSML tags but keep the content\n return text\n .replace(/<[^>]*>/g, '') // Remove all XML/SSML tags\n .replace(/\\s+/g, ' ') // Normalize whitespace\n .trim();\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../../../backend/FlowConfig.ts", "../../../backend/shared/logger.ts", "../../../backend/shared/respond.ts", "../../../backend/shared/snsClient.ts", "../../../backend/shared/getVar.ts", "../../../backend/shared/transformFlowConfig.ts", "../../../backend/shared/permissions.ts"],
4
+ "sourcesContent": ["import {\n APIGatewayProxyEvent,\n APIGatewayProxyStructuredResultV2,\n Context,\n} from 'aws-lambda';\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport {\n DynamoDBDocumentClient,\n GetCommand,\n ScanCommand,\n PutCommand,\n DeleteCommand,\n} from '@aws-sdk/lib-dynamodb';\nimport { logEvent } from './shared/logger';\nimport { FlowConfigEnv } from '../infrastructure/api/FlowConfig/FlowConfig.interface';\nimport { respondError, respondMessage, respondObject } from './shared/respond';\nimport { sendError } from './shared/snsClient';\nimport { transformFlowConfig } from './shared/transformFlowConfig';\nimport { validateFlowConfigPermission } from './shared/permissions';\n\nconst env = process.env as unknown as FlowConfigEnv;\nconst client = new DynamoDBClient();\nconst docClient = DynamoDBDocumentClient.from(client);\nimport { FlowConfig, FlowConfigList, FlowConfigSummary } from './shared/models';\nexport const handler = async (\n event: APIGatewayProxyEvent,\n context?: Context\n): Promise<APIGatewayProxyStructuredResultV2> => {\n logEvent(event, context);\n\n try {\n const method = event.httpMethod;\n const path = event.path;\n const pathParameters = event.pathParameters;\n\n // Extract user claims from Cognito authorizer\n const claims = event.requestContext.authorizer?.claims;\n\n if (!claims) {\n return respondObject(401, new Error('Unauthorized'));\n }\n\n // Route to appropriate handler\n if (method === 'GET' && path === '/api/flow-config') {\n return await listFlowConfigs(event, claims);\n } else if (method === 'GET' && pathParameters?.id) {\n return await getFlowConfig(pathParameters.id, claims);\n } else if (method === 'POST' && path === '/api/flow-config/preview') {\n return await previewFlowConfig(event, claims);\n } else if (method === 'POST' && pathParameters?.id) {\n return await saveFlowConfig(pathParameters.id, event, claims);\n } else if (method === 'DELETE' && pathParameters?.id) {\n return await deleteFlowConfig(pathParameters.id, claims);\n }\n\n return respondMessage(404, 'Not Found');\n } catch (error) {\n await sendError('Unhandled Error: api/flow-config', error as Error);\n return respondError(error);\n }\n};\n\nasync function listFlowConfigs(\n event: APIGatewayProxyEvent,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Get all flow configs from DynamoDB\n const scanCommand = new ScanCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n });\n\n const response = await docClient.send(scanCommand);\n const flowConfigs = response.Items || [];\n\n // Get query parameters for filtering\n const pattern = event.queryStringParameters?.pattern;\n\n // Filter by pattern if provided\n let filteredConfigs = flowConfigs;\n if (pattern) {\n filteredConfigs = flowConfigs.filter((config) =>\n config.id.startsWith(pattern)\n );\n }\n\n // Check permissions for each flow config\n const resultItems: FlowConfigSummary[] = [];\n\n for (const config of filteredConfigs) {\n // Check access level using user claims\n const accessLevel = validateFlowConfigPermission(claims, config.id, 'Read');\n if (accessLevel) {\n resultItems.push({\n id: config.id,\n description: config.description,\n accessLevel,\n });\n }\n }\n\n const result: FlowConfigList = { items: resultItems };\n return respondObject(200, result);\n } catch (error) {\n throw new Error(`Error listing flow configs: ${error}`);\n }\n}\n\nasync function getFlowConfig(\n flowConfigId: string,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Check permissions\n const accessLevel = validateFlowConfigPermission(claims, flowConfigId, 'Read');\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // Get flow config from DynamoDB\n const getCommand = new GetCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n const response = await docClient.send(getCommand);\n\n if (!response.Item) {\n return respondMessage(404, 'Flow Config not found');\n }\n\n return respondObject(200, response.Item as FlowConfig);\n } catch (error) {\n throw new Error(`Error getting flow config ${flowConfigId}: ${error}`);\n }\n}\n\nasync function saveFlowConfig(\n flowConfigId: string,\n event: APIGatewayProxyEvent,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n // Parse and validate request body first (outside try-catch)\n if (!event.body) {\n return respondMessage(400, 'Request body required');\n }\n\n let body: FlowConfig;\n try {\n body = JSON.parse(event.body) as FlowConfig;\n } catch (error) {\n return respondMessage(400, 'Invalid JSON in request body');\n }\n\n // Ensure ID in body matches path parameter\n body.id = flowConfigId;\n\n // Basic validation\n if (!body.description || !body.variables || !body.prompts) {\n return respondMessage(\n 400,\n 'Missing required fields: description, variables, prompts'\n );\n }\n\n // Validate variables are strings\n for (const [key, value] of Object.entries(body.variables)) {\n if (typeof value !== 'string') {\n return respondMessage(400, `Variable ${key} must be a string`);\n }\n }\n\n // Validate prompts structure\n for (const [promptName, promptData] of Object.entries(body.prompts)) {\n for (const [lang, langData] of Object.entries(promptData)) {\n if (!langData.voice) {\n return respondMessage(\n 400,\n `Prompt ${promptName} for language ${lang} must have a voice variant`\n );\n }\n }\n }\n\n // Check size constraints (approximate)\n const itemSize = JSON.stringify(body).length;\n if (itemSize > 380000) {\n // Leave some buffer for DynamoDB 400KB limit\n return respondMessage(413, 'Flow config exceeds maximum size limit');\n }\n\n try {\n // Check if flow config exists\n const getCommand = new GetCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n const response = await docClient.send(getCommand);\n const existingConfig = response.Item;\n\n // Determine required permission level\n const action = existingConfig ? 'Edit' : 'Create';\n\n // Check permissions\n const accessLevel = validateFlowConfigPermission(claims, flowConfigId, action);\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // For FlowConfigEdit users, validate they're only changing values, not structure\n if (accessLevel === 'Edit' && existingConfig) {\n const structuralChangeError = validateEditOnlyChanges(existingConfig as FlowConfig, body);\n if (structuralChangeError) {\n return respondMessage(403, `FlowConfigEdit users cannot make structural changes: ${structuralChangeError}`);\n }\n }\n\n // Save to DynamoDB\n const putCommand = new PutCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Item: body,\n });\n\n await docClient.send(putCommand);\n\n const statusCode = existingConfig ? 200 : 201;\n return respondObject(statusCode, body);\n } catch (error) {\n throw new Error(`Error saving flow config ${flowConfigId}: ${error}`);\n }\n}\n\nasync function deleteFlowConfig(\n flowConfigId: string,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Check permissions\n const accessLevel = validateFlowConfigPermission(claims, flowConfigId, 'Delete');\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // Check if flow config exists\n const getCommand = new GetCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n const response = await docClient.send(getCommand);\n\n if (!response.Item) {\n return respondMessage(404, 'Flow Config not found');\n }\n\n // Delete from DynamoDB\n const deleteCommand = new DeleteCommand({\n TableName: env.FLOW_CONFIGS_TABLE_NAME,\n Key: { id: flowConfigId },\n });\n\n await docClient.send(deleteCommand);\n\n return respondMessage(204, '');\n } catch (error) {\n throw new Error(`Error deleting flow config ${flowConfigId}: ${error}`);\n }\n}\n\nasync function previewFlowConfig(\n event: APIGatewayProxyEvent,\n claims: Record<string, string>\n): Promise<APIGatewayProxyStructuredResultV2> {\n try {\n // Parse request body\n if (!event.body) {\n return respondMessage(400, 'Request body required');\n }\n\n let requestData;\n try {\n requestData = JSON.parse(event.body);\n } catch (error) {\n return respondMessage(400, 'Invalid JSON in request body');\n }\n\n // Extract and validate required fields\n const { flowConfig, lang: language, channel } = requestData;\n\n if (!flowConfig) {\n return respondMessage(400, 'flowConfig is required');\n }\n\n if (!language) {\n return respondMessage(400, 'lang is required');\n }\n\n if (!channel) {\n return respondMessage(400, 'channel is required');\n }\n\n // Validate parameters\n if (!/^[a-z]{2}-[A-Z]{2}$/.test(language)) {\n return respondMessage(\n 400,\n 'Invalid language code format. Expected format: en-US'\n );\n }\n\n if (!['voice', 'chat'].includes(channel)) {\n return respondMessage(400, 'Invalid channel. Must be \"voice\" or \"chat\"');\n }\n\n // Basic validation of flowConfig structure\n if (\n !flowConfig.id ||\n !flowConfig.description ||\n !flowConfig.variables ||\n !flowConfig.prompts\n ) {\n return respondMessage(\n 400,\n 'FlowConfig must have id, description, variables, and prompts'\n );\n }\n\n // Check permissions for the flow config ID\n const accessLevel = validateFlowConfigPermission(claims, flowConfig.id, 'Read');\n if (!accessLevel) {\n return respondMessage(403, 'Access denied');\n }\n\n // Use shared transformation function directly\n const result = transformFlowConfig(flowConfig, {\n language,\n channel: channel as 'voice' | 'chat',\n });\n\n return respondObject(200, result);\n } catch (error) {\n throw new Error(`Error previewing flow config: ${error}`);\n }\n}\n\n/**\n * Validate that FlowConfigEdit users are only changing values, not structure\n * @param existingConfig The existing flow config from database\n * @param newConfig The new flow config being saved\n * @returns Error message if structural changes detected, null if only value changes\n */\nfunction validateEditOnlyChanges(existingConfig: FlowConfig, newConfig: FlowConfig): string | null {\n // Check if description changed (not allowed for Edit users)\n if (existingConfig.description !== newConfig.description) {\n return 'Cannot modify description';\n }\n\n // Check if variable keys changed (not allowed for Edit users)\n const existingVarKeys = Object.keys(existingConfig.variables || {}).sort();\n const newVarKeys = Object.keys(newConfig.variables || {}).sort();\n \n if (existingVarKeys.length !== newVarKeys.length || \n !existingVarKeys.every((key, index) => key === newVarKeys[index])) {\n return 'Cannot add or remove variables';\n }\n\n // Check if prompt structure changed (not allowed for Edit users)\n const existingPromptKeys = Object.keys(existingConfig.prompts || {}).sort();\n const newPromptKeys = Object.keys(newConfig.prompts || {}).sort();\n \n if (existingPromptKeys.length !== newPromptKeys.length || \n !existingPromptKeys.every((key, index) => key === newPromptKeys[index])) {\n return 'Cannot add or remove prompts';\n }\n\n // Check if languages were removed for each prompt (adding is allowed)\n for (const promptName of existingPromptKeys) {\n const existingPrompt = existingConfig.prompts[promptName];\n const newPrompt = newConfig.prompts[promptName];\n \n const existingLangs = Object.keys(existingPrompt || {});\n const newLangs = Object.keys(newPrompt || {});\n \n // Check if any existing languages were removed (not allowed)\n for (const existingLang of existingLangs) {\n if (!newLangs.includes(existingLang)) {\n return `Cannot remove language ${existingLang} from prompt ${promptName}`;\n }\n }\n \n // Adding languages and channels is allowed, so no further validation needed\n }\n\n return null; // No structural changes detected\n}\n\n", "import { Logger } from '@aws-lambda-powertools/logger';\nimport { ConnectContactFlowEvent, Context } from 'aws-lambda';\n\nexport const logger = new Logger();\n\n/**\n * Call this method in your lambda handler to capture event details\n * @todo use logger.appendKeys to add attributes like ContactId\n */\nexport const logEvent = (event?: unknown, context?: Context) => {\n if (context) {\n logger.addContext(context);\n }\n if (event) {\n logger.logEventIfEnabled(event);\n }\n\n const ContactData = (event as ConnectContactFlowEvent)?.Details?.ContactData;\n if (ContactData) {\n const { InstanceARN, ContactId } = ContactData;\n logger.appendKeys({\n connectInstanceId: InstanceARN?.split('/').pop(),\n connectContactId: ContactId\n });\n }\n};\n", "// Utility functions for consistent API response\n\nimport { logger } from './logger';\n\nexport const respond = (statusCode: number, body: string) => ({\n statusCode,\n body,\n});\n\nexport const respondObject = (statusCode: number, obj?: object) =>\n respond(statusCode, JSON.stringify(obj));\n\nexport const respondMessage = (statusCode: number, message: string) =>\n respondObject(statusCode, { message });\n\n/**\n * Log error and respond with HTTP 500\n * Note: You can conditionally hide the message from the user based on environment\n */\nexport const respondError = (error: unknown) => {\n logger.error('Unhandled Server Error', error as Error);\n return respondMessage(500, (error as Error).message);\n};\n", "import { PublishCommand, SNSClient } from '@aws-sdk/client-sns';\nimport { logger } from './logger';\nimport { getVar } from './getVar';\n\nconst client = new SNSClient();\n\nconst ALERT_TOPIC_ARN = getVar('ALERT_TOPIC_ARN');\n\n/**\n * Send unhandled exceptions to admins so that the issue can be handled before a client complains\n */\nexport const sendError = async (\n subject: string,\n error: string | Error\n): Promise<void> => {\n try {\n await client.send(\n new PublishCommand({\n TopicArn: ALERT_TOPIC_ARN,\n Subject: subject,\n Message: typeof error === 'string' ? error : error.message,\n })\n );\n } catch (error) {\n logger.error('Error sending message to SNS', {\n error,\n sns: { subject, message: error },\n });\n }\n};\n", "/**\n * Get environment variable and throw a descriptive error if its undefined\n */\nexport function getVar(name: string, defaultValue?: string): string {\n const val = process.env[name] || defaultValue;\n if (!val) {\n throw new Error(`Environment variable \"${name}\" is not defined`);\n }\n return val;\n}\n", "import { logger } from './logger';\nimport { FlowConfig } from './models';\n\nexport interface TransformOptions {\n language: string;\n channel: 'voice' | 'chat';\n}\n\n/**\n * Transform a FlowConfig object into a Record<string, string> for Amazon Connect\n * This function is shared between the preview API and GetConfig lambda\n */\nexport function transformFlowConfig(\n config: FlowConfig,\n options: TransformOptions\n): Record<string, string> {\n const { language, channel } = options;\n\n logger.debug('Transforming flow config', {\n configId: config.id,\n language,\n channel,\n });\n\n // Extract variables\n const variables = config.variables || {};\n\n // Resolve prompts for the specified language and channel\n const prompts: Record<string, string> = {};\n const rawPrompts = config.prompts || {};\n\n for (const [promptName, promptData] of Object.entries(rawPrompts)) {\n if (language in promptData) {\n const langData = promptData[language];\n\n // Use channel-specific prompt, fallback to voice\n if (channel === 'chat' && langData.chat) {\n prompts[promptName] = langData.chat;\n } else if (langData.voice) {\n // For chat channel without chat content, strip SSML tags from voice content\n if (channel === 'chat') {\n prompts[promptName] = stripSSML(langData.voice);\n } else {\n prompts[promptName] = langData.voice;\n }\n }\n } else {\n logger.warn(`Language ${language} not found for prompt ${promptName}`, {\n configId: config.id,\n promptName,\n availableLanguages: Object.keys(promptData),\n });\n }\n }\n\n const result: Record<string, string> = {\n ...variables,\n ...prompts,\n };\n\n // Check response size (Amazon Connect has 32KB limit)\n const responseSize = JSON.stringify(result).length;\n if (responseSize > 30000) {\n // Leave some buffer\n logger.warn('Response size approaching Amazon Connect limit', {\n responseSize,\n configId: config.id,\n limit: 32768,\n });\n }\n\n logger.info('Successfully transformed FlowConfig', {\n configId: config.id,\n language,\n channel,\n variableCount: Object.keys(variables).length,\n promptCount: Object.keys(prompts).length,\n responseSize,\n });\n\n return result;\n}\n\n/**\n * Strip SSML tags from voice content for chat channel\n */\nfunction stripSSML(text: string): string {\n // Remove SSML tags but keep the content\n return text\n .replace(/<[^>]*>/g, '') // Remove all XML/SSML tags\n .replace(/\\s+/g, ' ') // Normalize whitespace\n .trim();\n}\n", "/**\n * Cognito User Groups permission validation utilities\n * \n * This module provides functions to validate user permissions based on\n * Cognito User Group membership for role-based access control (RBAC).\n */\n\nexport type AccessLevel = 'Full' | 'Edit' | 'Read';\nexport type Action = 'Create' | 'Read' | 'Edit' | 'Delete';\n\n/**\n * Cognito User Groups for FlowConfig application\n */\nexport const COGNITO_GROUPS = {\n ADMIN: 'FlowConfigAdmin',\n EDIT: 'FlowConfigEdit',\n READ: 'FlowConfigRead',\n} as const;\n\n/**\n * Extract Cognito groups from user claims\n * @param claims User claims from Cognito JWT token\n * @returns Array of group names the user belongs to\n */\nexport function extractCognitoGroups(claims: Record<string, string>): string[] {\n // Cognito includes groups in the 'cognito:groups' claim as a comma-separated string\n const groupsClaim = claims['cognito:groups'];\n if (!groupsClaim) {\n return [];\n }\n \n // Handle both string and array formats\n if (typeof groupsClaim === 'string') {\n return groupsClaim.split(',').map(group => group.trim());\n }\n \n // If it's already an array (in some cases), return it\n if (Array.isArray(groupsClaim)) {\n return groupsClaim;\n }\n \n return [];\n}\n\n/**\n * Check if user has any FlowConfig group membership\n * @param claims User claims from Cognito JWT token\n * @returns true if user belongs to at least one FlowConfig group\n */\nexport function hasFlowConfigAccess(claims: Record<string, string>): boolean {\n const userGroups = extractCognitoGroups(claims);\n const flowConfigGroups = Object.values(COGNITO_GROUPS);\n \n return userGroups.some(group => flowConfigGroups.includes(group as any));\n}\n\n/**\n * Get the highest access level for a user based on their group memberships\n * @param claims User claims from Cognito JWT token\n * @returns Highest access level or null if no access\n */\nexport function getAccessLevel(claims: Record<string, string>): AccessLevel | null {\n const userGroups = extractCognitoGroups(claims);\n \n // Check in order of highest to lowest priority\n if (userGroups.includes(COGNITO_GROUPS.ADMIN)) {\n return 'Full';\n }\n \n if (userGroups.includes(COGNITO_GROUPS.EDIT)) {\n return 'Edit';\n }\n \n if (userGroups.includes(COGNITO_GROUPS.READ)) {\n return 'Read';\n }\n \n return null;\n}\n\n/**\n * Check if user has permission to perform a specific action\n * @param claims User claims from Cognito JWT token\n * @param action The action being performed\n * @returns AccessLevel if authorized, null if not authorized\n */\nexport function checkActionPermission(\n claims: Record<string, string>,\n action: Action\n): AccessLevel | null {\n const accessLevel = getAccessLevel(claims);\n \n if (!accessLevel) {\n return null;\n }\n \n // Map actions to required access levels\n switch (action) {\n case 'Read':\n // All groups can read\n return accessLevel;\n \n case 'Edit':\n // Edit and Admin can edit values\n if (accessLevel === 'Edit' || accessLevel === 'Full') {\n return accessLevel;\n }\n return null;\n \n case 'Create':\n case 'Delete':\n // Only Admin can create or delete\n if (accessLevel === 'Full') {\n return accessLevel;\n }\n return null;\n \n default:\n return null;\n }\n}\n\n/**\n * Check if user can perform a structural change (add/remove fields)\n * Only FlowConfigAdmin users can perform structural changes\n * @param claims User claims from Cognito JWT token\n * @returns true if user can make structural changes\n */\nexport function canMakeStructuralChanges(claims: Record<string, string>): boolean {\n const accessLevel = getAccessLevel(claims);\n return accessLevel === 'Full';\n}\n\n/**\n * Validate that a user has permission for a flow config operation\n * This is the main function to be used by API endpoints\n * @param claims User claims from Cognito JWT token\n * @param _flowConfigId The flow config ID (not used in v1, but kept for v2 compatibility)\n * @param action The action being performed\n * @returns AccessLevel if authorized, null if not authorized\n */\nexport function validateFlowConfigPermission(\n claims: Record<string, string>,\n _flowConfigId: string,\n action: Action\n): AccessLevel | null {\n // In v1, all permissions are global (flowConfigId is ignored)\n // This parameter is kept for v2 compatibility when per-config permissions are added\n \n if (!hasFlowConfigAccess(claims)) {\n return null;\n }\n \n return checkActionPermission(claims, action);\n}"],
5
+ "mappings": "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",
6
6
  "names": ["error", "client"]
7
7
  }