Migrate from WebSync Cloud to LiveSwitch Cloud
LiveSwitch extends the WebSync to support the operations that are required by its new features. The extended API combines expressiveness with simplicity and offers full support for WebSockets, cross-platform messaging, and all the features that you've come to expect from WebSync.
Cheat Sheet
WebSync Cloud | → LiveSwitch Cloud |
---|---|
https://v4.websync.fm/websync.ashx | https://cloud.liveswitch.io |
Client.RequestUrl | Client.GatewayUrl |
Client.DomainName | Client.ApplicationId |
Client.DomainKey | Client.SharedSecret |
Client.Connect | Client.Register |
Client.Disconnect | Client.Unregister |
Client.Bind | Client.Update |
Client.Unbind | Client.Update |
Client.Subscribe | Client.Join |
Client.Unsubscribe | Client.Leave |
Client.Publish | Channel.SendMessage |
SubscribeArgs.OnReceive | Channel.OnMessage |
SubscribeArgs.OnClientSubscribe | Channel.OnRemoteClientJoin |
SubscribeArgs.OnUserJoin | Channel.OnRemoteClientJoin |
SubscribeArgs.OnClientUnsubscribe | Channel.OnRemoteClientLeave |
SubscribeArgs.OnUserLeave | Channel.OnRemoteClientLeave |
Publisher | Web API |
Getting Started
- First, create a LiveSwitch Cloud account if you don't have one.
- Create an Application using the Live
Switch Cloud Console . - Download the latest SDK and add it to your app.
You're ready to migrate.
Migrating involves converting code that references WebSync into code that references the LiveSwitch equivalents.
Not sure where to start? Just remove the WebSync SDK from your app and let your IDE tell you where to look.
Key Differences
There are some key differences to be aware of when migrating:
- Registering in LiveSwitch == connecting in WebSync.
- Unregistering in LiveSwitch == disconnecting in WebSync.
- Joining a channel in LiveSwitch == subscribing to a channel in WebSync.
- Leaving a channel in LiveSwitch == unsubscribing from a channel in WebSync.
- Updating a client in LiveSwitch == binding records to or unbinding records from a client in WebSync.
- Sending a message in LiveSwitch == publishing a message in WebSync.
- LiveSwitch uses a promise-based async/await model, as opposed to WebSync's callback model.
- Client requests are authorized by JSON web tokens.
Servers
LiveSwitch uses https://cloud.liveswitch.io as its signalling endpoint, also called the gateway URL. This is equivalent to the "request URL" in WebSync, which is https://v4.websync.fm/websync.ashx for WebSync Cloud.
Tokens
Tokens are used to authorize Register and Join requests. Register tokens authorize the initial connection, while Join tokens authorize a connected client to join a new channel. Register tokens can include a list of channels to join right away as part of the initial connection.
LiveSwitch tokens are simple JSON Web Tokens, an open, industry-standard method for representing claims securely between two parties.
Your LiveSwitch Cloud shared secret is used to sign tokens. It is very important to protect this shared secret. Generating tokens in a server-side API that can provide tokens to authorized users of your system is strongly recommended.
The simplest way to generate a token is to use the LiveSwitch SDK: LiveSwitch SDK
var registerToken = fm.livewitch.Token.generateClientRegisterToken(...);
var joinToken = fm.livewitch.Token.generateClientJoinToken(...);
You can also use a third-party library, like jsonwebtoken: Third-Party Library
var sharedSecret = '...';
var registerToken = jwt.sign({
type: 'register',
applicationId: '...',
userId: '...',
deviceId: '...',
channels: [{ // optional, any count
id: '...'
}],
clientRoles: ['...'], // optional, any count
region: '...' // optional
}, sharedSecret, {
algorithm: 'HS256',
expiresIn: 300 // seconds
});
var joinToken = jwt.sign({
type: 'join',
applicationId: '...',
userId: '...',
deviceId: '...',
clientId: '...'
channel: [{ // exactly 1 required
id: '...'
}]
}, sharedSecret, {
algorithm: 'HS256',
expiresIn: 300 // seconds
});
Migrate
Register (Connect)
Registering in LiveSwitch is equivalent to connecting in WebSync. Registering with the server requires a signed token for authorization.
Your LiveSwitch shared secret is equivalent to your WebSync domain key. It is private information for your servers only.
Your LiveSwitch Application ID is equivalent to your WebSync domain name. It is public information to include in your client apps.
Before (WebSync)
var domainName = "localhost";
var domainKey = "11111111-1111-1111-1111-111111111111";
var client = new Client(domainKey, domainName);
client.Connect(new ConnectArgs
{
OnSuccess = (connectSuccessArgs) =>
{
Console.WriteLine("Success!");
},
OnFailure = (connectFailureArgs) =>
{
Console.WriteLine($"Failure: {connectFailureArgs.Exception}");
}
});
After (LiveSwitch)
var applicationId = "12345678-1234-1234-1234-1234567890ab";
var sharedSecret = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
var client = new Client("https://cloud.liveswitch.io", applicationId);
var registerToken = Token.GenerateClientRegisterToken(applicationId, null, null, client.Id, null, new ChannelClaim[0], sharedSecret);
try
{
await client.Register(registerToken);
Console.WriteLine("Success!");
}
catch (Exception ex)
{
Console.WriteLine($"Failure: {ex}");
}
Notice the null
parameters when creating the Register token. These represent additional metadata (user ID, device ID, client roles) that can be bound to the client and verified on the server.
Also notice that an array of channels can be provided when registering. If provided, the server joins the client to these channels on registration. A Channel
array is returned from the call to Register
.
Unregister(Disconnect)
Unregistering in LiveSwitch is equivalent to disconnecting in WebSync. Before (WebSync)
client.Disconnect(new DisconnectArgs
{
OnComplete = (disconnectCompleteArgs) =>
{
Console.WriteLine("Done!");
}
});
After (LiveSwitch)
await client.Unregister();
Console.WriteLine("Done!");
Update (Bind)
Updating in LiveSwitch is equivalent to binding and unbinding in WebSync.
LiveSwitch takes a simple approach to client metadata that allows it to scale better than WebSync. Each client has a UserAlias
, a DeviceAlias
, and a Tag
property. These values can be changed anytime by calling Update
. Any of these three string properties can have any value, but the intention is that UserAlias
represents the user's human-readable display name, DeviceAlias
represents the device's human-readable display name, and Tag
represents any additional metadata the app needs. Before (WebSync)
client.Bind(new BindArgs(new[]
{
new Record("userName", Serializer.SerializeString("John Doe")),
new Record("deviceName", Serializer.SerializeString("John's Phone")),
new Record("otherData", Serializer.SerializeString("something else"))
})
{
OnSuccess = (bindSuccessArgs) =>
{
Console.WriteLine("Success!");
},
OnFailure = (bindFailureArgs) =>
{
Console.WriteLine($"Failure: {bindFailureArgs.Exception}");
}
});
After (LiveSwitch)
var config = client.Config;
config.UserAlias = "John Doe";
config.DeviceAlias = "John's Phone";
config.Tag = "something else";
try
{
await client.Update(config);
Console.WriteLine("Success!");
}
catch (Exception ex)
{
Console.WriteLine($"Failure: {ex}");
}
Join (Subscribe)
Joining in LiveSwitch is equivalent to subscribing in WebSync. Joining a channel requires a signed token for authorization.
LiveSwitch introduces a new Channel
type that is returned by the call to Join
. Received messages are delivered to channel-level event handlers. Before (WebSync)
var channelId = "/my/channel";
client.Subscribe(new SubscribeArgs(channelId)
{
OnSuccess = (subscribeSuccessArgs) =>
{
Console.WriteLine("Success!");
},
OnFailure = (subscribeFailureArgs) =>
{
Console.WriteLine($"Failure: {subscribeFailureArgs.Exception}");
},
OnReceive = (subscribeReceiveArgs) =>
{
Console.WriteLine($"Received a message from {subscribeReceiveArgs.PublishingClient.ClientId}: {Serializer.DeserializeString(subscribeReceiveArgs.DataJson)}");
}
});
After (LiveSwitch)
var channelId = "my-channel";
var joinToken = Token.GenerateClientJoinToken(applicationId, null, null, client.Id, channelId, sharedSecret);
try
{
var channel = await client.Join(channelId, joinToken);
channel.OnMessage += (remoteClient, message) =>
{
Console.WriteLine($"Received a message from {remoteClient.Id}: {message}");
};
Console.WriteLine("Success!");
}
catch (Exception ex)
{
Console.WriteLine($"Failure: {ex}");
}
Leave (Unsubscribe)
Leaving in LiveSwitch is equivalent to unsubscribing in WebSync. Before (WebSync)
var channelId = "/my/channel";
client.Unsubscribe(new UnsubscribeArgs(channelId)
{
OnSuccess = (unsubscribeSuccessArgs) =>
{
Console.WriteLine("Success!");
},
OnFailure = (unsubscribeFailureArgs) =>
{
Console.WriteLine($"Failure: {unsubscribeFailureArgs.Exception}");
}
});
After (LiveSwitch)
var channelId = "my-channel";
try
{
await client.Leave(channelId);
Console.WriteLine("Success!");
}
catch (Exception ex)
{
Console.WriteLine($"Failure: {ex}");
}
Send Messages (Publish)
Sending messages in LiveSwitch is equivalent to publishing in WebSync.
LiveSwitch uses Channel
objects for contextual sending and receiving of messages, as opposed to global Client
methods in WebSync. Before (WebSync)
var channelId = "/my/channel";
client.Publish(new PublishArgs(channelId, Serializer.SerializeString("message"))
{
OnSuccess = (publishSuccessArgs) =>
{
Console.WriteLine("Success!");
},
OnFailure = (publishFailureArgs) =>
{
Console.WriteLine($"Failure: {publishFailureArgs.Exception}");
}
});
After (LiveSwitch)
try
{
await channel.SendMessage("message");
Console.WriteLine("Success!");
}
catch (Exception ex)
{
Console.WriteLine($"Failure: {ex}");
}
Channel-level methods to send a message to a specific user, device, or client are also available in LiveSwitch.
Send Private Messages
Sending private messages within a public channel is a LiveSwitch feature has no direct equivalent in WebSync.
For each SendMessage
method variant, there is an equivalent channel-level OnMessage
event variant. Sending Private Messages
try
{
await channel.SendUserMessage("john.doe", "message");
Console.WriteLine("Success!");
}
catch (Exception ex)
{
Console.WriteLine($"Failure: {ex}");
}
Receive Private Messages
channel.OnUserMessage += (remoteClient, message) =>
{
Console.WriteLine($"Received a private message from {remoteClient.Id}: {message}");
};
Server-Side Messaging (Publishers)
The REST API in LiveSwitch replaces the Publisher
class in WebSync.
Because the SDK is no longer required, publishing is a simple matter of sending an HTTP request authorized with a valid API key. Before (WebSync)
var domainKey = "11111111-1111-1111-1111-111111111111";
var channelId = "/my/channel";
var publisher = new Publisher(domainKey);
publisher.Publish(channelId, Serializer.SerializeString("hello"));
After (LiveSwitch)
curl -X POST "https://api.liveswitch.io/Applications/12345678-1234-1234-1234-1234567890ab/SendChannelMessage" \
-H "X-API-Key: aabbccddeeff00112233445566778899 \
-H "Content-Type: application/json" \
-d "{\"channelId\":\"my-channel\",\"message\":{\"message\":\"hello\"}}"
The REST API allows other properties to be spoofed with the message, including the user ID, device ID, user alias, device alias, client tag, and client roles.
Presence
Presence in LiveSwitch is part of the core, instead of an extension like WebSync. Before (WebSync)
client.Subscribe(new SubscribeArgs(...)
{
OnSuccess = (subscribeSuccessArgs) =>
{
foreach (var subscribedClient in subscribeSuccessArgs.GetSubscribedClients()[channelId])
{
if (subscribedClient.ClientId != client.ClientId)
{
Console.WriteLine($"{subscribedClient.ClientId} is already here.");
}
}
}
}
.SetOnClientSubscribe((clientSubscribeArgs) =>
{
Console.WriteLine($"{clientSubscribeArgs.SubscribedClient.ClientId} has joined.");
})
.SetOnClientUnsubscribe((clientUnsubscribeArgs) =>
{
Console.WriteLine($"{clientUnsubscribeArgs.SubscribedClient.ClientId} has left.");
}));
After (LiveSwitch)
var channel = await client.Join(...);
foreach (var remoteClient in channel.RemoteClientInfos)
{
Console.WriteLine($"{remoteClient.Id} is already here.");
}
channel.OnRemoteClientJoin += (remoteClient) =>
{
Console.WriteLine($"{remoteClient.Id} has joined.");
};
channel.OnRemoteClientLeave += (remoteClient) =>
{
Console.WriteLine($"{remoteClient.Id} has left.");
};
Port a Chat App
Let's port a simple .NET chat app from WebSync to LiveSwitch.
Note that with WebSync, you need to separate calls to Connect
, Bind
, and Subscribe
, but with LiveSwitch, a single call to Register
can do it all.
Before (WebSync)
using FM;
using FM.WebSync;
using FM.WebSync.Subscribers;
using System;
using System.Threading.Tasks;
namespace Chat
{
class Program
{
static void Main(string[] args)
{
new Program().Run().GetAwaiter().GetResult();
}
private async Task Run()
{
var exit = new TaskCompletionSource<bool>();
var domainName = "localhost";
var domainKey = "11111111-1111-1111-1111-111111111111";
var channelId = "/my/chat";
var nameKey = "name";
Console.WriteLine("Let's chat!");
Console.WriteLine();
Console.Write("Please enter your name: ");
var name = Console.ReadLine();
Console.WriteLine();
var client = new Client(domainKey, domainName);
Console.WriteLine("Connecting...");
client.Connect(new ConnectArgs
{
OnSuccess = (connectSuccessArgs) =>
{
Console.WriteLine("Connected!");
Console.WriteLine("Binding...");
client.Bind(new BindArgs(nameKey, Serializer.SerializeString(name))
{
OnSuccess = (bindSuccessArgs) =>
{
Console.WriteLine("Bound!");
Console.WriteLine("Subscribing...");
client.Subscribe(new SubscribeArgs(channelId)
{
OnSuccess = (subscribeSuccessArgs) =>
{
Console.WriteLine("Subscribed!");
Console.WriteLine();
_ = Task.Run(() =>
{
Console.WriteLine("Type a message and press Enter to send. Type exit to quit.");
Console.WriteLine();
foreach (var subscribedClient in subscribeSuccessArgs.GetSubscribedClients()[channelId])
{
if (subscribedClient.ClientId != client.ClientId)
{
var remoteName = Serializer.DeserializeString(subscribedClient.GetBoundRecordValueJson(nameKey));
Console.WriteLine($"{remoteName} is already here.");
}
}
while (!exit.Task.IsCompleted)
{
var message = Console.ReadLine();
if (message == "exit")
{
exit.TrySetResult(true);
}
client.Publish(new PublishArgs(channelId, Serializer.SerializeString(message))
{
OnFailure = (publishFailureArgs) =>
{
Console.WriteLine($"Publish failed. {publishFailureArgs.Exception}");
}
});
}
});
},
OnFailure = (subscribeFailureArgs) =>
{
Console.WriteLine($"Subscribe failed. {subscribeFailureArgs.Exception}.");
},
OnReceive = (subscribeReceiveArgs) =>
{
if (subscribeReceiveArgs.PublishingClient.ClientId != client.ClientId)
{
var remoteName = Serializer.DeserializeString(subscribeReceiveArgs.PublishingClient.GetBoundRecordValueJson(nameKey));
Console.WriteLine($"{remoteName}: {Serializer.DeserializeString(subscribeReceiveArgs.DataJson)}");
}
}
}
.SetOnClientSubscribe((clientSubscribeArgs) =>
{
var remoteName = Serializer.DeserializeString(clientSubscribeArgs.SubscribedClient.GetBoundRecordValueJson(nameKey));
Console.WriteLine($"{remoteName} has joined.");
})
.SetOnClientUnsubscribe((clientUnsubscribeArgs) =>
{
var remoteName = Serializer.DeserializeString(clientUnsubscribeArgs.UnsubscribedClient.GetBoundRecordValueJson(nameKey));
Console.WriteLine($"{remoteName} has left.");
}));
},
OnFailure = (bindFailureArgs) =>
{
Console.WriteLine($"Bind failed. {bindFailureArgs.Exception}");
bindFailureArgs.Retry = false;
}
});
},
OnFailure = (connectFailureArgs) =>
{
Console.WriteLine($"Connect failed. {connectFailureArgs.Exception}");
connectFailureArgs.Retry = false;
}
});
await exit.Task;
Console.WriteLine("Disconnecting...");
client.Disconnect(new DisconnectArgs
{
OnComplete = (disconnectCompleteArgs) =>
{
Console.WriteLine("Disconnected!");
}
});
}
}
}
After (LiveSwitch)
using FM.LiveSwitch;
using System;
using System.Threading.Tasks;
namespace Chat
{
class Program
{
static void Main(string[] args)
{
new Program().Run().GetAwaiter().GetResult();
}
private async Task Run()
{
var exit = new TaskCompletionSource<bool>();
var applicationId = "12345678-1234-1234-1234-1234567890ab";
var sharedSecret = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
var channelId = "my-chat";
Console.WriteLine("Let's chat!");
Console.WriteLine();
Console.Write("Please enter your name: ");
var name = Console.ReadLine();
Console.WriteLine();
var client = new Client("https://cloud.liveswitch.io", applicationId)
{
UserAlias = name
};
Console.WriteLine("Registering...");
try
{
var channels = await client.Register(Token.GenerateClientRegisterToken(client, new[] { channelId }, sharedSecret)).AsTaskAsync();
Console.WriteLine("Registered!");
Console.WriteLine();
var channel = channels[0];
Console.WriteLine("Type a message and press Enter to send. Type exit to quit.");
Console.WriteLine();
foreach (var remoteClient in channel.RemoteClientInfos)
{
Console.WriteLine($"{remoteClient.UserAlias} is already here.");
}
channel.OnRemoteClientJoin += (remoteClient) =>
{
Console.WriteLine($"{remoteClient.UserAlias} has joined.");
};
channel.OnRemoteClientLeave += (remoteClient) =>
{
Console.WriteLine($"{remoteClient.UserAlias} has left.");
};
channel.OnMessage += (remoteClient, message) =>
{
if (remoteClient.Id != client.Id)
{
Console.WriteLine($"{remoteClient.UserAlias}: {message}");
}
};
_ = Task.Run(async () =>
{
while (!exit.Task.IsCompleted)
{
var message = Console.ReadLine();
if (message == "exit")
{
exit.TrySetResult(true);
}
try
{
await channel.SendMessage(message).AsTaskAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Send failed. {ex}");
}
}
});
}
catch (Exception ex)
{
Console.WriteLine($"Register failed. {ex}");
}
await exit.Task;
Console.WriteLine("Unregistering...");
try
{
await client.Unregister().AsTaskAsync();
Console.WriteLine("Unregistered!");
}
catch (Exception ex)
{
Console.WriteLine($"Unregister failed. {ex}");
}
}
}
}