Conversation
instead of window.opener it is more robust note that in cases where window.opener is null, the auth window will not be auto closed (I don't know how to fix that)
|
There was a problem hiding this comment.
Pull request overview
This PR updates Uppy’s OAuth token handoff to use a WebSocket-based callback channel (with legacy HTML postMessage fallback) to avoid failures when window.opener is unavailable (eg due to COOP / app-to-browser redirects), particularly impacting Dropbox/Instagram flows.
Changes:
- Add an
authCallbackTokenparam to the OAuth connect flow and deliver the final auth result via Companion’s WebSocket server + emitter. - Preserve backward compatibility by keeping the legacy HTML
postMessagemechanism when no callback token is present. - Adjust Companion socket routing/state storage and update tests/examples accordingly.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/@uppy/provider-views/src/ProviderView/AuthView.tsx | Disables auth submit buttons while starting auth. |
| packages/@uppy/companion/test/mockserver.js | Exposes Companion emitter from the mock server for WebSocket/auth callback testing. |
| packages/@uppy/companion/test/mockoauthstate.js | Updates oauth-state mock signature/behavior for new state key usage. |
| packages/@uppy/companion/test/callback.test.js | Adds tests for WebSocket-based token delivery and retains legacy HTML fallback test. |
| packages/@uppy/companion/src/standalone/index.js | Returns emitter from standalone server bootstrap. |
| packages/@uppy/companion/src/server/socket.js | Adds a WebSocket “router” to handle both upload sockets and auth-callback sockets. |
| packages/@uppy/companion/src/server/helpers/html.js | Introduces shared HTML templates for success/error pages and legacy auth callback HTML. |
| packages/@uppy/companion/src/server/controllers/send-token.js | Sends token via emitter (WebSocket path) when callback token is present; otherwise uses legacy HTML. |
| packages/@uppy/companion/src/server/controllers/connect.js | Persists authCallbackToken into encrypted OAuth state. |
| packages/@uppy/companion/src/server/controllers/callback.js | Emits auth failure via emitter (WebSocket path) when callback token is present; otherwise uses legacy HTML. |
| packages/@uppy/companion/src/server/Uploader.js | Adjusts redis storage prefix constant for upload token state. |
| packages/@uppy/companion-client/src/getAllowedHosts.ts | Removes isOriginAllowed helper from companion-client. |
| packages/@uppy/companion-client/src/getAllowedHosts.test.ts | Removes tests for the removed isOriginAllowed helper. |
| packages/@uppy/companion-client/src/Provider.ts | Implements OAuth login via WebSocket auth callback token instead of postMessage. |
| examples/vue/test/index.test.ts | Updates remote source auth test assertions (post-click expectation removed). |
| examples/sveltekit/test/index.test.ts | Updates remote source auth test assertions (post-click expectation removed). |
| examples/react/test/index.test.tsx | Updates remote source auth test assertions (post-click expectation removed). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| authWindow?.addEventListener('close', () => { | ||
| reject(new Error('Authentication window was closed by user')) | ||
| closeSocket() | ||
| }) |
There was a problem hiding this comment.
authWindow?.addEventListener('close', ...) won’t fire: Window objects don’t emit a close event. This means you no longer detect the user closing the auth popup (the old polling via authWindow.closed did). Reintroduce a reliable close detection mechanism (eg polling) and ensure it’s cleaned up in finally.
| const path = req.url | ||
|
|
||
| // authentication callback token? | ||
| const authCallbackTokenMatch = path.match( | ||
| /^\/api2\/auth-callback\/token\/([^/]+)/, | ||
| ) | ||
| const authCallbackToken = authCallbackTokenMatch?.[1] | ||
|
|
||
| emitter().emit(`connection:${token}`) | ||
| emitter().on(token, send) | ||
| // or token that identifies which ongoing upload's progress, the socket connection wishes to listen to. | ||
| const uploadTokenMatch = path.match(/^\/api\/([^/]+)/) | ||
| const uploadToken = uploadTokenMatch?.[1] |
There was a problem hiding this comment.
The new path matching is anchored to the start of req.url (^/api...). This breaks setups where Companion is mounted under a path prefix (eg /companion/api/<token>), which previously worked because the token was extracted with replace(/^.*\/api\//, ''). Please update the routing/token extraction to handle optional prefixes for both upload sockets and the new auth-callback sockets.
There was a problem hiding this comment.
agreed this would break routing when companion is mounted under a path prefix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
I think only way would to be change the redirect flow so it doesn't need different endpoints / domains / use of companion endpoints (perhaps opton for us to handle the Oauth and pass it to companion client side) But a much deeper change. |
qxprakash
left a comment
There was a problem hiding this comment.
overall looks good, minor comments and I tested few bugs which were reported by copilot
and other fixes
mifi
left a comment
There was a problem hiding this comment.
i've now resolved the feedback i believe
|
once @qxprakash has reviewed this we should merge and make a release asap, so people can start to test |
qxprakash
left a comment
There was a problem hiding this comment.
looks good, I've tested the bugs they're all fixed. I'm fine with doing a major release with the breaking change if we don't have any other alternatives.
| const { app } = standalone() | ||
| const { app, companionOptions } = standalone() | ||
|
|
||
| companion.socket(app.listen(port)) |
There was a problem hiding this comment.
this is a breaking API change , can we think of any alternatives to support path prefixes without a breaking change or this is the only way ?
There was a problem hiding this comment.
is it a breaking change though? start-server is just a script to run companion
There was a problem hiding this comment.
it's breaking for users who integrate companion into their express app by calling the public API directly ,
it broke both our examples -
uppy/examples/companion/server/index.js
Line 78 in 71a34b9
uppy/examples/aws-companion/server.cjs
Line 69 in 71a34b9
as it now requires an extra argument companionOptions
| // if we don't setTimeout, the window doesn't really close (I don't know why). | ||
| setTimeout(() => authWindow?.close(), 1) | ||
| this.uppy.log(`Closing auth callback socket ${authCallbackToken}`) |
There was a problem hiding this comment.
I will check this if I get time or maybe we can debug this later after we've merged this PR
Removed console log for externalBasePath.
instead of window.opener - it is more robust
inspired by #4110
closes #6246
closes #4107
Biggest caveat: In cases where window.opener is null (Dropbox), the auth window will not be auto closed (I don't know how to fix that). It will show the user a message "Authentication successful. You may now close this page." https://github.com/transloadit/uppy/pull/6248/changes#diff-ef5b69c4ab5b83168eaba6f57047bb07c53e3a426f375b42fcb32d79c746872cR22