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

window is not defined error when creating supabase client in nodejs server side #786

Open Vichet97 opened 11 months ago

Vichet97 commented 11 months ago

Bug report

Describe the bug

I try to create client in nodejs but always get window is not defined error as attached below. Then when i try to debug the source code through stack trace, it turned on that isBrowser() return a True value. But when i manually set it to false, it will work.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. create a fastify​ project
  2. import and createClient(host, key, {db: {schema: abc}​}
  3. run the server with node index.js
  4. Got error as attached

Screenshots

This is the error IMG_20230606_170256_892

Then i debug and got True value when printing isBrowser() which it should be False. I manually change code here then it works.

IMG_20230606_170300_006

System information

j4w8n commented 11 months ago

I commented on a Discord post for this same thing. Maybe it was yours.

In Discord I made the comment that this checks document now, not window. But I made the mistake of thinking it was gotrue-js code. I just looked in supabase-js and they have the same helper function, but still use window.

This likely can be changed to match gotrue-js code, but I'll let the experts decide that.

Vichet97 commented 11 months ago

Yes, that's mine. But posting here will be more visible to other people who have the same issue. So when will it be fixed ? Else can you show me where to change this, i can make a temporary fork

j4w8n commented 11 months ago

I'm not sure when or even if it'll be fixed because the maintainers need to decide if it's something that should be fixed.

Code is https://github.com/supabase/supabase-js/blob/master/src/lib/helpers.ts#L16

Vichet97 commented 11 months ago

Why is there a question whether it should be fixed when it just doesn't work and not usable?

silentworks commented 11 months ago

I've been unable to replicate this issue. I've tried in the same version of Node you are using (which is no longer supported by Node themselves btw) and I still didn't get any error. The code I'm using is below.

const { createClient } = require("@supabase/supabase-js");

const fastify = require("fastify")({ logger: true });

const supabase = createClient(
  "http://localhost:54321",  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0",
  { db: { schema: "abc" } }
);

fastify.get("/", async (request, reply) => {
  const { data } = await supabase.from("countries").select("*");
  return { hello: "world", countries: data };
});

// Run the server!
const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();
j4w8n commented 11 months ago

During my nice insomnia overnight, I went back to the screenshot and realized this is referencing gotrue-js code, not supabase-js.

silentworks commented 11 months ago

@j4w8n but the issue mentions the latest @supabase/supabase-js which is what I tested and this also makes use of the gotrue code underneath. Were you able to replicate this by any chance?

j4w8n commented 11 months ago

@j4w8n but the issue mentions the latest @supabase/supabase-js which is what I tested and this also makes use of the gotrue code underneath. Were you able to replicate this by any chance?

I haven't had a chance to try it yet. Might in a few hours.

Vichet97 commented 11 months ago

It's from gotrue-js. But I also have many other dependencies installed like firestore, etc. Or maybe the document variable is just used for other purposes but not in my code. It might have made the document != undefined since gotrue only check that variable to identify browser. It's not the right way to check. But surely, that document is not the document in browser.

silentworks commented 11 months ago

Please provide a minimal reproducible repository for us to test as we aren't able to replicate the issue you are having here. Also you should be testing this as a standalone project and not a mix with other libraries to be sure where the issue is from. Please upgrade your Node version too as 14 is no longer supported and has gone pass its EOL.

Vichet97 commented 11 months ago

I found the issue. It will show error if you declare const a = { document } = { document: true }​ in another js file then require('other.js')​ in the same js file where you call that Supabase createClient.

However, it doesn't have any problem if you just declare const a = { document } = { document: true }​ in the same file

j4w8n commented 11 months ago

I found the issue. It will show error if you declare const a = { document } = { document: true }​ in another js file then require('other.js')​ in the same js file where you call that Supabase createClient.

However, it doesn't have any problem if you just declare const a = { document } = { document: true }​ in the same file

@Vichet97 out of curiosity, what's your use case for this?

Vichet97 commented 11 months ago

It's a property in Firestore and MassiveJS. So I declared some default properties with default values to setup structure in my project. It was not a plain property with only document field. There are other fields as well but it was just an example

Vichet97 commented 11 months ago

Anyway, that isBrowser function is not significant to detect whether it's a browser.

j4w8n commented 11 months ago

What do you think would work better?

Vichet97 commented 11 months ago

Can check typeof window == "object" then verify some of its default properties ?

j4w8n commented 11 months ago

There were issues with window, because it exists in React Native and was causing problems; so they changed it to document.

At this point I'm wondering if what happened to you is more of an edge case (I haven't seen/heard of any other tickets with a similar scenario), but maybe something like this? There's a precedent for similar code in the gotrue-js repo here

-export const isBrowser = () => typeof document !== 'undefined'
+export const isBrowser = () => typeof document !== 'undefined' && document.addEventListener
Vichet97 commented 11 months ago

Well, can use document but should do the same as i mentioned above like window

DanielSepulveda commented 7 months ago

I am also experiencing this issue. I am trying to use supabase inside an airplane task and I am getting this error:

Screenshot 2023-10-04 at 16 53 44

This is the code that I have:

import { createClient } from "@supabase/supabase-js";
import airplane from "airplane";

