|
@@ -23,12 +23,116 @@ const corsHeaders = {
|
|
|
* Request and response bodies are proxied as-is.
|
|
* Request and response bodies are proxied as-is.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
-// Create HTTP client that forces HTTP/1.1 only (no HTTP/2)
|
|
|
|
|
-// This is required because ShopRenter has issues with HTTP/2
|
|
|
|
|
-const httpClient = Deno.createHttpClient({
|
|
|
|
|
- http1: true, // Enable HTTP/1.1
|
|
|
|
|
- http2: false, // Disable HTTP/2
|
|
|
|
|
-})
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Makes an HTTP/1.0 request using raw TCP/TLS connection
|
|
|
|
|
+ * This is necessary because ShopRenter API strictly requires HTTP/1.0
|
|
|
|
|
+ * and Deno's fetch API only supports HTTP/1.1 and HTTP/2
|
|
|
|
|
+ */
|
|
|
|
|
+async function makeHttp10Request(
|
|
|
|
|
+ hostname: string,
|
|
|
|
|
+ path: string,
|
|
|
|
|
+ method: string,
|
|
|
|
|
+ headers: Record<string, string>,
|
|
|
|
|
+ body?: string
|
|
|
|
|
+): Promise<{ status: number; statusText: string; headers: Record<string, string>; body: string }> {
|
|
|
|
|
+ // Open TLS connection
|
|
|
|
|
+ const conn = await Deno.connectTls({
|
|
|
|
|
+ hostname: hostname,
|
|
|
|
|
+ port: 443,
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Build HTTP/1.0 request
|
|
|
|
|
+ const requestLines: string[] = [
|
|
|
|
|
+ `${method} ${path} HTTP/1.0`,
|
|
|
|
|
+ `Host: ${hostname}`,
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ // Add all headers
|
|
|
|
|
+ for (const [key, value] of Object.entries(headers)) {
|
|
|
|
|
+ requestLines.push(`${key}: ${value}`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Add Connection: close for HTTP/1.0
|
|
|
|
|
+ requestLines.push('Connection: close')
|
|
|
|
|
+
|
|
|
|
|
+ // Empty line to end headers
|
|
|
|
|
+ requestLines.push('')
|
|
|
|
|
+
|
|
|
|
|
+ // Add body if present
|
|
|
|
|
+ if (body) {
|
|
|
|
|
+ requestLines.push(body)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const request = requestLines.join('\r\n')
|
|
|
|
|
+
|
|
|
|
|
+ console.log('[HTTP/1.0] Sending request:', request.split('\r\n').slice(0, 10).join('\n'))
|
|
|
|
|
+
|
|
|
|
|
+ // Send request
|
|
|
|
|
+ const encoder = new TextEncoder()
|
|
|
|
|
+ await conn.write(encoder.encode(request))
|
|
|
|
|
+
|
|
|
|
|
+ // Read response
|
|
|
|
|
+ const decoder = new TextDecoder()
|
|
|
|
|
+ const buffer = new Uint8Array(1024 * 1024) // 1MB buffer
|
|
|
|
|
+ let responseData = ''
|
|
|
|
|
+
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ const n = await conn.read(buffer)
|
|
|
|
|
+ if (n === null) break
|
|
|
|
|
+ responseData += decoder.decode(buffer.subarray(0, n))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log('[HTTP/1.0] Received response length:', responseData.length)
|
|
|
|
|
+
|
|
|
|
|
+ // Parse HTTP response
|
|
|
|
|
+ const headerEndIndex = responseData.indexOf('\r\n\r\n')
|
|
|
|
|
+ if (headerEndIndex === -1) {
|
|
|
|
|
+ throw new Error('Invalid HTTP response: no header/body separator found')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const headerSection = responseData.substring(0, headerEndIndex)
|
|
|
|
|
+ const bodySection = responseData.substring(headerEndIndex + 4)
|
|
|
|
|
+
|
|
|
|
|
+ // Parse status line
|
|
|
|
|
+ const lines = headerSection.split('\r\n')
|
|
|
|
|
+ const statusLine = lines[0]
|
|
|
|
|
+ const statusMatch = statusLine.match(/HTTP\/1\.[01]\s+(\d+)\s+(.*)/)
|
|
|
|
|
+
|
|
|
|
|
+ if (!statusMatch) {
|
|
|
|
|
+ throw new Error(`Invalid HTTP status line: ${statusLine}`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const status = parseInt(statusMatch[1])
|
|
|
|
|
+ const statusText = statusMatch[2]
|
|
|
|
|
+
|
|
|
|
|
+ // Parse headers
|
|
|
|
|
+ const responseHeaders: Record<string, string> = {}
|
|
|
|
|
+ for (let i = 1; i < lines.length; i++) {
|
|
|
|
|
+ const colonIndex = lines[i].indexOf(':')
|
|
|
|
|
+ if (colonIndex > 0) {
|
|
|
|
|
+ const key = lines[i].substring(0, colonIndex).trim()
|
|
|
|
|
+ const value = lines[i].substring(colonIndex + 1).trim()
|
|
|
|
|
+ responseHeaders[key] = value
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log('[HTTP/1.0] Response received:', status, statusText)
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ status,
|
|
|
|
|
+ statusText,
|
|
|
|
|
+ headers: responseHeaders,
|
|
|
|
|
+ body: bodySection,
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ try {
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('[HTTP/1.0] Error closing connection:', e)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
serve(async (req) => {
|
|
serve(async (req) => {
|
|
|
// Handle CORS preflight
|
|
// Handle CORS preflight
|
|
@@ -86,13 +190,14 @@ serve(async (req) => {
|
|
|
|
|
|
|
|
const apiPath = pathMatch[1]
|
|
const apiPath = pathMatch[1]
|
|
|
|
|
|
|
|
- // Construct ShopRenter API URL - use api2.myshoprenter.hu domain
|
|
|
|
|
- const shopRenterUrl = `https://${shopName}.api2.myshoprenter.hu${apiPath}${url.search}`
|
|
|
|
|
|
|
+ // Construct ShopRenter API URL components
|
|
|
|
|
+ const hostname = `${shopName}.api2.myshoprenter.hu`
|
|
|
|
|
+ const fullPath = `${apiPath}${url.search}`
|
|
|
|
|
|
|
|
- console.log(`[ShopRenter Proxy] Forwarding ${req.method} request to: ${shopRenterUrl}`)
|
|
|
|
|
|
|
+ console.log(`[ShopRenter Proxy] Forwarding ${req.method} request to: https://${hostname}${fullPath}`)
|
|
|
|
|
|
|
|
// Prepare headers for ShopRenter API request
|
|
// Prepare headers for ShopRenter API request
|
|
|
- const shopRenterHeaders = new Headers()
|
|
|
|
|
|
|
+ const shopRenterHeaders: Record<string, string> = {}
|
|
|
|
|
|
|
|
// Copy all headers from the original request except Host, Authorization, and custom headers
|
|
// Copy all headers from the original request except Host, Authorization, and custom headers
|
|
|
for (const [key, value] of req.headers.entries()) {
|
|
for (const [key, value] of req.headers.entries()) {
|
|
@@ -103,45 +208,47 @@ serve(async (req) => {
|
|
|
!lowerKey.startsWith('x-shoprenter-') &&
|
|
!lowerKey.startsWith('x-shoprenter-') &&
|
|
|
!lowerKey.startsWith('x-client-info')
|
|
!lowerKey.startsWith('x-client-info')
|
|
|
) {
|
|
) {
|
|
|
- shopRenterHeaders.set(key, value)
|
|
|
|
|
|
|
+ shopRenterHeaders[key] = value
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Set Authorization header with ShopRenter token
|
|
// Set Authorization header with ShopRenter token
|
|
|
- shopRenterHeaders.set('Authorization', `Bearer ${shopRenterToken}`)
|
|
|
|
|
|
|
+ shopRenterHeaders['Authorization'] = `Bearer ${shopRenterToken}`
|
|
|
|
|
|
|
|
// Ensure required headers are present
|
|
// Ensure required headers are present
|
|
|
- if (!shopRenterHeaders.has('Content-Type')) {
|
|
|
|
|
- shopRenterHeaders.set('Content-Type', 'application/json')
|
|
|
|
|
|
|
+ if (!shopRenterHeaders['Content-Type']) {
|
|
|
|
|
+ shopRenterHeaders['Content-Type'] = 'application/json'
|
|
|
}
|
|
}
|
|
|
- if (!shopRenterHeaders.has('Accept')) {
|
|
|
|
|
- shopRenterHeaders.set('Accept', 'application/json')
|
|
|
|
|
|
|
+ if (!shopRenterHeaders['Accept']) {
|
|
|
|
|
+ shopRenterHeaders['Accept'] = 'application/json'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Read request body if present
|
|
// Read request body if present
|
|
|
- let body = null
|
|
|
|
|
|
|
+ let body: string | undefined = undefined
|
|
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
|
const contentType = req.headers.get('Content-Type') || ''
|
|
const contentType = req.headers.get('Content-Type') || ''
|
|
|
if (contentType.includes('application/json')) {
|
|
if (contentType.includes('application/json')) {
|
|
|
try {
|
|
try {
|
|
|
body = await req.text()
|
|
body = await req.text()
|
|
|
|
|
+ if (body) {
|
|
|
|
|
+ shopRenterHeaders['Content-Length'] = body.length.toString()
|
|
|
|
|
+ }
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.error('[ShopRenter Proxy] Error reading request body:', e)
|
|
console.error('[ShopRenter Proxy] Error reading request body:', e)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Make request to ShopRenter API using HTTP/1.1 only (HTTP/2 disabled)
|
|
|
|
|
- // ShopRenter API requires HTTP/1.x - higher versions cause auth errors
|
|
|
|
|
- console.log(`[ShopRenter Proxy] Making request with HTTP/1.1 to: ${shopRenterUrl}`)
|
|
|
|
|
- console.log(`[ShopRenter Proxy] Headers:`, Object.fromEntries(shopRenterHeaders.entries()))
|
|
|
|
|
|
|
+ console.log(`[ShopRenter Proxy] Making HTTP/1.0 request to: ${hostname}${fullPath}`)
|
|
|
|
|
|
|
|
- const shopRenterResponse = await fetch(shopRenterUrl, {
|
|
|
|
|
- method: req.method,
|
|
|
|
|
- headers: shopRenterHeaders,
|
|
|
|
|
- body: body,
|
|
|
|
|
- client: httpClient, // Use HTTP/1.1-only client
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ // Make HTTP/1.0 request using raw TCP/TLS connection
|
|
|
|
|
+ const shopRenterResponse = await makeHttp10Request(
|
|
|
|
|
+ hostname,
|
|
|
|
|
+ fullPath,
|
|
|
|
|
+ req.method,
|
|
|
|
|
+ shopRenterHeaders,
|
|
|
|
|
+ body
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
console.log(`[ShopRenter Proxy] ShopRenter API responded with status: ${shopRenterResponse.status}`)
|
|
console.log(`[ShopRenter Proxy] ShopRenter API responded with status: ${shopRenterResponse.status}`)
|
|
|
|
|
|
|
@@ -149,7 +256,7 @@ serve(async (req) => {
|
|
|
const responseHeaders = new Headers(corsHeaders)
|
|
const responseHeaders = new Headers(corsHeaders)
|
|
|
|
|
|
|
|
// Copy relevant headers from ShopRenter response
|
|
// Copy relevant headers from ShopRenter response
|
|
|
- for (const [key, value] of shopRenterResponse.headers.entries()) {
|
|
|
|
|
|
|
+ for (const [key, value] of Object.entries(shopRenterResponse.headers)) {
|
|
|
const lowerKey = key.toLowerCase()
|
|
const lowerKey = key.toLowerCase()
|
|
|
// Copy headers but skip transfer-encoding and connection as they're handled by the Edge Function
|
|
// Copy headers but skip transfer-encoding and connection as they're handled by the Edge Function
|
|
|
if (lowerKey !== 'transfer-encoding' && lowerKey !== 'connection') {
|
|
if (lowerKey !== 'transfer-encoding' && lowerKey !== 'connection') {
|
|
@@ -157,12 +264,9 @@ serve(async (req) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Get response body
|
|
|
|
|
- const responseBody = await shopRenterResponse.text()
|
|
|
|
|
-
|
|
|
|
|
// Return proxied response
|
|
// Return proxied response
|
|
|
return new Response(
|
|
return new Response(
|
|
|
- responseBody,
|
|
|
|
|
|
|
+ shopRenterResponse.body,
|
|
|
{
|
|
{
|
|
|
status: shopRenterResponse.status,
|
|
status: shopRenterResponse.status,
|
|
|
statusText: shopRenterResponse.statusText,
|
|
statusText: shopRenterResponse.statusText,
|