|
|
@@ -0,0 +1,158 @@
|
|
|
+import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
|
+import { wrapHandler } from '../_shared/error-handler.ts'
|
|
|
+
|
|
|
+const corsHeaders = {
|
|
|
+ 'Access-Control-Allow-Origin': '*',
|
|
|
+ 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-shoprenter-shop',
|
|
|
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * ShopRenter API Proxy
|
|
|
+ *
|
|
|
+ * This Edge Function acts as a transparent proxy to the ShopRenter API.
|
|
|
+ * It solves the HTTP version compatibility issue where n8n uses HTTP/2+
|
|
|
+ * but ShopRenter API requires HTTP/1.0.
|
|
|
+ *
|
|
|
+ * Usage from n8n:
|
|
|
+ * 1. Set Authorization header with Bearer token (from ShopRenter OAuth)
|
|
|
+ * 2. Set X-ShopRenter-Shop header with shop name (e.g., "myshop")
|
|
|
+ * 3. Make requests to: /shoprenter-proxy/api/{endpoint}
|
|
|
+ * 4. The proxy will forward to: https://{shop}.shoprenter.hu/api/{endpoint}
|
|
|
+ *
|
|
|
+ * All request methods (GET, POST, PUT, PATCH, DELETE) are supported.
|
|
|
+ * All headers (except Host) are forwarded transparently.
|
|
|
+ * Request and response bodies are proxied as-is.
|
|
|
+ */
|
|
|
+
|
|
|
+serve(wrapHandler('shoprenter-proxy', async (req) => {
|
|
|
+ // Handle CORS preflight
|
|
|
+ if (req.method === 'OPTIONS') {
|
|
|
+ return new Response('ok', { headers: corsHeaders })
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Extract shop name from custom header
|
|
|
+ const shopName = req.headers.get('X-ShopRenter-Shop')
|
|
|
+ if (!shopName) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({
|
|
|
+ error: 'Missing X-ShopRenter-Shop header',
|
|
|
+ message: 'Please provide the shop name in the X-ShopRenter-Shop header (e.g., "myshop" for myshop.shoprenter.hu)'
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract API endpoint from URL path
|
|
|
+ const url = new URL(req.url)
|
|
|
+ const pathMatch = url.pathname.match(/\/shoprenter-proxy(\/.*)$/)
|
|
|
+
|
|
|
+ if (!pathMatch || !pathMatch[1]) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({
|
|
|
+ error: 'Invalid API endpoint',
|
|
|
+ message: 'Request path must be in format: /shoprenter-proxy/api/{endpoint}'
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ const apiPath = pathMatch[1]
|
|
|
+
|
|
|
+ // Construct ShopRenter API URL
|
|
|
+ const shopRenterUrl = `https://${shopName}.shoprenter.hu${apiPath}${url.search}`
|
|
|
+
|
|
|
+ console.log(`[ShopRenter Proxy] Forwarding ${req.method} request to: ${shopRenterUrl}`)
|
|
|
+
|
|
|
+ // Prepare headers for ShopRenter API request
|
|
|
+ const shopRenterHeaders = new Headers()
|
|
|
+
|
|
|
+ // Copy all headers from the original request except Host and custom headers
|
|
|
+ for (const [key, value] of req.headers.entries()) {
|
|
|
+ const lowerKey = key.toLowerCase()
|
|
|
+ if (lowerKey !== 'host' && !lowerKey.startsWith('x-shoprenter-')) {
|
|
|
+ shopRenterHeaders.set(key, value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ensure required headers are present
|
|
|
+ if (!shopRenterHeaders.has('Content-Type')) {
|
|
|
+ shopRenterHeaders.set('Content-Type', 'application/json')
|
|
|
+ }
|
|
|
+ if (!shopRenterHeaders.has('Accept')) {
|
|
|
+ shopRenterHeaders.set('Accept', 'application/json')
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read request body if present
|
|
|
+ let body = null
|
|
|
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
|
+ const contentType = req.headers.get('Content-Type') || ''
|
|
|
+ if (contentType.includes('application/json')) {
|
|
|
+ try {
|
|
|
+ body = await req.text()
|
|
|
+ } catch (e) {
|
|
|
+ console.error('[ShopRenter Proxy] Error reading request body:', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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,
|
|
|
+ // Force HTTP/1.1 to avoid ShopRenter API issues
|
|
|
+ // Note: This is handled by Deno's fetch by default
|
|
|
+ })
|
|
|
+
|
|
|
+ console.log(`[ShopRenter Proxy] ShopRenter API responded with status: ${shopRenterResponse.status}`)
|
|
|
+
|
|
|
+ // Prepare response headers
|
|
|
+ const responseHeaders = new Headers(corsHeaders)
|
|
|
+
|
|
|
+ // Copy relevant headers from ShopRenter response
|
|
|
+ for (const [key, value] of shopRenterResponse.headers.entries()) {
|
|
|
+ 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') {
|
|
|
+ responseHeaders.set(key, value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read response body
|
|
|
+ const responseBody = await shopRenterResponse.text()
|
|
|
+
|
|
|
+ // Return proxied response
|
|
|
+ return new Response(
|
|
|
+ responseBody,
|
|
|
+ {
|
|
|
+ status: shopRenterResponse.status,
|
|
|
+ statusText: shopRenterResponse.statusText,
|
|
|
+ headers: responseHeaders
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[ShopRenter Proxy] Error:', error)
|
|
|
+
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({
|
|
|
+ error: 'Proxy error',
|
|
|
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
|
+ details: error instanceof Error ? error.stack : undefined
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ status: 500,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+}))
|