Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fetch override #245

Conversation

BartoszJarocki
Copy link
Contributor

@BartoszJarocki BartoszJarocki commented Feb 2, 2024

This PR renames fetch to fetchOptions and adds an additional option to provide own fetch method.

fixes #243

Copy link
Contributor

@lukeocodes lukeocodes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I really appreciate the effort here.

The only issue with this is that it is a breaking change and we're not ready to add another major version to our release channel. I would encourage you to add a new property (not breaking) like fetchClient.

I would then replace fetchWithAuth with a function that switched Fetch if it was supplied, and then called fetchWithAuth to return a preconfigured fetch instance using the right class and the auth headers.

Superbase has a good example, and they use cross-fetch too https://github.com/supabase/supabase-js/blob/master/src/lib/fetch.ts#L6

Also any tests you're able to provide would be very helpful

Copy link
Contributor

@davidvonthenen davidvonthenen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clicked approve on the wrong PR. Should have been this one:
#246 (review)

@BartoszJarocki
Copy link
Contributor Author

@lukeocodes understood, my thinking was that it'll be a bit confusing to have fetch as fetch options and something different to actually override fetch. now, I changed the implementation to be similar to supabase example, thank you! would you mind taking a look again?

regarding tests, I personally am not a fan of testing protected methods so the only option I see is to add tests like that to each client type

  it("should use custom fetch when provided", async () => {
    const fetch = async () => {
      return new Response(JSON.stringify({ customFetch: true }));
    };
 
    const client = createClient(faker.string.alphanumeric(40), {
      global: { url: "https://api.mock.deepgram.com" },
      customFetch: fetch,
    });
 
    const { result, error } = await client.manage.getProjectBalances(faker.string.uuid());
 
    assert.isNull(error);
    assert.isNotNull(result);
    assert.containsAllDeepKeys(result, ["customFetch"]);
  });

wdyt?

@lukeocodes
Copy link
Contributor

Looks good to me! I'm going to personally test this on browsers, edge functions and Node environments to ensure we're not introducing a regression. I'll try and get this reviewed by the end of the week. Really appreciate you coming back with fixes for the PR

@BartoszJarocki
Copy link
Contributor Author

@lukeocodes awesome, please let me know if there's anything missing!

@lukeocodes lukeocodes self-assigned this Feb 17, 2024
@lukeocodes lukeocodes added the enhancement New feature or request label Feb 17, 2024
@pkpio
Copy link

pkpio commented Mar 4, 2024

Any update on this? Would be great to also see an example somewhere of a custom fetch impl to use - trying to workaround the 5 mins timeout errors of the default nodejs fetch

@BartoszJarocki
Copy link
Contributor Author

@myselfhimself
Copy link

@lukeocodes understood, my thinking was that it'll be a bit confusing to have fetch as fetch options and something different to actually override fetch. now, I changed the implementation to be similar to supabase example, thank you! would you mind taking a look again?

regarding tests, I personally am not a fan of testing protected methods so the only option I see is to add tests like that to each client type

  it("should use custom fetch when provided", async () => {
    const fetch = async () => {
      return new Response(JSON.stringify({ customFetch: true }));
    };
 
    const client = createClient(faker.string.alphanumeric(40), {
      global: { url: "https://api.mock.deepgram.com" },
      customFetch: fetch,
    });
 
    const { result, error } = await client.manage.getProjectBalances(faker.string.uuid());
 
    assert.isNull(error);
    assert.isNotNull(result);
    assert.containsAllDeepKeys(result, ["customFetch"]);
  });

wdyt?

Your test looks good, it could be added to https://github.com/deepgram/deepgram-js-sdk/blob/main/test/client.test.ts if I am not mistaken..

@lukeocodes
Copy link
Contributor

I'll calve out time to review and test this next week!

@myselfhimself
Copy link

myselfhimself commented Apr 10, 2024

Hello here is some example working code using an AbortController .signal-enabled custom fetch with @BartoszJarocki 's pull request:

import { DeepgramError, createClient } from "@deepgram/sdk";
import { SettingsManagerCookies as SettingsManager } from "./settingsManager";

export class DeepgramClientCancellationError extends DeepgramError {
    originalError: unknown;

    constructor(message: string, originalError: unknown) {
        super(message);
        this.name = "DeepgramClientCancellationError";
        this.originalError = originalError;
    }
}

export const createDeepgramClient = (apiKey?: string, signal?: AbortSignal) => {
    async function cancellableFetch(
        input: RequestInfo | URL, init?: RequestInit
    ): Promise<Response> {
        // Use cross-fetch with the AbortController signal
        try {
            const response = await fetch(input, { ...init, signal });
            console.log("got response in custom Fetch, with signal" + JSON.stringify(signal));
            return response;
        } catch (error) {
            if ((error as DOMException).name === "AbortError") {
                // Handle fetch abort due to timeout
                throw new DeepgramClientCancellationError("Deepgram query aborted by user", error);
            } else {
                // Re-throw other errors
                throw error;
            }
        }
    }



    var proxyUrl = "http://127.0.0.1:8889";

    console.log("creating client with Deepgram experimental customFetch parameter");
    return createClient("proxy", {
        restProxy: { url: proxyUrl },
        fetch: { headers: { forceApiKey: apiKey ?? (SettingsManager.getApiKey()) } },
        customFetch: cancellableFetch
    });
}

