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

Flutter集成GetStream偶发1000未授权token未定义报错排查

GetStream Flutter 偶发 StreamChatNetworkError(code: 1000, message: Unauthorised, token not defined) 报错修复

问题现象

  • 偶发报错,复现路径不固定,调试难度高,错误信息为 StreamChatNetworkError(code: 1000. message: Unauthorised, token not defined)
  • 临时解决方案为登出后重新登录,强制重连GetStream服务重新获取token,但该方案严重影响用户体验
  • 报错固定在打开双人聊天消息页时触发
  • 业务逻辑要求用户必须先在首页完成Stream连接流程才能进入消息页,理论上打开消息页时不会出现token未授权问题

相关代码片段

main.dart 客户端初始化代码

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  final client = StreamChatClient(
      STREAM_API_KEY,
    logLevel: Level.OFF,
  );
  runApp(MyApp(
    client: client,
  ));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key, required this.client}) : super(key: key);

  final StreamChatClient client;

  @override
  Widget build(BuildContext context) {
    return Provider<BaseAuth>(
      create: (context) => Auth(),
      child: MaterialApp(
        title: 'App',
        theme: ThemeData(
          primarySwatch: Colors.indigo,
        ),
        home: const SplashscreenWidget(),
        debugShowCheckedModeBanner: false,
        builder: (context, widget) => StreamChat(
          client: client,
          child: widget!,
        ),
      ),
    );
  }
}

首页Stream连接逻辑代码

void initState() {
    super.initState();
    // 仅从注册/登录页进入时才连接用户
    if(widget.fromSignUpOrLogin) {
      connectUser = _connectUser();
    }
  }

_connectUser() async {
  final client = StreamChatCore.of(context).client;
  FireStoreDatabase db = FireStoreDatabase(uid: FirebaseAuth.instance.currentUser!.uid);
  AppUser appUser = await db.readUser();

  final user = su.User(id: uid,
      extraData: {
    "name": appUser.username,
    "image": appUser.photoUrl,
      }
  );

  await client.connectUser(
      user,
      appUser.streamToken
  );
}

双人聊天消息页代码

late Channel globalChannel;
    
initMessageSystem() async {
  globalChannel = widget.client.channel('messaging', id: widget.channelId);
  await globalChannel.watch();
}

@override
void initState() {
  super.initState();
  initMessageSystem();
}

@override
Widget build(BuildContext context) {
  return StreamChat(
    client: widget.client,
    child: StreamChannel(
      child: _buildBody(),
      channel: globalChannel,
    ),
    streamChatThemeData: StreamChatThemeData(
      colorTheme: StreamColorTheme.light(
        accentPrimary: Colors.purpleAccent,
        textHighEmphasis: Colors.purpleAccent,
      ),
      ownMessageTheme: const StreamMessageThemeData(
        messageBackgroundColor: Colors.purpleAccent,
        messageTextStyle: TextStyle(
          color: Colors.white
        )
      )
    )
  );
}

解决方案

问题根因

  1. 首页连接逻辑仅靠fromSignUpOrLogin标记触发,冷启动APP、应用从后台恢复、websocket意外断开、token过期等场景下不会自动重连,此时进入消息页直接调用channel.watch()就会触发未授权错误
  2. 消息页重复嵌套StreamChat根组件,会重置客户端上下文状态,偶发导致上层已建立的连接状态丢失,是触发偶现报错的核心原因
  3. 缺少连接状态前置校验和异常重试逻辑,没有对token失效场景做兜底处理

1. 客户端连接状态校验方法

GetStream Dart客户端内置了连接状态判断属性,无需自行维护标记位:

// 校验当前是否存在有效用户连接
bool isValidConnected = client.state.currentUser != null && client.wsConnectionStatus == ConnectionStatus.connected;
  • client.state.currentUser:非空代表已执行过connectUser流程,用户身份信息已写入客户端
  • client.wsConnectionStatus:连接状态枚举,ConnectionStatus.connected代表与Stream服务的websocket连接正常,connecting代表正在连接,disconnected代表连接断开

2. 具体修复步骤

  • 第一步:移除消息页build方法中嵌套的StreamChat根组件,全局仅保留main.dart中MaterialApp builder内的StreamChat实例即可。页面级主题配置可以通过StreamChatTheme组件单独传入,重复包裹根组件会生成新的状态上下文,导致无法读取上层已建立的连接信息。
  • 第二步:修改首页连接逻辑,不再仅靠fromSignUpOrLogin标记判断是否需要连接,每次进入首页都先校验连接状态,未连接则自动执行连接流程:
@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
    final client = StreamChatCore.of(context).client;
    if (client.state.currentUser == null || client.wsConnectionStatus != ConnectionStatus.connected) {
      await _connectUser();
    }
  });
}
  • 第三步:在消息页initMessageSystem方法中增加前置校验和异常捕获,避免直接调用watch触发未授权错误:
initMessageSystem() async {
  final client = widget.client;
  // 前置校验连接状态,无效则先重连
  if (client.state.currentUser == null || client.wsConnectionStatus != ConnectionStatus.connected) {
    FireStoreDatabase db = FireStoreDatabase(uid: FirebaseAuth.instance.currentUser!.uid);
    AppUser appUser = await db.readUser();
    final user = su.User(
      id: FirebaseAuth.instance.currentUser!.uid,
      extraData: {
        "name": appUser.username,
        "image": appUser.photoUrl,
      }
    );
    await client.connectUser(user, appUser.streamToken);
  }
  globalChannel = client.channel('messaging', id: widget.channelId);
  // 增加异常重试逻辑
  try {
    await globalChannel.watch();
  } on StreamChatNetworkError catch (e) {
    if (e.code == 1000) {
      // token失效场景,拉取新token后重试一次
      final newToken = await 你封装的获取Stream用户Token方法();
      await client.connectUser(client.state.currentUser!, newToken);
      await globalChannel.watch();
    } else {
      rethrow;
    }
  }
}

3. 额外优化建议

  • 可配置全局token过期监听,统一处理token刷新逻辑,无需在每个页面单独实现:
client.on(EventType.tokenExpired).listen((event) async {
  // 拉取新token后调用client.connectUser更新连接即可
});
  • 调试阶段不要将logLevel设置为Level.OFF,调整为Level.INFO可以从日志中看到websocket断开、token失效的具体时机,大幅降低偶现问题的定位成本。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:01:09