supabase / supabase-js

An isomorphic Javascript client for Supabase. Query your Supabase database, subscribe to realtime events, upload and download files, browse typescript examples, invoke postgres functions via rpc, invoke supabase edge functions, query pgvector.
https://supabase.com
MIT License
2.86k stars 220 forks source link

"XMLHttpRequest is not defined" on sveltekit endpoints when deployed on Vercel #154

Closed Pixselve closed 2 years ago

Pixselve commented 3 years ago

Bug report

Describe the bug

Using the client for auth or for querying the database in a sveltekit endpoint throw an error :

{
  data: null,
  error: ReferenceError: XMLHttpRequest is not defined
      at file:///var/task/server/app.mjs:2522:21
      at new Promise (<anonymous>)
      at fetch2 (file:///var/task/server/app.mjs:2517:16)
      at file:///var/task/server/app.mjs:2646:7
      at new Promise (<anonymous>)
      at file:///var/task/server/app.mjs:2645:12
      at Generator.next (<anonymous>)
      at file:///var/task/server/app.mjs:2619:67
      at new Promise (<anonymous>)
      at __awaiter$8 (file:///var/task/server/app.mjs:2601:10)
}

To Reproduce

I made a repository exposing the issue : https://github.com/Pixselve/supabase-sveltekit-endpoints Click on a button to fetch the endpoint and observe the cloud functions logs on Vercel.

Expected behavior

Requests should not fail and should give the appropriate data.

System information

kiwicopple commented 3 years ago

Thanks for reporting this one @Pixselve - i'll transfer it to the JS library repo

stupidawesome commented 3 years ago

Related? vitejs/vite/pull/2996

kiwicopple commented 3 years ago

Could be - we already had to do a bunch of work to make Vite work:

https://github.com/supabase/supabase-js/issues/89 https://github.com/supabase/supabase-js/issues/155

Is this still causing problems on the latest supabase-js?

stupidawesome commented 3 years ago

I've been trying to get this working over the weekend, my package.json is:

{
    "devDependencies": {
        "@commitlint/cli": "^12.1.1",
        "@commitlint/config-conventional": "^12.1.1",
        "@sveltejs/adapter-node": "next",
        "@sveltejs/adapter-vercel": "^1.0.0-next.10",
        "@sveltejs/kit": "next",
        "autoprefixer": "^10.2.5",
        "cssnano": "^5.0.0",
        "husky": "^6.0.0",
        "openapi-typescript": "^3.2.3",
        "postcss": "^8.2.10",
        "postcss-load-config": "^3.0.1",
        "prettier": "~2.2.1",
        "prettier-plugin-svelte": "^2.2.0",
        "pretty-quick": "^3.1.0",
        "svelte": "^3.29.0",
        "svelte-preprocess": "^4.7.0",
        "tailwindcss": "^2.1.1",
        "tslib": "^2.0.0",
        "typescript": "^4.0.0",
        "vite": "^2.1.0"
    },
    "type": "module",
    "dependencies": {
        "@supabase/supabase-js": "^1.11.6"
    },
}

With the following svelte.config.cjs

module.exports = {
    preprocess: [
        sveltePreprocess({
            defaults: {
                style: "postcss",
            },
            postcss: true,
        }),
    ],
    kit: {
        adapter: vercel(),
        target: "#svelte",
        vite: {
            ssr: {
                noExternal: process.env.NODE_ENV === 'production' ? Object.keys(pkg.dependencies || {}) : []
            },
        },
    },
};

Deploying this configuration to vercel serverless produces the "XMLHTTPRequest is not defined" error in production. In dev mode it works fine, but if you build and run yarn start you can see the error.

Edit: updated config to correctly reproduce error.

In the generated code you can clearly see the browserPonyfill being inlined instead of node-fetch.

From server/app.mjs image image

stupidawesome commented 3 years ago

I note that the PR mentioned above has already been merged. I tried building Vite locally and it doesn't appear to make a difference.

i-infra commented 3 years ago

Strongly suspect this is https://github.com/lquixada/cross-fetch/issues/78 "Cross-fetch is not usable in service workers" in disguise.

kiwicopple commented 3 years ago

In that case, you could try this: https://github.com/supabase/supabase/tree/master/examples/with-cloudflare-workers

i-infra commented 3 years ago
async function supabaseInsert (table, arg) {
 return fetch(`${supabaseUrl}/rest/v1/${table}`, {
  headers: {
    Apikey: supabaseKey,
    Authorization: `Bearer ${supabaseKey}`,
    "Content-Type": "application/json",
    Prefer: "return=representation"
  },
  method: "POST",
 body: JSON.stringify(arg)
})
}

async function supabaseFrom (table, filter) {
return fetch(`${supabaseUrl}/rest/v1/${table}?${filter}`, {
  headers: {
    Apikey: supabaseKey,
    Authorization: `Bearer ${supabaseKey}`,
  }
})
}

got it done after an hour or two of npm misadventures.

In any case, appreciate the prompt response @kiwicopple ! Supabase is working marvelously and took exactly one evening to actually get working. The exploration also introduced me to postgrest, which seems a phenomenal building block for supabase's product! Cheers!

stupidawesome commented 3 years ago

I think I've gotten to the bottom of this.

  1. Serverless deployments require bundling of node_modules. That's why you need noExternal to include all node dependencies otherwise you get errors during deployment. This causes dev mode to break, so only add node deps to noExternal when process.env.NODE_ENV = "production.

  2. Vite creates two bundles, a server bundle "functions/node/render/server/app.mjs" and a client bundle "static/_app/*". The problem is that it only reads dependencies once:

https://github.com/vitejs/vite/blob/344d77e9735bc907b9383ad729afb0a8daa2af5f/packages/vite/src/node/plugins/resolve.ts#L466

First off, cross-fetch always resolves to the browser entry point. The way the entry point is resolved does not take into account whether the bundler is in SSR mode or not.

  1. But even if it did resolve correctly, Vite only does one pass over each dependency and then caches it. You can't have the same package resolve to a different entry point for SSR unless you remove resolvedImports['.'] or flush it between each phase.

So my hacky workaround at the moment is to force disable the browser entry point during SSR module generation, and to disable module caching.

image

This will need to be fixed by Vite.

jcs224 commented 3 years ago

Hi all, I think this issue can be fixed all the way at the core dependency, even deeper than cross-fetch, but might require more input and attention to actually push it through. It could fix not just this problem, but a lot of other tangential issues as well.

https://github.com/github/fetch/pull/956

kiwicopple commented 3 years ago

Hey @jcs224 - it looks like the linked issue was resolved. Does it also solve this issue?

Pixselve commented 3 years ago

Just tried using the same code in the repository when I started the issue (I only updated svelte kit) and it now works perfectly. Thanks to you all!

jcs224 commented 3 years ago

@Pixselve interesting it worked for you.. cross-fetch hasn't yet updated the dependency needed with the underlying changes. :thinking: Maybe Vite made some adjustment that made it work?

@kiwicopple I think the change is going to have to propagate through cross-fetch before it will do us any good. Maybe I'll bump this again on the cross-fetch side.

Nick-Mazuk commented 2 years ago

It looks like FaunaDB solved this. Essentially, they added an extra config argument that allows users to opt-out of cross-fetch by passing in a custom fetch function.

Issue: https://github.com/fauna/faunadb-js/issues/207 PR: https://github.com/fauna/faunadb-js/pull/214

So a potential solution for this is:

import { createClient } from '@supabase/supabase-js'

export const supabase = createClient(
    import.meta.env.VITE_SUPABASE_URL,
    import.meta.env.VITE_SUPABASE_KEY,
    {
        fetch: import.meta.env.USE_NATIVE_FETCH ? fetch : undefined,
    }
)

If no fetch is provided, cross-fetch is used. @kiwicopple, would this be straightforward to implement?

Fedeorlandau commented 2 years ago

Hi there, I believe this is why I'm getting this error with svelte kit and supabase:

Screen Shot 2021-09-18 at 14 05 29

__layout.svelte

<script lang="ts">
    import { user } from '../store/sessionStore';
    import { supabase } from '$lib/supabaseClient';
    import { browser } from '$app/env';

    user.set(supabase.auth.user());

    if (browser) {
        supabase.auth.onAuthStateChange((event, session) => {
            console.log('updating with', session);
            user.set(session!.user);
        });
    }
</script>

<slot />

Hope to get a fix soon. Thank you all for your hard work.

jacobwgillespie commented 2 years ago

I've opened several PRs that allow specifying a custom fetch implementation as an option - ideally I think cross-fetch should support environments like Cloudflare Workers given its use in the ecosystem, but a custom fetch option enables a workaround today, gives flexibility for future environments (e.g. ones that don't yet exist or are more esoteric), and allows you to provide a mocked fetch for testing or otherwise customize fetch based on your specific needs.

import { createClient } from '@supabase/supabase-js'

const supabase = createClient('url', 'key', { fetch: fetch })

The main @supabase/supabase-js library wraps other client libraries, so each needs a PR to enable the option.

Wrapped Clients

supabase-js (depends on the other three PRs)

TomasHubelbauer commented 2 years ago

FYI this also happens when supabase-js is used within NextJS 12 middleware. Since the PRs above aren't merged yet, I am not sure there's a solution other than using the REST API directly over the SDK.

kiwicopple commented 2 years ago

Thanks to a very impressive series of PRs from @jacobwgillespie , this now has a workaround

https://github.com/supabase/supabase-js/releases/tag/v1.27.0

after upgrading to v1.27.0 you can use a custom fetch implementation

import { createClient } from '@supabase/supabase-js'

// Provide a custom `fetch` implementation as an option
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', { fetch: fetch })

Jacob - if you send your details (and tshirt size) to swag@supabase.io we will send you a nice package 🙏

PH4NTOMiki commented 2 years ago

Hello @kiwicopple it's not working for me, i do it createClient('..','..',{fetch:fetch}) but it still doesn't work, Cloudflare workers shows Invalid invocation

jacobwgillespie commented 2 years ago

@PH4NTOMiki that's probably unclear documentation on my part, I believe if you directly pass the native fetch function as the custom fetch, JavaScript will treat this as an "illegal invocation" as it tries to execute a native method in the context of the Supabase options argument rather than in the global context it was attached to.

You can pass a custom function or bind the fetch function to work around that issue. For Cloudflare Workers specifically, there is no global or window object like you'd have in other runtimes, but you do have self, so:

const supabase = createClient('...', '...', {fetch: fetch.bind(self)})

Really the example I added to the README should have probably been { fetch: customFetch } to avoid confusion, I can look at opening a few more PRs. 🙂

sbutler-gh commented 2 years ago

Hello @kiwicopple it's not working for me, i do it createClient('..','..',{fetch:fetch}) but it still doesn't work, Cloudflare workers shows Invalid invocation

I'm getting a similar issue — adding const supabase = createClient('...', '...', {fetch: fetch.bind(self)}) still results in the XMLHTTPrequest error when deployed on Cloudflare pages.

@jacobwgillespie @kiwicopple can you provide a code example of what that customFetch function should be and how it should be included in an endpoint?

In my case, here is the original call I'm making

    async function submitForm(e) {
        var formData = new FormData(e.target);

        formData.append('loaded_address', loaded_address);
        formData.append('address', address);

        const response = await fetch(`./submitform`, {
      method: 'post',
      body: formData
    })
        response.ok ? ( form_submit = "success" ) : (form_submit = "error" )

    }

And here is the SvelteKit endpoint it hits, which makes the Supabase request and produces the FetchError: XMLHttpRequest is not defined error:

    // import supabase from "$lib/db"
import { variables } from '$lib/variables';

import { createClient } from '@supabase/supabase-js'

const supabase = createClient( import.meta.env.VITE_SUPABASE_URL,
    import.meta.env.VITE_SUPABASE_ANON_KEY, { fetch: fetch })

export async function post(request) {

const {data, error} = await supabase
.from('support_responses')
.insert({support_info: request.body.get('support_info'), introduction: request.body.get('introduction'), contact: request.body.get('contact'), loaded_location: request.body.get('loaded_address'), searched_location: request.body.get('address')})

if (error) {
return {
    status: 500,
    body: error
  }
}
else {

return {
    status: 200,
    body: {
      data
    }
  }
}

}
jacobwgillespie commented 2 years ago

@sbutler-gh I believe you want this when creating the client in Cloudflare Workers:

const supabase = createClient( import.meta.env.VITE_SUPABASE_URL,
    import.meta.env.VITE_SUPABASE_ANON_KEY, { fetch: fetch.bind(self) })

Note the fetch: fetch.bind(self). It would probably also work with fetch: (...args) => fetch(...args).

sbutler-gh commented 2 years ago

@sbutler-gh I believe you want this when creating the client in Cloudflare Workers:

const supabase = createClient( import.meta.env.VITE_SUPABASE_URL,
    import.meta.env.VITE_SUPABASE_ANON_KEY, { fetch: fetch.bind(self) })

Note the fetch: fetch.bind(self). It would probably also work with fetch: (...args) => fetch(...args).

Huge thanks for the help on this @jacobwgillespie . When I try fetch: fetch.bind(self), I get the following error locally and when building for production:

Error when evaluating SSR module /Users/sam/just-start/src/routes/submitform.js: ReferenceError: self is not defined at /Users/sam/just-start/src/routes/submitform.js:7:65 at async instantiateModule (/Users/sam/just-start/node_modules/vite/dist/node/chunks/dep-f5e099f1.js:66443:9) self is not defined ReferenceError: self is not defined at /Users/sam/just-start/src/routes/submitform.js:7:65 at async instantiateModule (/Users/sam/just-start/node_modules/vite/dist/node/chunks/dep-f5e099f1.js:66443:9)

When I try fetch: (...args) => fetch(...args), it works in development and when building locally, but results in the same XMLHTTPrequest error when deployed on CF. Any other thoughts?

jacobwgillespie commented 2 years ago

@sbutler-gh I think your repo isn't actually using the up-to-date version of @supabase/supabase-js, see here:

https://github.com/sbutler-gh/just-start/blob/c32ecef9d35bfbcb67f187f6725b66986a288b0a/package-lock.json#L325-L326

It appears it's using version 1.24.0, and the ability to pass in a custom fetch implementation was added in 1.27.0 (and 1.28.1 is the latest version).

sbutler-gh commented 2 years ago

@sbutler-gh I think your repo isn't actually using the up-to-date version of @supabase/supabase-js, see here:

sbutler-gh/just-start@c32ecef/package-lock.json#L325-L326

It appears it's using version 1.24.0, and the ability to pass in a custom fetch implementation was added in 1.27.0 (and 1.28.1 is the latest version).

After running npm update to update packages and re-deploying, it now works as expected in production on Cloudflare Pages! (using fetch: (...args) => fetch(...args), didn't try the other syntaxes yet.). Thank you SO MUCH @jacobwgillespie ! I can't thank you enough!

TomasHubelbauer commented 2 years ago

For Next users wondering if this (v1.27+) allows Supabase client use in the middleware API - yes:

import { createClient } from '@supabase/supabase-js';
import { NextRequest, NextResponse } from 'next/server';

export async function middleware(request: NextRequest) {
  const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { fetch });
  …

This works great and there is not even need to rebind fetch using fetch.bind(self) or (...args) => fetch(...args)!

Crenshinibon commented 1 year ago

Is there probably a regression in the 2.0 branch? I have trouble getting the server side code to work. Tried the new {global: {fetch: ...}} config option. But that doesn't seem to work. Even using Node 18 didn't help.

It's a vite/sveltekit project.

require is not defined
ReferenceError: require is not defined
    at [...]/node_modules/.pnpm/cross-fetch@3.1.5/node_modules/cross-fetch/dist/node-ponyfill.js:1:32
    at instantiateModule (file:///[...]/node_modules/.pnpm/vite@3.1.8/node_modules/vite/dist/node/chunks/dep-4da11a5e.js:53445:15)