fix(integration): escaping content for json body in custom integrations (#4728)

* fix: add jsonEscape option for placeholder replacement

Enhance the replacePlaceholders method to support JSON escaping of values when specified in options. This improves handling of JSON strings in templates.

* fix(integration): add default Content-Type header for non-GET methods
This commit is contained in:
Whitewater 2025-11-18 20:17:19 +08:00 committed by GitHub
parent 387a342665
commit 6d808df3e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 3 deletions

View File

@ -116,6 +116,7 @@ export class CustomIntegrationManager {
options: {
urlEncode?: boolean
htmlEscape?: boolean
jsonEscape?: boolean
} = {},
): string {
let result = template
@ -149,6 +150,11 @@ export class CustomIntegrationManager {
.replaceAll("'", "'")
}
if (options.jsonEscape) {
// Learn more https://stackoverflow.com/questions/4253367/how-to-escape-a-json-string-containing-newline-characters-using-javascript
processedValue = JSON.stringify(processedValue).slice(1, -1)
}
result = result.replaceAll(placeholder, processedValue)
})
@ -175,13 +181,15 @@ export class CustomIntegrationManager {
Object.entries(fetchTemplate.headers).forEach(([key, value]) => {
const processedKey = this.replacePlaceholders(key, context)
const processedValue = this.replacePlaceholders(value, context)
processedHeaders[processedKey] = processedValue
// Field names are case-insensitive.
processedHeaders[processedKey.toLowerCase()] = processedValue
})
// Process body without URL encoding
let processedBody: string | undefined
if (fetchTemplate.body) {
processedBody = this.replacePlaceholders(fetchTemplate.body, context)
const jsonEscape = fetchTemplate.headers["content-type"]?.toLowerCase() === "application/json"
processedBody = this.replacePlaceholders(fetchTemplate.body, context, { jsonEscape })
}
return {

View File

@ -325,6 +325,38 @@ export const CustomIntegrationModalContent = ({
[form],
)
const handleMethodChange = useCallback(
(onChange: (value: string) => void) => (value: string) => {
onChange(value)
const currentHeaders: Record<string, string> = form.getValues("fetchTemplate.headers") || {}
if (value !== "GET") {
// Add default Content-Type header for non-GET methods
const hasContentType = Object.keys(currentHeaders).some(
(key) => key.toLowerCase() === "content-type",
)
if (!hasContentType) {
form.setValue("fetchTemplate.headers", {
...currentHeaders,
"Content-Type": "application/json",
})
}
} else {
// Remove Content-Type: application/json header for GET method
const filteredHeaders: Record<string, string> = {}
Object.entries(currentHeaders).forEach(([key, value]) => {
if (key.toLowerCase() !== "content-type" || value.toLowerCase() !== "application/json") {
filteredHeaders[key] = value
}
})
form.setValue("fetchTemplate.headers", filteredHeaders)
}
},
[form],
)
// Memoized items
const integrationTypeItems = useMemo(
() => [
@ -455,7 +487,7 @@ export const CustomIntegrationModalContent = ({
<FormControl>
<MethodSelector
value={field.value}
onChange={field.onChange}
onChange={handleMethodChange(field.onChange)}
items={httpMethodItems}
/>
</FormControl>