Skip to content
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

[WIP] TF-3157 Implement web socket push #3168

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
80d8cdd
TF-3157 Update jmap-dart-client dependency
tddang-linagora Sep 23, 2024
6833c03
TF-3157 Implement web socket push
tddang-linagora Sep 23, 2024
575edd9
fixup! TF-3157 Implement web socket push
tddang-linagora Sep 23, 2024
dbf30b0
fixup! TF-3157 Implement web socket push
tddang-linagora Sep 25, 2024
c5a0186
TF-3157 Update web socket with background service worker
tddang-linagora Sep 27, 2024
2eef328
fixup! TF-3157 Implement web socket push
tddang-linagora Oct 9, 2024
1fa0cdb
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 9, 2024
f3b6444
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 9, 2024
60eb5f2
TF-3157 Stub BroadcastChannel for mobile build
tddang-linagora Oct 9, 2024
27ba630
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 9, 2024
cb2f306
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 10, 2024
b664717
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 11, 2024
402b954
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 11, 2024
8d91f02
fixup! TF-3157 Update jmap-dart-client dependency
tddang-linagora Oct 14, 2024
3f18542
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 14, 2024
055b2c1
fixup! TF-3157 Implement web socket push
tddang-linagora Oct 22, 2024
e879dec
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 22, 2024
7775412
fixup! TF-3157 Implement web socket push
tddang-linagora Oct 23, 2024
196c3e8
fixup! TF-3157 Update web socket with background service worker
tddang-linagora Oct 23, 2024
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 contact/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -651,11 +651,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: f55a1862c1486197ea6a79feac31776ebeddc66c
ref: "enhancement/web-socket-ticket-capability"
resolved-ref: "6b3eccb4dabb496cfd7d9d9cee422457281c9cc9"
url: "https://github.com/linagora/jmap-dart-client.git"
source: git
version: "0.2.2"
version: "0.2.3"
js:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion contact/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dependencies:
jmap_dart_client:
git:
url: https://github.com/linagora/jmap-dart-client.git
ref: main
ref: enhancement/web-socket-ticket-capability

### Dependencies from pub.dev ###
equatable: 2.0.5
Expand Down
6 changes: 3 additions & 3 deletions email_recovery/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: f55a1862c1486197ea6a79feac31776ebeddc66c
ref: "enhancement/web-socket-ticket-capability"
resolved-ref: "6b3eccb4dabb496cfd7d9d9cee422457281c9cc9"
url: "https://github.com/linagora/jmap-dart-client.git"
source: git
version: "0.2.2"
version: "0.2.3"
js:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion email_recovery/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies:
jmap_dart_client:
git:
url: https://github.com/linagora/jmap-dart-client.git
ref: main
ref: enhancement/web-socket-ticket-capability

### Dependencies from pub.dev ###
equatable: 2.0.5
Expand Down
6 changes: 3 additions & 3 deletions fcm/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: f55a1862c1486197ea6a79feac31776ebeddc66c
ref: "enhancement/web-socket-ticket-capability"
resolved-ref: "6b3eccb4dabb496cfd7d9d9cee422457281c9cc9"
url: "https://github.com/linagora/jmap-dart-client.git"
source: git
version: "0.2.2"
version: "0.2.3"
js:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion fcm/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies:
jmap_dart_client:
git:
url: https://github.com/linagora/jmap-dart-client.git
ref: main
ref: enhancement/web-socket-ticket-capability

### Dependencies from pub.dev ###
equatable: 2.0.5
Expand Down
6 changes: 3 additions & 3 deletions forward/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: f55a1862c1486197ea6a79feac31776ebeddc66c
ref: "enhancement/web-socket-ticket-capability"
resolved-ref: "6b3eccb4dabb496cfd7d9d9cee422457281c9cc9"
url: "https://github.com/linagora/jmap-dart-client.git"
source: git
version: "0.2.2"
version: "0.2.3"
js:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion forward/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies:
jmap_dart_client:
git:
url: https://github.com/linagora/jmap-dart-client.git
ref: main
ref: enhancement/web-socket-ticket-capability

### Dependencies from pub.dev ###
equatable: 2.0.5
Expand Down
6 changes: 2 additions & 4 deletions lib/features/base/action/ui_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ abstract class Action with EquatableMixin {}

abstract class UIAction extends Action {}

abstract class FcmAction extends Action {}

