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

Refactor: Reduce reliance of the package on 3rd party dependencies #72

Merged
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion example/flutter_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
Expand Down
2 changes: 1 addition & 1 deletion example/reactive/client/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class _MyHomePageState extends State<MyHomePage> {
return Container(
child: Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
style: Theme.of(context).textTheme.headlineMedium,
),
);
},
Expand Down
1 change: 0 additions & 1 deletion lib/src/channel.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:async';

import 'package:logging/logging.dart';
import 'package:pedantic/pedantic.dart';

import 'events.dart';
import 'exceptions.dart';
Expand Down
56 changes: 40 additions & 16 deletions lib/src/events.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import 'package:equatable/equatable.dart';

import 'channel.dart';
import 'socket.dart';

/// Base socket event
abstract class PhoenixSocketEvent extends Equatable {
@override
bool get stringify => true;
abstract class PhoenixSocketEvent {
const PhoenixSocketEvent();
}

/// Open event for a [PhoenixSocket].
class PhoenixSocketOpenEvent extends PhoenixSocketEvent {
@override
List<Object?> get props => [];
const PhoenixSocketOpenEvent();
}

/// Close event for a [PhoenixSocket].
class PhoenixSocketCloseEvent extends PhoenixSocketEvent {
/// Default constructor for this close event.
PhoenixSocketCloseEvent({
const PhoenixSocketCloseEvent({
this.reason,
this.code,
});
Expand All @@ -30,30 +26,52 @@ class PhoenixSocketCloseEvent extends PhoenixSocketEvent {
final int? code;

@override
List<Object?> get props => [code, reason];
bool operator ==(Object other) {
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
return other is PhoenixSocketCloseEvent &&
other.reason == reason &&
other.code == code;
}

@override
int get hashCode => Object.hash(runtimeType, reason, code);

@override
String toString() {
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
return 'PhoenixSocketCloseEvent(reason: $reason, code: $code)';
}
}

/// Error event for a [PhoenixSocket].
class PhoenixSocketErrorEvent extends PhoenixSocketEvent {
/// Default constructor for the error event.
PhoenixSocketErrorEvent({
const PhoenixSocketErrorEvent({
this.error,
this.stacktrace,
});

/// The error that happened on the socket
final dynamic error;
final Object? error;

/// The stacktrace associated with the error.
final dynamic stacktrace;

@override
List<Object?> get props => [error];
bool operator ==(Object other) {
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
return other is PhoenixSocketErrorEvent && other.error == error;
}

@override
int get hashCode => Object.hash(runtimeType, error);

@override
String toString() {
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
return 'PhoenixSocketErrorEvent(error: $error)';
}
}

/// Encapsulates constants used in the protocol over [PhoenixChannel].
class PhoenixChannelEvent extends Equatable {
PhoenixChannelEvent._(this.value);
class PhoenixChannelEvent {
const PhoenixChannelEvent._(this.value);

/// A reply event name for a given push ref value.
factory PhoenixChannelEvent.replyFor(String ref) =>
Expand All @@ -63,7 +81,7 @@ class PhoenixChannelEvent extends Equatable {
///
/// This is the event name used when a user of the library sends a message
/// on a channel.
factory PhoenixChannelEvent.custom(name) => PhoenixChannelEvent._(name);
const PhoenixChannelEvent.custom(String name) : value = name;

/// Instantiates a PhoenixChannelEvent from
/// one of the values used in the wire protocol.
Expand All @@ -83,6 +101,7 @@ class PhoenixChannelEvent extends Equatable {
throw ArgumentError.value(value);
}
}

static const String __closeEventName = 'phx_close';
static const String __errorEventName = 'phx_error';
static const String __joinEventName = 'phx_join';
Expand Down Expand Up @@ -120,5 +139,10 @@ class PhoenixChannelEvent extends Equatable {
value.startsWith(__replyEventName);

@override
List<Object> get props => [value];
bool operator ==(Object other) {
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
return other is PhoenixChannelEvent && other.value == value;
}

@override
int get hashCode => Object.hash(runtimeType, value);
}
21 changes: 17 additions & 4 deletions lib/src/message.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:equatable/equatable.dart';
import 'package:logging/logging.dart';

import 'channel.dart';
Expand All @@ -9,7 +8,7 @@ final Logger _logger = Logger('phoenix_socket.message');

/// Class that encapsulate a message being sent or received on a
/// [PhoenixSocket].
class Message extends Equatable {
class Message {
/// Given a parsed JSON coming from the backend, yield
/// a [Message] instance.
factory Message.fromJson(List<dynamic> parts) {
Expand Down Expand Up @@ -91,10 +90,24 @@ class Message extends Equatable {
}

@override
List<Object?> get props => [joinRef, ref, topic, event, payload];
bool operator ==(Object other) {
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
return other is Message &&
other.joinRef == joinRef &&
other.ref == ref &&
other.topic == topic &&
other.event == event &&
other.payload == payload;
}

@override
bool get stringify => true;
int get hashCode {
return Object.hash(runtimeType, joinRef, ref, topic, event, payload);
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
}

@override
String toString() {
return 'Message(joinRef: $joinRef, ref: $ref, topic: $topic, event: $event, payload: $payload)';
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
}

/// Whether the message is a reply message.
bool get isReply => event.isReply;
Expand Down
29 changes: 12 additions & 17 deletions lib/src/message_serializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,31 @@ import 'dart:convert';

import 'message.dart';

typedef DecoderCallback = dynamic Function(String rawData);
typedef EncoderCallback = String Function(Object? data);

/// Default class to serialize [Message] instances to JSON.
class MessageSerializer {
late Function decoder;
late Function encoder;

MessageSerializer._();
final DecoderCallback _decoder;
final EncoderCallback _encoder;

/// Default constructor returning the singleton instance of this class.
factory MessageSerializer({decoder, encoder}) {
MessageSerializer instance = _instance ??= MessageSerializer._();
instance.decoder = decoder ?? jsonDecode;
instance.encoder = encoder ?? jsonEncode;

return instance;
}

static MessageSerializer? _instance;
const MessageSerializer({
DecoderCallback decoder = jsonDecode,
EncoderCallback encoder = jsonEncode,
}) : _decoder = decoder,
_encoder = encoder;

/// Yield a [Message] from some raw string arriving from a websocket.
Message decode(dynamic rawData) {
if (rawData is String || rawData is List<int>) {
return Message.fromJson(decoder(rawData));
return Message.fromJson(_decoder(rawData));
} else {
throw ArgumentError('Received a non-string or a non-list of integers');
}
}

/// Given a [Message], return the raw string that would be sent through
/// a websocket.
String encode(Message message) {
return encoder(message.encode());
}
String encode(Message message) => _encoder(message.encode());
}
42 changes: 24 additions & 18 deletions lib/src/push.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import 'dart:async';

import 'package:equatable/equatable.dart';
import 'package:logging/logging.dart';
import 'package:quiver/collection.dart';

import 'channel.dart';
import 'events.dart';
import 'exceptions.dart';
import 'message.dart';

/// Encapsulates the response to a [Push].
class PushResponse extends Equatable {
class PushResponse {
/// Builds a PushResponse from a status and response.
PushResponse({
const PushResponse({
this.status,
this.response,
});
Expand All @@ -31,10 +29,10 @@ class PushResponse extends Equatable {
/// }
/// ```
factory PushResponse.fromMessage(Message message) {
final data = message.payload!;
final data = message.payload;
return PushResponse(
status: data['status'] as String?,
response: data['response'],
status: data?['status'] as String?,
response: data?['response'],
);
}

Expand All @@ -56,10 +54,19 @@ class PushResponse extends Equatable {
bool get isTimeout => status == 'timeout';

@override
List<Object?> get props => [status, response];
bool operator ==(Object other) {
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
return other is PushResponse &&
other.status == status &&
other.response == response;
}

@override
int get hashCode => Object.hash(status, response);

@override
bool get stringify => true;
String toString() {
return 'PushResponse(status: $status, response: $response)';
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Type of function that should return a push payload
Expand All @@ -81,8 +88,7 @@ class Push {
_responseCompleter = Completer<PushResponse>();

final Logger _logger;
final ListMultimap<String, void Function(PushResponse)> _receivers =
ListMultimap();
final Map<String, List<void Function(PushResponse)>> _receivers = {};
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved

/// The event name associated with the pushed message
final PhoenixChannelEvent? event;
Expand Down Expand Up @@ -182,7 +188,8 @@ class Push {
String status,
void Function(PushResponse) callback,
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
) {
_receivers[status].add(callback);
final receiver = (_receivers[status] ??= [])..add(callback);
_receivers[status] = receiver;
TesteurManiak marked this conversation as resolved.
Show resolved Hide resolved
}

/// Schedule a timeout to be triggered if no reply occurs
Expand Down Expand Up @@ -244,23 +251,22 @@ class Push {
}

_logger.finer(() {
if (_receivers[response.status].isNotEmpty) {
return 'Triggering ${_receivers[response.status].length} callbacks';
if (_receivers[response.status] case final receiver?
when receiver.isNotEmpty) {
return 'Triggering ${receiver.length} callbacks';
}
return 'Not triggering any callbacks';
});

final receivers = _receivers[response.status].toList();
final receivers = _receivers[response.status]?.toList() ?? const [];
clearReceivers();
for (final cb in receivers) {
cb(response);
}
}

/// Dispose the set of waiters associated with this push.
void clearReceivers() {
_receivers.clear();
}
void clearReceivers() => _receivers.clear();

// Remove existing waiters and reset completer
void cleanUp() {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import 'dart:async';
import 'dart:core';
import 'dart:math';

import 'package:collection/collection.dart' show IterableExtension;
import 'package:logging/logging.dart';
import 'package:phoenix_socket/src/utils/iterable_extensions.dart';
import 'package:rxdart/rxdart.dart';
import 'package:web_socket_channel/status.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
Expand Down
7 changes: 2 additions & 5 deletions lib/src/socket_options.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import 'package:meta/meta.dart';

import 'message_serializer.dart';

/// Options for the open Phoenix socket.
///
/// Provided durations are all in milliseconds.
@immutable
class PhoenixSocketOptions {
/// Create a PhoenixSocketOptions
PhoenixSocketOptions({
const PhoenixSocketOptions({
/// The duration after which a connection attempt
/// is considered failed
Duration? timeout,
Expand Down Expand Up @@ -41,7 +38,7 @@ class PhoenixSocketOptions {
this.dynamicParams,
MessageSerializer? serializer,
}) : _timeout = timeout ?? const Duration(seconds: 10),
serializer = serializer ?? MessageSerializer(),
serializer = serializer ?? const MessageSerializer(),
_heartbeat = heartbeat ?? const Duration(seconds: 30),
assert(!(params != null && dynamicParams != null),
"Can't set both params and dynamicParams");
Expand Down
15 changes: 15 additions & 0 deletions lib/src/utils/iterable_extensions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Taken from: https://github.com/dart-lang/collection/blob/master/lib/src/iterable_extensions.dart

extension IterableExtensions<T> on Iterable<T> {
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T) test) {
for (final element in this) {
if (test(element)) return element;
}
return null;
}
}
Loading