Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit0cf2f28

Browse files
chore: add handler and router forcoder scheme URIs (#145)
Relates to#96.Closes#95
1 parent49fd303 commit0cf2f28

File tree

9 files changed

+249
-3
lines changed

9 files changed

+249
-3
lines changed

‎Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ struct DesktopApp: App {
1717
Window("Sign In", id:Windows.login.rawValue){
1818
LoginForm()
1919
.environmentObject(appDelegate.state)
20-
}
21-
.windowResizability(.contentSize)
20+
}.handlesExternalEvents(matching:Set()) // Don't handle deep links
21+
.windowResizability(.contentSize)
2222
SwiftUI.Settings{
2323
SettingsView<CoderVPNService>()
2424
.environmentObject(appDelegate.vpn)
@@ -30,7 +30,7 @@ struct DesktopApp: App {
3030
.environmentObject(appDelegate.state)
3131
.environmentObject(appDelegate.fileSyncDaemon)
3232
.environmentObject(appDelegate.vpn)
33-
}
33+
}.handlesExternalEvents(matching:Set()) // Don't handle deep links
3434
}
3535
}
3636

@@ -40,6 +40,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
4040
letvpn:CoderVPNService
4141
letstate:AppState
4242
letfileSyncDaemon:MutagenDaemon
43+
leturlHandler:URLHandler
4344

4445
overrideinit(){
4546
vpn=CoderVPNService()
@@ -65,6 +66,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
6566
await fileSyncDaemon.tryStart()
6667
}
6768
self.fileSyncDaemon= fileSyncDaemon
69+
urlHandler=URLHandler(state: state, vpn: vpn)
6870
}
6971

7072
func applicationDidFinishLaunching(_:Notification){
@@ -134,6 +136,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
134136
returntrue
135137
}
136138

