1+ import 'dart:convert' ;
2+ // ignore: uri_does_not_exist
3+ import 'dart:html' ;
4+
5+ import '../../parse_server_sdk.dart' ;
6+
7+ enum LiveQueryEvent { create, enter, update, leave, delete, error }
8+
9+ class LiveQuery {
10+ LiveQuery ({bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
11+ _client = client ??
12+ ParseHTTPClient (
13+ sendSessionId:
14+ autoSendSessionId ?? ParseCoreData ().autoSendSessionId,
15+ securityContext: ParseCoreData ().securityContext);
16+
17+ _debug = isDebugEnabled (objectLevelDebug: debug);
18+ _sendSessionId =
19+ autoSendSessionId ?? ParseCoreData ().autoSendSessionId ?? true ;
20+ }
21+
22+ WebSocket _webSocket;
23+ ParseHTTPClient _client;
24+ bool _debug;
25+ bool _sendSessionId;
26+ Map <String , dynamic > _connectMessage;
27+ Map <String , dynamic > _subscribeMessage;
28+ Map <String , dynamic > _unsubscribeMessage;
29+ Map <String , Function > eventCallbacks = < String , Function > {};
30+ int _requestIdCount = 1 ;
31+ final List <String > _liveQueryEvent = < String > [
32+ 'create' ,
33+ 'enter' ,
34+ 'update' ,
35+ 'leave' ,
36+ 'delete' ,
37+ 'error'
38+ ];
39+ final String _printConstLiveQuery = 'LiveQuery: ' ;
40+
41+ int _requestIdGenerator () {
42+ return _requestIdCount++ ;
43+ }
44+
45+ // ignore: always_specify_types
46+ Future subscribe (QueryBuilder query) async {
47+ String _liveQueryURL = _client.data.liveQueryURL;
48+ if (_liveQueryURL.contains ('https' )) {
49+ _liveQueryURL = _liveQueryURL.replaceAll ('https' , 'wss' );
50+ } else if (_liveQueryURL.contains ('http' )) {
51+ _liveQueryURL = _liveQueryURL.replaceAll ('http' , 'ws' );
52+ }
53+
54+ final String _className = query.object.parseClassName;
55+ final List <String > keysToReturn = query.limiters['keys' ]? .split (',' );
56+ query.limiters.clear (); //Remove limits in LiveQuery
57+ final String _where = query.buildQuery ().replaceAll ('where=' , '' );
58+
59+ //Convert where condition to Map
60+ Map <String , dynamic > _whereMap = Map <String , dynamic >();
61+ if (_where != '' ) {
62+ _whereMap = json.decode (_where);
63+ }
64+
65+ final int requestId = _requestIdGenerator ();
66+
67+ try {
68+ _webSocket = new WebSocket (_liveQueryURL);
69+ await _webSocket.onOpen.first;
70+
71+ if (_webSocket != null && _webSocket.readyState == WebSocket .OPEN ) {
72+ if (_debug) {
73+ print ('$_printConstLiveQuery : Socket opened' );
74+ }
75+ } else {
76+ if (_debug) {
77+ print ('$_printConstLiveQuery : Error when connection client' );
78+ return Future <void >.value (null );
79+ }
80+ }
81+
82+
83+ _webSocket.onMessage.listen ((MessageEvent e) {
84+ final dynamic message = e.data;
85+ if (_debug) {
86+ print ('$_printConstLiveQuery : Listen: $message ' );
87+ }
88+
89+ final Map <String , dynamic > actionData = jsonDecode (message);
90+
91+ if (eventCallbacks.containsKey (actionData['op' ])) {
92+ if (actionData.containsKey ('object' )) {
93+ final Map <String , dynamic > map = actionData['object' ];
94+ final String className = map['className' ];
95+ eventCallbacks[actionData['op' ]](
96+ ParseObject (className).fromJson (map));
97+ } else {
98+ eventCallbacks[actionData['op' ]](actionData);
99+ }
100+ }
101+ }, onDone: () {
102+ if (_debug) {
103+ print ('$_printConstLiveQuery : Done' );
104+ }
105+ }, onError: (Object error) {
106+ if (_debug) {
107+ print (
108+ '$_printConstLiveQuery : Error: ${error .runtimeType .toString ()}' );
109+ }
110+ return Future <ParseResponse >.value (handleException (
111+ Exception (error), ParseApiRQ .liveQuery, _debug, _className));
112+ });
113+
114+ //The connect message is sent from a client to the LiveQuery server.
115+ //It should be the first message sent from a client after the WebSocket connection is established.
116+ _connectMessage = < String , String > {
117+ 'op' : 'connect' ,
118+ 'applicationId' : _client.data.applicationId
119+ };
120+ if (_sendSessionId) {
121+ _connectMessage['sessionToken' ] = _client.data.sessionId;
122+ }
123+
124+ if (_client.data.clientKey != null )
125+ _connectMessage['clientKey' ] = _client.data.clientKey;
126+ if (_client.data.masterKey != null )
127+ _connectMessage['masterKey' ] = _client.data.masterKey;
128+
129+ if (_debug) {
130+ print ('$_printConstLiveQuery : ConnectMessage: $_connectMessage ' );
131+ }
132+ _webSocket.sendString (jsonEncode (_connectMessage));
133+
134+ //After a client connects to the LiveQuery server,
135+ //it can send a subscribe message to subscribe a ParseQuery.
136+ _subscribeMessage = < String , dynamic > {
137+ 'op' : 'subscribe' ,
138+ 'requestId' : requestId,
139+ 'query' : < String , dynamic > {
140+ 'className' : _className,
141+ 'where' : _whereMap,
142+ if (keysToReturn != null && keysToReturn.isNotEmpty)
143+ 'fields' : keysToReturn
144+ }
145+ };
146+ if (_sendSessionId) {
147+ _subscribeMessage['sessionToken' ] = _client.data.sessionId;
148+ }
149+
150+ if (_debug) {
151+ print ('$_printConstLiveQuery : SubscribeMessage: $_subscribeMessage ' );
152+ }
153+
154+ _webSocket.sendString (jsonEncode (_subscribeMessage));
155+
156+ //Mount message for Unsubscribe
157+ _unsubscribeMessage = < String , dynamic > {
158+ 'op' : 'unsubscribe' ,
159+ 'requestId' : requestId,
160+ };
161+ } on Exception catch (e) {
162+ if (_debug) {
163+ print ('$_printConstLiveQuery : Error: ${e .toString ()}' );
164+ }
165+ return handleException (e, ParseApiRQ .liveQuery, _debug, _className);
166+ }
167+ }
168+
169+ void on (LiveQueryEvent op, Function callback) {
170+ eventCallbacks[_liveQueryEvent[op.index]] = callback;
171+ }
172+
173+ Future <void > unSubscribe () async {
174+ if (_webSocket != null && _webSocket.readyState == WebSocket .OPEN ) {
175+ _webSocket.sendString (jsonEncode (_unsubscribeMessage));
176+ if (_debug) {
177+ print ('$_printConstLiveQuery : Socket closed' );
178+ }
179+ await _webSocket.close ();
180+ }
181+ }
182+ }
0 commit comments