Add connection id related apis (#68)
* Add connection id related apis * Update to resolve parameter order * Add sample with connectionId APIs * Encode the connectonId
This commit is contained in:
Родитель
bcc4e549f8
Коммит
415e35d9cb
|
@ -26,12 +26,18 @@ public class SignalRGroupAction {
|
|||
* @param groupName Group to add user to or remove user from
|
||||
* @param userId User to add to or remove from group
|
||||
*/
|
||||
public SignalRGroupAction(String action, String groupName, String userId) {
|
||||
public SignalRGroupAction(String action, String groupName, String userId, String connectionId) {
|
||||
this.action = action;
|
||||
this.groupName = groupName;
|
||||
this.userId = userId;
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection to add to or remove from group
|
||||
*/
|
||||
public String connectionId = "";
|
||||
|
||||
/**
|
||||
* User to add to or remove from group
|
||||
*/
|
||||
|
|
|
@ -35,6 +35,11 @@ public class SignalRMessage {
|
|||
this.arguments.addAll(Arrays.asList(arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* ConnectionId to send the message to
|
||||
*/
|
||||
public String connectionId = "";
|
||||
|
||||
/**
|
||||
* User to send the message to
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
<PropertyGroup Label="Package Versions">
|
||||
<MicroBuildCorePackageVersion>0.3.0</MicroBuildCorePackageVersion>
|
||||
<MicrosoftAzureSignalRManagement>1.0.0-preview1-10420</MicrosoftAzureSignalRManagement>
|
||||
<MicrosoftAzureSignalRManagement>1.0.0-preview1-10449</MicrosoftAzureSignalRManagement>
|
||||
<MicrosoftAzureWebJobsPackageVersion>3.0.4</MicrosoftAzureWebJobsPackageVersion>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.8.0</MicrosoftNETTestSdkPackageVersion>
|
||||
<MoqPackageVersion>4.9.0</MoqPackageVersion>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
|
||||
$(RestoreSources);
|
||||
https://api.nuget.org/v3/index.json;
|
||||
https://www.myget.org/F/azure-signalr-dev/api/v3/index.json
|
||||
</RestoreSources>
|
||||
<RestoreSources Condition="'$(UseLocalFeed)' == 'true'">
|
||||
$(LocalFeed);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Serverless Chat</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css">
|
||||
|
@ -6,10 +7,13 @@
|
|||
window.apiBaseUrl = 'http://localhost:7071';
|
||||
</script>
|
||||
<style>
|
||||
.slide-fade-enter-active, .slide-fade-leave-active {
|
||||
.slide-fade-enter-active,
|
||||
.slide-fade-leave-active {
|
||||
transition: all 1s ease;
|
||||
}
|
||||
.slide-fade-enter, .slide-fade-leave-to {
|
||||
|
||||
.slide-fade-enter,
|
||||
.slide-fade-leave-to {
|
||||
height: 0px;
|
||||
overflow-y: hidden;
|
||||
opacity: 0;
|
||||
|
@ -29,7 +33,8 @@
|
|||
<label for="checkbox">Send To Default Group: {{ this.defaultgroup }}</label>
|
||||
</div>
|
||||
<form v-on:submit.prevent="sendNewMessage(checked)">
|
||||
<input type="text" v-model="newMessage" id="message-box" class="form-control" placeholder="Type message here..." />
|
||||
<input type="text" v-model="newMessage" id="message-box" class="form-control"
|
||||
placeholder="Type message here..." />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,13 +54,21 @@
|
|||
<a href="#" v-on:click.prevent="sendPrivateMessage(message.Sender)">
|
||||
<span class="text-info small"><strong>{{ message.Sender || message.sender }}</strong></span>
|
||||
</a>
|
||||
<a href="#" v-on:click.prevent="addToGroup(message.Sender || message.sender)">
|
||||
<span v-if="message.ConnectionId || message.connectionId" class="badge badge-secondary">Connection: {{ message.ConnectionId || message.connectionId }}</span>
|
||||
<a href="#" v-on:click.prevent="addToGroup(null, message.Sender || message.sender)">
|
||||
<span class="badge badge-primary">AddGroup</span>
|
||||
</a>
|
||||
<a href="#" v-on:click.prevent="removeFromGroup(message.Sender || message.sender)">
|
||||
<span class="badge badge-primary">RemoveGroup</span>
|
||||
</a>
|
||||
<span v-if="message.IsPrivate || message.isPrivate" class="badge badge-secondary">private message</span>
|
||||
<a href="#" v-on:click.prevent="removeFromGroup(null, message.Sender || message.sender)">
|
||||
<span class="badge badge-primary">RemoveGroup</span>
|
||||
</a>
|
||||
<a href="#" v-on:click.prevent="addToGroup(message.ConnectionId || message.connectionId, message.Sender || message.sender)">
|
||||
<span v-if="message.ConnectionId || message.connectionId" class="badge badge-primary">AddConnectionToGroup</span>
|
||||
</a>
|
||||
<a href="#" v-on:click.prevent="removeFromGroup(message.ConnectionId || message.connectionId, message.Sender || message.sender)">
|
||||
<span v-if="message.ConnectionId || message.connectionId" class="badge badge-primary">RemoveConnectionFromGroup</span>
|
||||
</a>
|
||||
<span v-if="message.IsPrivate || message.isPrivate" class="badge badge-secondary">private
|
||||
message</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ message.Text || message.text }}
|
||||
|
@ -64,123 +77,146 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.0.3/dist/browser/signalr.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.0.3/dist/browser/signalr.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
|
||||
|
||||
<script>
|
||||
const data = {
|
||||
username: '',
|
||||
defaultgroup: 'AzureSignalR',
|
||||
checked: false,
|
||||
newMessage: '',
|
||||
messages: [],
|
||||
ready: false
|
||||
};
|
||||
const app = new Vue({
|
||||
el: '#app',
|
||||
data: data,
|
||||
methods: {
|
||||
sendNewMessage: function (isToGroup) {
|
||||
if(isToGroup) {
|
||||
sendMessage(this.username, null, this.defaultgroup, this.newMessage);
|
||||
}
|
||||
else {
|
||||
sendMessage(this.username, null, null, this.newMessage);
|
||||
}
|
||||
this.newMessage = '';
|
||||
},
|
||||
sendPrivateMessage: function (recipient) {
|
||||
const messageText = prompt('Send private message to ' + recipient);
|
||||
if (messageText) {
|
||||
sendMessage(this.username, recipient, null, messageText);
|
||||
}
|
||||
},
|
||||
addToGroup: function (recipient) {
|
||||
var r = confirm('Add user ' + recipient + ' to group: ' + this.defaultgroup);
|
||||
if(r) {
|
||||
addGroup(this.username, recipient, this.defaultgroup);
|
||||
}
|
||||
},
|
||||
removeFromGroup: function (recipient) {
|
||||
var r = confirm('Remove user ' + recipient + ' from group: ' + this.defaultgroup);
|
||||
if(r) {
|
||||
removeGroup(this.username, recipient, this.defaultgroup);
|
||||
<script>
|
||||
const data = {
|
||||
username: '',
|
||||
defaultgroup: 'AzureSignalR',
|
||||
checked: false,
|
||||
newMessage: '',
|
||||
messages: [],
|
||||
myConnectionId: '',
|
||||
ready: false
|
||||
};
|
||||
const app = new Vue({
|
||||
el: '#app',
|
||||
data: data,
|
||||
methods: {
|
||||
sendNewMessage: function (isToGroup) {
|
||||
if (isToGroup) {
|
||||
sendMessage(this.username, null, this.defaultgroup, this.newMessage);
|
||||
}
|
||||
else {
|
||||
sendMessage(this.username, null, null, this.newMessage);
|
||||
}
|
||||
this.newMessage = '';
|
||||
},
|
||||
sendPrivateMessage: function (recipient) {
|
||||
const messageText = prompt('Send private message to ' + recipient);
|
||||
|
||||
if (messageText) {
|
||||
sendMessage(this.username, recipient, null, messageText);
|
||||
}
|
||||
},
|
||||
addToGroup: function (connectionId, recipient) {
|
||||
var r;
|
||||
if (connectionId) {
|
||||
r = confirm('Add connection ' + connectionId + ' to group: ' + this.defaultgroup);
|
||||
} else {
|
||||
r = confirm('Add user ' + recipient + ' to group: ' + this.defaultgroup);
|
||||
}
|
||||
|
||||
if (r) {
|
||||
addGroup(this.username, recipient, connectionId, this.defaultgroup);
|
||||
}
|
||||
},
|
||||
removeFromGroup: function (connectionId, recipient) {
|
||||
var r;
|
||||
if (connectionId) {
|
||||
r = confirm('Remove connection ' + connectionId + ' from group: ' + this.defaultgroup);
|
||||
} else {
|
||||
r = confirm('Remove user ' + recipient + ' from group: ' + this.defaultgroup);
|
||||
}
|
||||
|
||||
if (r) {
|
||||
removeGroup(this.username, recipient, connectionId, this.defaultgroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const apiBaseUrl = prompt('Enter the Azure Function app base URL', window.apiBaseUrl);
|
||||
data.username = prompt("Enter your username");
|
||||
if (!data.username) {
|
||||
alert("No username entered. Reload page and try again.");
|
||||
throw "No username entered";
|
||||
}
|
||||
});
|
||||
const apiBaseUrl = prompt('Enter the Azure Function app base URL', window.apiBaseUrl);
|
||||
data.username = prompt("Enter your username");
|
||||
if (!data.username) {
|
||||
alert("No username entered. Reload page and try again.");
|
||||
throw "No username entered";
|
||||
}
|
||||
getConnectionInfo().then(info => {
|
||||
// make compatible with old and new SignalRConnectionInfo
|
||||
info.accessToken = info.accessToken || info.accessKey;
|
||||
info.url = info.url || info.endpoint;
|
||||
data.ready = true;
|
||||
const options = {
|
||||
accessTokenFactory: () => info.accessToken
|
||||
};
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(info.url, options)
|
||||
.configureLogging(signalR.LogLevel.Information)
|
||||
.build();
|
||||
connection.on('newMessage', newMessage);
|
||||
connection.onclose(() => console.log('disconnected'));
|
||||
console.log('connecting...');
|
||||
connection.start()
|
||||
.then(() => console.log('connected!'))
|
||||
.catch(console.error);
|
||||
}).catch(alert);
|
||||
function getAxiosConfig() {
|
||||
const config = {
|
||||
headers: {'x-ms-signalr-userid': data.username}
|
||||
};
|
||||
return config;
|
||||
}
|
||||
function getConnectionInfo() {
|
||||
return axios.post(`${apiBaseUrl}/api/negotiate`, null , getAxiosConfig())
|
||||
.then(resp => resp.data);
|
||||
}
|
||||
function sendMessage(sender, recipient, groupname, messageText) {
|
||||
return axios.post(`${apiBaseUrl}/api/messages`, {
|
||||
recipient: recipient,
|
||||
isPrivate: recipient != null,
|
||||
groupname: groupname,
|
||||
sender: sender,
|
||||
text: messageText
|
||||
}, getAxiosConfig()).then(resp => resp.data);
|
||||
}
|
||||
function addGroup(sender, recipient, groupName) {
|
||||
return axios.post(`${apiBaseUrl}/api/addToGroup`, {
|
||||
recipient: recipient,
|
||||
groupname: groupName
|
||||
}, getAxiosConfig()).then(resp => {
|
||||
if(resp.status == 200) {
|
||||
getConnectionInfo().then(info => {
|
||||
// make compatible with old and new SignalRConnectionInfo
|
||||
info.accessToken = info.accessToken || info.accessKey;
|
||||
info.url = info.url || info.endpoint;
|
||||
data.ready = true;
|
||||
const options = {
|
||||
accessTokenFactory: () => info.accessToken
|
||||
};
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(info.url, options)
|
||||
.configureLogging(signalR.LogLevel.Information)
|
||||
.build();
|
||||
connection.on('newMessage', newMessage);
|
||||
connection.on('newConnection', newConnection)
|
||||
connection.onclose(() => console.log('disconnected'));
|
||||
console.log('connecting...');
|
||||
connection.start()
|
||||
.then(() => console.log('connected!'))
|
||||
.catch(console.error);
|
||||
}).catch(alert);
|
||||
function getAxiosConfig() {
|
||||
const config = {
|
||||
headers: { 'x-ms-signalr-userid': data.username }
|
||||
};
|
||||
return config;
|
||||
}
|
||||
function getConnectionInfo() {
|
||||
return axios.post(`${apiBaseUrl}/api/negotiate`, null, getAxiosConfig())
|
||||
.then(resp => resp.data);
|
||||
}
|
||||
function sendMessage(sender, recipient, groupname, messageText) {
|
||||
return axios.post(`${apiBaseUrl}/api/messages`, {
|
||||
connectionId: data.myConnectionId,
|
||||
recipient: recipient,
|
||||
isPrivate: recipient != null,
|
||||
groupname: groupname,
|
||||
sender: sender,
|
||||
text: messageText
|
||||
}, getAxiosConfig()).then(resp => resp.data);
|
||||
}
|
||||
function addGroup(sender, recipient, connectionId, groupName) {
|
||||
return axios.post(`${apiBaseUrl}/api/addToGroup`, {
|
||||
connectionId: connectionId,
|
||||
recipient: recipient,
|
||||
groupname: groupName
|
||||
}, getAxiosConfig()).then(resp => {
|
||||
if (resp.status == 200) {
|
||||
confirm("Add Successfully")
|
||||
}});
|
||||
}
|
||||
function removeGroup(sender, recipient, groupName) {
|
||||
return axios.post(`${apiBaseUrl}/api/removeFromGroup`, {
|
||||
recipient: recipient,
|
||||
groupname: groupName
|
||||
}, getAxiosConfig()).then(resp => {
|
||||
if(resp.status == 200) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function removeGroup(sender, recipient, connectionId, groupName) {
|
||||
return axios.post(`${apiBaseUrl}/api/removeFromGroup`, {
|
||||
connectionId: connectionId,
|
||||
recipient: recipient,
|
||||
groupname: groupName
|
||||
}, getAxiosConfig()).then(resp => {
|
||||
if (resp.status == 200) {
|
||||
confirm("Remove Successfully")
|
||||
}});
|
||||
}
|
||||
let counter = 0;
|
||||
function newMessage(message) {
|
||||
message.id = counter++; // vue transitions need an id
|
||||
data.messages.unshift(message);
|
||||
}
|
||||
</script>
|
||||
}
|
||||
});
|
||||
}
|
||||
let counter = 0;
|
||||
function newMessage(message) {
|
||||
message.id = counter++; // vue transitions need an id
|
||||
data.messages.unshift(message);
|
||||
};
|
||||
function newConnection(message) {
|
||||
data.myConnectionId = message.ConnectionId;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -4,6 +4,7 @@
|
|||
<AzureFunctionsVersion>v2</AzureFunctionsVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.EventGrid" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -3,14 +3,17 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Azure.EventGrid.Models;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace FunctionApp
|
||||
{
|
||||
|
@ -39,7 +42,6 @@ namespace FunctionApp
|
|||
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest req,
|
||||
[SignalR(HubName = "simplechat")]IAsyncCollector<SignalRMessage> signalRMessages)
|
||||
{
|
||||
|
||||
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
||||
|
||||
return signalRMessages.AddAsync(
|
||||
|
@ -60,10 +62,12 @@ namespace FunctionApp
|
|||
|
||||
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
||||
|
||||
var decodedfConnectionId = GetBase64DecodedString(message.ConnectionId);
|
||||
|
||||
return signalRGroupActions.AddAsync(
|
||||
new SignalRGroupAction
|
||||
{
|
||||
ConnectionId = decodedfConnectionId,
|
||||
UserId = message.Recipient,
|
||||
GroupName = message.Groupname,
|
||||
Action = GroupAction.Add
|
||||
|
@ -78,23 +82,83 @@ namespace FunctionApp
|
|||
|
||||
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
||||
|
||||
var decodedfConnectionId = GetBase64DecodedString(message.ConnectionId);
|
||||
|
||||
return signalRGroupActions.AddAsync(
|
||||
new SignalRGroupAction
|
||||
{
|
||||
ConnectionId = message.ConnectionId,
|
||||
UserId = message.Recipient,
|
||||
GroupName = message.Groupname,
|
||||
Action = GroupAction.Remove
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetBase64EncodedString(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(source));
|
||||
}
|
||||
|
||||
private static string GetBase64DecodedString(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(Convert.FromBase64String(source));
|
||||
}
|
||||
|
||||
|
||||
public static class EventGridTriggerCSharp
|
||||
{
|
||||
[FunctionName("onConnection")]
|
||||
public static Task EventGridTest([EventGridTrigger]EventGridEvent eventGridEvent,
|
||||
[SignalR(HubName = "simplechat")]IAsyncCollector<SignalRMessage> signalRMessages)
|
||||
{
|
||||
if (eventGridEvent.EventType == "Microsoft.SignalRService.ClientConnectionConnected")
|
||||
{
|
||||
var message = ((JObject) eventGridEvent.Data).ToObject<SignalREvent>();
|
||||
|
||||
return signalRMessages.AddAsync(
|
||||
new SignalRMessage
|
||||
{
|
||||
ConnectionId = message.ConnectionId,
|
||||
Target = "newConnection",
|
||||
Arguments = new[] { new ChatMessage
|
||||
{
|
||||
// ConnectionId is not recommand to send to client directly.
|
||||
// Here's a simple encryption for an easier sample.
|
||||
ConnectionId = GetBase64EncodedString(message.ConnectionId),
|
||||
}}
|
||||
});
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class ChatMessage
|
||||
{
|
||||
public string Sender { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Groupname { get; set; }
|
||||
public string Recipient { get; set; }
|
||||
public string ConnectionId { get; set; }
|
||||
public bool IsPrivate { get; set; }
|
||||
}
|
||||
|
||||
public class SignalREvent
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string HubName { get; set; }
|
||||
public string ConnectionId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,12 +38,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
|||
Arguments = message.Arguments
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(message.UserId) && !string.IsNullOrEmpty(message.GroupName))
|
||||
if (!string.IsNullOrEmpty(message.ConnectionId))
|
||||
{
|
||||
throw new ArgumentException("GroupName and UserId can not be specified at the same time.");
|
||||
await client.SendToConnection(hubName, message.ConnectionId, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message.UserId))
|
||||
else if (!string.IsNullOrEmpty(message.UserId))
|
||||
{
|
||||
await client.SendToUser(hubName, message.UserId, data).ConfigureAwait(false);
|
||||
}
|
||||
|
@ -59,13 +58,32 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
|||
else if (convertItem.GetType() == typeof(SignalRGroupAction))
|
||||
{
|
||||
SignalRGroupAction groupAction = convertItem as SignalRGroupAction;
|
||||
if (groupAction.Action == GroupAction.Add)
|
||||
|
||||
if (!string.IsNullOrEmpty(groupAction.ConnectionId))
|
||||
{
|
||||
await client.AddUserToGroup(hubName, groupAction.UserId, groupAction.GroupName).ConfigureAwait(false);
|
||||
if (groupAction.Action == GroupAction.Add)
|
||||
{
|
||||
await client.AddConnectionToGroup(hubName, groupAction.ConnectionId, groupAction.GroupName).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await client.RemoveConnectionFromGroup(hubName, groupAction.ConnectionId, groupAction.GroupName).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(groupAction.UserId))
|
||||
{
|
||||
if (groupAction.Action == GroupAction.Add)
|
||||
{
|
||||
await client.AddUserToGroup(hubName, groupAction.UserId, groupAction.GroupName).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await client.RemoveUserFromGroup(hubName, groupAction.UserId, groupAction.GroupName).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await client.RemoveUserFromGroup(hubName, groupAction.UserId, groupAction.GroupName).ConfigureAwait(false);
|
||||
throw new ArgumentException($"ConnectionId and UserId cannot be null or empty together");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -7,10 +7,19 @@ using System.Runtime.Serialization;
|
|||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that contains parameters needed for group operations.
|
||||
/// Either the group operation on connectionId or userId is supported.
|
||||
/// If connectionId and userId are both set, it will be resolved by the following order:
|
||||
/// 1. ConnectionId
|
||||
/// 2. UserId
|
||||
/// </summary>
|
||||
[JsonObject]
|
||||
public class SignalRGroupAction
|
||||
{
|
||||
[JsonProperty("userId"), JsonRequired]
|
||||
[JsonProperty("connectionId")]
|
||||
public string ConnectionId { get; set; }
|
||||
[JsonProperty("userId")]
|
||||
public string UserId { get; set; }
|
||||
[JsonProperty("groupName"), JsonRequired]
|
||||
public string GroupName { get; set; }
|
||||
|
|
|
@ -5,9 +5,19 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that contains parameters needed for sending messages.
|
||||
/// There are three kinds of scope to send, and if more than one
|
||||
/// scopes are set, it will be resolved by the following order:
|
||||
/// 1. ConnectionId
|
||||
/// 2. UserId
|
||||
/// 3. GroupName
|
||||
/// </summary>
|
||||
[JsonObject]
|
||||
public class SignalRMessage
|
||||
{
|
||||
[JsonProperty("connectionId")]
|
||||
public string ConnectionId { get; set; }
|
||||
[JsonProperty("userId")]
|
||||
public string UserId { get; set; }
|
||||
[JsonProperty("groupName")]
|
||||
|
|
|
@ -60,6 +60,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
|||
await serviceHubContext.Clients.All.SendCoreAsync(data.Target, data.Arguments);
|
||||
}
|
||||
|
||||
public async Task SendToConnection(string hubName, string connectionId, SignalRData data)
|
||||
{
|
||||
var serviceHubContext = await serviceHubContextStore.GetOrAddAsync(hubName);
|
||||
await serviceHubContext.Clients.Client(connectionId).SendCoreAsync(data.Target, data.Arguments);
|
||||
}
|
||||
|
||||
public async Task SendToUser(string hubName, string userId, SignalRData data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
|
@ -108,6 +114,34 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
|||
await serviceHubContext.UserGroups.RemoveFromGroupAsync(userId, groupName);
|
||||
}
|
||||
|
||||
public async Task AddConnectionToGroup(string hubName, string connectionId, string groupName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(connectionId))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(connectionId)} cannot be null or empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(groupName))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(groupName)} cannot be null or empty");
|
||||
}
|
||||
var serviceHubContext = await serviceHubContextStore.GetOrAddAsync(hubName);
|
||||
await serviceHubContext.Groups.AddToGroupAsync(connectionId, groupName);
|
||||
}
|
||||
|
||||
public async Task RemoveConnectionFromGroup(string hubName, string connectionId, string groupName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(connectionId))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(connectionId)} cannot be null or empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(groupName))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(groupName)} cannot be null or empty");
|
||||
}
|
||||
var serviceHubContext = await serviceHubContextStore.GetOrAddAsync(hubName);
|
||||
await serviceHubContext.Groups.RemoveFromGroupAsync(connectionId, groupName);
|
||||
}
|
||||
|
||||
private static IEnumerable<Claim> BuildJwtClaims(IEnumerable<Claim> customerClaims, string prefix)
|
||||
{
|
||||
if (customerClaims != null)
|
||||
|
|
|
@ -9,9 +9,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
|||
internal interface IAzureSignalRSender
|
||||
{
|
||||
Task SendToAll(string hubName, SignalRData data);
|
||||
Task SendToConnection(string hubName, string connectionId, SignalRData data);
|
||||
Task SendToUser(string hubName, string userId, SignalRData data);
|
||||
Task SendToGroup(string hubName, string group, SignalRData data);
|
||||
Task AddUserToGroup(string hubName, string userId, string groupName);
|
||||
Task RemoveUserFromGroup(string hubName, string userId, string groupName);
|
||||
Task AddConnectionToGroup(string hubName, string connectionId, string groupName);
|
||||
Task RemoveConnectionFromGroup(string hubName, string connectionId, string groupName);
|
||||
}
|
||||
}
|
|
@ -135,17 +135,108 @@ namespace SignalRServiceExtension.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAsync_SendMessage_WithBothUserIdAndGroupNameThrowException()
|
||||
public async Task AddAsync_SendMessage_WithBothUserIdAndGroupName_UsePriorityOrder()
|
||||
{
|
||||
var signalRSenderMock = new Mock<IAzureSignalRSender>();
|
||||
var collector = new SignalRAsyncCollector<SignalRMessage>(signalRSenderMock.Object, "chathub");
|
||||
|
||||
var item = new SignalRMessage
|
||||
await collector.AddAsync(new SignalRMessage
|
||||
{
|
||||
UserId = "user1",
|
||||
GroupName = "group1",
|
||||
Target = "newMessage",
|
||||
Arguments = new object[] { "arg1", "arg2" }
|
||||
});
|
||||
|
||||
signalRSenderMock.Verify(
|
||||
c => c.SendToUser("chathub", "user1", It.IsAny<SignalRData>()),
|
||||
Times.Once);
|
||||
signalRSenderMock.VerifyNoOtherCalls();
|
||||
var actualData = (SignalRData)signalRSenderMock.Invocations[0].Arguments[2];
|
||||
Assert.Equal("newMessage", actualData.Target);
|
||||
Assert.Equal("arg1", actualData.Arguments[0]);
|
||||
Assert.Equal("arg2", actualData.Arguments[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAsync_WithConnectionId_CallsSendToUser()
|
||||
{
|
||||
var signalRSenderMock = new Mock<IAzureSignalRSender>();
|
||||
var collector = new SignalRAsyncCollector<SignalRMessage>(signalRSenderMock.Object, "chathub");
|
||||
|
||||
await collector.AddAsync(new SignalRMessage
|
||||
{
|
||||
ConnectionId = "connection1",
|
||||
Target = "newMessage",
|
||||
Arguments = new object[] { "arg1", "arg2" }
|
||||
});
|
||||
|
||||
signalRSenderMock.Verify(
|
||||
c => c.SendToConnection("chathub", "connection1", It.IsAny<SignalRData>()),
|
||||
Times.Once);
|
||||
signalRSenderMock.VerifyNoOtherCalls();
|
||||
var actualData = (SignalRData)signalRSenderMock.Invocations[0].Arguments[2];
|
||||
Assert.Equal("newMessage", actualData.Target);
|
||||
Assert.Equal("arg1", actualData.Arguments[0]);
|
||||
Assert.Equal("arg2", actualData.Arguments[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAsync_WithConnectionId_CallsAddConnectionToGroup()
|
||||
{
|
||||
var signalRSenderMock = new Mock<IAzureSignalRSender>();
|
||||
var collector = new SignalRAsyncCollector<SignalRGroupAction>(signalRSenderMock.Object, "chathub");
|
||||
|
||||
await collector.AddAsync(new SignalRGroupAction
|
||||
{
|
||||
ConnectionId = "connection1",
|
||||
GroupName = "group1",
|
||||
Action = GroupAction.Add
|
||||
});
|
||||
|
||||
signalRSenderMock.Verify(
|
||||
c => c.AddConnectionToGroup("chathub", "connection1", "group1"),
|
||||
Times.Once);
|
||||
signalRSenderMock.VerifyNoOtherCalls();
|
||||
var actualData = signalRSenderMock.Invocations[0];
|
||||
Assert.Equal("chathub", actualData.Arguments[0]);
|
||||
Assert.Equal("connection1", actualData.Arguments[1]);
|
||||
Assert.Equal("group1", actualData.Arguments[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAsync_WithConnectionId_CallsRemoveConnectionFromGroup()
|
||||
{
|
||||
var signalRSenderMock = new Mock<IAzureSignalRSender>();
|
||||
var collector = new SignalRAsyncCollector<SignalRGroupAction>(signalRSenderMock.Object, "chathub");
|
||||
|
||||
await collector.AddAsync(new SignalRGroupAction
|
||||
{
|
||||
ConnectionId = "connection1",
|
||||
GroupName = "group1",
|
||||
Action = GroupAction.Remove
|
||||
});
|
||||
|
||||
signalRSenderMock.Verify(
|
||||
c => c.RemoveConnectionFromGroup("chathub", "connection1", "group1"),
|
||||
Times.Once);
|
||||
signalRSenderMock.VerifyNoOtherCalls();
|
||||
var actualData = signalRSenderMock.Invocations[0];
|
||||
Assert.Equal("chathub", actualData.Arguments[0]);
|
||||
Assert.Equal("connection1", actualData.Arguments[1]);
|
||||
Assert.Equal("group1", actualData.Arguments[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAsync_GroupOperation_WithoutParametersThrowException()
|
||||
{
|
||||
var signalRSenderMock = new Mock<IAzureSignalRSender>();
|
||||
var collector = new SignalRAsyncCollector<SignalRGroupAction>(signalRSenderMock.Object, "chathub");
|
||||
|
||||
var item = new SignalRGroupAction
|
||||
{
|
||||
GroupName = "group1",
|
||||
Action = GroupAction.Add
|
||||
};
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => collector.AddAsync(item));
|
||||
|
|
Загрузка…
Ссылка в новой задаче