Repository: svix/svix-webhooks (language: rust)

Issue:
Svix does not work with Cloudflare Worker
## Bug Report

### Version
^1.24.0

### Platform
Darwin Bernardos-MacBook-Pro.local 23.5.0 Darwin Kernel Version 23.5.0: Wed May  1 20:12:58 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6000 arm64

### Description

I'm not able to use Svix with Cloudflare Workers.

You can reproduce it doing the following steps:

1. Start a cloudflare worker project: https://developers.cloudflare.com/workers/get-started/guide/
2. Try to do the first step from svix quickstart: https://docs.svix.com/quickstart#creating-a-consumer-application
3. You'll see the following error: `The 'credentials' field on 'RequestInitializerDict' is not implemented`

I think the error is related to this issue: https://github.com/cloudflare/workers-sdk/issues/2514



Discussion / hints:
Whoops, missed this. Based on your report it sounds like a cloudflare workers issue though, no? Is it fixed now that the other ticket is closed?
The Fetch API on CF's worker didn't support the `credentials` field. So the SDK from svix cause the issue by set it to `same-origin`.

https://github.com/svix/svix-webhooks/blob/e5dbc7556f699e5f62092d2f57e979333f775534/javascript/src/openapi/http/isomorphic-fetch.ts#L40

The fix can be easy like the last comment on that issue:
https://github.com/cloudflare/workers-sdk/issues/2514#issuecomment-2178070014
> The Fetch API on CF's worker didn't support the `credentials` field. So the SDK from svix cause the issue by set it to `same-origin`.
> 
> https://github.com/svix/svix-webhooks/blob/e5dbc7556f699e5f62092d2f57e979333f775534/javascript/src/openapi/http/isomorphic-fetch.ts#L40
> 
> The fix can be easy like the last comment on that issue: [cloudflare/workers-sdk#2514 (comment)](https://github.com/cloudflare/workers-sdk/issues/2514#issuecomment-2178070014)

I don't have access to fetch functions because I'm using svix SDK. Right now I removed svix sdk and add my own fetch functions to access svix api.

I was wondering if it makes sense to add this fix you mentioned inside svix sdk and not on our (svix customer) side. CloudFlare workers are getting more and more famous nowadays and it would increase the developer experience if I could start a CF worker and add svix with no errors.
Yeah, I think it's probably a good idea to do it. Though it's annoying that this needs to be done in the first place.

Fix patch:
diff --git a/javascript/src/openapi/http/isomorphic-fetch.ts b/javascript/src/openapi/http/isomorphic-fetch.ts
index 9a35f2ab1..f62d4f388 100644
--- a/javascript/src/openapi/http/isomorphic-fetch.ts
+++ b/javascript/src/openapi/http/isomorphic-fetch.ts
@@ -1,4 +1,4 @@
-import {HttpLibrary, RequestContext, ResponseContext} from './http';
+import { HttpLibrary, RequestContext, ResponseContext } from './http';
 import { from, Observable } from '../rxjsStub';
 import "svix-fetch";
 
@@ -6,49 +6,53 @@ const numRetries = 2;
 const sleep = (interval: number) => new Promise(resolve => setTimeout(resolve, interval));
 
 export class IsomorphicFetchHttpLibrary implements HttpLibrary {
+  public send(request: RequestContext): Observable<ResponseContext> {
+    const resultPromise = this.sendWithRetry(request, numRetries, 50, 1);
+    return from<Promise<ResponseContext>>(resultPromise);
+  }
 
-    public send(request: RequestContext): Observable<ResponseContext> {
-      const resultPromise = this.sendWithRetry(request, numRetries, 50, 1);
-      return from<Promise<ResponseContext>>(resultPromise);
-    }
+  private async sendWithRetry(request: RequestContext, triesLeft: number, nextInterval: number, retryCount: number): Promise<ResponseContext> {
+    try {
+      const response = await this.sendOnce(request);
+      if (triesLeft <= 0 || response.httpStatusCode < 500) {
+        return response;
+      }
+    } catch (e) {
+      if (triesLeft <= 0) {
+        throw e;
+      }
+    };
+    await sleep(nextInterval);
+    const headers = request.getHeaders();
+    headers['svix-retry-count'] = retryCount.toString()
+    return await this.sendWithRetry(request, --triesLeft, nextInterval * 2, ++retryCount);
+  }
 
-    private async sendWithRetry(request: RequestContext, triesLeft: number, nextInterval: number, retryCount: number): Promise<ResponseContext> {
-      try {
-        const response = await this.sendOnce(request);
-        if (triesLeft <= 0 || response.httpStatusCode < 500) {
-          return response;
-        }
-      } catch (e) {
-        if (triesLeft <= 0) {
-          throw e;
-        }
-      };
-      await sleep(nextInterval);
-      const headers = request.getHeaders();
-      headers['svix-retry-count'] = retryCount.toString()
-      return await this.sendWithRetry(request, --triesLeft, nextInterval * 2, ++retryCount);
-    }
-  
-    private sendOnce(request: RequestContext): Promise<ResponseContext> {
-        let method = request.getHttpMethod().toString();
-        let body = request.getBody();
+  private sendOnce(request: RequestContext): Promise<ResponseContext> {
+    let method = request.getHttpMethod().toString();
+    let body = request.getBody();
+
+    // Cloudflare Workers fail if the credentials option is used in a fetch call.
+    // This work around that. Source:
+    // https://github.com/cloudflare/workers-sdk/issues/2514#issuecomment-2178070014
+    const isCredentialsSupported = "credentials" in Request.prototype;
 
-        return fetch(request.getUrl(), {
-            method: method,
-            body: body as any,
-            headers: request.getHeaders(),
-            credentials: "same-origin"
-        }).then((resp: any) => {
-            const headers: { [name: string]: string } = {};
-            resp.headers.forEach((value: string, name: string) => {
-              headers[name] = value;
-            });
+    return fetch(request.getUrl(), {
+      method: method,
+      body: body as any,
+      headers: request.getHeaders(),
+      credentials: isCredentialsSupported ? "same-origin" : undefined
+    }).then((resp: any) => {
+      const headers: { [name: string]: string } = {};
+      resp.headers.forEach((value: string, name: string) => {
+        headers[name] = value;
+      });
 
-            const body = {
-              text: () => resp.text(),
-              binary: () => resp.blob()
-            };
-            return new ResponseContext(resp.status, headers, body);
-        });
-    }
+      const body = {
+        text: () => resp.text(),
+        binary: () => resp.blob()
+      };
+      return new ResponseContext(resp.status, headers, body);
+    });
+  }
 }


Test patch: