Skip to content

Commit

Permalink
#1245: add SSE group and broadcast level control support
Browse files Browse the repository at this point in the history
  • Loading branch information
Badgerati committed Mar 19, 2024
1 parent c05d911 commit c2f6977
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 91 deletions.
4 changes: 2 additions & 2 deletions examples/sse.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Start-PodeServer -Threads 3 {

# open local sse connection, and send back data
Add-PodeRoute -Method Get -Path '/data' -ScriptBlock {
Set-PodeSseConnection -Name 'Data' -Scope Local
ConvertTo-PodeSseConnection -Name 'Data' -Scope Local
Send-PodeSseEvent -Id 1234 -EventType Action -Data 'hello, there!'
Start-Sleep -Seconds 3
Send-PodeSseEvent -Id 1337 -EventType BoldOne -Data 'general kenobi'
Expand All @@ -26,7 +26,7 @@ Start-PodeServer -Threads 3 {
}

Add-PodeRoute -Method Get -Path '/sse' -ScriptBlock {
Set-PodeSseConnection -Name 'Test'
ConvertTo-PodeSseConnection -Name 'Test'
}

Add-PodeTimer -Name 'SendEvent' -Interval 10 -ScriptBlock {
Expand Down
2 changes: 2 additions & 0 deletions src/Listener/PodeHttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class PodeHttpRequest : PodeRequest

public string SseClientId { get; private set; }
public string SseClientName { get; private set; }
public string SseClientGroup { get; private set; }
public bool HasSseClientId
{
get => !string.IsNullOrEmpty(SseClientId);
Expand Down Expand Up @@ -304,6 +305,7 @@ private int ParseHeaders(string[] reqLines, string newline)
if (HasSseClientId)
{
SseClientName = $"{Headers["X-Pode-Sse-Client-Name"]}";
SseClientGroup = $"{Headers["X-Pode-Sse-Client-Group"]}";
}

// keep-alive?
Expand Down
14 changes: 10 additions & 4 deletions src/Listener/PodeListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void AddSseConnection(PodeServerEvent sse)
}
}

public void SendSseEvent(string name, string[] clientIds, string eventType, string data, string id = null)
public void SendSseEvent(string name, string[] groups, string[] clientIds, string eventType, string data, string id = null)
{
Task.Factory.StartNew(() => {
if (!ServerEvents.ContainsKey(name))
Expand All @@ -155,12 +155,15 @@ public void SendSseEvent(string name, string[] clientIds, string eventType, stri
continue;
}

ServerEvents[name][clientId].Context.Response.SendSseEvent(eventType, data, id);
if (ServerEvents[name][clientId].IsForGroup(groups))
{
ServerEvents[name][clientId].Context.Response.SendSseEvent(eventType, data, id);
}
}
}, CancellationToken);
}

public void CloseSseConnection(string name, string[] clientIds)
public void CloseSseConnection(string name, string[] groups, string[] clientIds)
{
Task.Factory.StartNew(() => {
if (!ServerEvents.ContainsKey(name))
Expand All @@ -180,7 +183,10 @@ public void CloseSseConnection(string name, string[] clientIds)
continue;
}

ServerEvents[name][clientId].Context.Response.CloseSseConnection();
if (ServerEvents[name][clientId].IsForGroup(groups))
{
ServerEvents[name][clientId].Context.Response.CloseSseConnection();
}
}
}, CancellationToken);
}
Expand Down
11 changes: 8 additions & 3 deletions src/Listener/PodeResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public void Flush()
}
}

