-
-
Notifications
You must be signed in to change notification settings - Fork 214
feat: Add aggregate query support #1041
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
base: master
Are you sure you want to change the base?
Changes from all commits
8ad7e19
e65a3f5
c093ff1
29d83f5
3a4feb6
bd2b057
7dabdcb
953b54a
a255b0a
18ea019
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,10 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// If you change this file, you should apply the same changes to the 'parse_websocket_io.dart' file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| library; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'dart:html' as html; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'dart:async'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'package:socket_io_client/socket_io_client.dart' as IO; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'package:web_socket_channel/html.dart'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'package:web_socket_channel/web_socket_channel.dart'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,18 +16,41 @@ class WebSocket { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static const int closing = 2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static const int closed = 3; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final html.WebSocket _webSocket; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final IO.Socket _webSocket; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static final Map<String, int> _states={ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'closed':3, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'opening':0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'open':1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'closing':2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static Future<WebSocket> connect(String liveQueryURL) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final html.WebSocket webSocket = html.WebSocket(liveQueryURL); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await webSocket.onOpen.first; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return WebSocket._(webSocket); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Completer<WebSocket> completer= Completer(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final IO.Socket webSocket = IO.io( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| liveQueryURL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IO.OptionBuilder().setTransports(['websocket']).enableReconnection().build() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| webSocket.connect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| webSocket.onConnect((handler){ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if(!completer.isCompleted){ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completer.complete(WebSocket._(webSocket)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| webSocket.onConnectError((handler){ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if(!completer.isCompleted){ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completer.completeError('unable to connect to the server $handler'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return completer.future; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
27
to
48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Address multiple issues in the connect method. Several improvements needed:
🔎 Proposed improvements static Future<WebSocket> connect(String liveQueryURL) async {
- Completer<WebSocket> completer= Completer();
+ final Completer<WebSocket> completer = Completer();
final IO.Socket webSocket = IO.io(
liveQueryURL,
IO.OptionBuilder().setTransports(['websocket']).enableReconnection().build()
);
webSocket.connect();
- webSocket.onConnect((handler){
- if(!completer.isCompleted){
+ webSocket.onConnect((handler) {
+ if (!completer.isCompleted) {
completer.complete(WebSocket._(webSocket));
}
});
- webSocket.onConnectError((handler){
- if(!completer.isCompleted){
- completer.completeError('unable to connect to the server $handler');
+ webSocket.onConnectError((handler) {
+ if (!completer.isCompleted) {
+ completer.completeError(
+ Exception('Unable to connect to the server: $handler')
+ );
}
});
-
-
- return completer.future;
+ return completer.future.timeout(
+ const Duration(seconds: 30),
+ onTimeout: () {
+ webSocket.disconnect();
+ throw TimeoutException('WebSocket connection timeout');
+ },
+ );
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int get readyState => _webSocket.readyState; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int get readyState => _states[_webSocket.io.readyState]!; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n packages/dart/lib/src/network/parse_websocket_html.dart | head -100Repository: parse-community/Parse-SDK-Flutter Length of output: 1982 🏁 Script executed: rg "_states\s*=" packages/dart/lib/src/network/parse_websocket_html.dart -A 10 -B 2Repository: parse-community/Parse-SDK-Flutter Length of output: 472 🏁 Script executed: rg "readyState" packages/dart/lib/src/network/parse_websocket_html.dart -B 5 -A 5Repository: parse-community/Parse-SDK-Flutter Length of output: 254 🏁 Script executed: cat -n packages/dart/lib/src/network/parse_websocket_io.dart | head -100Repository: parse-community/Parse-SDK-Flutter Length of output: 1097 🏁 Script executed: rg "readyState" packages/dart/lib/src/network/parse_websocket_io.dart -B 5 -A 5Repository: parse-community/Parse-SDK-Flutter Length of output: 321 🏁 Script executed: fd pubspec.yaml packages/dart | xargs cat | grep -A 5 "socket_io_client"Repository: parse-community/Parse-SDK-Flutter Length of output: 166 🏁 Script executed: rg "io\.readyState" packages/dart -B 3 -A 3Repository: parse-community/Parse-SDK-Flutter Length of output: 635 🏁 Script executed: rg "readyState" packages/dart --type dart -B 2 -A 2Repository: parse-community/Parse-SDK-Flutter Length of output: 2388 🏁 Script executed: fd "pubspec.lock" packages/dart -exec cat {} \; | grep -A 20 "socket_io_client"Repository: parse-community/Parse-SDK-Flutter Length of output: 223 🏁 Script executed: find packages/dart -name "pubspec.lock" -type f -exec head -200 {} \; | grep -A 30 "socket_io_client:"Repository: parse-community/Parse-SDK-Flutter Length of output: 59 🏁 Script executed: rg "socket_io_client" packages/dart --type yamlRepository: parse-community/Parse-SDK-Flutter Length of output: 130 🌐 Web query:
💡 Result: The readyState values follow Dart/WebSocket constants:
Sources: socket_io_client (pub.dev v3.1.2) and Dart WebSocket readyState/connecting constant. [1] https://pub.dev/packages/socket_io_client/versions/3.1.2/example [2] https://api.dart.dev/stable/dart-io/WebSocket/connecting-constant.html 🌐 Web query:
💡 Result: Short answer — readyState uses the standard WebSocket integer states:
These are the WebSocket readyState values used by dart:io / dart:html (and therefore what socket_io_client will reflect for the underlying socket). See Dart WebSocket readyState and the socket_io_client package docs. [1][2][3] References:
[1] https://api.dart.dev/dart-io/WebSocket/readyState.html Remove the string-to-int state mapping; The 🔎 Recommended fix- int get readyState => _states[_webSocket.io.readyState]!;
+ int get readyState => _webSocket.io.readyState;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Future<void> close() async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _webSocket.close(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Future<IO.Socket> close() async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _webSocket.disconnect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Breaking API change: close() return type modified. The return type changed from 🔎 Suggested fix to maintain backward compatibility- Future<IO.Socket> close() async {
- return _webSocket.disconnect();
+ Future<void> close() async {
+ _webSocket.disconnect();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WebSocketChannel createWebSocketChannel() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
|
|
||
| part of '../../parse_server_sdk.dart'; | ||
|
|
||
| class ParseAggregate extends ParseObject { | ||
|
|
||
| ParseAggregate( | ||
| this.functionName, { | ||
| bool? debug, | ||
| ParseClient? client, | ||
| bool? autoSendSessionId, | ||
| }) : super( | ||
| functionName, | ||
| client: client, | ||
| autoSendSessionId: autoSendSessionId, | ||
| debug: debug, | ||
| ) { | ||
| _path = '$keyEndPointAggregate$functionName'; | ||
| } | ||
|
|
||
| final String functionName; | ||
|
|
||
| @override | ||
| late String _path; | ||
|
|
||
| Future<ParseResponse> execute(Map<String, dynamic> pipeline, {Map<String, String>? headers}) async { | ||
| final String uri = '${ParseCoreData().serverUrl}$_path'; | ||
|
|
||
| Map<String, String> parameters = {}; | ||
|
|
||
| if (pipeline.isEmpty) { | ||
| throw ArgumentError( | ||
| 'pipeline must not be empty. Please add pipeline operations to aggregate data. ' | ||
| 'Example: {"\$group": {"_id": "\$userId", "totalScore": {"\$sum": "\$score"}}}', | ||
| ); | ||
| } else { | ||
| parameters.addAll({ | ||
| 'pipeline': jsonEncode(pipeline.entries.map((e) => {e.key: e.value}).toList()) | ||
mskayali marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
| _setObjectData(pipeline); | ||
| } | ||
|
|
||
| try { | ||
| print(Uri.parse(uri).replace(queryParameters: parameters).toString()); | ||
mskayali marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| final ParseNetworkResponse result = await _client.get( | ||
| Uri.parse(uri).replace(queryParameters: parameters).toString(), | ||
| options: ParseNetworkOptions(headers: headers) | ||
| ); | ||
| return ParseResponse.fromParseNetworkResponse(result); | ||
| } on Exception catch (e) { | ||
| return handleException(e, ParseApiRQ.execute, _debug, parseClassName); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| part of '../../parse_server_sdk.dart'; | ||
|
|
||
| class ParseJobs extends ParseObject { | ||
| /// Creates a new cloud function object | ||
| /// | ||
| /// {https://docs.parseplatform.org/cloudcode/guide/} | ||
| ParseJobs( | ||
| this.functionName, { | ||
| bool? debug, | ||
| ParseClient? client, | ||
| bool? autoSendSessionId, | ||
| }) : super( | ||
| functionName, | ||
| client: client, | ||
| autoSendSessionId: autoSendSessionId, | ||
| debug: debug, | ||
| ) { | ||
| _path = '$keyEndPointJobs$functionName'; | ||
| } | ||
|
|
||
| final String functionName; | ||
|
|
||
| @override | ||
| // ignore: overridden_fields | ||
| late String _path; | ||
|
|
||
| /// Executes a cloud function | ||
| /// | ||
| /// To add the parameters, create an object and call [set](value to set) | ||
| Future<ParseResponse> execute({Map<String, dynamic>? parameters, Map<String, String>? headers}) async { | ||
| final String uri = '${ParseCoreData().serverUrl}$_path'; | ||
| if (parameters != null) { | ||
| _setObjectData(parameters); | ||
| } | ||
| try { | ||
| final ParseNetworkResponse result = await _client.post(uri, options: ParseNetworkOptions(headers: headers), data: json.encode(_getObjectData())); | ||
| return handleResponse<ParseJobs>(this, result, ParseApiRQ.execute, _debug, parseClassName); | ||
| } on Exception catch (e) { | ||
| return handleException(e, ParseApiRQ.execute, _debug, parseClassName); | ||
| } | ||
| } | ||
|
|
||
| /// Executes a cloud function that returns a ParseObject type | ||
| /// | ||
| /// To add the parameters, create an object and call [set](value to set) | ||
| Future<ParseResponse> executeObjectFunction<T extends ParseObject>({Map<String, dynamic>? parameters, Map<String, String>? headers}) async { | ||
| final String uri = '${ParseCoreData().serverUrl}$_path'; | ||
| if (parameters != null) { | ||
| _setObjectData(parameters); | ||
| } | ||
| try { | ||
| final ParseNetworkResponse result = await _client.post(uri, options: ParseNetworkOptions(headers: headers), data: json.encode(_getObjectData())); | ||
| return handleResponse<T>(this, result, ParseApiRQ.executeObjectionFunction, _debug, parseClassName); | ||
| } on Exception catch (e) { | ||
| return handleException(e, ParseApiRQ.executeObjectionFunction, _debug, parseClassName); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
socket_io_client dart package latest version 2025 security💡 Result:
Latest stable socket_io_client (Dart) on pub.dev: 3.1.3 (published recently). [1]
Known/security notes:
Practical recommendations:
Sources:
[1] pub.dev — socket_io_client package page and versions.
[2] CVE-2024-38355 / Snyk / CVE details (Socket.IO uncaught error crash).
[3] GitHub Advisory / CVE-2025-61765 (python-socketio pickle deserialization advisory).
🌐 Web query:
socket_io_client pub.dev security vulnerabilities advisories💡 Result:
Known/security advisories affecting Socket.IO clients (summary):
Recommendations: upgrade client/server packages to the patched versions above, validate/limit inputs, avoid trusting untrusted message-queue payloads (or use safe serializers), and monitor the Socket.IO GitHub security advisories for new issues. (github.com)
Citations:
Update
socket_io_clientto version 3.1.3.The imported
socket_io_clientpackage is at v3.1.2; version 3.1.3 is available and should be used to ensure the latest stability and bug fixes. No public vulnerabilities specific to the Dart package have been identified, though general Socket.IO ecosystem best practices (e.g., keep the server implementation patched) should be followed.🤖 Prompt for AI Agents