Browse Source

fix: implement HTTP/1.0 for shoprenter-proxy to fix auth errors #55

Claude 5 months ago
parent
commit
e3d95ba
1 changed files with 128 additions and 12 deletions
  1. 128 12
      supabase/functions/shoprenter-proxy/index.ts

+ 128 - 12
supabase/functions/shoprenter-proxy/index.ts

@@ -23,6 +23,124 @@ const corsHeaders = {
  * Request and response bodies are proxied as-is.
  */
 
+/**
+ * Makes an HTTP/1.0 request to ShopRenter API
+ * This is required because ShopRenter has a bug where it only accepts HTTP/1.0
+ */
+async function makeHttp10Request(
+  urlString: string,
+  method: string,
+  headers: Headers,
+  body: string | null
+): Promise<{ status: number; statusText: string; headers: Record<string, string>; body: string }> {
+  const url = new URL(urlString)
+  const hostname = url.hostname
+  const port = url.port || '443'
+  const path = url.pathname + url.search
+
+  // Build HTTP/1.0 request
+  let request = `${method} ${path} HTTP/1.0\r\n`
+  request += `Host: ${hostname}\r\n`
+
+  // Add all headers
+  for (const [key, value] of headers.entries()) {
+    request += `${key}: ${value}\r\n`
+  }
+
+  // Add Connection: close for HTTP/1.0
+  request += `Connection: close\r\n`
+  request += `\r\n`
+
+  // Add body if present
+  if (body) {
+    request += body
+  }
+
+  console.log(`[HTTP/1.0] Connecting to ${hostname}:${port}`)
+
+  // Connect to server (TLS for HTTPS)
+  const conn = await Deno.connectTls({
+    hostname: hostname,
+    port: parseInt(port),
+  })
+
+  try {
+    // Send request
+    const encoder = new TextEncoder()
+    await conn.write(encoder.encode(request))
+
+    console.log('[HTTP/1.0] Request sent, reading response...')
+
+    // Read response
+    const decoder = new TextDecoder()
+    const chunks: Uint8Array[] = []
+    const buffer = new Uint8Array(4096)
+
+    while (true) {
+      const n = await conn.read(buffer)
+      if (n === null) break // EOF
+      chunks.push(buffer.slice(0, n))
+    }
+
+    // Combine all chunks
+    const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
+    const responseData = new Uint8Array(totalLength)
+    let offset = 0
+    for (const chunk of chunks) {
+      responseData.set(chunk, offset)
+      offset += chunk.length
+    }
+
+    const responseText = decoder.decode(responseData)
+
+    // Parse HTTP response
+    const lines = responseText.split('\r\n')
+    const statusLine = lines[0]
+    const statusMatch = statusLine.match(/HTTP\/1\.[01] (\d+) (.*)/)
+
+    if (!statusMatch) {
+      throw new Error(`Invalid HTTP response: ${statusLine}`)
+    }
+
+    const status = parseInt(statusMatch[1])
+    const statusText = statusMatch[2]
+
+    // Parse headers
+    const responseHeaders: Record<string, string> = {}
+    let i = 1
+    for (; i < lines.length; i++) {
+      const line = lines[i]
+      if (line === '') break // End of headers
+
+      const colonIndex = line.indexOf(':')
+      if (colonIndex > 0) {
+        const key = line.substring(0, colonIndex).trim()
+        const value = line.substring(colonIndex + 1).trim()
+        responseHeaders[key] = value
+      }
+    }
+
+    // Get body (everything after empty line)
+    const bodyStartIndex = responseText.indexOf('\r\n\r\n')
+    const responseBody = bodyStartIndex >= 0 ? responseText.substring(bodyStartIndex + 4) : ''
+
+    console.log(`[HTTP/1.0] Response received: ${status} ${statusText}`)
+
+    return {
+      status,
+      statusText,
+      headers: responseHeaders,
+      body: responseBody,
+    }
+  } finally {
+    try {
+      conn.close()
+    } catch (e) {
+      console.error('[HTTP/1.0] Error closing connection:', e)
+    }
+  }
+}
+
 serve(async (req) => {
   // Handle CORS preflight
   if (req.method === 'OPTIONS') {
@@ -124,13 +242,14 @@ serve(async (req) => {
       }
     }
 
-    // Make request to ShopRenter API using fetch
-    // Deno's fetch implementation will use HTTP/1.1 by default
-    const shopRenterResponse = await fetch(shopRenterUrl, {
-      method: req.method,
-      headers: shopRenterHeaders,
-      body: body,
-    })
+    // Make request to ShopRenter API using HTTP/1.0
+    // ShopRenter API requires HTTP/1.0 specifically - higher versions cause auth errors
+    const shopRenterResponse = await makeHttp10Request(
+      shopRenterUrl,
+      req.method,
+      shopRenterHeaders,
+      body
+    )
 
     console.log(`[ShopRenter Proxy] ShopRenter API responded with status: ${shopRenterResponse.status}`)
 
@@ -138,7 +257,7 @@ serve(async (req) => {
     const responseHeaders = new Headers(corsHeaders)
 
     // 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()
       // Copy headers but skip transfer-encoding and connection as they're handled by the Edge Function
       if (lowerKey !== 'transfer-encoding' && lowerKey !== 'connection') {
@@ -146,12 +265,9 @@ serve(async (req) => {
       }
     }
 
-    // Read response body
-    const responseBody = await shopRenterResponse.text()
-
     // Return proxied response
     return new Response(
-      responseBody,
+      shopRenterResponse.body,
       {
         status: shopRenterResponse.status,
         statusText: shopRenterResponse.statusText,