export default airplane.task(
  {
    slug: "list_users",
    name: "list_users",
  },
  async () => {
    const supabase = createClient(
      "redacted url",
      "redacted service key",
      {
        auth: {
          autoRefreshToken: false,
          persistSession: false,
        },
      },
    );

    const { data } = await supabase.auth.admin.listUsers();
    return data.users;
  },
);
schybo commented 7 months ago

@DanielSepulveda Experiencing a similar issue with an Airplane.dev task and wondering if it's how the process loads - it's interesting that the !isBrowser() doesn't short circuit ahead of the actual window check

julian-hecker commented 7 months ago

Also having this issue in react-native with the latest version of expo, following the user management expo supabase tutorial

clemwo commented 6 months ago

Hey, I'm having a similar issue I think. I'm using expo with supabase and instantiate the client like so:

export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

On iOS and Android that works fine but on web I get the following error:

ReferenceError: window is not defined
    at http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:192612:9
    at http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:192586:21
    at new Promise (<anonymous>)
    at createPromise (http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:192584:12)
    at Object.getItem (http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:192611:14)
    at http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:42084:33
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:1462:26)
    at _next (http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:1481:11)
    at http://localhost:8081/node_modules/expo-router/node/render.bundle//&platform=web&dev=true&minify=false&resolver.environment=node&transform.environment=node:1486:9

In react native community async storage they even have added web as a feature which looks like it would solve my issue: https://github.com/react-native-async-storage/async-storage/releases/tag/v1.9.0

But react native community async storage is deprecated so Im wondering whether this is expected to not work in the current async storage. Im a beginner with expo and supabase and all that stuff so I would appreciate some input :)

clemwo commented 6 months ago

I found a solution which should probably also work for the issue @julian-hecker is describing. I got help from a colleague and we found out that the issue is caused by localStorage being undefined during bundling with metro.

A workaround is to write a custom class that implements getItem, setItem & removeItem that simply returns null if localStorage is undefined (and returns AsyncStorage on mobile)

class SupabaseStorage {
  async getItem(key: string) {
    if (Platform.OS === "web") {
      if (typeof localStorage === "undefined") {
        return null;
      }
      return localStorage.getItem(key);
    }
    return AsyncStorage.getItem(key);
  }
  async removeItem(key: string) {
    if (Platform.OS === "web") {
      return localStorage.removeItem(key);
    }
    return AsyncStorage.removeItem(key);
  }
  async setItem(key: string, value: string) {
    if (Platform.OS === "web") {
      return localStorage.setItem(key, value);
    }
    return AsyncStorage.setItem(key, value);
  }
}

export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: new SupabaseStorage(),
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

We found a related issue here: https://github.com/expo/expo/issues/23895 And the fix in this commit https://github.com/expo/expo/commit/1277abdd1ecb21aa43b22a48282439c97b1c1798

Note: we haven't tested this for a long time but I just wanted to share this quick fix in case it helps

aydahealth commented 5 months ago

Also having this issue in react-native with the latest version of expo, following the user management expo supabase tutorial

I'm running into this same issue

glanikali commented 5 months ago

I found a solution which should probably also work for the issue @julian-hecker is describing. I got help from a colleague and we found out that the issue is caused by localStorage being undefined during bundling with metro.

A workaround is to write a custom class that implements getItem, setItem & removeItem that simply returns null if localStorage is undefined (and returns AsyncStorage on mobile)

class SupabaseStorage {
  async getItem(key: string) {
    if (Platform.OS === "web") {
      if (typeof localStorage === "undefined") {
        return null;
      }
      return localStorage.getItem(key);
    }
    return AsyncStorage.getItem(key);
  }
  async removeItem(key: string) {
    if (Platform.OS === "web") {
      return localStorage.removeItem(key);
    }
    return AsyncStorage.removeItem(key);
  }
  async setItem(key: string, value: string) {
    if (Platform.OS === "web") {
      return localStorage.setItem(key, value);
    }
    return AsyncStorage.setItem(key, value);
  }
}

export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: new SupabaseStorage(),
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

We found a related issue here: expo/expo#23895 And the fix in this commit expo/expo@1277abd

Note: we haven't tested this for a long time but I just wanted to share this quick fix in case it helps

This worked for me on web builds. thanks

indiyon commented 5 months ago

I found a solution which should probably also work for the issue @julian-hecker is describing. I got help from a colleague and we found out that the issue is caused by localStorage being undefined during bundling with metro.

....

Worked for me as well

PabloVallejo commented 4 months ago

@clemwo your solution worked for me! Thanks!

julian-hecker commented 4 months ago

Thanks all. The solution that worked for me is this one:

export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
    auth: {
        // https://github.com/supabase/supabase-js/issues/870
        ...(Platform.OS !== 'web' ? { storage: AsyncStorage } : {}),
        autoRefreshToken: true,
        persistSession: true,
        detectSessionInUrl: false,
    },
});

Only set storage if the platform isn't web. It defaults to localstorage if you just don't set it. Have a look at this bug https://github.com/supabase/supabase-js/issues/870

salmoro commented 3 months ago

All solutions here seem to be framework-specific workarounds but aren't addressing the issue at its core and for all users. isBrowser() should be a more elaborate check than just just typeof document !== 'undefined'.

Maybe even just typeof document !== 'undefined' && typeof window === 'object'?

artypineda commented 3 weeks ago

I LOVE YOU! Thanks this fixed the issue.

...(Platform.OS !== 'web' ? { storage: AsyncStorage } : {}),