// Example usage:
var controller = new AbortController();
var client = createDeepgramClient(undefined /* using my settings manager */, controller.signal);
client.listen.prerecorded.transcribeFile(binFile, options).then((result) => {}).catch((err) => { if(err.name == "AbortController") { console.log("was cancelled"); })
// Some cancelling somewhere, on some click() or interrupt.
controller.abort();

To use the custom fetch pull request, I used the following package.json line: "@deepgram/sdk": "github:deepgram/deepgram-js-sdk#pull/245", and I had to cd into node_modules/@deepgram/sdk and run npm i followed by npm run build.

The forceApikey mechanism allows a deepgram client to tell a deepgram proxy to use a different API key, it is described in the deepgram http proxy project., though no deepgram proxy implementation has been published yet IMHO.

@myselfhimself
Copy link

I am about to release a product with this fork...
Here are some information for people wanting to use the fork with package.json in a CI/CD pipeline:
package.json:

...
"scripts": {
"prebuild": "ls node_modules/@deepgram/sdk | grep -q tsconfig.json && npm i --prefix node_modules/@deepgram/sdk && npm run build --prefix node_modules/@deepgram/sdk
....}
...
"dependencies": {
...
    "@deepgram/sdk": "https://github.com/BartoszJarocki/deepgram-js-sdk/tarball/feat/add-fetch-override",
...
}
...

i want to avoid taking any new namespaces while i think about the best way to do this
@lukeocodes
Copy link
Contributor

@BartoszJarocki thanks for your patience on this. If you could please add the test just as you had it, that will be great.

FYI, I have modified the namespace of the custom fetch to _experimentalCustomFetch. I am keen not to take up any more namespaces before I figure out how I want to allow settings to differ between products on the same instance. We have some thinking to do (as we've added more products to the SDK).

Once we have the test, this is good to go. You can add it to client.test.ts. Thanks again for your patience and help.

@BartoszJarocki
Copy link
Contributor Author

@lukeocodes I'm glad it's good to go! I just added a test 84a4e27.

@lukeocodes
Copy link
Contributor

lukeocodes commented Apr 25, 2024 via email

@BartoszJarocki
Copy link
Contributor Author

awesome, sounds good!

@lukeocodes lukeocodes changed the base branch from main to release/3.x.x-20240425 April 27, 2024 10:46
@lukeocodes lukeocodes merged commit bc3031d into deepgram:release/3.x.x-20240425 Apr 27, 2024
1 check passed
lukeocodes added a commit that referenced this pull request Apr 27, 2024
* feat: add _experimentalCustomFetch config to override fetch (#245)

* feat: allow to provide custom fetch method to AbstractRestfulClient

* fix: allow to provide both fetch and fetch options

* refactor: init custom fetch in resolveFetch

* rename `customFetch` to `_experimentalCustomFetch`

i want to avoid taking any new namespaces while i think about the best way to do this

* use `_experimentalCustomFetch` instead of `customFetch` for fetch override

* test: added custom fetch test

---------

Co-authored-by: Luke Oliff <luke@lukeoliff.com>

* chore: Bump es5-ext from 0.10.62 to 0.10.64 (#250)

Bumps [es5-ext](https://github.com/medikoo/es5-ext) from 0.10.62 to 0.10.64.
- [Release notes](https://github.com/medikoo/es5-ext/releases)
- [Changelog](https://github.com/medikoo/es5-ext/blob/main/CHANGELOG.md)
- [Commits](medikoo/es5-ext@v0.10.62...v0.10.64)

---
updated-dependencies:
- dependency-name: es5-ext
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: add get auth token details method to manage client (#262)

* chore: edit example for is_final and endpointing together with utterance end (#264)

* fix: add missing expiration_date to CreateProjectKeyResponse (#265)

* fix: update user agent to include JS or node (#269)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Bartosz Jarocki <bartosz.jarocki@hey.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: DamienDeepgram <138720050+DamienDeepgram@users.noreply.github.com>
Co-authored-by: Lachlan Donald <lachlan@ljd.cc>
@myselfhimself
Copy link

Now using the 3.3.0 version, with a unit test to ensure the experimental custom fetch parameter keeps working before releasing myself. Thank you @BartoszJarocki @lukeocodes !!

@lukeocodes
Copy link
Contributor

Thanks again!

I have now (now that I have some time) been looking at a long term solution for transmitter overrides (websocket and fetch), custom headers, namespaced config per product (transcription and speech intelligence can operate differently), etc, etc.

I have a good solution that is backwards compatible, and will maintain compatibility with the experimental property we included here.

Mind if I include you in the review of it when it is ready?

@myselfhimself
Copy link

Good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Increase in UND_ERR_HEADERS_TIMEOUT errors
5 participants