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

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

Closed
Pixselve opened this issue Apr 12, 2021 · 27 comments
Closed

Comments

@Pixselve
Copy link

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

  • OS: Vercel Node.js Runtime
  • Version of supabase-js: 1.10.0
  • Version of Node.js: 14
@Pixselve Pixselve changed the title "XMLHttpRequest is not defined" on sveletkit endpoints when deployed on Vercel "XMLHttpRequest is not defined" on sveltekit endpoints when deployed on Vercel Apr 12, 2021
@kiwicopple
Copy link
Member

kiwicopple commented Apr 14, 2021

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

@kiwicopple kiwicopple transferred this issue from supabase/supabase Apr 14, 2021
@stupidawesome
Copy link

Related? vitejs/vite/pull/2996

@kiwicopple
Copy link
Member

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

#89
#155

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

@stupidawesome
Copy link

stupidawesome commented Apr 18, 2021

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
Copy link

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
Copy link

i-infra commented Apr 18, 2021

Strongly suspect this is lquixada/cross-fetch#78 "Cross-fetch is not usable in service workers" in disguise.

@kiwicopple
Copy link
Member

@i-infra
Copy link

i-infra commented Apr 18, 2021

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
Copy link

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
Copy link

jcs224 commented Apr 23, 2021

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.

JakeChampion/fetch#956

@kiwicopple
Copy link
Member

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

@Pixselve
Copy link
Author

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
Copy link

jcs224 commented May 16, 2021

@Pixselve interesting it worked for you.. cross-fetch hasn't yet updated the dependency needed with the underlying changes. 🤔 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
Copy link

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: fauna/faunadb-js#207
PR: fauna/faunadb-js#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
Copy link

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
Copy link
Contributor

jacobwgillespie commented Nov 6, 2021

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
Copy link

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
Copy link
Member

kiwicopple commented Nov 8, 2021

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 [email protected] we will send you a nice package 🙏

@PH4NTOMiki
Copy link

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
Copy link
Contributor

@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
Copy link

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
Copy link
Contributor

@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
Copy link

@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
Copy link
Contributor

@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
Copy link

@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
Copy link

TomasHubelbauer commented Jan 5, 2022

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
Copy link

Crenshinibon commented Oct 21, 2022

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/[email protected]/node_modules/cross-fetch/dist/node-ponyfill.js:1:32
    at instantiateModule (file:///[...]/node_modules/.pnpm/[email protected]/node_modules/vite/dist/node/chunks/dep-4da11a5e.js:53445:15)

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

No branches or pull requests