如何在Flutter中使用Pusher的私有通道

Abd*_*her 0 notifications push-notification dart pusher flutter

我正在使用Pusher_channels_flutter来获取实时通知。

我想在Flutter中使用Pusher的Private通道。

await pusher.subscribe(channelName: "private-chat.5");

但我得到了这个错误:

日志:错误:PlatformException(错误,无法订阅私有或存在通道,因为尚未设置授权者。在连接到 Pusher 之前调用 PusherOptions.setAuthorizer(),null,java.lang.IllegalStateException:无法订阅私有或存在通道因为还没有设置Authorizer,在连接Pusher之前调用PusherOptions.setAuthorizer()

当我添加onAuthorizer功能时,我是这样添加的:

dynamic onAuthorizer(String channelName, String socketId, dynamic options) async {
    return {
     "auth": "foo:bar",
    "channel_data": '{"user_id": 1}',
    "shared_secret": "foobar"
    };
  }
Run Code Online (Sandbox Code Playgroud)

但我得到了这个错误:

日志:onError:订阅身份验证数据中的密钥无效:“令牌”代码:null 异常:null

auth我应该在 onAuthorizer 映射的和channel_data和键的值中放入什么shared_secret

我的完整代码:

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:shared_preferences/shared_preferences.dart';
import 'package:pusher_channels_flutter/pusher_channels_flutter.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  PusherChannelsFlutter pusher = PusherChannelsFlutter.getInstance();
  String _log = 'output:\n';
  final _apiKey = TextEditingController();
  final _cluster = TextEditingController();
  final _channelName = TextEditingController();
  final _eventName = TextEditingController();
  final _channelFormKey = GlobalKey<FormState>();
  final _eventFormKey = GlobalKey<FormState>();
  final _listViewController = ScrollController();
  final _data = TextEditingController();

  void log(String text) {
    print("LOG: $text");
    setState(() {
      _log += text + "\n";
      Timer(
          const Duration(milliseconds: 100),
          () => _listViewController
              .jumpTo(_listViewController.position.maxScrollExtent));
    });
  }

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

  void onConnectPressed() async {


    if (!_channelFormKey.currentState!.validate()) {
      return;
    }
    // Remove keyboard
    FocusScope.of(context).requestFocus(FocusNode());
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString("apiKey", "mykey"); //_apiKey.text);
    prefs.setString("cluster", "eu"); // _cluster.text);
    prefs.setString("channelName", "private-chat.5"); //_channelName.text);

    try {
      await pusher.init(
        apiKey: "mykey", //_apiKey.text,
        cluster: "eu", //_cluster.text,
        onConnectionStateChange: onConnectionStateChange,
        onError: onError,
        onSubscriptionSucceeded: onSubscriptionSucceeded,
        onEvent: onEvent,
        onSubscriptionError: onSubscriptionError,
        onDecryptionFailure: onDecryptionFailure,
        onMemberAdded: onMemberAdded,
        onMemberRemoved: onMemberRemoved,
        // authEndpoint: "<Your Authendpoint Url>",
        onAuthorizer: onAuthorizer,
      //   authParams: {
      // 'params': { 'foo': 'bar' },
      // 'headers': { 'X-CSRF-Token': 'SOME_CSRF_TOKEN' }
      // }
      );

      await pusher.subscribe(channelName:  "private-chat.5"); // _channelName.text,);
      await pusher.connect();
    } catch (e) {
      log("ERROR: $e");
    }
  }
  dynamic onAuthorizer(String channelName, String socketId, dynamic options) async {
    return {
     "auth": "foo:bar",
    "channel_data": '{"user_id": 1}',
    "shared_secret": "foobar"
    };
  }
   Future<void> pusherDiconnect() async {
    await pusher.unsubscribe(channelName:   "private-chat.5"); //_channelName.text,);
    await pusher.disconnect();

    print("pusherDiconnect");
  }

  void onConnectionStateChange(dynamic currentState, dynamic previousState) {
    log("Connection: $currentState");
  }

  void onError(String message, int? code, dynamic e) {
    log("onError: $message code: $code exception: $e");
  }

  void onEvent(PusherEvent event) {
    log("onEvent: $event");
  }

  void onSubscriptionSucceeded(String channelName, dynamic data) {
    log("onSubscriptionSucceeded: $channelName data: $data");
    final me = pusher.getChannel(channelName)?.me;
    log("Me: $me");
  }

  void onSubscriptionError(String message, dynamic e) {
    log("onSubscriptionError: $message Exception: $e");
  }

  void onDecryptionFailure(String event, String reason) {
    log("onDecryptionFailure: $event reason: $reason");
  }

  void onMemberAdded(String channelName, PusherMember member) {
    log("onMemberAdded: $channelName user: $member");
  }

  void onMemberRemoved(String channelName, PusherMember member) {
    log("onMemberRemoved: $channelName user: $member");
  }


  void onTriggerEventPressed() async {
    var eventFormValidated = _eventFormKey.currentState!.validate();

    if (!eventFormValidated) {
      return;
    }
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString("eventName", _eventName.text);
    prefs.setString("data", _data.text);
    pusher.trigger(PusherEvent(
        channelName: _channelName.text,
        eventName: _eventName.text,
        data: _data.text));
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      _apiKey.text = prefs.getString("apiKey") ?? '';
      _cluster.text = prefs.getString("cluster") ?? 'eu';
      _channelName.text = prefs.getString("channelName") ?? 'my-channel';
      _eventName.text = prefs.getString("eventName") ?? 'client-event';
      _data.text = prefs.getString("data") ?? 'test';
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(pusher.connectionState == 'DISCONNECTED'
              ? 'Pusher Channels Example'
              : _channelName.text),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: ListView(
              controller: _listViewController,
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              children: <Widget>[
                if (pusher.connectionState != 'CONNECTED')
                  Form(
                      key: _channelFormKey,
                      child: Column(children: <Widget>[
                        TextFormField(
                          controller: _apiKey,
                          validator: (String? value) {
                            return (value != null && value.isEmpty)
                                ? 'Please enter your API key.'
                                : null;
                          },
                          decoration:
                              const InputDecoration(labelText: 'API Key'),
                        ),
                        TextFormField(
                          controller: _cluster,
                          validator: (String? value) {
                            return (value != null && value.isEmpty)
                                ? 'Please enter your cluster.'
                                : null;
                          },
                          decoration: const InputDecoration(
                            labelText: 'Cluster',
                          ),
                        ),
                        TextFormField(
                          controller: _channelName,
                          validator: (String? value) {
                            return (value != null && value.isEmpty)
                                ? 'Please enter your channel name.'
                                : null;
                          },
                          decoration: const InputDecoration(
                            labelText: 'Channel',
                          ),
                        ),
                        ElevatedButton(
                          onPressed: onConnectPressed,
                          child: const Text('Connect'),
                        )
                      ]))
                else
                  Form(
                    key: _eventFormKey,
                    child: Column(children: <Widget>[
                      ListView.builder(
                          scrollDirection: Axis.vertical,
                          shrinkWrap: true,
                          itemCount: pusher
                              .channels[_channelName.text]?.members.length,
                          itemBuilder: (context, index) {
                            final member = pusher
                                .channels[_channelName.text]!.members.values
                                .elementAt(index);

                            return ListTile(
                                title: Text(member.userInfo.toString()),
                                subtitle: Text(member.userId));
                          }),
                      TextFormField(
                        controller: _eventName,
                        validator: (String? value) {
                          return (value != null && value.isEmpty)
                              ? 'Please enter your event name.'
                              : null;
                        },
                        decoration: const InputDecoration(
                          labelText: 'Event',
                        ),
                      ),
                      TextFormField(
                        controller: _data,
                        decoration: const InputDecoration(
                          labelText: 'Data',
                        ),
                      ),
                      ElevatedButton(
                        onPressed: onTriggerEventPressed,
                        child: const Text('Trigger Event'),
                      ),
                      ElevatedButton(
                        onPressed: (){
                          pusherDiconnect();
                        },
                        child: const Text('pusher Diconnect'),
                      ),
                    ]),
                  ),
                SingleChildScrollView(
                    scrollDirection: Axis.vertical, child: Text(_log)),
              ]),
        ),
      ),
    );
  }
}


