如何在Flutter中使用Pusher的私人频道



我使用pusher_channels_flutter包来获取实时通知。

我想在Flutter中使用Pusher的私人频道。

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

但我得到了这个错误:

日志:错误:PlatformException(错误,由于未设置授权程序,无法订阅专用或呈现频道。在连接到Pusher之前调用PusherOptions.setAuthorizer((,null,java.lang.IollegalStateException:由于未设置授权程序,无法预订专用或呈现通道。在连接到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"
};
}

但我得到了这个错误:

LOG:onError:订阅身份验证数据中的密钥无效:"token"代码:null异常:null

我应该在onAuthorizer映射的值中放入什么3和CCD_ 4以及CCD_?

我的完整代码:

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)),
]),
),
),
);
}
}

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

若其他人遇到同样的问题,我会把我的代码粘贴到这里。

这些代码对我来说非常有效,请检查您的用户tokenonAuthorizer中使用的CCD_7

初始化initState 的推进器

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

以及随时可用的

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

这是_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");
}
}

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;
}

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

这些是的其余功能


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");
}

我用它来解决别人的问题。

用以下代码替换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")}",
};
}

p.S:使用https://pub.dev/packages/crypto访问Hmac方法

参见参考资料:https://pusher.com/docs/channels/library_auth_reference/auth-signatures/

您使用的onAuthorizer函数就是一个例子,您需要替换为生成必要的auth令牌的函数(如https://pusher.com/docs/channels/library_auth_reference/auth-signatures/),

例如:

dynamic onAuthorizer(String channelName, String socketId, dynamic options) {

var response = Api().post('auth', {
"socket_id": socketId,
"channel_name": channelName,
});
var data = null;
response.then((response){
data = response.data;

return jsonDecode(data); // {"auth":"<redacted>:<redacted>"}
});   }

我也被困了很长一段时间,但我设法订阅了私人频道并向它们触发了事件

以下是我的做法。

  1. 在example/main.dart中的pusher.init((中取消注释onauthorizer函数。(来自本示例项目(

  2. 对端点进行api调用,返回authorizer令牌。响应必须是json格式的:{"身份验证":"密钥:密钥"}有关身份验证端点和响应的更多信息

  3. 您必须解码步骤2中的密钥,并将其返回到您在第一步中注释掉的onAuthorizer方法。

  4. 您现在可以订阅并触发推送私人频道的活动!但请记住,您必须添加";PRIVATE-";,在CHANNELNAME之前,请记住您必须添加">客户端-";,在事件名称之前所以channelname";公司";,你想订阅的必须是";私人公司;并且事件";位置";,将变成";客户端位置";,例如,如果你想推送flutters应用程序用户的当前位置。

该方法看起来像这样:

dynamic onAuthorizer(String channelName, String socketId, dynamic options) {
var pusherAuthKey = http.post(
Uri.parse('YOUR-AUTHENTICATION-ENDPOINT'),
headers: {
'Authorization': '<YOUR-BEARER-TOKEN>'
},
body: 'socket_id=$socketId&channel_name=$channelName',
);
var data = null;
var authorizer = pusherAuthKey.then((pusherAuthKey) {
data = pusherAuthKey.body;
_authResponse = data;
return jsonDecode(data);
});
return authorizer;
}

最新更新