Add WebAPI handler for extensions

- Rename http.ts to web.ts handler
- Implement handleWebMethodAsync with haextension.web.fetch support
- Add base64 body encoding/decoding
- Add timeout support with AbortController
- Convert response headers and body to proper format
- Update message handler to route haextension.web.* methods
- Add TODO for permission checks

This enables extensions to make web requests through the host app,
bypassing iframe CORS restrictions.
This commit is contained in:
2025-11-11 13:27:53 +01:00
parent 554cb7762d
commit 203f81e775
4 changed files with 96 additions and 18 deletions

View File

@ -7,7 +7,7 @@ import {
import {
handleDatabaseMethodAsync,
handleFilesystemMethodAsync,
handleHttpMethodAsync,
handleWebMethodAsync,
handlePermissionsMethodAsync,
handleContextMethodAsync,
handleStorageMethodAsync,
@ -165,8 +165,8 @@ const registerGlobalMessageHandler = () => {
result = await handleDatabaseMethodAsync(request, instance.extension)
} else if (request.method.startsWith('haextension.fs.')) {
result = await handleFilesystemMethodAsync(request, instance.extension)
} else if (request.method.startsWith('haextension.http.')) {
result = await handleHttpMethodAsync(request, instance.extension)
} else if (request.method.startsWith('haextension.web.')) {
result = await handleWebMethodAsync(request, instance.extension)
} else if (request.method.startsWith('haextension.permissions.')) {
result = await handlePermissionsMethodAsync(request, instance.extension)
} else {

View File

@ -1,14 +0,0 @@
import type { IHaexHubExtension } from '~/types/haexhub'
import type { ExtensionRequest } from './types'
export async function handleHttpMethodAsync(
request: ExtensionRequest,
extension: IHaexHubExtension,
) {
if (!extension || !request) {
throw new Error('Extension not found')
}
// TODO: Implementiere HTTP Commands im Backend
throw new Error('HTTP methods not yet implemented')
}

View File

@ -1,7 +1,7 @@
// Export all handler functions
export { handleDatabaseMethodAsync } from './database'
export { handleFilesystemMethodAsync } from './filesystem'
export { handleHttpMethodAsync } from './http'
export { handleWebMethodAsync } from './web'
export { handlePermissionsMethodAsync } from './permissions'
export { handleContextMethodAsync, setContextGetters } from './context'
export { handleStorageMethodAsync } from './storage'

View File

@ -0,0 +1,92 @@
import type { IHaexHubExtension } from '~/types/haexhub'
import type { ExtensionRequest } from './types'
export async function handleWebMethodAsync(
request: ExtensionRequest,
extension: IHaexHubExtension,
) {
if (!extension || !request) {
throw new Error('Extension not found')
}
// TODO: Add permission check for web requests
// This should verify that the extension has permission to make web requests
// before proceeding with the fetch operation
const { method, params } = request
if (method === 'haextension.web.fetch') {
return await handleWebFetchAsync(params)
}
throw new Error(`Unknown web method: ${method}`)
}
async function handleWebFetchAsync(params: Record<string, unknown>) {
const url = params.url as string
const method = (params.method as string) || 'GET'
const headers = (params.headers as Record<string, string>) || {}
const body = params.body as string | undefined
const timeout = (params.timeout as number) || 30000
if (!url) {
throw new Error('URL is required')
}
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
const fetchOptions: RequestInit = {
method,
headers,
signal: controller.signal,
}
// Convert base64 body back to binary if present
if (body) {
const binaryString = atob(body)
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
fetchOptions.body = bytes
}
const response = await fetch(url, fetchOptions)
clearTimeout(timeoutId)
// Read response as ArrayBuffer
const responseBody = await response.arrayBuffer()
// Convert ArrayBuffer to base64
const bytes = new Uint8Array(responseBody)
let binary = ''
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
const base64Body = btoa(binary)
// Convert headers to plain object
const responseHeaders: Record<string, string> = {}
response.headers.forEach((value, key) => {
responseHeaders[key] = value
})
return {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
body: base64Body,
url: response.url,
}
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request timeout after ${timeout}ms`)
}
throw new Error(`Fetch failed: ${error.message}`)
}
throw new Error('Fetch failed with unknown error')
}
}