Skip to content

Commit 45f3b8b

Browse files
authored
Merge pull request #126 from darkroomengineering/port/browser-passkeys
feat(browser): passkey/WebAuthn support in browser panes
2 parents 17d1b02 + ddac90c commit 45f3b8b

8 files changed

Lines changed: 2024 additions & 0 deletions

File tree

GhosttyTabs.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
A5001403 /* TerminalPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001413 /* TerminalPanelView.swift */; };
8080
A5001404 /* BrowserPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001414 /* BrowserPanelView.swift */; };
8181
A5007420 /* BrowserPopupWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5007421 /* BrowserPopupWindowController.swift */; };
82+
NRBW0001 /* BrowserWebAuthnSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = NRBW0002 /* BrowserWebAuthnSupport.swift */; };
8283
A5001420 /* MarkdownPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001418 /* MarkdownPanel.swift */; };
8384
A5001421 /* MarkdownPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001419 /* MarkdownPanelView.swift */; };
8485
MDRD00000000000000000001 /* MarkdownDocumentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = MDRD00000000000000000002 /* MarkdownDocumentView.swift */; };
@@ -362,6 +363,7 @@
362363
A5001413 /* TerminalPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/TerminalPanelView.swift; sourceTree = "<group>"; };
363364
A5001414 /* BrowserPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserPanelView.swift; sourceTree = "<group>"; };
364365
A5007421 /* BrowserPopupWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserPopupWindowController.swift; sourceTree = "<group>"; };
366+
NRBW0002 /* BrowserWebAuthnSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserWebAuthnSupport.swift; sourceTree = "<group>"; };
365367
A5001415 /* PanelContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/PanelContentView.swift; sourceTree = "<group>"; };
366368
A5001418 /* MarkdownPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownPanel.swift; sourceTree = "<group>"; };
367369
A5001419 /* MarkdownPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownPanelView.swift; sourceTree = "<group>"; };
@@ -728,6 +730,7 @@
728730
A5001413 /* TerminalPanelView.swift */,
729731
A5001414 /* BrowserPanelView.swift */,
730732
A5007421 /* BrowserPopupWindowController.swift */,
733+
NRBW0002 /* BrowserWebAuthnSupport.swift */,
731734
A5001418 /* MarkdownPanel.swift */,
732735
A5001419 /* MarkdownPanelView.swift */,
733736
MDRD00000000000000000002 /* MarkdownDocumentView.swift */,
@@ -1113,6 +1116,7 @@
11131116
A5001403 /* TerminalPanelView.swift in Sources */,
11141117
A5001404 /* BrowserPanelView.swift in Sources */,
11151118
A5007420 /* BrowserPopupWindowController.swift in Sources */,
1119+
NRBW0001 /* BrowserWebAuthnSupport.swift in Sources */,
11161120
A5001420 /* MarkdownPanel.swift in Sources */,
11171121
A5001421 /* MarkdownPanelView.swift in Sources */,
11181122
MDRD00000000000000000001 /* MarkdownDocumentView.swift in Sources */,

Resources/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
<string>A program running within Programa would like to use your microphone.</string>
4848
<key>NSCameraUsageDescription</key>
4949
<string>A program running within Programa would like to use your camera.</string>
50+
<key>NSBluetoothAlwaysUsageDescription</key>
51+
<string>A program running within Programa would like to use Bluetooth to discover passkeys and security keys.</string>
5052
<key>NSPrincipalClass</key>
5153
<string>NSApplication</string>
5254
<key>NSAppleScriptEnabled</key>

