Skip to content

Add LiveQuery support for Flutter Web #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/parse_server_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
import 'package:web_socket_channel/io.dart';
import 'package:xxtea/xxtea.dart';

export 'src/network/parse_live_query.dart'
if (dart.library.js) 'src/network/parse_live_query_web.dart';

part 'package:parse_server_sdk/src/objects/response/parse_error_response.dart';

part 'package:parse_server_sdk/src/objects/response/parse_exception_response.dart';
Expand All @@ -46,8 +48,6 @@ part 'src/enums/parse_enum_api_rq.dart';

part 'src/network/parse_http_client.dart';

part 'src/network/parse_live_query.dart';

part 'src/network/parse_query.dart';

part 'src/objects/parse_acl.dart';
Expand Down
13 changes: 9 additions & 4 deletions lib/src/network/parse_live_query.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
part of flutter_parse_sdk;
import 'dart:convert';
import 'dart:io';

import 'package:web_socket_channel/io.dart';

import '../../parse_server_sdk.dart';

enum LiveQueryEvent { create, enter, update, leave, delete, error }

Expand Down Expand Up @@ -51,7 +56,7 @@ class LiveQuery {
final String _className = query.object.parseClassName;
final List<String> keysToReturn = query.limiters['keys']?.split(',');
query.limiters.clear(); //Remove limits in LiveQuery
final String _where = query._buildQuery().replaceAll('where=', '');
final String _where = query.buildQuery().replaceAll('where=', '');

//Convert where condition to Map
Map<String, dynamic> _whereMap = Map<String, dynamic>();
Expand Down Expand Up @@ -89,7 +94,7 @@ class LiveQuery {
final String className = map['className'];
if (className == '_User') {
eventCallbacks[actionData['op']](
ParseUser._getEmptyUser().fromJson(map));
ParseUser(null, null, null).fromJson(map));
} else {
eventCallbacks[actionData['op']](
ParseObject(className).fromJson(map));
Expand Down Expand Up @@ -188,4 +193,4 @@ class LiveQuery {
await _webSocket.close();
}
}
}
}
182 changes: 182 additions & 0 deletions lib/src/network/parse_live_query_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import 'dart:convert';
// ignore: uri_does_not_exist
import 'dart:html';

import '../../parse_server_sdk.dart';

enum LiveQueryEvent { create, enter, update, leave, delete, error }

