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 groupName Group to add user to or remove user from
|
||||||
* @param userId User to add to or remove from group
|
* @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.action = action;
|
||||||
this.groupName = groupName;
|
this.groupName = groupName;
|
||||||
this.userId = userId;
|
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
|
* User to add to or remove from group
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -35,6 +35,11 @@ public class SignalRMessage {
|
||||||
this.arguments.addAll(Arrays.asList(arguments));
|
this.arguments.addAll(Arrays.asList(arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectionId to send the message to
|
||||||
|
*/
|
||||||
|
public String connectionId = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User to send the message to
|
* User to send the message to
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Package Versions">
|
<PropertyGroup Label="Package Versions">
|
||||||
<MicroBuildCorePackageVersion>0.3.0</MicroBuildCorePackageVersion>
|
<MicroBuildCorePackageVersion>0.3.0</MicroBuildCorePackageVersion>
|
||||||
<MicrosoftAzureSignalRManagement>1.0.0-preview1-10420</MicrosoftAzureSignalRManagement>
|
<MicrosoftAzureSignalRManagement>1.0.0-preview1-10449</MicrosoftAzureSignalRManagement>
|
||||||
<MicrosoftAzureWebJobsPackageVersion>3.0.4</MicrosoftAzureWebJobsPackageVersion>
|
<MicrosoftAzureWebJobsPackageVersion>3.0.4</MicrosoftAzureWebJobsPackageVersion>
|
||||||
<MicrosoftNETTestSdkPackageVersion>15.8.0</MicrosoftNETTestSdkPackageVersion>
|
<MicrosoftNETTestSdkPackageVersion>15.8.0</MicrosoftNETTestSdkPackageVersion>
|
||||||
<MoqPackageVersion>4.9.0</MoqPackageVersion>
|
<MoqPackageVersion>4.9.0</MoqPackageVersion>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
|
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
|
||||||
$(RestoreSources);
|
$(RestoreSources);
|
||||||
https://api.nuget.org/v3/index.json;
|
https://api.nuget.org/v3/index.json;
|
||||||
|
https://www.myget.org/F/azure-signalr-dev/api/v3/index.json
|
||||||
</RestoreSources>
|
</RestoreSources>
|
||||||
<RestoreSources Condition="'$(UseLocalFeed)' == 'true'">
|
<RestoreSources Condition="'$(UseLocalFeed)' == 'true'">
|
||||||
$(LocalFeed);
|
$(LocalFeed);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Serverless Chat</title>
|
<title>Serverless Chat</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css">
|
<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';
|
window.apiBaseUrl = 'http://localhost:7071';
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.slide-fade-enter-active, .slide-fade-leave-active {
|
.slide-fade-enter-active,
|
||||||
|
.slide-fade-leave-active {
|
||||||
transition: all 1s ease;
|
transition: all 1s ease;
|
||||||
}
|
}
|
||||||
.slide-fade-enter, .slide-fade-leave-to {
|
|
||||||
|
.slide-fade-enter,
|
||||||
|
.slide-fade-leave-to {
|
||||||
height: 0px;
|
height: 0px;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -29,7 +33,8 @@
|
||||||
<label for="checkbox">Send To Default Group: {{ this.defaultgroup }}</label>
|
<label for="checkbox">Send To Default Group: {{ this.defaultgroup }}</label>
|
||||||
</div>
|
</div>
|
||||||
<form v-on:submit.prevent="sendNewMessage(checked)">
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,13 +54,21 @@
|
||||||
<a href="#" v-on:click.prevent="sendPrivateMessage(message.Sender)">
|
<a href="#" v-on:click.prevent="sendPrivateMessage(message.Sender)">
|
||||||
<span class="text-info small"><strong>{{ message.Sender || message.sender }}</strong></span>
|
<span class="text-info small"><strong>{{ message.Sender || message.sender }}</strong></span>
|
||||||
</a>
|
</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>
|
<span class="badge badge-primary">AddGroup</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" v-on:click.prevent="removeFromGroup(message.Sender || message.sender)">
|
<a href="#" v-on:click.prevent="removeFromGroup(null, message.Sender || message.sender)">
|
||||||
<span class="badge badge-primary">RemoveGroup</span>
|
<span class="badge badge-primary">RemoveGroup</span>
|
||||||
</a>
|
</a>
|
||||||
<span v-if="message.IsPrivate || message.isPrivate" class="badge badge-secondary">private message</span>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
{{ message.Text || message.text }}
|
{{ message.Text || message.text }}
|
||||||
|
@ -64,123 +77,146 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.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/@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/axios@0.18.0/dist/axios.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const data = {
|
const data = {
|
||||||
username: '',
|
username: '',
|
||||||
defaultgroup: 'AzureSignalR',
|
defaultgroup: 'AzureSignalR',
|
||||||
checked: false,
|
checked: false,
|
||||||
newMessage: '',
|
newMessage: '',
|
||||||
messages: [],
|
messages: [],
|
||||||
ready: false
|
myConnectionId: '',
|
||||||
};
|
ready: false
|
||||||
const app = new Vue({
|
};
|
||||||
el: '#app',
|
const app = new Vue({
|
||||||
data: data,
|
el: '#app',
|
||||||
methods: {
|
data: data,
|
||||||
sendNewMessage: function (isToGroup) {
|
methods: {
|
||||||
if(isToGroup) {
|
sendNewMessage: function (isToGroup) {
|
||||||
sendMessage(this.username, null, this.defaultgroup, this.newMessage);
|
if (isToGroup) {
|
||||||
}
|
sendMessage(this.username, null, this.defaultgroup, this.newMessage);
|
||||||
else {
|
}
|
||||||
sendMessage(this.username, null, null, this.newMessage);
|
else {
|
||||||
}
|
sendMessage(this.username, null, null, this.newMessage);
|
||||||
this.newMessage = '';
|
}
|
||||||
},
|
this.newMessage = '';
|
||||||
sendPrivateMessage: function (recipient) {
|
},
|
||||||
const messageText = prompt('Send private message to ' + recipient);
|
sendPrivateMessage: function (recipient) {
|
||||||
if (messageText) {
|
const messageText = prompt('Send private message to ' + recipient);
|
||||||
sendMessage(this.username, recipient, null, messageText);
|
|
||||||
}
|
if (messageText) {
|
||||||
},
|
sendMessage(this.username, recipient, null, messageText);
|
||||||
addToGroup: function (recipient) {
|
}
|
||||||
var r = confirm('Add user ' + recipient + ' to group: ' + this.defaultgroup);
|
},
|
||||||
if(r) {
|
addToGroup: function (connectionId, recipient) {
|
||||||
addGroup(this.username, recipient, this.defaultgroup);
|
var r;
|
||||||
}
|
if (connectionId) {
|
||||||
},
|
r = confirm('Add connection ' + connectionId + ' to group: ' + this.defaultgroup);
|
||||||
removeFromGroup: function (recipient) {
|
} else {
|
||||||
var r = confirm('Remove user ' + recipient + ' from group: ' + this.defaultgroup);
|
r = confirm('Add user ' + recipient + ' to group: ' + this.defaultgroup);
|
||||||
if(r) {
|
}
|
||||||
removeGroup(this.username, recipient, 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";
|
||||||
}
|
}
|
||||||
});
|
getConnectionInfo().then(info => {
|
||||||
const apiBaseUrl = prompt('Enter the Azure Function app base URL', window.apiBaseUrl);
|
// make compatible with old and new SignalRConnectionInfo
|
||||||
data.username = prompt("Enter your username");
|
info.accessToken = info.accessToken || info.accessKey;
|
||||||
if (!data.username) {
|
info.url = info.url || info.endpoint;
|
||||||
alert("No username entered. Reload page and try again.");
|
data.ready = true;
|
||||||
throw "No username entered";
|
const options = {
|
||||||
}
|
accessTokenFactory: () => info.accessToken
|
||||||
getConnectionInfo().then(info => {
|
};
|
||||||
// make compatible with old and new SignalRConnectionInfo
|
const connection = new signalR.HubConnectionBuilder()
|
||||||
info.accessToken = info.accessToken || info.accessKey;
|
.withUrl(info.url, options)
|
||||||
info.url = info.url || info.endpoint;
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
data.ready = true;
|
.build();
|
||||||
const options = {
|
connection.on('newMessage', newMessage);
|
||||||
accessTokenFactory: () => info.accessToken
|
connection.on('newConnection', newConnection)
|
||||||
};
|
connection.onclose(() => console.log('disconnected'));
|
||||||
const connection = new signalR.HubConnectionBuilder()
|
console.log('connecting...');
|
||||||
.withUrl(info.url, options)
|
connection.start()
|
||||||
.configureLogging(signalR.LogLevel.Information)
|
.then(() => console.log('connected!'))
|
||||||
.build();
|
.catch(console.error);
|
||||||
connection.on('newMessage', newMessage);
|
}).catch(alert);
|
||||||
connection.onclose(() => console.log('disconnected'));
|
function getAxiosConfig() {
|
||||||
console.log('connecting...');
|
const config = {
|
||||||
connection.start()
|
headers: { 'x-ms-signalr-userid': data.username }
|
||||||
.then(() => console.log('connected!'))
|
};
|
||||||
.catch(console.error);
|
return config;
|
||||||
}).catch(alert);
|
}
|
||||||
function getAxiosConfig() {
|
function getConnectionInfo() {
|
||||||
const config = {
|
return axios.post(`${apiBaseUrl}/api/negotiate`, null, getAxiosConfig())
|
||||||
headers: {'x-ms-signalr-userid': data.username}
|
.then(resp => resp.data);
|
||||||
};
|
}
|
||||||
return config;
|
function sendMessage(sender, recipient, groupname, messageText) {
|
||||||
}
|
return axios.post(`${apiBaseUrl}/api/messages`, {
|
||||||
function getConnectionInfo() {
|
connectionId: data.myConnectionId,
|
||||||
return axios.post(`${apiBaseUrl}/api/negotiate`, null , getAxiosConfig())
|
recipient: recipient,
|
||||||
.then(resp => resp.data);
|
isPrivate: recipient != null,
|
||||||
}
|
groupname: groupname,
|
||||||
function sendMessage(sender, recipient, groupname, messageText) {
|
sender: sender,
|
||||||
return axios.post(`${apiBaseUrl}/api/messages`, {
|
text: messageText
|
||||||
recipient: recipient,
|
}, getAxiosConfig()).then(resp => resp.data);
|
||||||
isPrivate: recipient != null,
|
}
|
||||||
groupname: groupname,
|
function addGroup(sender, recipient, connectionId, groupName) {
|
||||||
sender: sender,
|
return axios.post(`${apiBaseUrl}/api/addToGroup`, {
|
||||||
text: messageText
|
connectionId: connectionId,
|
||||||
}, getAxiosConfig()).then(resp => resp.data);
|
recipient: recipient,
|
||||||
}
|
groupname: groupName
|
||||||
function addGroup(sender, recipient, groupName) {
|
}, getAxiosConfig()).then(resp => {
|
||||||
return axios.post(`${apiBaseUrl}/api/addToGroup`, {
|
if (resp.status == 200) {
|
||||||
recipient: recipient,
|
|
||||||
groupname: groupName
|
|
||||||
}, getAxiosConfig()).then(resp => {
|
|
||||||
if(resp.status == 200) {
|
|
||||||
confirm("Add Successfully")
|
confirm("Add Successfully")
|
||||||
}});
|
}
|
||||||
}
|
});
|
||||||
function removeGroup(sender, recipient, groupName) {
|
}
|
||||||
return axios.post(`${apiBaseUrl}/api/removeFromGroup`, {
|
function removeGroup(sender, recipient, connectionId, groupName) {
|
||||||
recipient: recipient,
|
return axios.post(`${apiBaseUrl}/api/removeFromGroup`, {
|
||||||
groupname: groupName
|
connectionId: connectionId,
|
||||||
}, getAxiosConfig()).then(resp => {
|
recipient: recipient,
|
||||||
if(resp.status == 200) {
|
groupname: groupName
|
||||||
|
}, getAxiosConfig()).then(resp => {
|
||||||
|
if (resp.status == 200) {
|
||||||
confirm("Remove Successfully")
|
confirm("Remove Successfully")
|
||||||
}});
|
}
|
||||||
}
|
});
|
||||||
let counter = 0;
|
}
|
||||||
function newMessage(message) {
|
let counter = 0;
|
||||||
message.id = counter++; // vue transitions need an id
|
function newMessage(message) {
|
||||||
data.messages.unshift(message);
|
message.id = counter++; // vue transitions need an id
|
||||||
}
|
data.messages.unshift(message);
|
||||||
</script>
|
};
|
||||||
|
function newConnection(message) {
|
||||||
|
data.myConnectionId = message.ConnectionId;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -4,6 +4,7 @@
|
||||||
<AzureFunctionsVersion>v2</AzureFunctionsVersion>
|
<AzureFunctionsVersion>v2</AzureFunctionsVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.EventGrid" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
|
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -3,14 +3,17 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Azure.EventGrid.Models;
|
||||||
using Microsoft.Azure.WebJobs;
|
using Microsoft.Azure.WebJobs;
|
||||||
|
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
|
||||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||||
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
|
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace FunctionApp
|
namespace FunctionApp
|
||||||
{
|
{
|
||||||
|
@ -39,7 +42,6 @@ namespace FunctionApp
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest req,
|
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest req,
|
||||||
[SignalR(HubName = "simplechat")]IAsyncCollector<SignalRMessage> signalRMessages)
|
[SignalR(HubName = "simplechat")]IAsyncCollector<SignalRMessage> signalRMessages)
|
||||||
{
|
{
|
||||||
|
|
||||||
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
||||||
|
|
||||||
return signalRMessages.AddAsync(
|
return signalRMessages.AddAsync(
|
||||||
|
@ -60,10 +62,12 @@ namespace FunctionApp
|
||||||
|
|
||||||
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
||||||
|
|
||||||
|
var decodedfConnectionId = GetBase64DecodedString(message.ConnectionId);
|
||||||
|
|
||||||
return signalRGroupActions.AddAsync(
|
return signalRGroupActions.AddAsync(
|
||||||
new SignalRGroupAction
|
new SignalRGroupAction
|
||||||
{
|
{
|
||||||
|
ConnectionId = decodedfConnectionId,
|
||||||
UserId = message.Recipient,
|
UserId = message.Recipient,
|
||||||
GroupName = message.Groupname,
|
GroupName = message.Groupname,
|
||||||
Action = GroupAction.Add
|
Action = GroupAction.Add
|
||||||
|
@ -78,23 +82,83 @@ namespace FunctionApp
|
||||||
|
|
||||||
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
|
||||||
|
|
||||||
|
var decodedfConnectionId = GetBase64DecodedString(message.ConnectionId);
|
||||||
|
|
||||||
return signalRGroupActions.AddAsync(
|
return signalRGroupActions.AddAsync(
|
||||||
new SignalRGroupAction
|
new SignalRGroupAction
|
||||||
{
|
{
|
||||||
|
ConnectionId = message.ConnectionId,
|
||||||
UserId = message.Recipient,
|
UserId = message.Recipient,
|
||||||
GroupName = message.Groupname,
|
GroupName = message.Groupname,
|
||||||
Action = GroupAction.Remove
|
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 class ChatMessage
|
||||||
{
|
{
|
||||||
public string Sender { get; set; }
|
public string Sender { get; set; }
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
public string Groupname { get; set; }
|
public string Groupname { get; set; }
|
||||||
public string Recipient { get; set; }
|
public string Recipient { get; set; }
|
||||||
|
public string ConnectionId { get; set; }
|
||||||
public bool IsPrivate { 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
|
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);
|
||||||
}
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(message.UserId))
|
||||||
if (!string.IsNullOrEmpty(message.UserId))
|
|
||||||
{
|
{
|
||||||
await client.SendToUser(hubName, message.UserId, data).ConfigureAwait(false);
|
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))
|
else if (convertItem.GetType() == typeof(SignalRGroupAction))
|
||||||
{
|
{
|
||||||
SignalRGroupAction groupAction = convertItem as 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
|
else
|
||||||
{
|
{
|
||||||
await client.RemoveUserFromGroup(hubName, groupAction.UserId, groupAction.GroupName).ConfigureAwait(false);
|
throw new ArgumentException($"ConnectionId and UserId cannot be null or empty together");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -7,10 +7,19 @@ using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
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]
|
[JsonObject]
|
||||||
public class SignalRGroupAction
|
public class SignalRGroupAction
|
||||||
{
|
{
|
||||||
[JsonProperty("userId"), JsonRequired]
|
[JsonProperty("connectionId")]
|
||||||
|
public string ConnectionId { get; set; }
|
||||||
|
[JsonProperty("userId")]
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
[JsonProperty("groupName"), JsonRequired]
|
[JsonProperty("groupName"), JsonRequired]
|
||||||
public string GroupName { get; set; }
|
public string GroupName { get; set; }
|
||||||
|
|
|
@ -5,9 +5,19 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
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]
|
[JsonObject]
|
||||||
public class SignalRMessage
|
public class SignalRMessage
|
||||||
{
|
{
|
||||||
|
[JsonProperty("connectionId")]
|
||||||
|
public string ConnectionId { get; set; }
|
||||||
[JsonProperty("userId")]
|
[JsonProperty("userId")]
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
[JsonProperty("groupName")]
|
[JsonProperty("groupName")]
|
||||||
|
|
|
@ -60,6 +60,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
||||||
await serviceHubContext.Clients.All.SendCoreAsync(data.Target, data.Arguments);
|
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)
|
public async Task SendToUser(string hubName, string userId, SignalRData data)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(userId))
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
@ -108,6 +114,34 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
||||||
await serviceHubContext.UserGroups.RemoveFromGroupAsync(userId, groupName);
|
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)
|
private static IEnumerable<Claim> BuildJwtClaims(IEnumerable<Claim> customerClaims, string prefix)
|
||||||
{
|
{
|
||||||
if (customerClaims != null)
|
if (customerClaims != null)
|
||||||
|
|
|
@ -9,9 +9,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
|
||||||
internal interface IAzureSignalRSender
|
internal interface IAzureSignalRSender
|
||||||
{
|
{
|
||||||
Task SendToAll(string hubName, SignalRData data);
|
Task SendToAll(string hubName, SignalRData data);
|
||||||
|
Task SendToConnection(string hubName, string connectionId, SignalRData data);
|
||||||
Task SendToUser(string hubName, string userId, SignalRData data);
|
Task SendToUser(string hubName, string userId, SignalRData data);
|
||||||
Task SendToGroup(string hubName, string group, SignalRData data);
|
Task SendToGroup(string hubName, string group, SignalRData data);
|
||||||
Task AddUserToGroup(string hubName, string userId, string groupName);
|
Task AddUserToGroup(string hubName, string userId, string groupName);
|
||||||
Task RemoveUserFromGroup(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]
|
[Fact]
|
||||||
public async Task AddAsync_SendMessage_WithBothUserIdAndGroupNameThrowException()
|
public async Task AddAsync_SendMessage_WithBothUserIdAndGroupName_UsePriorityOrder()
|
||||||
{
|
{
|
||||||
var signalRSenderMock = new Mock<IAzureSignalRSender>();
|
var signalRSenderMock = new Mock<IAzureSignalRSender>();
|
||||||
var collector = new SignalRAsyncCollector<SignalRMessage>(signalRSenderMock.Object, "chathub");
|
var collector = new SignalRAsyncCollector<SignalRMessage>(signalRSenderMock.Object, "chathub");
|
||||||
|
|
||||||
var item = new SignalRMessage
|
await collector.AddAsync(new SignalRMessage
|
||||||
{
|
{
|
||||||
UserId = "user1",
|
UserId = "user1",
|
||||||
GroupName = "group1",
|
GroupName = "group1",
|
||||||
Target = "newMessage",
|
Target = "newMessage",
|
||||||
Arguments = new object[] { "arg1", "arg2" }
|
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));
|
await Assert.ThrowsAsync<ArgumentException>(() => collector.AddAsync(item));
|
||||||
|
|
Загрузка…
Ссылка в новой задаче