Resources/InfoPlist.xcstrings

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
{
22
"sourceLanguage": "en",
33
"strings": {
4+
"NSBluetoothAlwaysUsageDescription": {
5+
"extractionState": "manual",
6+
"localizations": {
7+
"en": {
8+
"stringUnit": {
9+
"state": "translated",
10+
"value": "A program running within Programa would like to use Bluetooth to discover passkeys and security keys."
11+
}
12+
},
13+
"ja": {
14+
"stringUnit": {
15+
"state": "translated",
16+
"value": "Programa内で実行中のプログラムが、パスキーとセキュリティキーを検出するためにBluetoothの使用を求めています。"
17+
}
18+
}
19+
}
20+
},
421
"NSCameraUsageDescription": {
522
"extractionState": "manual",
623
"localizations": {

Sources/Panels/BrowserPanel.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,7 @@ final class BrowserPanel: Panel, ObservableObject {
11691169
private var navigationDelegate: BrowserNavigationDelegate?
11701170
private var uiDelegate: BrowserUIDelegate?
11711171
private var downloadDelegate: BrowserDownloadDelegate?
1172+
private var webAuthnCoordinator: BrowserWebAuthnCoordinator?
11721173
private var webViewObservers: [NSKeyValueObservation] = []
11731174
private var activeDownloadCount: Int = 0
11741175

@@ -1486,6 +1487,17 @@ final class BrowserPanel: Panel, ObservableObject {
14861487
forMainFrameOnly: true
14871488
)
14881489
)
1490+
// WebAuthn can originate from same-origin child frames. The native
1491+
// bridge rejects first-time authorization requests from cross-origin
1492+
// subframes before touching the shared browser authorization state.
1493+
configuration.userContentController.addUserScript(
1494+
WKUserScript(
1495+
source: BrowserWebAuthnBridgeContract.scriptSource,
1496+
injectionTime: .atDocumentStart,
1497+
forMainFrameOnly: false,
1498+
in: .page
1499+
)
1500+
)
14891501
}
14901502

14911503
private func bindWebView(_ webView: ProgramaWebView) {
@@ -1504,6 +1516,9 @@ final class BrowserPanel: Panel, ObservableObject {
15041516
webView.uiDelegate = uiDelegate
15051517
setupObservers(for: webView)
15061518
setupReactGrabMessageHandler(for: webView)
1519+
let webAuthnCoordinator = BrowserWebAuthnCoordinator()
1520+
webAuthnCoordinator.install(on: webView)
1521+
self.webAuthnCoordinator = webAuthnCoordinator
15071522
setupIMECompositionTracking(for: webView)
15081523
}
15091524

@@ -1800,6 +1815,8 @@ final class BrowserPanel: Panel, ObservableObject {
18001815
faviconTask = nil
18011816
faviconRefreshGeneration &+= 1
18021817
BrowserWindowPortalRegistry.detach(webView: previousWebView)
1818+
webAuthnCoordinator?.uninstall(from: previousWebView)
1819+
webAuthnCoordinator = nil
18031820
previousWebView.stopLoading()
18041821
previousWebView.navigationDelegate = nil
18051822
previousWebView.uiDelegate = nil
@@ -2155,6 +2172,8 @@ final class BrowserPanel: Panel, ObservableObject {
21552172
faviconTask = nil
21562173
faviconRefreshGeneration &+= 1
21572174
BrowserWindowPortalRegistry.detach(webView: oldWebView)
2175+
webAuthnCoordinator?.uninstall(from: oldWebView)
2176+
webAuthnCoordinator = nil
21582177
oldWebView.stopLoading()
21592178
oldWebView.navigationDelegate = nil
21602179
oldWebView.uiDelegate = nil
@@ -2296,6 +2315,8 @@ final class BrowserPanel: Panel, ObservableObject {
22962315
popup.closePopup()
22972316
}
22982317

2318+
webAuthnCoordinator?.uninstall(from: webView)
2319+
webAuthnCoordinator = nil
22992320
webView.stopLoading()
23002321
webView.navigationDelegate = nil
23012322
webView.uiDelegate = nil
@@ -2907,8 +2928,11 @@ final class BrowserPanel: Panel, ObservableObject {
29072928
}
29082929
webViewObservers.removeAll()
29092930
webViewCancellables.removeAll()
2931+
let webAuthnCoordinator = webAuthnCoordinator
2932+
self.webAuthnCoordinator = nil
29102933
let webView = webView
29112934
Task { @MainActor in
2935+
webAuthnCoordinator?.uninstall(from: webView)
29122936
BrowserWindowPortalRegistry.detach(webView: webView)
29132937
}
29142938
}
@@ -2999,6 +3023,8 @@ extension BrowserPanel {
29993023
webViewObservers.removeAll()
30003024
webViewCancellables.removeAll()
30013025
BrowserWindowPortalRegistry.detach(webView: oldWebView)
3026+
webAuthnCoordinator?.uninstall(from: oldWebView)
3027+
webAuthnCoordinator = nil
30023028
oldWebView.stopLoading()
30033029
oldWebView.navigationDelegate = nil
30043030
oldWebView.uiDelegate = nil

Sources/Panels/BrowserPopupWindowController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
7878
private let popupUIDelegate: PopupUIDelegate
7979
private let popupNavigationDelegate: PopupNavigationDelegate
8080
private let downloadDelegate: BrowserDownloadDelegate
81+
private let webAuthnCoordinator: BrowserWebAuthnCoordinator
8182

8283
private static var associatedObjectKey: UInt8 = 0
8384
private var hasShownPanel = false
@@ -111,6 +112,7 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
111112
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
112113
BrowserThemeSettings.apply(openerPanel?.currentBrowserThemeMode ?? BrowserThemeSettings.mode(), to: webView)
113114
self.webView = webView
115+
self.webAuthnCoordinator = BrowserWebAuthnCoordinator()
114116

115117
// --- Window sizing from WKWindowFeatures ---
116118
let defaultWidth: CGFloat = 800
@@ -203,6 +205,7 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
203205
navDel.downloadDelegate = dlDel
204206
webView.uiDelegate = uiDel
205207
webView.navigationDelegate = navDel
208+
webAuthnCoordinator.install(on: webView)
206209

207210
// Context menu "Open Link in New Tab" → open in opener's workspace,
208211
// not as a nested popup. Falls back to system browser if opener is gone.
@@ -299,6 +302,7 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
299302
urlObservation = nil
300303

301304
// Tear down web view
305+
webAuthnCoordinator.uninstall(from: webView)
302306
webView.stopLoading()
303307
webView.navigationDelegate = nil
304308
webView.uiDelegate = nil

0 commit comments

Comments
 (0)