Run Code Online (Sandbox Code Playgroud)

小智 5

将 onAuthorizer 回调替换为以下代码:

 getSignature(String value) {
    var key = utf8.encode('<your-pusher-app-secret>');
    var bytes = utf8.encode(value);

    var hmacSha256 = Hmac(sha256, key); // HMAC-SHA256
    var digest = hmacSha256.convert(bytes);
    print("HMAC signature in string is: $digest");
    return digest;
  }

dynamic onAuthorizer(String channelName, String socketId, dynamic options) {
    
    return {
      "auth": "<your-pusher-key>:${getSignature("$socketId:$channelName")}",
    };
  }
Run Code Online (Sandbox Code Playgroud)

PS:使用https://pub.dev/packages/crypto来访问Hmac方法

请参阅参考:https ://pusher.com/docs/channels/library_auth_reference/auth-signatures/


Zia*_*Zia 5

我在使用订阅私人频道时遇到问题pusher_channels_flutter

如果其他人遇到同样的问题,我将在这里粘贴我的代码。

这些代码非常适合我,请检查您token使用的用户onAuthorizer

在 initState 初始化推送器

  @override
  void initState() {
    super.initState();
    _initPusher();
  }
Run Code Online (Sandbox Code Playgroud)

并可处置

  @override
  void dispose() {
    _pusher.disconnect();
    _pusher.unsubscribe(channelName: "private-notification.${_user!.id}");
    super.dispose();
  }
