Skip to content

Commit 2b77c37

Browse files
author
James Brundage
committed
feat: Get-WebSocket ( Fixes #2 )
1 parent 0a13568 commit 2b77c37

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

Commands/Get-WebSocket.ps1

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
function Get-WebSocket {
2+
<#
3+
.SYNOPSIS
4+
WebSockets in PowerShell.
5+
.DESCRIPTION
6+
Get-WebSocket allows you to connect to a websocket and handle the output.
7+
.EXAMPLE
8+
# Create a WebSocket job that connects to a WebSocket and outputs the results.
9+
Get-WebSocket -WebSocketUri "wss://localhost:9669"
10+
.EXAMPLE
11+
# Get is the default verb, so we can just say WebSocket.
12+
websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post
13+
.EXAMPLE
14+
websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail |
15+
Foreach-Object {
16+
$in = $_
17+
if ($in.commit.record.text -match '[\p{IsHighSurrogates}\p{IsLowSurrogates}]+') {
18+
$matches.0
19+
}
20+
}
21+
#>
22+
[CmdletBinding(PositionalBinding=$false)]
23+
param(
24+
# The Uri of the WebSocket to connect to.
25+
[Parameter(Position=0,ValueFromPipelineByPropertyName)]
26+
[Alias('Url','Uri')]
27+
[uri]$WebSocketUri,
28+
29+
# A ScriptBlock that will handle the output of the WebSocket.
30+
[ScriptBlock]
31+
$Handler,
32+
33+
# Any variables to declare in the WebSocket job.
34+
[Collections.IDictionary]
35+
$Variable = @{},
36+
37+
# The name of the WebSocket job.
38+
[string]
39+
$Name,
40+
41+
# The script to run when the WebSocket job starts.
42+
[ScriptBlock]
43+
$InitializationScript = {},
44+
45+
# The buffer size. Defaults to 16kb.
46+
[int]
47+
$BufferSize = 16kb,
48+
49+
# The ScriptBlock to run after connection to a websocket.
50+
# This can be useful for making any initial requests.
51+
[ScriptBlock]
52+
$OnConnect,
53+
54+
# The ScriptBlock to run when an error occurs.
55+
[ScriptBlock]
56+
$OnError,
57+
58+
# The ScriptBlock to run when the WebSocket job outputs an object.
59+
[ScriptBlock]
60+
$OnOutput,
61+
62+
# The Scriptblock to run when the WebSocket job produces a warning.
63+
[ScriptBlock]
64+
$OnWarning,
65+
66+
# If set, will tail the output of the WebSocket job, outputting results continuously instead of outputting a websocket job.
67+
[switch]
68+
$Watch,
69+
70+
# The maximum time to wait for a connection to be established.
71+
# By default, this is 7 seconds.
72+
[TimeSpan]
73+
$ConnectionTimeout = '00:00:07',
74+
75+
# The Runspace where the handler should run.
76+
# Runspaces allow you to limit the scope of the handler.
77+
[Runspace]
78+
$Runspace,
79+
80+
# The RunspacePool where the handler should run.
81+
# RunspacePools allow you to limit the scope of the handler to a pool of runspaces.
82+
[Management.Automation.Runspaces.RunspacePool]
83+
[Alias('Pool')]
84+
$RunspacePool
85+
)
86+
87+
begin {
88+
$SocketJob = {
89+
param([Collections.IDictionary]$Variable)
90+
91+
foreach ($keyValue in $variable.GetEnumerator()) {
92+
$ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value)
93+
}
94+
95+
if ((-not $WebSocketUri) -or $webSocket) {
96+
throw "No WebSocketUri"
97+
}
98+
99+
if (-not $WebSocketUri.Scheme) {
100+
$WebSocketUri = [uri]"wss://$WebSocketUri"
101+
}
102+
103+
if (-not $BufferSize) {
104+
$BufferSize = 16kb
105+
}
106+
107+
$CT = [Threading.CancellationToken]::None
108+
109+
if (-not $webSocket) {
110+
$ws = [Net.WebSockets.ClientWebSocket]::new()
111+
$null = $ws.ConnectAsync($WebSocketUri, $CT).Wait()
112+
} else {
113+
$ws = $WebSocket
114+
}
115+
116+
$Variable.WebSocket = $ws
117+
118+
119+
while ($true) {
120+
if ($ws.State -ne 'Open') {break }
121+
$Buf = [byte[]]::new($BufferSize)
122+
$Seg = [ArraySegment[byte]]::new($Buf)
123+
$null = $ws.ReceiveAsync($Seg, $CT).Wait()
124+
$JS = $OutputEncoding.GetString($Buf, 0, $Buf.Count)
125+
if ([string]::IsNullOrWhitespace($JS)) { continue }
126+
try {
127+
$webSocketMessage = ConvertFrom-Json $JS
128+
if ($handler) {
129+
$psCmd =
130+
if ($runspace.LanguageMode -eq 'NoLanguage' -or
131+
$runspacePool.InitialSessionState.LanguageMode -eq 'NoLanguage') {
132+
$handler.GetPowerShell()
133+
} elseif ($Runspace -or $RunspacePool) {
134+
[PowerShell]::Create().AddScript($handler)
135+
}
136+
if ($psCmd) {
137+
if ($Runspace) {
138+
$psCmd.Runspace = $Runspace
139+
} elseif ($RunspacePool) {
140+
$psCmd.RunspacePool = $RunspacePool
141+
}
142+
} else {
143+
$webSocketMessage | . $handler
144+
}
145+
146+
} else {
147+
$webSocketMessage
148+
}
149+
} catch {
150+
Write-Error $_
151+
}
152+
}
153+
}
154+
}
155+
156+
process {
157+
foreach ($keyValuePair in $PSBoundParameters.GetEnumerator()) {
158+
$Variable[$keyValuePair.Key] = $keyValuePair.Value
159+
}
160+
$webSocketJob =
161+
if ($WebSocketUri) {
162+
if (-not $name) {
163+
$Name = $WebSocketUri
164+
}
165+
166+
Start-ThreadJob -ScriptBlock $SocketJob -Name $Name -InitializationScript $InitializationScript -ArgumentList $Variable
167+
} elseif ($WebSocket) {
168+
if (-not $name) {
169+
$name = "websocket"
170+
}
171+
Start-ThreadJob -ScriptBlock $SocketJob -Name $Name -InitializationScript $InitializationScript -ArgumentList $Variable
172+
}
173+
174+
$subscriptionSplat = @{
175+
EventName = 'DataAdded'
176+
MessageData = $webSocketJob
177+
SupportEvent = $true
178+
}
179+
$eventSubscriptions = @(
180+
if ($OnOutput) {
181+
Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Output -Action $OnOutput
182+
}
183+
if ($OnError) {
184+
Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Error -Action $OnError
185+
}
186+
if ($OnWarning) {
187+
Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Warning -Action $OnWarning
188+
}
189+
)
190+
if ($eventSubscriptions) {
191+
$variable['EventSubscriptions'] = $eventSubscriptions
192+
}
193+
194+
$webSocketConnectTimeout = [DateTime]::Now + $ConnectionTimeout
195+
while (-not $variable['WebSocket'] -and
196+
([DateTime]::Now -lt $webSocketConnectTimeout)) {
197+
Start-Sleep -Milliseconds 0
198+
}
199+
200+
foreach ($keyValuePair in $Variable.GetEnumerator()) {
201+
$webSocketJob.psobject.properties.add(
202+
[psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true
203+
)
204+
}
205+
$webSocketJob.pstypenames.insert(0, 'WebSocketJob')
206+
if ($Watch) {
207+
do {
208+
$webSocketJob | Receive-Job
209+
Start-Sleep -Milliseconds (
210+
7, 11, 13, 17, 19, 23 | Get-Random
211+
)
212+
} while ($webSocketJob.State -in 'Running','NotStarted')
213+
} else {
214+
$webSocketJob
215+
}
216+
}
217+
}

0 commit comments

Comments
 (0)