public string SetSseConnection(PodeSseScope scope, string clientId, string name, int retry, bool allowAllOrigins)
public string SetSseConnection(PodeSseScope scope, string clientId, string name, string group, int retry, bool allowAllOrigins)
{
// do nothing for no scope
if (scope == PodeSseScope.None)
Expand Down Expand Up @@ -225,15 +225,20 @@ public string SetSseConnection(PodeSseScope scope, string clientId, string name,
Headers.Set("X-Pode-Sse-Client-Id", clientId);
Headers.Set("X-Pode-Sse-Client-Name", name);

if (!string.IsNullOrEmpty(group))
{
Headers.Set("X-Pode-Sse-Client-Group", group);
}

// send headers, and open event
Send();
SendSseRetry(retry);
SendSseEvent("pode.open", $"{{\"clientId\":\"{clientId}\",\"name\":\"{name}\"}}");
SendSseEvent("pode.open", $"{{\"clientId\":\"{clientId}\",\"group\":\"{group}\",\"name\":\"{name}\"}}");

// if global, cache connection in listener
if (scope == PodeSseScope.Global)
{
Context.Listener.AddSseConnection(new PodeServerEvent(Context, name, clientId));
Context.Listener.AddSseConnection(new PodeServerEvent(Context, name, group, clientId));
}

// return clientId
Expand Down
20 changes: 19 additions & 1 deletion src/Listener/PodeServerEvent.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
using System;
using System.Linq;

namespace Pode
{
public class PodeServerEvent : IDisposable
{
public PodeContext Context { get; private set; }
public string Name { get; private set; }
public string Group { get; private set; }
public string ClientId { get; private set; }
public DateTime Timestamp { get; private set; }

public PodeServerEvent(PodeContext context, string name, string clientId)
public PodeServerEvent(PodeContext context, string name, string group, string clientId)
{
Context = context;
Name = name;
Group = group;
ClientId = clientId;
Timestamp = DateTime.UtcNow;
}

public bool IsForGroup(string[] groups)
{
if (groups == default(string[]) || groups.Length == 0)
{
return true;
}

if (string.IsNullOrEmpty(Group))
{
return false;
}

return groups.Any(x => x.Equals(Group, StringComparison.OrdinalIgnoreCase));
}

public void Dispose()
{
Context.Dispose(true);
Expand Down
6 changes: 4 additions & 2 deletions src/Pode.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,17 @@
'Send-PodeResponse',

# sse
'Set-PodeSseConnection',
'ConvertTo-PodeSseConnection',
'Send-PodeSseEvent',
'Close-PodeSseConnection',
'Test-PodeSseClientIdSigned',
'Test-PodeSseClientIdValid',
'Get-PodeSseClientId',
'New-PodeSseClientId',
'Enable-PodeSseSigning',
'Disable-PodeSseSigning',
'Set-PodeSseBroadcastLevel',
'Get-PodeSseBroadcastLevel',
'Test-PodeSseBroadcastLevel',

# utility helpers
'Close-PodeDisposable',
Expand Down
7 changes: 4 additions & 3 deletions src/Private/Context.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,10 @@ function New-PodeContext {
}

$ctx.Server.Sse = @{
Signed = $false
Secret = $null
Strict = $false
Signed = $false
Secret = $null
Strict = $false
BroadcastLevel = @{}
}

$ctx.Server.WebSockets = @{
Expand Down
1 change: 1 addition & 0 deletions src/Private/FileWatchers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ function Start-PodeFileWatcherRunspace {
Parameters = @{}
Lockable = $PodeContext.Threading.Lockables.Global
Timestamp = [datetime]::UtcNow
Metadata = @{}
}

# do we have any parameters?
Expand Down
3 changes: 3 additions & 0 deletions src/Private/PodeServer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ function Start-PodeWebServer {
AcceptEncoding = $null
Ranges = $null
Sse = $null
Metadata = @{}
}

# if iis, and we have an app path, alter it
Expand Down Expand Up @@ -181,6 +182,7 @@ function Start-PodeWebServer {

$WebEvent.Sse = @{
Name = $WebEvent.Request.SseClientName
Group = $WebEvent.Request.SseClientGroup
ClientId = $WebEvent.Request.SseClientId
LastEventId = $null
IsLocal = $false
Expand Down Expand Up @@ -379,6 +381,7 @@ function Start-PodeWebServer {
ClientId = $context.Signal.ClientId
Timestamp = $context.Timestamp
Streamed = $true
Metadata = @{}
}

# endpoint name
Expand Down
1 change: 1 addition & 0 deletions src/Private/Schedules.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ function Invoke-PodeInternalScheduleLogic {
Event = @{
Lockable = $PodeContext.Threading.Lockables.Global
Sender = $Schedule
Metadata = @{}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/Private/Serverless.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function Start-PodeAzFuncServer {
TransferEncoding = $null
AcceptEncoding = $null
Ranges = $null
Metadata = @{}
}

$WebEvent.Endpoint.Address = ((Get-PodeHeader -Name 'host') -split ':')[0]
Expand Down Expand Up @@ -180,6 +181,7 @@ function Start-PodeAwsLambdaServer {
TransferEncoding = $null
AcceptEncoding = $null
Ranges = $null
Metadata = @{}
}

$WebEvent.Endpoint.Protocol = (Get-PodeHeader -Name 'X-Forwarded-Proto')
Expand Down
1 change: 1 addition & 0 deletions src/Private/ServiceServer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function Start-PodeServiceServer {
# the event object
$ServiceEvent = @{
Lockable = $PodeContext.Threading.Lockables.Global
Metadata = @{}
}

# invoke the service handlers
Expand Down
1 change: 1 addition & 0 deletions src/Private/SmtpServer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ function Start-PodeSmtpServer {
Name = $null
}
Timestamp = [datetime]::UtcNow
Metadata = @{}
}

# endpoint name
Expand Down
1 change: 1 addition & 0 deletions src/Private/Tasks.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function Invoke-PodeInternalTask {
Event = @{
Lockable = $PodeContext.Threading.Lockables.Global
Sender = $Task
Metadata = @{}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Private/TcpServer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ function Start-PodeTcpServer {
}
Parameters = $null
Timestamp = [datetime]::UtcNow
Metadata = @{}
}

# endpoint name
Expand Down
1 change: 1 addition & 0 deletions src/Private/Timers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ function Invoke-PodeInternalTimer {
$global:TimerEvent = @{
Lockable = $PodeContext.Threading.Lockables.Global
Sender = $Timer
Metadata = @{}
}

# add main timer args
Expand Down
1 change: 1 addition & 0 deletions src/Private/WebSockets.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function Start-PodeWebSocketRunspace {
Files = $null
Lockable = $PodeContext.Threading.Lockables.Global
Timestamp = [datetime]::UtcNow
Metadata = @{}
}

# find the websocket definition
Expand Down
12 changes: 12 additions & 0 deletions src/Public/Cookies.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ Inform browsers to remove the cookie.
.PARAMETER Secure
Only allow the cookie on secure (HTTPS) connections.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Set-PodeCookie -Name 'Views' -Value 2
Expand Down Expand Up @@ -117,6 +120,9 @@ The name of the cookie to retrieve.
.PARAMETER Secret
The secret used to unsign the cookie's value.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.PARAMETER Raw
If supplied, the cookie returned will be the raw .NET Cookie object for manipulation.
Expand Down Expand Up @@ -179,6 +185,9 @@ The name of the cookie to retrieve.
.PARAMETER Secret
The secret used to unsign the cookie's value.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Get-PodeCookieValue -Name 'Views'
Expand Down Expand Up @@ -284,6 +293,9 @@ The name of the cookie to test.
.PARAMETER Secret
A secret to use for attempting to unsign the cookie's value.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Test-PodeCookieSigned -Name 'Views' -Secret 'hunter2'
#>
Expand Down
18 changes: 18 additions & 0 deletions src/Public/Headers.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ The value to set against the header.
.PARAMETER Secret
If supplied, the secret with which to sign the header's value.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Add-PodeHeader -Name 'X-AuthToken' -Value 'AA-BB-CC-33'
#>
Expand Down Expand Up @@ -63,6 +66,9 @@ A hashtable of headers to be appended.
.PARAMETER Secret
If supplied, the secret with which to sign the header values.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Add-PodeHeaderBulk -Values @{ Name1 = 'Value1'; Name2 = 'Value2' }
#>
Expand Down Expand Up @@ -138,6 +144,9 @@ The name of the header to retrieve.
.PARAMETER Secret
The secret used to unsign the header's value.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Get-PodeHeader -Name 'X-AuthToken'
#>
Expand Down Expand Up @@ -184,6 +193,9 @@ The value to set against the header.
.PARAMETER Secret
If supplied, the secret with which to sign the header's value.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Set-PodeHeader -Name 'X-AuthToken' -Value 'AA-BB-CC-33'
#>
Expand Down Expand Up @@ -233,6 +245,9 @@ A hashtable of headers to be set.
.PARAMETER Secret
If supplied, the secret with which to sign the header values.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Set-PodeHeaderBulk -Values @{ Name1 = 'Value1'; Name2 = 'Value2' }
#>
Expand Down Expand Up @@ -282,6 +297,9 @@ The name of the header to test.
.PARAMETER Secret
A secret to use for attempting to unsign the header's value.
.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.
.EXAMPLE
Test-PodeHeaderSigned -Name 'X-Header-Name' -Secret 'hunter2'
#>
Expand Down
Loading

0 comments on commit c2f6977

Please sign in to comment.