@@ -2,7 +2,7 @@ import 'dart:async';
2
2
import 'dart:convert';
3
3
import 'package:flutter/foundation.dart';
4
4
import 'package:web_socket_channel/web_socket_channel.dart';
5
- import 'package:web_socket_channel/status.dart';
5
+ import 'package:web_socket_channel/status.dart' as status ;
6
6
import 'exception.dart';
7
7
import 'realtime_subscription.dart';
8
8
import 'client.dart';
@@ -15,15 +15,20 @@ typedef GetFallbackCookie = String? Function();
15
15
16
16
mixin RealtimeMixin {
17
17
late Client client;
18
- final Map <String , List < StreamController < RealtimeMessage >> > _channels = {};
18
+ final Set <String > _channels = {};
19
19
WebSocketChannel? _websok;
20
20
String? _lastUrl;
21
21
late WebSocketFactory getWebSocket;
22
22
GetFallbackCookie? getFallbackCookie;
23
23
int? get closeCode => _websok?.closeCode;
24
+ int _subscriptionsCounter = 0;
25
+ Map<int , RealtimeSubscription > _subscriptions = {};
26
+ bool _notifyDone = true;
27
+ StreamSubscription? _websocketSubscription;
24
28
25
29
Future<dynamic > _closeConnection() async {
26
- await _websok?.sink.close(normalClosure);
30
+ await _websocketSubscription?.cancel();
31
+ await _websok?.sink.close(status.normalClosure, 'Ending session');
27
32
_lastUrl = null;
28
33
}
29
34
@@ -36,14 +41,16 @@ mixin RealtimeMixin {
36
41
if (_lastUrl == uri.toString() && _websok?.closeCode == null) {
37
42
return;
38
43
}
44
+ _notifyDone = false;
39
45
await _closeConnection();
40
46
_lastUrl = uri.toString();
41
47
_websok = await getWebSocket(uri);
48
+ _notifyDone = true;
42
49
}
43
50
debugPrint('subscription: $_lastUrl');
44
51
45
52
try {
46
- _websok?.stream.listen((response) {
53
+ _websocketSubscription = _websok?.stream.listen((response) {
47
54
final data = RealtimeResponse.fromJson(response);
48
55
switch (data.type) {
49
56
case 'error':
@@ -67,48 +74,45 @@ mixin RealtimeMixin {
67
74
break;
68
75
case 'event':
69
76
final message = RealtimeMessage.fromMap(data.data);
70
- for(var channel in message.channels ) {
71
- if (_channels[ channel] != null ) {
72
- for( var stream in _channels[ channel]! ) {
73
- stream.sink .add(message);
77
+ for (var subscription in _subscriptions.values ) {
78
+ for (var channel in message.channels ) {
79
+ if (subscription.channels.contains( channel) ) {
80
+ subscription.controller .add(message);
74
81
}
75
82
}
76
83
}
77
84
break;
78
85
}
79
86
}, onDone: () {
80
- for (var list in _channels.values) {
81
- for (var stream in list) {
82
- stream.close();
83
- }
87
+ if (!_notifyDone) return;
88
+ for (var subscription in _subscriptions.values) {
89
+ subscription.close();
84
90
}
85
91
_channels.clear();
86
92
_closeConnection();
87
93
}, onError: (err, stack) {
88
- for (var list in _channels.values) {
89
- for (var stream in list) {
90
- stream.sink.addError(err, stack);
91
- }
94
+ for (var subscription in _subscriptions.values) {
95
+ subscription.controller.addError(err, stack);
92
96
}
93
97
if (_websok?.closeCode != null && _websok?.closeCode != 1008) {
94
98
debugPrint("Reconnecting in one second.");
95
99
Future.delayed(Duration(seconds: 1), _createSocket);
96
100
}
97
101
});
98
102
} catch (e) {
99
- if (e is {{ spec . title | caseUcfirst }}Exception ) {
103
+ if (e is AppwriteException ) {
100
104
rethrow;
101
105
}
102
106
if (e is WebSocketChannelException) {
103
- throw {{ spec . title | caseUcfirst }}Exception (e.message);
107
+ throw AppwriteException (e.message);
104
108
}
105
- throw {{ spec . title | caseUcfirst }}Exception (e.toString());
109
+ throw AppwriteException (e.toString());
106
110
}
107
111
}
108
112
109
113
Uri _prepareUri() {
110
114
if (client.endPointRealtime == null) {
111
- throw {{ spec . title | caseUcfirst }}Exception (
115
+ throw AppwriteException (
112
116
"Please set endPointRealtime to connect to realtime server");
113
117
}
114
118
var uri = Uri.parse(client.endPointRealtime!);
@@ -118,43 +122,49 @@ mixin RealtimeMixin {
118
122
port: uri.port,
119
123
queryParameters: {
120
124
"project": client.config['project'],
121
- "channels[]": _channels.keys. toList(),
125
+ "channels[]": _channels.toList(),
122
126
},
123
127
path: uri.path + "/realtime",
124
128
);
125
129
}
126
130
127
131
RealtimeSubscription subscribeTo(List<String > channels) {
128
132
StreamController<RealtimeMessage > controller = StreamController.broadcast();
129
- for(var channel in channels) {
130
- if (!_channels.containsKey(channel)) {
131
- _channels[channel] = [];
132
- }
133
- _channels[channel]!.add(controller);
134
- }
133
+ _channels.addAll(channels);
135
134
Future.delayed(Duration.zero, () => _createSocket());
135
+ int counter = _subscriptionsCounter++;
136
136
RealtimeSubscription subscription = RealtimeSubscription(
137
- stream: controller.stream,
137
+ controller: controller,
138
+ channels: channels,
138
139
close: () async {
140
+ _subscriptions.remove(counter);
141
+ _subscriptionsCounter--;
139
142
controller.close();
140
- for(var channel in channels) {
141
- _channels[channel]!.remove(controller);
142
- if (_channels[channel]!.isEmpty) {
143
- _channels.remove(channel);
144
- }
145
- }
146
- if(_channels.isNotEmpty) {
143
+ _cleanup(channels);
144
+
145
+ if (_channels.isNotEmpty) {
147
146
await Future.delayed(Duration.zero, () => _createSocket());
148
147
} else {
149
148
await _closeConnection();
150
149
}
151
150
});
151
+ _subscriptions[counter] = subscription;
152
152
return subscription;
153
153
}
154
154
155
+ void _cleanup(List<String > channels) {
156
+ for (var channel in channels) {
157
+ bool found = _subscriptions.values
158
+ .any((subscription) => subscription.channels.contains(channel));
159
+ if (!found) {
160
+ _channels.remove(channel);
161
+ }
162
+ }
163
+ }
164
+
155
165
void handleError(RealtimeResponse response) {
156
166
if (response.data['code'] == 1008) {
157
- throw {{ spec . title | caseUcfirst }}Exception (response.data["message"], response.data["code"]);
167
+ throw AppwriteException (response.data["message"], response.data["code"]);
158
168
} else {
159
169
debugPrint("Reconnecting in one second.");
160
170
Future.delayed(const Duration(seconds: 1), () {
0 commit comments