class LiveQuery {
LiveQuery({bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
_client = client ??
ParseHTTPClient(
sendSessionId:
autoSendSessionId ?? ParseCoreData().autoSendSessionId,
securityContext: ParseCoreData().securityContext);

_debug = isDebugEnabled(objectLevelDebug: debug);
_sendSessionId =
autoSendSessionId ?? ParseCoreData().autoSendSessionId ?? true;
}

WebSocket _webSocket;
ParseHTTPClient _client;
bool _debug;
bool _sendSessionId;
Map<String, dynamic> _connectMessage;
Map<String, dynamic> _subscribeMessage;
Map<String, dynamic> _unsubscribeMessage;
Map<String, Function> eventCallbacks = <String, Function>{};
int _requestIdCount = 1;
final List<String> _liveQueryEvent = <String>[
'create',
'enter',
'update',
'leave',
'delete',
'error'
];
final String _printConstLiveQuery = 'LiveQuery: ';

int _requestIdGenerator() {
return _requestIdCount++;
}

// ignore: always_specify_types
Future subscribe(QueryBuilder query) async {
String _liveQueryURL = _client.data.liveQueryURL;
if (_liveQueryURL.contains('https')) {
_liveQueryURL = _liveQueryURL.replaceAll('https', 'wss');
} else if (_liveQueryURL.contains('http')) {
_liveQueryURL = _liveQueryURL.replaceAll('http', 'ws');
}

final String _className = query.object.parseClassName;
final List<String> keysToReturn = query.limiters['keys']?.split(',');
query.limiters.clear(); //Remove limits in LiveQuery
final String _where = query.buildQuery().replaceAll('where=', '');

//Convert where condition to Map
Map<String, dynamic> _whereMap = Map<String, dynamic>();
if (_where != '') {
_whereMap = json.decode(_where);
}

final int requestId = _requestIdGenerator();

try {
_webSocket = new WebSocket(_liveQueryURL);
await _webSocket.onOpen.first;

if (_webSocket != null && _webSocket.readyState == WebSocket.OPEN) {
if (_debug) {
print('$_printConstLiveQuery: Socket opened');
}
} else {
if (_debug) {
print('$_printConstLiveQuery: Error when connection client');
return Future<void>.value(null);
}
}


_webSocket.onMessage.listen((MessageEvent e) {
final dynamic message = e.data;
if (_debug) {
print('$_printConstLiveQuery: Listen: $message');
}

final Map<String, dynamic> actionData = jsonDecode(message);

if (eventCallbacks.containsKey(actionData['op'])) {
if (actionData.containsKey('object')) {
final Map<String, dynamic> map = actionData['object'];
final String className = map['className'];
eventCallbacks[actionData['op']](
ParseObject(className).fromJson(map));
} else {
eventCallbacks[actionData['op']](actionData);
}
}
}, onDone: () {
if (_debug) {
print('$_printConstLiveQuery: Done');
}
}, onError: (Object error) {
if (_debug) {
print(
'$_printConstLiveQuery: Error: ${error.runtimeType.toString()}');
}
return Future<ParseResponse>.value(handleException(
Exception(error), ParseApiRQ.liveQuery, _debug, _className));
});

//The connect message is sent from a client to the LiveQuery server.
//It should be the first message sent from a client after the WebSocket connection is established.
_connectMessage = <String, String>{
'op': 'connect',
'applicationId': _client.data.applicationId
};
if (_sendSessionId) {
_connectMessage['sessionToken'] = _client.data.sessionId;
}

if (_client.data.clientKey != null)
_connectMessage['clientKey'] = _client.data.clientKey;
if (_client.data.masterKey != null)
_connectMessage['masterKey'] = _client.data.masterKey;

if (_debug) {
print('$_printConstLiveQuery: ConnectMessage: $_connectMessage');
}
_webSocket.sendString(jsonEncode(_connectMessage));

//After a client connects to the LiveQuery server,
//it can send a subscribe message to subscribe a ParseQuery.
_subscribeMessage = <String, dynamic>{
'op': 'subscribe',
'requestId': requestId,
'query': <String, dynamic>{
'className': _className,
'where': _whereMap,
if (keysToReturn != null && keysToReturn.isNotEmpty)
'fields': keysToReturn
}
};
if (_sendSessionId) {
_subscribeMessage['sessionToken'] = _client.data.sessionId;
}

if (_debug) {
print('$_printConstLiveQuery: SubscribeMessage: $_subscribeMessage');
}

_webSocket.sendString(jsonEncode(_subscribeMessage));

//Mount message for Unsubscribe
_unsubscribeMessage = <String, dynamic>{
'op': 'unsubscribe',
'requestId': requestId,
};
} on Exception catch (e) {
if (_debug) {
print('$_printConstLiveQuery: Error: ${e.toString()}');
}
return handleException(e, ParseApiRQ.liveQuery, _debug, _className);
}
}

void on(LiveQueryEvent op, Function callback) {
eventCallbacks[_liveQueryEvent[op.index]] = callback;
}

Future<void> unSubscribe() async {
if (_webSocket != null && _webSocket.readyState == WebSocket.OPEN) {
_webSocket.sendString(jsonEncode(_unsubscribeMessage));
if (_debug) {
print('$_printConstLiveQuery: Socket closed');
}
await _webSocket.close();
}
}
}
4 changes: 2 additions & 2 deletions lib/src/network/parse_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ class QueryBuilder<T extends ParseObject> {
///
/// Make sure to call this after defining your queries
Future<ParseResponse> query() async {
return object.query(_buildQuery());
return object.query(buildQuery());
}

Future<ParseResponse> distinct(String className) async {
Expand All @@ -289,7 +289,7 @@ class QueryBuilder<T extends ParseObject> {
}

/// Builds the query for Parse
String _buildQuery() {
String buildQuery() {
queries = _checkForMultipleColumnInstances(queries);
return 'where={${buildQueries(queries)}}${getLimiters(limiters)}';
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ homepage: https://github.com/phillwiggins/flutter_parse_sdk
author: PhillWiggins <[email protected]>

environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.2.2 <3.0.0"

dependencies:
flutter:
Expand Down