96e996e9c2
- lib/soap.ts: executeSoapCommand (port de ac_soap.py; POST SOAP + basic auth al worldserver, timeout 5s, null si inaccesible). AC_SOAP_* en .env.local. - lib/characters.ts: getGameCharacters / characterIsOfflineAndOwned (acore_characters). - lib/character-services.ts: soapCharacterAction genérico (valida propiedad+offline, cooldown 12h vía home_revive/unstuckhistory, SOAP, registra historial) -> reviveCharacter / unstuckCharacter. - components/CharacterActionForm.tsx (cliente reutilizable: select + botón). - Routes /api/character/revive|unstuck (guard de sesión) y páginas app/[locale]/revive|unstuck (SSR, guardas, i18n). Namespace Services. Verificado: API sin sesión 401, páginas redirigen a login, cliente SOAP devuelve null limpio sin worldserver. Patrón base para el resto de servicios de personaje. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35 lines
1.2 KiB
TypeScript
35 lines
1.2 KiB
TypeScript
// Cliente SOAP al worldserver de AzerothCore (port de home/ac_soap.py).
|
|
function escapeXml(s: string): string {
|
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
}
|
|
|
|
/** Ejecuta un comando en el worldserver por SOAP. Devuelve el texto de respuesta o null. */
|
|
export async function executeSoapCommand(command: string): Promise<string | null> {
|
|
const url = process.env.AC_SOAP_URL
|
|
const user = process.env.AC_SOAP_USER ?? ''
|
|
const pass = process.env.AC_SOAP_PASSWORD ?? ''
|
|
const urn = process.env.AC_SOAP_URN ?? 'urn:AC'
|
|
if (!url) return null
|
|
|
|
const soap = `<?xml version="1.0" encoding="utf-8"?>
|
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="${urn}">
|
|
<SOAP-ENV:Body><ns1:executeCommand><command>${escapeXml(command)}</command></ns1:executeCommand></SOAP-ENV:Body>
|
|
</SOAP-ENV:Envelope>`
|
|
|
|
try {
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: 'Basic ' + Buffer.from(`${user}:${pass}`).toString('base64'),
|
|
'Content-Type': 'application/xml',
|
|
},
|
|
body: soap,
|
|
signal: AbortSignal.timeout(5000),
|
|
})
|
|
if (!res.ok) return null
|
|
return await res.text()
|
|
} catch {
|
|
return null // worldserver inaccesible
|
|
}
|
|
}
|