abstract class FcmStateChangeAction extends FcmAction {
abstract class PushNotificationStateChangeAction extends UIAction {
dab246 marked this conversation as resolved.
Show resolved Hide resolved
final TypeName typeName;
final jmap.State newState;

FcmStateChangeAction(this.typeName, this.newState);
PushNotificationStateChangeAction(this.typeName, this.newState);
dab246 marked this conversation as resolved.
Show resolved Hide resolved
}
19 changes: 19 additions & 0 deletions lib/features/base/base_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ import 'package:tmail_ui_user/features/push_notification/domain/state/get_stored
import 'package:tmail_ui_user/features/push_notification/domain/usecases/destroy_firebase_registration_interactor.dart';
import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_stored_firebase_registration_interactor.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/bindings/web_socket_interactor_bindings.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/config/fcm_configuration.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_message_controller.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_token_controller.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/controller/web_socket_controller.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/notification/local_notification_manager.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_receiver.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart';
Expand Down Expand Up @@ -372,6 +374,23 @@ abstract class BaseController extends GetxController
}
}

void injectWebSocket(Session? session, AccountId? accountId) {
try {
requireCapability(
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
session!,
accountId!,
[
CapabilityIdentifier.jmapWebSocket,
CapabilityIdentifier.jmapWebSocketTicket
]
);
WebSocketInteractorBindings().dependencies();
WebSocketController.instance.initialize(accountId: accountId, session: session);
} catch(e) {
logError('$runtimeType::injectWebSocket(): exception: $e');
}
}

AuthenticationType get authenticationType => authorizationInterceptors.authenticationType;

bool get isAuthenticatedWithOidc => authenticationType == AuthenticationType.oidc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,11 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo
injectAutoCompleteBindings(session, currentAccountId);
injectRuleFilterBindings(session, currentAccountId);
injectVacationBindings(session, currentAccountId);
injectFCMBindings(session, currentAccountId);
if (PlatformInfo.isWeb) {
injectWebSocket(session, currentAccountId);
} else {
injectFCMBindings(session, currentAccountId);
}

_getVacationResponse();
spamReportController.getSpamReportStateAction();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';

