Skip to content

Commit

Permalink
feat(api): Subscription Reconnection (#2074)
Browse files Browse the repository at this point in the history
  • Loading branch information
Equartey authored and ragingsquirrel3 committed Nov 8, 2022
1 parent c566292 commit b05976e
Show file tree
Hide file tree
Showing 33 changed files with 1,004 additions and 181 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-18/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>
13 changes: 12 additions & 1 deletion packages/amplify/amplify_flutter_android/android/.project
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>amplify_core</name>
<name>amplify_flutter_android</name>
<comment>Project amplify_core created by Buildship.</comment>
<projects>
</projects>
Expand All @@ -20,4 +20,15 @@
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>0</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/amplify_core/lib/src/hub/amplify_hub_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ class AmplifyHubImpl extends AmplifyHub {
Future.wait<void>([
for (final stream in _availableStreams.values) stream.close(),
]).ignore();
Future.wait<void>([
for (final subs in _subscriptions.values)
for (final sub in subs) sub.cancel()
]).ignore();
_availableStreams.clear();
_subscriptions.clear();
}
Expand Down
5 changes: 4 additions & 1 deletion packages/amplify_core/lib/src/hub/hub_channel.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,4 +28,7 @@ enum HubChannel<HubEventPayload, E extends HubEvent<HubEventPayload>> {

/// Events of the DataStore category.
DataStore<DataStoreHubEventPayload, DataStoreHubEvent>(),

/// Events of the API category
Api<ApiHubEventPayload, ApiHubEvent>();
}
7 changes: 7 additions & 0 deletions packages/amplify_core/lib/src/types/api/api_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
* permissions and limitations under the License.
*/

// Packages
export 'package:retry/retry.dart' show RetryOptions;

// API Authorization
export 'auth/api_auth_provider.dart';
export 'auth/api_authorization_type.dart';
Expand All @@ -26,6 +29,10 @@ export 'graphql/graphql_request_type.dart';
export 'graphql/graphql_response.dart';
export 'graphql/graphql_response_error.dart';
export 'graphql/graphql_subscription_operation.dart';
export 'graphql/graphql_subscription_options.dart';

export 'hub/api_hub_event.dart';
export 'hub/api_subscription_hub_event.dart';

export 'rest/rest_exception.dart';
export 'rest/rest_operation.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ class GraphQLRequest<T> {
/// Only required if your backend has multiple GraphQL endpoints in the amplifyconfiguration.dart file. This parameter is then needed to specify which one to use for this request.
final String? apiName;

/// A map of Strings to dynamically use for custom headers in the http request.
final Map<String, String>? headers;

/// Authorization type to use for this request.
///
/// If not supplied, the request will use the default endpoint mode.
final APIAuthorizationType? authorizationMode;

/// A map of Strings to dynamically use for custom headers in the http request.
final Map<String, String>? headers;

/// A map of values to dynamically use for variable names in the `document`.
///
/// See https://graphql.org/learn/queries/#variables for more information.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import 'package:retry/retry.dart';

/// Configuration options for GraphQL Subscriptions and their WebSockets.
class GraphQLSubscriptionOptions {
/// Configure the ping interval for AppSync polling for subscription connections.
final Duration? pingInterval;

/// Configure the exponential retry strategy options
/// see: https://pub.dev/documentation/retry/latest/retry/RetryOptions-class.html
final RetryOptions? retryOptions;

const GraphQLSubscriptionOptions({this.pingInterval, this.retryOptions});
}
27 changes: 27 additions & 0 deletions packages/amplify_core/lib/src/types/api/hub/api_hub_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import 'package:amplify_core/amplify_core.dart';

class ApiHubEvent extends HubEvent<ApiHubEventPayload> {
ApiHubEvent(
super.eventName, {
super.payload,
});
}

abstract class ApiHubEventPayload {
const ApiHubEventPayload();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import 'package:amplify_core/amplify_core.dart';

/// {@template amplify_common.hub.api_network_state}
/// Network states for graphql subscriptions.
/// {@endtemplate}
enum NetworkState {
/// {@macro amplify_common.hub.api_network_state_connected}
connected,

/// {@macro amplify_common.hub.api_network_state_disconnected}
disconnected,

/// {@macro amplify_common.hub.api_network_state_failed}
failed;
}

/// {@template amplify_common.hub.api_intended_state}
/// The intended network states for graphql subscriptions.
/// {@endtemplate}
enum IntendedState {
/// {@macro amplify_common.hub.api_intended_state_connected}
connected,

/// {@macro amplify_common.hub.api_intended_state_disconnected}
disconnected;
}

/// {@template amplify_common.hub.api_subscription_status}
/// A consolidated subscription connection status determined by the network and
/// intended states.
/// {@endtemplate}
enum SubscriptionStatus {
/// {@macro amplify_common.hub.api_subscription_status_connected}
connected,

/// {@macro amplify_common.hub.api_subscription_status_disconnected}
disconnected,

/// {@macro amplify_common.hub.api_subscription_status_connected}
connecting,

/// {@macro amplify_common.hub.api_subscription_status_disconnected}
pendingDisconnected,

/// {@macro amplify_common.hub.api_subscription_status_failed}
failed;
}

class SubscriptionDetails extends ApiHubEventPayload
with
AWSEquatable<SubscriptionDetails>,
AWSSerializable<Map<String, Object?>>,
AWSDebuggable {
/// {@macro amplify_common.hub.api_network_state}
final NetworkState networkState;

/// {@macro amplify_common.hub.api_intended_state}
final IntendedState intendedState;

SubscriptionDetails(this.networkState, this.intendedState);

@override
List<Object?> get props => [intendedState, networkState];

@override
Map<String, String> toJson() => {
'networkState': networkState.name,
'intendedState': intendedState.name,
};

@override
String get runtimeTypeName => 'SubscriptionDetails';
}

class SubscriptionHubEvent extends ApiHubEvent
with
AWSEquatable<SubscriptionHubEvent>,
AWSSerializable<Map<String, Object?>>,
AWSDebuggable {
final SubscriptionDetails details;

static const String _name = 'SubscriptionHubEvent';

SubscriptionHubEvent._(this.details) : super(_name, payload: details);

/// {@template amplify_common.hub.api_subscription_status}
/// An overall status for GraphQL subscription connection, determined by the
/// underlying details [networkState] & [intendedState]
/// {@endtemplate}
SubscriptionStatus get status {
// Connection failed
if (details.networkState == NetworkState.failed) {
return SubscriptionStatus.failed;
}
// Connected with active subscriptions
if (details.networkState == NetworkState.connected &&
details.intendedState == IntendedState.connected) {
return SubscriptionStatus.connected;
}
// Disconnected with active subscriptions
if (details.networkState == NetworkState.disconnected &&
details.intendedState == IntendedState.connected) {
return SubscriptionStatus.connecting;
}
// Connected without active subscriptions
if (details.networkState == NetworkState.connected &&
details.intendedState == IntendedState.disconnected) {
return SubscriptionStatus.pendingDisconnected;
}

// disconnected without active subscriptions
return SubscriptionStatus.disconnected;
}

/// {@template amplify_common.hub.api_subscription_connected}
/// Emitted when a GraphQL subscription is connected.
/// {@endtemplate}
SubscriptionHubEvent.connected()
: this._(SubscriptionDetails(
NetworkState.connected, IntendedState.connected));

/// {@template amplify_common.hub.api_subscription_connected}
/// Emitted when a GraphQL subscription is connecting/reconnecting.
/// {@endtemplate}
SubscriptionHubEvent.connecting()
: this._(SubscriptionDetails(
NetworkState.disconnected, IntendedState.connected));

/// {@template amplify_common.hub.api_subscription_disconnected}
/// Emitted when a GraphQL subscription connection has disconnected.
/// {@endtemplate}
SubscriptionHubEvent.disconnected()
: this._(SubscriptionDetails(
NetworkState.disconnected, IntendedState.disconnected));

/// {@template amplify_common.hub.api_subscription_pending_disconnect}
/// Emitted when a GraphQL subscription connection is pending disconnect,
/// but should exist.
/// {@endtemplate}
SubscriptionHubEvent.pendingDisconnect()
: this._(SubscriptionDetails(
NetworkState.connected, IntendedState.disconnected));

/// {@template amplify_common.hub.api_subscription_failed}
/// Emitted when a GraphQL subscription connection has failed.
/// {@endtemplate}
SubscriptionHubEvent.failed()
: this._(SubscriptionDetails(
NetworkState.failed, IntendedState.disconnected));

@override
List<Object?> get props => [details, status];

@override
String get runtimeTypeName => 'SubscriptionHubEvent';

@override
Map<String, Object?> toJson() => <String, dynamic>{
'status': status.name,
'details': details.toJson(),
};
}
1 change: 1 addition & 0 deletions packages/amplify_core/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies:
json_annotation: ^4.6.0
logging: ^1.0.0
meta: ^1.7.0
retry: ^3.1.0
uuid: 3.0.6

dev_dependencies:
Expand Down
Loading

0 comments on commit b05976e

Please sign in to comment.