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

Realtime #76

Closed
barbinbrad opened this issue Sep 13, 2023 · 3 comments
Closed

Realtime #76

barbinbrad opened this issue Sep 13, 2023 · 3 comments

Comments

@barbinbrad
Copy link

I was having trouble getting realtime to work with RLS. I was able to get it working like this:

const getSupabaseClient = (supabaseKey: string, accessToken?: string) => {
  const global = accessToken
    ? {
        global: {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        },
      }
    : {};

  const client = createClient<Database>(SUPABASE_API_URL, supabaseKey, {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
    },
    ...global,
  });

  if (accessToken) {
    client.realtime.accessToken = accessToken;
    client.realtime.headers = {
      Authorization: `Bearer ${accessToken}`,
    };
  }

  return client;
};

Without this, the realtime claims in the realtime.subscriptions table were equivalent to a user with "role":"anon"

@barbinbrad
Copy link
Author

There might be a better way to do this, so I wanted to hold off on an MR. But fundamentally, this is what is needed to get realtime + RLS working.

@rphlmr
Copy link
Owner

rphlmr commented Sep 13, 2023

Hello, yes you are right.

Support for realtime in this repo has been removed (it is on a branch, outdated: https://github.com/rphlmr/supa-fly-stack/blob/feat/add-supabase-client-provider/app/integrations/supabase/provider.tsx).

I have plans to make improvements but time is missing to work on it right now 🫣

On production, I use an other strategy with a Context provider giving access to accessToken:

//provider

const AuthContext = createContext<{
    accessToken: string | undefined;
}>({ accessToken: undefined });

// in root.tsx, wrap <Outlet /> with <AuthProvider authSession={authSession}> to use access token in supabase'browser client
export const AuthProvider = ({
    children,
    authSession,
}: {
    children: ReactElement;
    authSession: Partial<AuthSession>;
}) => {
    // what root loader data returns
    const { accessToken, expiresIn } = authSession;
    const refresh = useFetcher();

    // trigger a refresh session at expire time. We'll send a post request to our resource route /refresh-session
    useInterval(() => {
        // ask to refresh only if expiresIn is defined
        // prevents trying to refresh when user is not logged in
        if (expiresIn)
            refresh.submit(null, {
                method: "post",
                action: "/refresh-session",
                encType: "application/x-www-form-urlencoded",
            });
    }, expiresIn);

    const value = useMemo(() => ({ accessToken }), [accessToken]);

    return (
        <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
    );
};

export const useAuth = () => {
    const context = useContext(AuthContext);

    if (isBrowser && context === undefined) {
        throw new Error(`useAuth must be used within a AuthProvider.`);
    }

    return context;
};
//route: refresh-session

export async function action({ request }: ActionArgs) {
    const authSession = await refreshAuthSession(request);

    return response.ok(
        { success: true },
        {
            authSession,
        }
    );
}
// in root.tsx
// authSession comes from root loader function
 <AuthProvider authSession={authSession}>
      <Outlet />
 </AuthProvider>
//usage

function useWatchLiveEvents() {
    const { accessToken } = useAuth();
    const revalidate = useRevalidator().revalidate;

    useEffect(() => {
        if (!accessToken) return;

        supabaseClient.realtime.setAuth(accessToken);

        const channel = supabaseClient
            .channel(`db-changes`)
            .on(
                "postgres_changes",
                {
                    event: "DELETE",
                    schema: "public",
                    table: "transaction",
                },
                () => {
                    revalidate();
                }
            )
            .subscribe();

        return () => {
            supabaseClient.removeChannel(channel);
        };
    }, [accessToken, revalidate]);
}

@barbinbrad
Copy link
Author

Ahh that's nice! Thanks for the tip. Closing this.

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

2 participants