@@ -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,35 +15,46 @@ 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;
28
+ bool _creatingSocket = false;
24
29
25
30
Future<dynamic > _closeConnection() async {
26
- await _websok?.sink.close(normalClosure);
31
+ await _websocketSubscription?.cancel();
32
+ await _websok?.sink.close(status.normalClosure, 'Ending session');
27
33
_lastUrl = null;
28
34
}
29
35
30
36
_createSocket() async {
37
+ if(_creatingSocket || _channels.isEmpty) return;
38
+ _creatingSocket = true;
31
39
final uri = _prepareUri();
32
40
if (_websok == null) {
33
41
_websok = await getWebSocket(uri);
34
42
_lastUrl = uri.toString();
35
43
} else {
36
44
if (_lastUrl == uri.toString() && _websok?.closeCode == null) {
45
+ _creatingSocket = false;
37
46
return;
38
47
}
48
+ _notifyDone = false;
39
49
await _closeConnection();
40
50
_lastUrl = uri.toString();
41
51
_websok = await getWebSocket(uri);
52
+ _notifyDone = true;
42
53
}
43
54
debugPrint('subscription: $_lastUrl');
44
55
45
56
try {
46
- _websok?.stream.listen((response) {
57
+ _websocketSubscription = _websok?.stream.listen((response) {
47
58
final data = RealtimeResponse.fromJson(response);
48
59
switch (data.type) {
49
60
case 'error':
@@ -67,28 +78,25 @@ mixin RealtimeMixin {
67
78
break;
68
79
case 'event':
69
80
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);
81
+ for (var subscription in _subscriptions.values ) {
82
+ for (var channel in message.channels ) {
83
+ if (subscription.channels.contains( channel) ) {
84
+ subscription.controller .add(message);
74
85
}
75
86
}
76
87
}
77
88
break;
78
89
}
79
90
}, onDone: () {
80
- for (var list in _channels.values) {
81
- for (var stream in list) {
82
- stream.close();
83
- }
91
+ if (!_notifyDone || _creatingSocket) return;
92
+ for (var subscription in _subscriptions.values) {
93
+ subscription.close();
84
94
}
85
95
_channels.clear();
86
96
_closeConnection();
87
97
}, onError: (err, stack) {
88
- for (var list in _channels.values) {
89
- for (var stream in list) {
90
- stream.sink.addError(err, stack);
91
- }
98
+ for (var subscription in _subscriptions.values) {
99
+ subscription.controller.addError(err, stack);
92
100
}
93
101
if (_websok?.closeCode != null && _websok?.closeCode != 1008) {
94
102
debugPrint("Reconnecting in one second.");
@@ -103,6 +111,8 @@ mixin RealtimeMixin {
103
111
throw {{spec .title | caseUcfirst }}Exception(e.message);
104
112
}
105
113
throw {{spec .title | caseUcfirst }}Exception(e.toString());
114
+ } finally {
115
+ _creatingSocket = false;
106
116
}
107
117
}
108
118
@@ -118,43 +128,49 @@ mixin RealtimeMixin {
118
128
port: uri.port,
119
129
queryParameters: {
120
130
"project": client.config['project'],
121
- "channels[]": _channels.keys. toList(),
131
+ "channels[]": _channels.toList(),
122
132
},
123
133
path: uri.path + "/realtime",
124
134
);
125
135
}
126
136
127
137
RealtimeSubscription subscribeTo(List<String > channels) {
128
138
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
- }
139
+ _channels.addAll(channels);
135
140
Future.delayed(Duration.zero, () => _createSocket());
141
+ int id = DateTime.now().microsecondsSinceEpoch;
136
142
RealtimeSubscription subscription = RealtimeSubscription(
137
- stream: controller.stream,
143
+ controller: controller,
144
+ channels: channels,
138
145
close: () async {
146
+ _subscriptions.remove(id);
147
+ _subscriptionsCounter--;
139
148
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) {
149
+ _cleanup(channels);
150
+
151
+ if (_channels.isNotEmpty) {
147
152
await Future.delayed(Duration.zero, () => _createSocket());
148
153
} else {
149
154
await _closeConnection();
150
155
}
151
156
});
157
+ _subscriptions[id] = subscription;
152
158
return subscription;
153
159
}
154
160
161
+ void _cleanup(List<String > channels) {
162
+ for (var channel in channels) {
163
+ bool found = _subscriptions.values
164
+ .any((subscription) => subscription.channels.contains(channel));
165
+ if (!found) {
166
+ _channels.remove(channel);
167
+ }
168
+ }
169
+ }
170
+
155
171
void handleError(RealtimeResponse response) {
156
172
if (response.data['code'] == 1008) {
157
- throw {{ spec . title | caseUcfirst }}Exception (response.data["message"], response.data["code"]);
173
+ throw AppwriteException (response.data["message"], response.data["code"]);
158
174
} else {
159
175
debugPrint("Reconnecting in one second.");
160
176
Future.delayed(const Duration(seconds: 1), () {
0 commit comments