typescript 在具有www.example.com身份验证的Next.js应用程序中使用Supabase和Zustand时,出现“No storage option exists to persist the session”错误Clerk.dev?

nzrxty8p  于 2023-06-30  发布在  TypeScript
关注(0)|答案(1)|浏览(137)

我已经设置了一个Next.js应用程序,其身份验证由www.example.com处理Clerk.dev,数据存储由Supabase管理。我也在使用Zustand进行状态管理。但是,我遇到了一个错误,显示“不存在存储选项来持久化会话,这可能会导致在使用auth时出现意外行为。”

"@clerk/nextjs": "^4.16.4",
    "next": "^12.0.9",
    "react": "17.0.2",
    "zustand": "^4.3.8"

下面是我的代码的简化版本:

文件:utils\supabase.ts

import { createClient, SupabaseClient } from '@supabase/supabase-js';

const supabaseUrl =  process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_KEY;

export const supabase: SupabaseClient = createClient(supabaseUrl, supabaseKey);

文件:store\useUserStore.ts

import { create } from 'zustand';
import { supabase } from '../utils/supabase';

type User = {
    id: string;
    email: string;
    custom_api: string;
    bot_type: string;
    discord_id: string;
};

type UserStore = {
    userData: User | null;
    status: 'idle' | 'loading' | 'error';
    error: string | null;
    fetchUser: (userId: string) => Promise<void>;
    createUser: (userId: string, user: Partial<User>) => Promise<void>;
    updateUser: (userId: string, updates: Partial<User>) => Promise<void>;
    deleteUser: (userId: string) => Promise<void>;
};

export const useUserStore = create<UserStore>((set) => ({
    userData: null,
    status: 'idle',
    error: null,
    fetchUser: async (userId) => {
        try {
            set({ status: 'loading' });
            const { data, error } = await supabase
            .from('users')
            .select()
            .eq('id', userId)
            if (error) {
                throw new Error('Error fetching user');
            }
            set({ userData: data![0] ?? null, status: 'idle', error: null });
        } catch (error) {
            set({ status: 'error', error: error.message });
        }
    },
    createUser: async (userId, user) => {
       // code here
    },
    updateUser: async (userId, updates) => {
        // code here
    },
    deleteUser: async (userId) => {
        // code here
    },
}));

错误消息指出不存在用于保持会话的存储选项,这可能导致在使用身份验证时出现意外行为。我想启用persistSession选项,但不确定如何继续。
对于其他上下文,我使用Clerk.dev进行身份验证,并使用JWT将其连接到Supabase。我还为SELECT操作设置了一个行级安全性(Row-LevelSecurity,RLS)策略,它针对所有(公共)角色并使用WITHCHECK表达式。
我将非常感谢有关如何解决此错误并正确配置会话存储以与www.example.com,Supabase和Zustand无缝工作的任何指导或建议Clerk.dev。谢谢你!
当时,我的代码结构如下:

import { createClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/nextjs'
/// .....
const supabaseClient = async supabaseAccessToken => {
    const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.NEXT_PUBLIC_SUPABASE_KEY,
        {
            global: { headers: { Authorization: `Bearer ${supabaseAccessToken}` } }
        }
    )
    return supabase
}
/// .....
export default function Messages() {
//...
const { session } = useSession()
//...
const supabaseAccessToken = await session.getToken({
    template: 'Supabase'
})
const supabase = await supabaseClient(supabaseAccessToken)
const { data } = await supabase
    .from('user')
    .insert(
        {
            id: session.user.id,
            email: session.user?.primaryEmailAddress?.emailAddress,
        }
    )
    .select()
setUserSettings(data[0])
//...

这段代码看起来很乱,因为我是直接在.tsx文件中执行的。我想使用状态管理(Zustand)来改进代码结构并坚持良好的实践。

k7fdbhmy

k7fdbhmy1#

我能够在不使用本地存储的情况下实现这一点,这是Discord社区中其他人推荐的。
起初,我不得不寻找一个替代方案。因此,我所做的是将会话管理纳入状态管理。在客户端组件中,下面是我的代码:

import {
  useSession,
  //....
} from '@clerk/nextjs'
import React, { useState, useEffect } from 'react';

interface ContentsProps {
  //...
}

const Contents = (props: ContentsProps) => {

  const { session } = useSession()
  const { userData, status, error, fetchUser } = useUserStore();
  //...
  useEffect(() => {
    if (session) {
      fetchUser(session.user.id, session);
    }
  }, []);

  return (
  //...
  );
};

export default Contents;

因此,我不再拥有./utils/supabase.ts file。我直接把它插入或包含在商店里。下面是我的./store/useUserStore.ts文件:

import { create } from 'zustand';
import { createClient } from '@supabase/supabase-js'

const supabaseClient = async supabaseAccessToken => {
    const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.NEXT_PUBLIC_SUPABASE_KEY,
        {
            global: { headers: { Authorization: `Bearer ${supabaseAccessToken}` } }
        }
    )
    return supabase
}

type User = {
    id: string;
    email: string;
    custom_api: string;
    bot_type: string;
    discord_id: string;
};

type UserStore = {
    userData: User | null;
    status: 'idle' | 'loading' | 'error';
    error: string | null;
    fetchUser: (userId: string, session: any) => Promise<void>;
    //....
};

export const useUserStore = create<UserStore>((set) => ({
    userData: null,
    status: 'idle',
    error: null,
    fetchUser: async (userId, session) => {
        try {
        set({ status: 'loading' });
        const supabaseAccessToken = await session.getToken({
            template: 'Supabase'
        })
        const supabase = await supabaseClient(supabaseAccessToken)
        const { data, error } = await supabase
            .from('users')
            .select()
            .eq('id', userId)
            console.log(data)
        if (error) {
            console.error(error)
            throw new Error('Error fetching user');
        }
        set({ userData: data![0] ?? null, status: 'idle', error: null });
        } catch (error) {
        set({ status: 'error', error: error.message });
        }
    },
    //....
}));

我希望这个增强的解释能够澄清我所做的更改,并提供对代码的更清晰的理解。

相关问题