abstract class WebSocketDatasource {
Stream<dynamic> getWebSocketChannel(Session session, AccountId accountId);
dab246 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import 'dart:convert';

import 'package:core/utils/app_logger.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
import 'package:jmap_dart_client/jmap/core/capability/web_socket_ticket_capability.dart';
import 'package:jmap_dart_client/jmap/core/capability/websocket_capability.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:rxdart/transformers.dart';
import 'package:tmail_ui_user/features/push_notification/data/datasource/web_socket_datasource.dart';
import 'package:tmail_ui_user/features/push_notification/data/network/web_socket_api.dart';
import 'package:tmail_ui_user/features/push_notification/domain/exceptions/web_socket_exceptions.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

class RemoteWebSocketDatasourceImpl implements WebSocketDatasource {
dab246 marked this conversation as resolved.
Show resolved Hide resolved
final WebSocketApi _webSocketApi;
final ExceptionThrower _exceptionThrower;

RemoteWebSocketDatasourceImpl(this._webSocketApi, this._exceptionThrower);
dab246 marked this conversation as resolved.
Show resolved Hide resolved

int _webSocketRetryRemained = 3;

@override
Stream getWebSocketChannel(Session session, AccountId accountId) {
return Stream
.castFrom(_getWebSocketChannel(session, accountId))
.doOnError(_exceptionThrower.throwException);
}

Stream _getWebSocketChannel(Session session, AccountId accountId) async* {
try {
_verifyWebSocketCapabilities(session, accountId);
dab246 marked this conversation as resolved.
Show resolved Hide resolved
final webSocketTicket = await _generateWebSocketTicket(session, accountId);
final webSocketUri = _getWebSocketUri(session, accountId);

final webSocketChannel = WebSocketChannel.connect(
dab246 marked this conversation as resolved.
Show resolved Hide resolved
Uri.parse('$webSocketUri?ticket=$webSocketTicket'),
protocols: ['jmap']);
await webSocketChannel.ready;
webSocketChannel.sink.add(jsonEncode({"@type": "WebSocketPushEnable"}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add dataTypes

Screenshot 2024-09-26 at 09 42 42

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we need to add this to our stream?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review latest code instead of old commits. This event was moved to web socket worker js

hoangdat marked this conversation as resolved.
Show resolved Hide resolved

yield* webSocketChannel.stream;
} catch (e) {
log('RemoteWebSocketDatasourceImpl::getWebSocketChannel():error: $e');
dab246 marked this conversation as resolved.
Show resolved Hide resolved
if (_webSocketRetryRemained > 0) {
_webSocketRetryRemained--;
yield* _getWebSocketChannel(session, accountId);
} else {
rethrow;
}
}
}

void _verifyWebSocketCapabilities(Session session, AccountId accountId) {
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
requireCapability(
session,
accountId,
[
CapabilityIdentifier.jmapWebSocket,
CapabilityIdentifier.jmapWebSocketTicket
]
);
}

Future<String> _generateWebSocketTicket(Session session, AccountId accountId) async {
final webSocketTicketCapability = session.getCapabilityProperties<WebSocketTicketCapability>(
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
accountId,
CapabilityIdentifier.jmapWebSocketTicket);

final webSocketTicketGenerationUrl = webSocketTicketCapability?.generationEndpoint;
if (webSocketTicketGenerationUrl == null) throw WebSocketTicketUnavailableException();
dab246 marked this conversation as resolved.
Show resolved Hide resolved
final webSocketTicket = await _webSocketApi.getWebSocketTicket('$webSocketTicketGenerationUrl');
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
if (webSocketTicket.ticket == null) throw WebSocketTicketUnavailableException();

return webSocketTicket.ticket!;
}

Uri _getWebSocketUri(Session session, AccountId accountId) {
final webSocketCapability = session.getCapabilityProperties<WebSocketCapability>(
accountId,
CapabilityIdentifier.jmapWebSocket);
if (webSocketCapability?.supportsPush != true) {
throw WebSocketPushNotSupportedException();
}
final webSocketUri = webSocketCapability?.url;
if (webSocketUri == null) throw WebSocketUriUnavailableException();

return webSocketUri;
}
}
29 changes: 29 additions & 0 deletions lib/features/push_notification/data/model/web_socket_ticket.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';

part 'web_socket_ticket.g.dart';

@JsonSerializable(includeIfNull: false)
class WebSocketTicket with EquatableMixin {
final String? ticket;
final String? clientAddress;
final DateTime? generatedOn;
final DateTime? validUntil;

WebSocketTicket({
required this.ticket,
required this.clientAddress,
required this.generatedOn,
required this.validUntil,
});

factory WebSocketTicket.fromJson(Map<String, dynamic> json) => _$WebSocketTicketFromJson(json);
Map<String, dynamic> toJson() => _$WebSocketTicketToJson(this);

@override
List<Object?> get props => [
ticket,
clientAddress,
generatedOn,
validUntil];
}
15 changes: 15 additions & 0 deletions lib/features/push_notification/data/network/web_socket_api.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'dart:convert';

import 'package:core/data/network/dio_client.dart';
import 'package:tmail_ui_user/features/push_notification/data/model/web_socket_ticket.dart';

class WebSocketApi {
final DioClient _dioClient;

WebSocketApi(this._dioClient);

Future<WebSocketTicket> getWebSocketTicket(String url) async {
final response = await _dioClient.post(url);
return WebSocketTicket.fromJson(jsonDecode(response.data));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:tmail_ui_user/features/push_notification/data/datasource/web_socket_datasource.dart';
import 'package:tmail_ui_user/features/push_notification/domain/repository/web_socket_repository.dart';

class WebSocketRepositoryImpl implements WebSocketRepository {
final WebSocketDatasource _webSocketDatasource;

WebSocketRepositoryImpl(this._webSocketDatasource);

@override
Stream getWebSocketChannel(Session session, AccountId accountId)
=> _webSocketDatasource.getWebSocketChannel(session, accountId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class WebSocketPushNotSupportedException implements Exception {}

class WebSocketUriUnavailableException implements Exception {}

class WebSocketTicketUnavailableException implements Exception {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';

abstract class WebSocketRepository {
Stream getWebSocketChannel(Session session, AccountId accountId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:jmap_dart_client/jmap/push/state_change.dart';

class InitializingWebSocketPushChannel extends LoadingState {}

class WebSocketPushStateReceived extends UIState {
final StateChange stateChange;

WebSocketPushStateReceived(this.stateChange);

@override
List<Object?> get props => [stateChange];
dab246 marked this conversation as resolved.
Show resolved Hide resolved
}

class WebSocketConnectionFailed extends FeatureFailure {

WebSocketConnectionFailed({super.exception});
}
Loading
Loading