Run Code Online (Sandbox Code Playgroud)

这是_initPusher()函数


  Future<void> _initPusher() async {
    _user = Provider.of<ProfileProvider>(context, listen: false).getUser;
    _pusher = PusherChannelsFlutter.getInstance();
    try {
      await _pusher.init(
        apiKey: pusherKey,
        cluster: "eu",
        onConnectionStateChange: onConnectionStateChange,
        onError: onError,
        onSubscriptionSucceeded: onSubscriptionSucceeded,
        onEvent: onEvent,
        onSubscriptionError: onSubscriptionError,
        onDecryptionFailure: onDecryptionFailure,
        onMemberAdded: onMemberAdded,
        onMemberRemoved: onMemberRemoved,
        //authEndpoint: "https://my-website.com/broadcasting/auth",
        onAuthorizer: onAuthorizer,
      );
      await _pusher.subscribe(channelName: "private-notification.${_user!.id}");
      await _pusher.connect();
    } catch (e) {
      print("error in initialization: $e");
    }
  }
Run Code Online (Sandbox Code Playgroud)

onAuthorizer()不知道如何管理参数,但它工作得很好。

这是onAuthorizer()函数

  dynamic onAuthorizer(
      String channelName, String socketId, dynamic options) async {
    String token = await Store.read("token");
    var authUrl = "$basePath/broadcasting/auth";
    var result = await http.post(
      Uri.parse(authUrl),
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ${token}',
      },
      body: 'socket_id=' + socketId + '&channel_name=' + channelName,
    );
    var json = jsonDecode(result.body);
    return json;
  }
Run Code Online (Sandbox Code Playgroud)

对于authUrl我的后端来说,Laravel如果有人在后端使用 laravel,则broadcasting/auth在输入 时必须有一个名为的路由php artisan route:list。否则修复后端的问题。

这些是其余的功能


  void onError(String message, int? code, dynamic e) {
    print("onError: $message code: $code exception: $e");
  }

  void onConnectionStateChange(dynamic currentState, dynamic previousState) {
    print("Connection: $currentState");
  }

  void onMemberRemoved(String channelName, PusherMember member) {
    print("onMemberRemoved: $channelName member: $member");
  }

  void onMemberAdded(String channelName, PusherMember member) {
    print("onMemberAdded: $channelName member: $member");
  }

  void onSubscriptionSucceeded(String channelName, dynamic data) {
    print("onSubscriptionSucceeded: $channelName data: $data");
  }

  void onSubscriptionError(String message, dynamic e) {
    print("onSubscriptionError: $message Exception: $e");
  }

  void onEvent(PusherEvent event) {
    print("onEvent: $event");
    Provider.of<NotificationProvider>(context, listen: false)
        .setNotificationCount(1);
  }

  void onDecryptionFailure(String event, String reason) {
    print("onDecryptionFailure: $event reason: $reason");
  }

Run Code Online (Sandbox Code Playgroud)

我希望它能解决某人的问题。