SSE
You can convert regular HTTP requests made to a Route into an SSE connection, allowing you to stream events from your server to one or more connected clients. Connections can be scoped to just the Route that converted the request and it will be closed at the end of the Route like a normal request flow (Local), or you can keep the connection open beyond the request flow and be used server-wide for sending events (Global).
SSE connections are typically made from client browsers via JavaScript, using the EventSource class.
Convert Request
To convert a request into an SSE connection use ConvertTo-PodeSseConnection
. This will automatically send back the appropriate HTTP response headers to the client, converting it into an SSE connection; allowing the connection to be kept open, and for events to be streamed back to the client. A -Name
must be supplied during the conversion, allowing for easier reference to all connections later on, and allowing for different connection groups (of which, you can also have -Group
within a Name as well).
Important
For a request to be convertible, it must have an Accept
HTTP request header value of text/event-stream
.
Any requests to the following Route will be converted to a globally scoped SSE connection, and be available under the Events
name:
Add-PodeRoute -Method Get -Path '/events' -ScriptBlock {
ConvertTo-PodeSseConnection -Name 'Events'
}
You could then use Send-PodeSseEvent
in a Schedule (more info below) to broadcast an event, every minute, to all connected clients within the Events
name:
Add-PodeSchedule -Name 'Example' -Cron (New-PodeCron -Every Minute) -ScriptBlock {
Send-PodeSseEvent -Name 'Events' -Data "Hello there! The datetime is: $([datetime]::Now.TimeOfDay)"
}
Once ConvertTo-PodeSseConnection
has been called, the $WebEvent
object will be extended to include a new SSE
property. This new property will have the following items:
Name | Description |
---|---|
Name | The Name given to the connection |
Group | An optional Group assigned to the connection within the Name |
ClientId | The assigned ClientId for the connection - this will be different to a passed ClientId if using signing |
LastEventId | The last EventId the client saw, if this is a reconnecting SSE request |
IsLocal | Is the connection Local or Global |
Therefore, after converting a request, you can get the client ID back via:
Add-PodeRoute -Method Get -Path '/events' -ScriptBlock {
ConvertTo-PodeSseConnection -Name 'Events'
$clientId = $WebEvent.Sse.ClientId
}
Tip
The Name, Group, and Client ID values are also sent back on the HTTP response during conversion as headers. These won't be available if you're using JavaScript's EventSource
class, but could be if using other SSE libraries. The headers are:
X-PODE-SSE-CLIENT-ID
X-PODE-SSE-NAME
X-PODE-SSE-GROUP
ClientIds
ClientIds created by ConvertTo-PodeSseConnection
will be a GUID by default however, you can supply your own IDs via the -ClientId
parameter:
Add-PodeRoute -Method Get -Path '/events' -ScriptBlock {
$clientId = Get-Random -Minimum 10000 -Maximum 999999
ConvertTo-PodeSseConnection -Name 'Events' -ClientId $clientId
}
You can also sign clientIds as well.
Scopes
The default scope for a new SSE connection is "Global", which means the connection will be stored internally and can be used outside of the converting Route to stream events back to the client.
The default scope for new SSE connections can be altered by using Set-PodeSseDefaultScope
. For example, if you wanted all new SSE connections to instead default to a Local scope:
Set-PodeSseDefaultScope -Scope Local
Global
A Globally scoped SSE connection is the default (unless altered via Set-PodeSseDefaultScope
). A Global connection has the following features:
- They are kept open, even after the Route that converted the request has finished.
- The connection is stored internally, so that events can be streamed to the clients from other Routes, Timers, etc.
- You can send events to a specific connection if you know the Name and ClientId for the connection.
- Global connections can be closed via
Close-PodeSseConnection
.
For example, the following will convert requests to /events
into global SSE connections, and then a Schedule will send events to them every minute:
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8090 -Protocol Http
Add-PodeRoute -Method Get -Path '/events' -ScriptBlock {
ConvertTo-PodeSseConnection -Name 'Events'
}
Add-PodeSchedule -Name 'Example' -Cron (New-PodeCron -Every Minute) -ScriptBlock {
Send-PodeSseEvent -Name 'Events' -Data "Hello there! The datetime is: $([datetime]::Now.TimeOfDay)"
}
}
Local
A Local connection has the following features:
- When the Route that converted the request has finished, the connection will be closed - the same as HTTP requests.
- The connection is not stored internally, it is only available for the lifecycle of the HTTP request.
- You can send events back to the connection from within the converting Route's scriptblock, but not from Timers, etc. When sending events back for local connections you will need to use the
-FromEvent
switch onSend-PodeSseEvent
.
For example, the following will convert requests to /events
into local SSE connections, and two events will be sent back to the client before the connection is closed:
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8090 -Protocol Http
Add-PodeRoute -Method Get -Path '/events' -ScriptBlock {
ConvertTo-PodeSseConnection -Name 'Events' -Scope Local
Send-PodeSseEvent -FromEvent -Data "Hello there! The datetime is: $([datetime]::Now.TimeOfDay)"
Start-Sleep -Seconds 10
Send-PodeSseEvent -FromEvent -Data "Hello there! The datetime is: $([datetime]::Now.TimeOfDay)"
}
}
Inbuilt Events
Pode has two inbuilt events that it will send to your SSE connections. These events will be sent automatically when a connection is opened, and when it is closed.
Important
It is recommended to listen for the close event from Pode, as this way you'll know when Pode has closed the connection and you can perform any required clean-up.
Open
When an SSE connection is opened, via ConvertTo-PodeSseConnection
, Pode will send a pode.open
event to your client. This event will also contain the clientId
, group
, and name
of the SSE connection.
You can listen for this event in JavaScript if using EventSource
, as follows:
const sse = new EventSource('/events');
sse.addEventListener('pode.open', (e) => {
var data = JSON.parse(e.data);
let clientId = data.clientId;
let group = data.group;
let name = data.name;
});
Close
When an SSE connection is closed, either via Close-PodeSseConnection
or when the Pode server stops, Pode will send a pode.close
event to your clients. This will be an empty event, purely for clean-up purposes.
You can listen for this event in JavaScript if using EventSource
, as follows:
const sse = new EventSource('/events');
sse.addEventListener('pode.close', (e) => {
sse.close();
});
Send Events
To send an event from the server to one or more connected clients, you can use Send-PodeSseEvent
. Using the -Data
parameter, you can either send a raw string value, or a more complex hashtable/psobject which will be auto-converted into a JSON string.
For example, to broadcast an event to all clients on an "Events" SSE connection:
# simple string
Send-PodeSseEvent -Name 'Events' -Data 'Hello there!'
# complex object
Send-PodeSseEvent -Name 'Events' -Data @{ Value = 'Hello there!' }
Or to send an event to a specific client:
Send-PodeSseEvent -Name 'Events' -ClientId 'some-client-id' -Data 'Hello there!'
You can also specify an optional -Id
and -EventType
for the SSE event being sent. The -EventType
can be used in JavaScript to register event listeners, and the -Id
is used by the browser to keep track of events being sent in case the connection is dropped.
$id = [int][datetime]::Now.TimeOfDay.TotalSeconds
$data = @{ Date = [datetime]::Now.ToString() }
Send-PodeSseEvent -Name 'Events' -Id $id -EventType 'Date' -Data $data
Broadcast Levels
By default, Pode will allow broadcasting of events to all clients for an SSE connection Name, Group, or a specific ClientId.
You can supply a custom broadcasting level for specific SSE connection names (or all), limiting broadcasting to requiring a specific ClientId for example, by using Set-PodeSseBroadcastLevel
. If a -Name
is not supplied then the level type is applied to all SSE connections.
For example, the following will only allow events to be broadcast to an SSE connection name if a ClientId is also specified on Send-PodeSseEvent
:
# apply to all SSE connections
Set-PodeSseBroadcastLevel -Type 'ClientId'
# apply to just SSE connections with name = Events
Set-PodeSseBroadcastLevel -Name 'Events' -Type 'ClientId'
The following levels are available:
Level | Description |
---|---|
Name | A Name is required. Groups/ClientIds are optional. |
Group | A Name is required. One of either a Group and ClientId is required. |
ClientId | A Name and a ClientId are required. |
Signing ClientIds
Similar to Sessions and Cookies, you can sign SSE connection ClientIds. This can be done by calling Enable-PodeSseSigning
and supplying a -Secret
to sign the ClientIds.
Tip
You can use the inbuilt Get-PodeServerDefaultSecret
function to retrieve an internal Pode server secret which can be used. However, be warned that this secret is regenerated to a random value on every server start/restart.
Enable-PodeSseSigning -Secret 'super-secret'
Enable-PodeSseSigning -Secret (Get-PodeServerDefaultSecret)
When signing is enabled, all clientIds will be signed regardless if they're an internally generated random GUID or supplied via -ClientId
on ConvertTo-PodeSseConnection
. A signed clientId will look as follows, and have the structure s:<clientId>.<signature>
:
s:5d12f974-7b1a-4524-ab93-6afbf42c4ffa.uvG49LcojTMuJ0l4yzBzr6jCqEV8gGC/0YgsYU1QEuQ=
You can also supply the -Strict
switch to Enable-PodeSseSigning
, which will extend the secret during signing with the client's IP Address and User Agent.
Request Headers
If you have an SSE connection open for a client, and you want to have the client send AJAX requests to the server but have the responses streamed back over the SSE connection, then you can identify the SSE connection for the client using the following HTTP headers:
X-PODE-SSE-CLIENT-ID
X-PODE-SSE-NAME
X-PODE-SSE-GROUP
At a minimum, you'll need the X-PODE-SSE-CLIENT-ID
header. If supplied Pode will automatically verify the client ID for you, including if the signing of the client ID is valid - if you're using client ID signing.
When these headers are supplied in a request, Pode will set up the $WebEvent.Sse
property again - similar to the property set up from conversion above:
Name | Description |
---|---|
Name | The Name for the connection from X-PODE-SSE-NAME |
Group | The Group for the connection from X-PODE-SSE-GROUP |
ClientId | The assigned ClientId for the connection from X-PODE-SSE-CLIENT-ID |
LastEventId | $null |
IsLocal | $false |
Note
If you only supply the Name or Group headers, then the $WebEvent.Sse
property will not be configured. The ClientId is required as a minimum.