139+
func application(_:NSApplication, open urls:[URL]){
140+
guardlet url= urls.firstelse{
141+
// We only accept one at time, for now
142+
return
143+
}
144+
do{try urlHandler.handle(url)}catch{
145+
// TODO: Push notification
146+
print(error.description)
147+
}
148+
}
149+
137150
privatefunc displayIconHiddenAlert(){
138151
letalert=NSAlert()
139152
alert.alertStyle=.informational

‎Coder-Desktop/Coder-Desktop/Info.plist

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
<!DOCTYPEplist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plistversion="1.0">
44
<dict>
5+
<key>CFBundleURLTypes</key>
6+
<array>
7+
<dict>
8+
<key>CFBundleTypeRole</key>
9+
<string>Editor</string>
10+
<key>CFBundleURLIconFile</key>
11+
<string>1024Icon</string>
12+
<key>CFBundleURLName</key>
13+
<string>com.coder.Coder-Desktop</string>
14+
<key>CFBundleURLSchemes</key>
15+
<array>
16+
<string>coder</string>
17+
</array>
18+
</dict>
19+
</array>
520
<key>NSAppTransportSecurity</key>
621
<dict>
722
<!--
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Foundation
2+
import VPNLib
3+
4+
@MainActor
5+
classURLHandler{
6+
letstate:AppState
7+
letvpn:anyVPNService
8+
letrouter:CoderRouter
9+
10+
init(state:AppState, vpn:anyVPNService){
11+
self.state= state
12+
self.vpn= vpn
13+
router=CoderRouter()
14+
}
15+
16+
func handle(_ url:URL)throws(RouterError){
17+
guard state.hasSession,let deployment= state.baseAccessURLelse{
18+
throw.noSession
19+
}
20+
guard deployment.host()== url.hostelse{
21+
throw.invalidAuthority(url.host()??"<none>")
22+
}
23+
do{
24+
switchtry router.match(url: url){
25+
caselet.open(workspace, agent, type):
26+
switch type{
27+
caselet.rdp(creds):
28+
handleRDP(workspace: workspace, agent: agent, creds: creds)
29+
}
30+
}
31+
}catch{
32+
throw.matchError(url: url)
33+
}
34+
35+
func handleRDP(workspace _:String, agent _:String, creds _:RDPCredentials){
36+
// TODO: Handle RDP
37+
}
38+
}
39+
}

‎Coder-Desktop/Resources/1024Icon.png

17.7 KB
Loading
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Foundation
2+
import URLRouting
3+
4+
// This is in VPNLib to avoid depending on `swift-collections` in both the app & extension.
5+
publicstructCoderRouter:ParserPrinter{
6+
publicinit(){}
7+
8+
publicvarbody:someParserPrinter<URLRequestData,CoderRoute>{
9+
Route(.case(CoderRoute.open(workspace:agent:route:))){
10+
Scheme("coder")
11+
// v0/open/ws/<workspace>/agent/<agent>/<openType>
12+
Path{"v0";"open";"ws";Parse(.string);"agent";Parse(.string)}
13+
openRouter
14+
}
15+
}
16+
17+
varopenRouter:someParserPrinter<URLRequestData,OpenRoute>{
18+
OneOf{
19+
Route(.memberwise(OpenRoute.rdp)){
20+
Path{"rdp"}
21+
Query{
22+
Parse(.memberwise(RDPCredentials.init)){
23+
Optionally{Field("username")}
24+
Optionally{Field("password")}
25+
}
26+
}
27+
}
28+
}
29+
}
30+
}
31+
32+
publicenumRouterError:Error{
33+
case invalidAuthority(String)
34+
case matchError(url:URL)
35+
case noSession
36+
37+
publicvardescription:String{
38+
switchself{
39+
caselet.invalidAuthority(authority):
40+
"Authority '\(authority)' does not match the host of the current Coder deployment."
41+
caselet.matchError(url):
42+
"Failed to handle\(url.absoluteString) because the format is unsupported."
43+
case.noSession:
44+
"Not logged in."
45+
}
46+
}
47+
48+
publicvarlocalizedDescription:String{ description}
49+
}
50+
51+
publicenumCoderRoute:Equatable,Sendable{
52+
case open(workspace:String, agent:String, route:OpenRoute)
53+
}
54+
55+
publicenumOpenRoute:Equatable,Sendable{
56+
case rdp(RDPCredentials)
57+
}
58+
59+
// Due to a Swift Result builder limitation, we can't flatten this out to `case rdp(String?, String?)`
60+
// https://github.com/pointfreeco/swift-url-routing/issues/50
61+
publicstructRDPCredentials:Equatable,Sendable{
62+
publicletusername:String?
63+
publicletpassword:String?
64+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import Foundation
2+
import Testing
3+
import URLRouting
4+
@testableimport VPNLib
5+
6+
@MainActor
7+
@Suite(.timeLimit(.minutes(1)))
8+
structCoderRouterTests{
9+
letrouter:CoderRouter
10+
11+
init(){
12+
router=CoderRouter()
13+
}
14+
15+
structRouteTestCase:CustomStringConvertible,Sendable{
16+
leturlString:String
17+
letexpectedRoute:CoderRoute?
18+
letdescription:String
19+
}
20+
21+
@Test("RDP routes", arguments:[
22+
// Valid routes
23+
RouteTestCase(
24+
urlString:"coder://coder.example.com/v0/open/ws/myworkspace/agent/dev/rdp?username=user&password=pass",
25+
expectedRoute:.open(
26+
workspace:"myworkspace",
27+
agent:"dev",
28+
route:.rdp(RDPCredentials(username:"user", password:"pass"))
29+
),
30+
description:"RDP with username and password"
31+
),
32+
RouteTestCase(
33+
urlString:"coder://coder.example.com/v0/open/ws/workspace-123/agent/agent-456/rdp",
34+
expectedRoute:.open(
35+
workspace:"workspace-123",
36+
agent:"agent-456",
37+
route:.rdp(RDPCredentials(username:nil, password:nil))
38+
),
39+
description:"RDP without credentials"
40+
),
41+
RouteTestCase(
42+
urlString:"coder://coder.example.com/v0/open/ws/workspace-123/agent/agent-456/rdp?username=user",
43+
expectedRoute:.open(
44+
workspace:"workspace-123",
45+
agent:"agent-456",
46+
route:.rdp(RDPCredentials(username:"user", password:nil))
47+
),
48+
description:"RDP with username only"
49+
),
50+
RouteTestCase(
51+
urlString:"coder://coder.example.com/v0/open/ws/workspace-123/agent/agent-456/rdp?password=pass",
52+
expectedRoute:.open(
53+
workspace:"workspace-123",
54+
agent:"agent-456",
55+
route:.rdp(RDPCredentials(username:nil, password:"pass"))
56+
),
57+
description:"RDP with password only"
58+
),
59+
RouteTestCase(
60+
urlString:"coder://coder.example.com/v0/open/ws/ws-special-chars/agent/agent-with-dashes/rdp",
61+
expectedRoute:.open(
62+
workspace:"ws-special-chars",
63+
agent:"agent-with-dashes",
64+
route:.rdp(RDPCredentials(username:nil, password:nil))
65+
),
66+
description:"RDP with special characters in workspace and agent IDs"
67+
),
68+
69+
// Invalid routes
70+
RouteTestCase(
71+
urlString:"coder://coder.example.com/invalid/path",
72+
expectedRoute:nil,
73+
description:"Completely invalid path"
74+
),
75+
RouteTestCase(
76+
urlString:"coder://coder.example.com/v1/open/ws/workspace-123/agent/agent-456/rdp",
77+
expectedRoute:nil,
78+
description:"Invalid version prefix (v1 instead of v0)"
79+
),
80+
RouteTestCase(
81+
urlString:"coder://coder.example.com/v0/open/workspace-123/agent/agent-456/rdp",
82+
expectedRoute:nil,
83+
description:"Missing 'ws' segment"
84+
),
85+
RouteTestCase(
86+
urlString:"coder://coder.example.com/v0/open/ws/workspace-123/rdp",
87+
expectedRoute:nil,
88+
description:"Missing agent segment"
89+
),
90+
RouteTestCase(
91+
urlString:"http://coder.example.com/v0/open/ws/workspace-123/agent/agent-456",
92+
expectedRoute:nil,
93+
description:"Wrong scheme"
94+
),
95+
])
96+
func testRdpRoutes(testCase:RouteTestCase)throws{
97+
leturl=URL(string: testCase.urlString)!
98+
99+
iflet expectedRoute= testCase.expectedRoute{
100+
letroute=try router.match(url: url)
101+
#expect(route== expectedRoute)
102+
}else{
103+
#expect(throws:(anyError).self){
104+
_=try router.match(url: url)
105+
}
106+
}
107+
}
108+
}

‎Coder-Desktop/project.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ packages:
126126
SDWebImageSVGCoder:
127127
url:https://github.com/SDWebImage/SDWebImageSVGCoder
128128
exactVersion:1.7.0
129+
URLRouting:
130+
url:https://github.com/pointfreeco/swift-url-routing
131+
revision:09b155d
132+
129133

130134
targets:
131135
Coder Desktop:
@@ -290,6 +294,7 @@ targets:
290294
-package:GRPC
291295
-package:Subprocess
292296
-package:Semaphore
297+
-package:URLRouting
293298
-target:CoderSDK
294299
embed:false
295300

‎Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ test: $(addprefix $(PROJECT)/Resources/,$(MUTAGEN_RESOURCES)) $(XCPROJECT) ## Ru
121121
-project$(XCPROJECT)\
122122
-scheme$(SCHEME)\
123123
-testPlan$(TEST_PLAN)\
124+
-skipMacroValidation\
124125
-skipPackagePluginValidation\
125126
CODE_SIGNING_REQUIRED=NO\
126127
CODE_SIGNING_ALLOWED=NO| xcbeautify

‎scripts/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ xcodebuild \
125125
-configuration"Release" \
126126
-archivePath"$ARCHIVE_PATH" \
127127
archive \
128+
-skipMacroValidation \
128129
-skipPackagePluginValidation \
129130
CODE_SIGN_STYLE=Manual \
130131
CODE_SIGN_IDENTITY="$CODE_SIGN_IDENTITY" \

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp