Endpoints
Pode has support for creating WebSocket endpoints, for server-to-client and/or client-to-server communications. WebSockets allow you to broadcast messages directly from your server to connected clients. This allows you to get real-time continuous updates on the frontend without having to constantly refresh the page, or by using async javascript.
Server Side
Listening
On the server side, the first thing to do is register a new endpoint to listen on. To do this you can use Add-PodeEndpoint
with a protocol of either Ws
or Wss
:
Add-PodeEndpoint -Address * -Port 8091 -Protocol Ws
# or for secure sockets:
Add-PodeEndpoint -Address * -Port 8091 -Certificate './path/cert.pfx' -CertificatePassword 'dummy' -Protocol Wss
Broadcasting
To broadcast a message from the server to all connected clients you can use Send-PodeSignal
. You can either send raw JSON data, or you can pass a HashTable/PSObject and it will be converted to JSON for you.
To broadcast some data to all clients from a POST route, you could use the following. This will get some message from one of the clients, and then broadcast it back to every client:
Add-PodeRoute -Method Post -Path '/broadcast' -ScriptBlock {
Send-PodeSignal -Value @{ Message = $WebEvent.Data['message'] }
}
Because you can register WebSockets on different paths, you can also broadcast messages to all clients connected using a certain path.
For example, the below would send some response time data to all clients connected and listening for response times on the /response-times
path:
Send-PodeSignal -Value @{ ResponseTimes = @(123, 101, 104) } -Path '/response-times'
You can also broadcast messages from Timers, or from Schedules.
Routes
When a client sends a message back to the server on the connected WebSocket, Pode will automatically call Send-PodeSignal
to re-broadcast the message back to all clients - or to a specific Path/ClientId if supplied by the sending client.
However, you can add custom route logic for WebSocket paths using Add-PodeSignalRoute
. This is much like Add-PodeRoute
, but allows you to run custom logic on paths for messages sent by clients. When you use a custom route, that route is responsible for calling Send-PodeSignal
.
Also like Add-PodeRoute
there is a $SignalEvent
object that you can use, which contains the client's message data, the raw Request/Response objects, etc.
For example, the following signal route will broadcast the current date back to all clients, if the main client sends the message [date]
on the /messages
path:
Add-PodeSignalRoute -Path '/messages' -ScriptBlock {
$msg = $SignalEvent.Data.Message
if ($msg -ieq '[date]') {
$msg = [datetime]::Now.ToString()
}
Send-PodeSignal -Value $msg
}
Signal Event
When using custom signal routes, the $SignalEvent
is a HashTable that is available for you to use - much like the $WebEvent
object for normal routes.
This $SignalEvent
object has the following properties:
Name | Type | Description |
---|---|---|
Data | hashtable | Contains the Message, an optional Path to broadcast back onto, and an optional ClientId to only broadcast back to |
Endpoint | hashtable | Contains the Address and Protocol of the endpoint being hit - such as "pode.example.com" or "127.0.0.2", or WS or WSS for the Protocol |
Lockable | hashtable | A synchronized hashtable that can be used with Lock-PodeObject |
Path | string | The path of the WebSocket - such as "/messages" |
Request | object | The raw Request object |
Response | object | The raw Response object |
Route | hashtable | The current Signal Route that is being invoked |
Streamed | bool | Specifies whether the current server type uses streams for the Request/Response, or raw strings |
Timestamp | datetime | The current date and time of the Signal |
Client Side
Receiving
On the client side, you need to use javascript to register a WebSocket and then bind the onmessage
event to do something when a broadcasted message is received.
To create a WebSocket, you can do something like the following which will bind a WebSocket onto the root path '/':
$(document).ready(() => {
// create the websocket
var ws = new WebSocket("ws://localhost:8091/");
// event for inbound messages to append them
ws.onmessage = function(evt) {
var data = JSON.parse(evt.data)
$('#messages').append(`<p>${data.Message}</p>`);
}
})
Sending
To send a message using the WebSocket, you can use the .send
function. When you send a message from client-to-server, the data must be a JSON value containing the message
, path
, and clientId
. Only the message
is mandatory.
For example, if you have a form with input, you can send the message as follows:
$('#form').submit(function(e) {
e.preventDefault();
ws.send(JSON.stringify({ message: $('#input').val() }));
$('#input').val('');
})
This will send the message to the server, which will in-turn broadcast it to all other clients. To broadcast the message to just clients connected on a specific path, such as /receive
:
$('#form').submit(function(e) {
e.preventDefault();
ws.send(JSON.stringify({ message: $('#input').val(), path: '/receive' }));
$('#input').val('');
})
If you just want the server to on respond directlt back to the sending client, and not broadcast to all clients, then set direct
to true:
$('#form').submit(function(e) {
e.preventDefault();
ws.send(JSON.stringify({ message: $('#input').val(), direct: true }));
$('#input').val('');
})
Full Example
This full example is a cut-down version of the one found in
/examples/web-signal.ps1
of the main repository.
If you open this example on multiple browsers, sending messages will be automatically received by all browsers without using async javascript!
The file structure for these files is:
server.ps1
/views
index.html
/public
script.js
The following is the Pode server code, that will create one route, which will be for some home page, with a button/input for broadcasting messages.
Start-PodeServer {
# listen
Add-PodeEndpoint -Address * -Port 8091 -Protocol Http
Add-PodeEndpoint -Address * -Port 8091 -Protocol Ws
# request for web page
Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
Write-PodeViewResponse -Path 'index'
}
}
Next we have the HTML web page with a basic button/input for broadcasting messages. There's also a <div>
to append received messages:
<html>
<head>
<title>WebSockets</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript" src="/script.js"></script>
</head>
<body>
<p>Clicking submit will broadcast the message to all connected clients</p>
<form id='bc-form'>
<input type='text' name='message' placeholder='Enter any random text' />
<input type='submit' value='Broadcast!' />
</form>
<div id='messages'></div>
</body>
</html>
Finally, the following is the client-side javascript to register a WebSocket for the client. It will also invoke the .send
function of the WebSocket when the button is clicked:
$(document).ready(() => {
// bind submit on the form to send message to the server
$('#bc-form').submit(function(e) {
e.preventDefault();
ws.send(JSON.stringify({
message: $('input[name=message]').val()
}));
$('input[name=message]').val('');
});
// create the websocket
var ws = new WebSocket("ws://localhost:8091/");
// event for inbound messages to append them
ws.onmessage = function(evt) {
$('#messages').append(`<p>${evt.data}</p>`);
}
});