You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter调用Supabase Edge函数删用户遇FormatException求助

调用Supabase Edge函数删除用户时触发FormatException异常

调用Supabase Edge函数删除用户时遭遇FormatException,错误信息如下:

FormatException: Unexpected character (at character 1) <!DOCTYPE html>

触发错误的Flutter代码片段

Future<void> handlingUserDeletion() async {
  try {
    await supabase.functions.invoke(
      "delete-user",
      headers: {
        'Authorization': 'Bearer ${supabase.auth.currentSession?.accessToken}'
      },
    );
    if (mounted) {
      supabase.auth.signOut();
    }
  } on PostgrestException catch (error) {
    debugPrint(error.message);
  } catch (e) {
    debugPrint(e.toString());
  }
}

Supabase Edge函数(delete-user)代码

import { serve } from 'https://deno.land/std@0.182.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.14.0';
import { corsHeaders } from '../_shared/cors.ts';

console.log("Delete user account function");

serve(async (request) => {

  // This is needed if you're planning to invoke your function from a browser.
  if (request.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    //Create instance of SupabaseClient
    const supabaseClient = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '',
      { global: { headers: { Authorization: request.headers.get('Authorization')! } } }
    );

    // Create a user object which contains the data we need to identify the user.id
    const {
      data: { user },
    } = await supabaseClient.auth.getUser()

    // Throw an error if there are any issues with identifying the users from the token
    if (!user) throw new Error('No user found for JWT!');
  
    // Create supabaseAdmin client which specifically uses the Service Role
    // Key in order to perform elavated administration actins
    const supabaseAdmin = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
    )

    // Call the deleteUser method on the supabaseAdmin client and pass the user.id
    const { data: deletion_data, error: deletion_error } = await supabaseAdmin.auth.admin.deleteUser(user.id, true);
    
    // Log deletion error so we can debug. Delete if not required! 
    // console.log(deletion_error);
    if (deletion_error) throw deletion_error;

    // Return a response of the user which has been deleted
    return new Response("User deleted: " + JSON.stringify(deletion_data, null, 2), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 200,
    });
  } catch (error) {
    // Return an error with the error message should it run in to any issues
    return new Response(JSON.stringify({ error: error.message }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 400,
    })
  }
});

cors.ts文件代码

export const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

版本信息

  • Flutter 3.10.6
  • supabase_flutter: ^1.8.1

异常原因分析

这个FormatException说明Flutter客户端接收到的响应不是合法的JSON格式,而是HTML内容。通常是Edge函数执行出错后,Supabase返回了默认的错误页面(HTML格式),而非函数本身定义的JSON错误响应。可能的触发点包括:

  1. Edge函数的成功响应格式不合法,返回了非纯JSON内容
  2. 环境变量未正确配置,导致Supabase客户端初始化失败
  3. 请求的Authorization令牌为空或无效,导致函数内部抛出未被正确处理的错误
  4. 函数内部的非空断言触发运行时错误,未进入catch块返回JSON响应

可行解决办法

  1. 修正Edge函数的响应格式
    成功响应中,不要在JSON前拼接字符串"User deleted: ",因为Content-Type设为application/json,必须返回纯JSON内容。修改成功响应代码:

    return new Response(JSON.stringify({ 
      message: "User deleted",
      data: deletion_data 
    }, null, 2), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 200,
    });
    
  2. 检查并确保环境变量正确配置
    在Supabase控制台的Edge函数设置中,确认SUPABASE_URL和SUPABASE_SERVICE_ROLE_KEY已正确添加,且值不为空。函数中可以添加空值检查,提前抛出明确错误:

    const supabaseUrl = Deno.env.get('SUPABASE_URL');
    const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
    if (!supabaseUrl || !serviceRoleKey) {
      throw new Error('Missing Supabase environment variables');
    }
    
  3. 处理Authorization头为空的情况

    • Flutter端:调用函数前先检查当前会话是否存在,避免发送空令牌:
      final session = supabase.auth.currentSession;
      if (session == null) {
        debugPrint('No active session');
        return;
      }
      await supabase.functions.invoke(
        "delete-user",
        headers: {
          'Authorization': 'Bearer ${session.accessToken}'
        },
      );
      
    • Edge函数端:移除非空断言,先检查Authorization头是否存在:
      const authHeader = request.headers.get('Authorization');
      if (!authHeader) {
        throw new Error('Authorization header missing');
      }
      const supabaseClient = createClient(
        supabaseUrl,
        serviceRoleKey,
        { global: { headers: { Authorization: authHeader } } }
      );
      
  4. 查看Edge函数执行日志
    登录Supabase控制台,进入对应的Edge函数页面,查看函数的执行日志,定位具体的错误点(比如获取用户失败、删除用户失败等)。

  5. 优化Flutter端的错误捕获
    可以在catch块中尝试获取响应的原始内容,进一步排查问题:

    catch (e) {
      debugPrint(e.toString());
      // 尝试获取函数返回的原始响应
      try {
        final response = await supabase.functions.invoke(
          "delete-user",
          headers: {
            'Authorization': 'Bearer ${supabase.auth.currentSession?.accessToken}'
          },
        );
        debugPrint('Raw response: ${response.data}');
      } catch (innerE) {
        debugPrint('Failed to get raw response: ${innerE.toString()}');
      }
    }
    

内容的提问来源于stack exchange,提问作者Sela404

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 18:02:48