The library was missing the ability to expose the full JSON response from the presence payload. This limitation exposed a major weakness in the library's design, which was the lack of a unified JSON response interface.
Also previously, the library abstracted the underlying JSON object type using an opaque object
type. This caused a lot of frustration due to the lack of type safety and the need to cast the object to the correct type.
Now, IJsonBox
interface is introduced to abstract the underlying JSON object type. It also lead to a unified interface for interacting with any JSON response.
More nice side-effects of this change were better performance and less memory usage.
If you are implementing your own IMessageSerializer, you may need to update your implementation to support the new IJsonBox
interface.
The new interface should be simpler, allowing you to easily migrate to the new version.
In order to migrate to the new version, you need to make sure you are using the new IJsonBox
interface instead of the object
type.
Also, the JsonResponse and JsonPayload extension methods are now removed in favor of the new IJsonBox
interface.
(The type system should detect all these issues.)
-reply.JsonResponse<ChannelError>()
+reply.Response.Unbox<ChannelError>()
-message.JsonPayload<PresenceEvent>()
+message.Payload.Unbox<PresenceEvent>()
The library underwent a major overhaul since the pre-release version, so it will be very difficult to document every change.
Here is a best-effort guide to the changes made in the latest release. Please feel free to raise a PR / issue in case something is missing.
IMPORTANT: The changes are not exhaustive.
IWebSocket
interface now requires the underlying socket to report its state.
public WebsocketState State {
get {
return ws.State switch {
WebSocketStates.Connecting => WebsocketState.Connecting,
WebSocketStates.Open => WebsocketState.Open,
WebSocketStates.Closing => WebsocketState.Closing,
_ => WebsocketState.Closed,
};
}
}
Instead of returning uint
, DelayedExecutor
now returns IDelayedExecution
instance. It is a simple object that "knows" how to cancel the delayed execution.
-public uint Execute(Action action, TimeSpan delay) {
+public IDelayedExecution Execute(Action action, TimeSpan delay) {
// ...
- return id;
+ return new DelayedExecution(id, this);
}
Enum values are now standardized as per the C# naming convention.
Avoiding to use the enum names as the corresponding event names also has the advantage of avoiding the use of .ToString()
on enums, which is much less performant than a simple switch with static strings.
-Message.InBoundEvent.phx_error.ToString()
+Message.InBoundEvent.Error.Serialized()
Also, Reply.Status
changed to ReplyStatus
, allowing reply.Status
to hold the status value.
-.Receive(Reply.Status.Ok, _ => callback(true))
+.Receive(ReplyStatus.Ok, _ => callback(true))
- Instead of passing parameters on connect / join, we pass them on initialization.
- It is required to explicitly pass a serializer instance along with the options to the socket.
- Channel creation has been renamed to
Channel
.
socket = new Socket(
+ url,
+ @params,
new BestHTTPWebsocketFactory(),
- new()
+ new(new JsonMessageSerializer())
);
-socket.Connect(url, @params);
+socket.Connect();
-var channel = socket.MakeChannel(topic);
-channel.Join(@params);
+var channel = socket.Channel(topic, @params);
+channel.Join();
Previously, pushing to a channel required a JObject
instance. This required coupling the caller with the serializer, not to a lot of redundant code.
Now, you can simply pass any object that you know the serializer can handle. The library will simply pass this object to the serializer before sending it to the server.
// here, chat is an instance of some custom class
-channel.PushJson("chat", JObject.FromObject(chat));
+channel.Push("chat", chat);
If you're interested in the Message.payload
property of a channel event, you can use the new generic On
method to get the payload mapped directly.
NOTE: you can't pass a method with this approach, due to how generics in C# work.
channel.On(
"on_costs_data",
- message => {
- var costs = message.payload.ToObject<CostsData>();
+ (CostsData costs) => {
@delegate.OnCostsData(costs);
}
);
If you would like to access the Message.payload
or Reply.response
properties directly, it is recommended to use the extension methods, as those property types are object
.
var payload = message.JSONPayload<JToken>();
// or...
var response = reply.JSONResponse<CustomType>();
Previously, only one subscriber could be attached to the event. Adding more subscribers, subsequently, would overwrite the previous one.
// OLD BEHAVIOUR
channel.On(@event, DoSomething);
channel.On(@event, DoSomethingElse);
// Only DoSomethingElse would be called
channel.Off(@event);
var sub1 = channel.On(@event, DoSomething);
var sub2 = channel.On(@event, DoSomethingElse);
// Both callbacks will be called
channel.Off(@event, sub1);
channel.Off(@event, sub2);
// accessing the underlying websocket adapter
-var adapter = socket.websocket as MyAdapter;
+var adapter = socket.conn as MyAdapter;
// channel canPush check
-channel.canPush;
+channel.CanPush();
Under the hood, the library now uses Phoenix V2 serialization format, which uses arrays instead of dictionaries to save on redundant JSON keys. It should be transparent to the user, since the backend will handle the serialization automatically based on the vsn
property sent with the request.