Skip to content

Fix Naver/Daum login not syncing back to the in-app WebView - #95

Merged
devxoul merged 5 commits into
mainfrom
fix/in-app-login-popup
Aug 7, 2026
Merged

Fix Naver/Daum login not syncing back to the in-app WebView#95
devxoul merged 5 commits into
mainfrom
fix/in-app-login-popup

Conversation

@devxoul

@devxoul devxoul commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

This continues #94 by @DusanBaek, which could not be merged as-is: CI was red and two reviewers (typeey, cubic) requested changes. The original fix is cherry-picked here with authorship preserved (337dced), and four commits are added on top to fix CI and address the review feedback. #94 will be closed in favor of this PR.

The fix (337dced, unchanged from #94): login popups (Naver, Daum) were opened via NSWorkspace.shared.open in the system browser, which has a cookie store separate from the app's WKWebView — so logging in there never reached the app. Popups now open in an in-app NSWindow built from the WKWebViewConfiguration WebKit passes to createWebViewWith, sharing the cookie store with the main dictionary view.

What's new on top of #94

CI was broken independently of this change

brew tap tuist/tuist && brew install --formula tuist now fails: the tap's cask declares conflicts_with formula:, a key Homebrew 6 removed, so any tuist lookup through the tap aborts (tuist/homebrew-tuist#41). This affects main too, not just this branch. Replaced with a pinned mise.toml (tuist = "4.200.5") and jdx/mise-action@v2 in both ci.yml and release.yml — this is what Tuist's CI docs now recommend; tuist/tuist-action is archived.

Review: popup delegates were reusing shared state

Both reviewers flagged that #94 set popupWebView.navigationDelegate = self; popupWebView.uiDelegate = self, reusing the main dictionary Coordinator. That coordinator drives the single main web view and owns shared state, so each popup navigation during login would flip the main view's isLoading, inject the dictionary's custom CSS into the login page, run its input-focusing script, and pollute lastLoadedURL.

Fixed with LoginPopupController, which owns the popup window, its web view, and its own delegates. The coordinator no longer touches popup navigation.

A regression #94 would have shipped

#94 deleted the createWebViewWith body wholesale, which removed the behavior added by 4d208b4 ("Fix #66: Open target=_blank links in default browser"). #66 is about Naver content links (e.g. "활용형 더보기") that open via target=_blank — full-site pages unusable at popover size, deliberately sent to the default browser. Opening every popup in-app would have regressed that.

Popups are now routed by destination via PopupRequest/PopupDisposition instead:

  • Known login hosts (nid.naver.com, logins.daum.net, accounts.kakao.com) → in-app window (shares the cookie store, the whole point of the fix).
  • Other http(s) → default browser (preserves 새 창 링크가 열리지 않는 문제 #66).
  • Anything else → denied.

Matching is on exact hosts, not suffixes — a suffix match would let ad/content hosts under naver.com or daum.net into a window that shares logged-in cookies. The one exception is blank popups (window.open("", name, features) is navigated only after the window exists), accepted only when the opening page is a dictionary page.

Other fixes made while addressing the reviews

  • The main view now reloads on the next popover open instead of immediately, so the input-focus script no longer runs against a hidden popover.
  • Fixed a pre-existing bug where reloadIfNeeded() never restored lastLoadedURL, which made the view reload on every popover open after a web content process crash.
  • The app-wide local key monitor was installed once and never removed, swallowing Escape for every window. The new login window made this visible: Escape could not close it, and the app is LSUIElement, so there is no menu bar and no Cmd+W. The monitor is now scoped to the popover's window.
  • The status item icon stayed lit when a .transient popover closed by itself, which now happens whenever the login window takes focus. Added NSPopoverDelegate.popoverDidClose.
  • Used plain NSApp.activate() instead of activate(ignoringOtherApps:), since 0746a14 removed the latter because it made Stage Manager slide other apps' windows aside.

Test plan

  • swiftformat --lint clean
  • xcodebuild build succeeds
  • xcodebuild test — 42 tests, 0 failures (17 new, covering the routing policy: login hosts, target=_blank content links via both .linkActivated and script, lookalike hosts like nid.naver.com.example.com, blank-popup bootstrap from trusted vs untrusted sources, and custom/file/javascript schemes)
  • Log in to Naver in the popup; confirm the popup closes and the main view shows a logged-in state
  • Same check for Daum
  • Confirm manual popup close also works
  • Confirm "활용형 더보기" still opens in the default browser
  • Confirm pasting a password from a password manager works in the popup window

The last five items were flagged on #94 and are still outstanding — they need a human with real Naver/Daum accounts to verify.


Summary by cubic

Fixes Naver/Daum login not syncing back to the app by opening login popups in an in‑app window that shares the main WKWebView cookie store. Keeps content links opened with target=_blank in the default browser.

  • Bug Fixes

    • Route popups by destination with a tested policy: exact login hosts (nid.naver.com, logins.daum.net, accounts.kakao.com) open in-app; blank popups from dictionary pages allowed; other http(s) open in the system browser; everything else denied.
    • Add LoginPopupController with its own delegates; forward naversearchapp:// and kakaotalk:// links from the popup to the system; reload the dictionary view on the next popover open after the popup closes.
    • Scope the key monitor to the popover window and reset the status item when the popover auto-closes; fix reloadIfNeeded() to persist lastLoadedURL.
  • Dependencies

    • Install tuist via mise pinned in mise.toml using jdx/mise-action@v2; remove the failing Homebrew tap.

Written for commit a3f8d99. Summary will update on new commits.

Review in cubic

Login popups were opened in the system default browser via
NSWorkspace, which stores cookies in a completely separate data
store from the app's WKWebView. Logging in there never reflected in
the app.

Popups are now presented in an in-app window using the same
WKWebViewConfiguration WebKit hands to createWebViewWith(_:for:),
so the login session lands in the same cookie store as the main
dictionary view. The main view reloads once the popup closes.

@typeey typeey Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The popup lifecycle and routing policy address the previous delegate-state and target=_blank regressions cleanly. Verification passed at 0ac7cc0, including lint, dependency setup, generation, build, and the test suite.

The real-account and password-manager scenarios remain appropriately called out as manual follow-up checks.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Allkdic/Views/DictionaryWebView.swift">

<violation number="1" location="Allkdic/Views/DictionaryWebView.swift:184">
P3: `needsSessionReload` is set unconditionally in the popup's `onClose`, regardless of whether the user actually logged in. Because the `.transient` popover auto-closes the moment the login window takes focus, this then triggers a full `webView.reload()` on the next popover open for every login-popup interaction — including a user who opens the login window and immediately closes it without authenticating. That reload discards the dictionary page's scroll/search state needlessly. Consider only scheduling the reload when a login actually completed.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Allkdic/Types/PopupRequest.swift Outdated
Comment thread Allkdic/Types/PopupRequest.swift Outdated
Comment thread Allkdic/Views/DictionaryWebView.swift Outdated
Comment thread Allkdic/Views/LoginPopupController.swift
Comment thread .github/workflows/ci.yml Outdated
let controller = LoginPopupController(configuration: configuration, title: self.dictionary.title)
controller.onClose = { [weak self] in
self?.loginPopupController = nil
self?.needsSessionReload = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: needsSessionReload is set unconditionally in the popup's onClose, regardless of whether the user actually logged in. Because the .transient popover auto-closes the moment the login window takes focus, this then triggers a full webView.reload() on the next popover open for every login-popup interaction — including a user who opens the login window and immediately closes it without authenticating. That reload discards the dictionary page's scroll/search state needlessly. Consider only scheduling the reload when a login actually completed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Allkdic/Views/DictionaryWebView.swift, line 184:

<comment>`needsSessionReload` is set unconditionally in the popup's `onClose`, regardless of whether the user actually logged in. Because the `.transient` popover auto-closes the moment the login window takes focus, this then triggers a full `webView.reload()` on the next popover open for every login-popup interaction — including a user who opens the login window and immediately closes it without authenticating. That reload discards the dictionary page's scroll/search state needlessly. Consider only scheduling the reload when a login actually completed.</comment>

<file context>
@@ -133,14 +148,45 @@ private struct WebView: NSViewRepresentable {
+      let controller = LoginPopupController(configuration: configuration, title: self.dictionary.title)
+      controller.onClose = { [weak self] in
+        self?.loginPopupController = nil
+        self?.needsSessionReload = true
+      }
+      controller.show()
</file context>

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this one, deliberately — the alternatives are less reliable than the cost they save.

To make the reload conditional we would need to detect that a login actually succeeded, and every available signal is guesswork:

  • Cookie diffing — opening the login page mutates CSRF/tracking/session-temp cookies even when the user cancels, so this produces false positives.
  • Known session cookie names (NID_AUT/NID_SES, _kawlt) — undocumented, provider-private, and free to change without notice.
  • Closed via window.close() vs. closed by the user — the closest thing to a real signal, but flows that redirect the opener instead of self-closing, or that self-close on failure, break it in both directions.

The failure modes are not symmetric. A needless reload costs the user their scroll position and current search. A missed reload silently leaves the app logged out — which is precisely the bug this PR exists to fix, and it would fail in exactly the hard-to-reproduce way that made the original report tricky.

Worth noting the reload is already deferred to the next popover open rather than firing immediately, so it does not interrupt anything mid-interaction, and the popup only ever appears because the user deliberately clicked a login button.

Happy to revisit if the cancel-then-reopen path turns out to be common in practice, but I would rather not trade a correctness guarantee for a state-preservation nicety on a heuristic.

devxoul added 4 commits August 8, 2026 02:13
The tuist tap's cask declares `conflicts_with formula:`, a key Homebrew 6
removed, so evaluating the tap aborts and every `tuist` lookup through it
fails. Any CI run now dies at the install step.

mise is what Tuist's CI docs recommend, and pinning the version in
mise.toml keeps CI and local development on the same toolchain.
See tuist/homebrew-tuist#41.
Dictionary pages use window.open for two unrelated things: login, which
must stay in the app to share its cookie store, and target=_blank content
links, which belong in the default browser per #66. Telling them apart
needs a rule, and the rule needs to be testable.

Matching is on exact hosts. A suffix match would let ad and content hosts
under naver.com or daum.net into a window that shares logged-in cookies.
Blank popups are the one exception, since window.open("", name, features)
is navigated only after the window exists; those are accepted only when
the page opening them is a dictionary page.

Modelled as plain values rather than WKNavigationAction because WebKit's
action objects cannot be constructed in a test.
The popup web view reused the dictionary Coordinator as its navigation and
UI delegate, so popup navigations drove the main view: they flipped its
loading indicator, injected the dictionary's custom CSS into the login page
and ran its input-focusing script against it. LoginPopupController now owns
the popup window, its web view and its own delegates, and the Coordinator
routes each popup by the policy instead of sending them all to one place.

The main view reloads on the next popover open rather than immediately, so
the focus script no longer runs against a hidden popover. That also fixes
reloadIfNeeded() never restoring lastLoadedURL, which made the view reload
on every popover open after a web content process crash.
The local key monitor is installed once and never removed, so it swallowed
Escape for every window in the app. The login window made that visible:
Escape could not close it, and the app has no menu bar, so there is no
Cmd+W either.

Also reset the status item icon when the popover closes by itself, which is
what happens when the login window takes focus away from a transient
popover.
@devxoul
devxoul force-pushed the fix/in-app-login-popup branch from 0ac7cc0 to a3f8d99 Compare August 7, 2026 17:13
@devxoul
devxoul merged commit 0d70d6c into main Aug 7, 2026
2 checks passed
@devxoul
devxoul deleted the fix/in-app-login-popup branch August 7, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

새 창 링크가 열리지 않는 문제

2 participants