Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
239 changes: 239 additions & 0 deletions samples/bidirectional-chat/content/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
<html>

<head>
<title>Serverless Chat</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css">
<script>
window.apiBaseUrl = 'http://localhost:7071';
</script>
<style>
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 1s ease;
}

.slide-fade-enter,
.slide-fade-leave-to {
height: 0px;
overflow-y: hidden;
opacity: 0;
}
</style>
</head>

<body>
<p>&nbsp;</p>
<div id="app" class="container">
<h3>Serverless chat</h3>
<div class="row" v-if="ready">
<div class="signalr-demo col-sm">
<hr />
<div id='groupchecked'>
<input type="checkbox" id="checkbox" v-model="checked">
<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..." />
</form>
</div>
</div>
<div class="row" v-if="!ready">
<div class="col-sm">
<div>Loading...</div>
</div>
</div>
<div v-if="ready">
<transition-group name="slide-fade" tag="div">
<div class="row" v-for="message in messages" v-bind:key="message.id">
<div class="col-sm">
<hr />
<div>
<div style="display: inline-block; padding-left: 12px;">
<div>
<a href="#" v-on:click.prevent="sendPrivateMessage(message.Sender)">
<span class="text-info small">
<strong>{{ message.Sender || message.sender }}</strong>
</span>
</a>
<span v-if="message.ConnectionId || message.connectionId">
<a href="#" v-on:click.prevent="sendToConnection(message.ConnectionId || message.connectionId)">
<span class="badge badge-primary">Connection: {{ message.ConnectionId || message.connectionId }}</span>
</a>
</span>
<a href="#" v-on:click.prevent="addUserToGroup(message.Sender || message.sender)">
<span class="badge badge-primary">AddUserToGroup</span>
</a>
<a href="#" v-on:click.prevent="removeUserFromGroup(message.Sender || message.sender)">
<span class="badge badge-primary">RemoveUserFromGroup</span>
</a>
<a href="#" v-on:click.prevent="addConnectionToGroup(message.ConnectionId || message.connectionId)">
<span v-if="message.ConnectionId || message.connectionId" class="badge badge-primary">AddConnectionToGroup</span>
</a>
<a href="#" v-on:click.prevent="removeConnectionIdFromGroup(message.ConnectionId || message.connectionId)">
<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 }}
</div>
</div>
</div>
</div>
</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/crypto-js@3.1.9-1/crypto-js.js"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@3.1.9-1/enc-base64.js"></script>
<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) {
connection.invoke("sendToGroup", this.defaultgroup, this.newMessage);
}
else {
connection.invoke("broadcast", this.newMessage);
}
this.newMessage = '';
},
sendPrivateMessage: function (user) {
const messageText = prompt('Send private message to ' + user);

if (messageText) {
connection.invoke("sendToUser", user, messageText);
}
},
sendToConnection: function (connectionId) {
const messageText = prompt('Send private message to connection ' + connectionId);

if (messageText) {
connection.invoke("sendToConnection", connectionId, messageText);
}
},
addConnectionToGroup: function(connectionId) {
confirm('Add connection ' + connectionId + ' to group: ' + this.defaultgroup);
connection.invoke("joinGroup", connectionId, this.defaultgroup);
},
addUserToGroup: function (user) {
r = confirm('Add user ' + user + ' to group: ' + this.defaultgroup);
connection.invoke("joinUserToGroup", user, this.defaultgroup);
},
removeConnectionIdFromGroup: function(connectionId) {
confirm('Remove connection ' + connectionId + ' from group: ' + this.defaultgroup);
connection.invoke("leaveGroup", connectionId, this.defaultgroup);
},
removeUserFromGroup: function(user) {
confirm('Remove user ' + user + ' from group: ' + this.defaultgroup);
connection.invoke("leaveUserFromGroup", user, this.defaultgroup);
}
}
});
const apiBaseUrl = prompt('Enter the Azure Function app base URL', window.apiBaseUrl);
data.username = prompt("Enter your username");
const isAdmin = confirm('Work as administrator? (only an administrator can broadcast messages)');
if (!data.username) {
alert("No username entered. Reload page and try again.");
throw "No username entered";
}
const connection = new signalR.HubConnectionBuilder()
.withUrl(apiBaseUrl + '/api', {
accessTokenFactory: () => {
return generateAccessToken(data.username)
}
})
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on('newMessage', onNewMessage);
connection.on('newConnection', onNewConnection)
connection.onclose(() => console.log('disconnected'));
console.log('connecting...');
connection.start()
.then(() => {
data.ready = true;
console.log('connected!');
})
.catch(console.error);
function getAxiosConfig() {
const config = {
headers: {
'x-ms-signalr-user-id': data.username,
'Authorization': 'Bearer ' + generateAccessToken(data.username)
}
};
return config;
}
let counter = 0;
function onNewMessage(message) {
message.id = counter++; // vue transitions need an id
data.messages.unshift(message);
};
function onNewConnection(message) {
data.myConnectionId = message.ConnectionId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to do a camel case conversion for class members? (ConnectionId -> connectionId) I think SignalR by default does this conversion.

}

function base64url(source) {
// Encode in classical base64
encodedSource = CryptoJS.enc.Base64.stringify(source);

// Remove padding equal characters
encodedSource = encodedSource.replace(/=+$/, '');

// Replace characters according to base64url specifications
encodedSource = encodedSource.replace(/\+/g, '-');
encodedSource = encodedSource.replace(/\//g, '_');

return encodedSource;
}

// this function should be in auth server, do not expose your secret
function generateAccessToken(userName) {
var header = {
"alg": "HS256",
"typ": "JWT"
};

var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
var encodedHeader = base64url(stringifiedHeader);

// customize your JWT token payload here
var data = {
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": userName,
"exp": 1699819025,
'admin': isAdmin
};

var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
var encodedData = base64url(stringifiedData);

var token = encodedHeader + "." + encodedData;

var secret = "myfunctionauthtest"; // do not expose your secret here

var signature = CryptoJS.HmacSHA256(token, secret);
signature = base64url(signature);

var signedToken = token + "." + signature;

return signedToken;
}
</script>
</body>

</html>
Loading