Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 13 additions & 3 deletions public/dropbox.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
Expand All @@ -17,7 +17,7 @@
const error = params.get("error");

if (code) getToken();
else if (error) window.opener.Cloud.providers.dropbox.returnError(params.get("error_description"));
else if (error) returnError(params.get("error_description"));
else startAuth();
Comment on lines 19 to 21
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

params.get("error_description") can be null, which will then be broadcast and eventually crash returnError in the opener (it calls replaceAll on the value). Provide a fallback string before calling returnError (and/or coerce description to a string inside returnError).

Copilot uses AI. Check for mistakes.

function startAuth() {
Expand All @@ -31,13 +31,23 @@
.catch(error => console.error(error));
}

function returnError(description) {
const channel = new BroadcastChannel("dropbox-auth");
channel.postMessage({type: "error", description});
channel.close();
window.close();
}

function getToken() {
auth.setCodeVerifier(window.sessionStorage.getItem("codeVerifier"));
auth
.getAccessTokenFromCode(REDIRECT_URI, code)
.then(resp => {
const token = resp.result.access_token;
window.opener.Cloud.providers.dropbox.setDropBoxToken(token);
const channel = new BroadcastChannel("dropbox-auth");
channel.postMessage({type: "token", token});
channel.close();
window.close();
})
.catch(error => {
console.error(error);
Comment on lines +49 to 53
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

If posting the token fails or getAccessTokenFromCode rejects, the opener tab will wait until its 120s timeout because this window never reports the failure via the channel. Consider reusing returnError(...) in the .catch(...) (and similarly in startAuth().catch(...)) so the opener can reject immediately and the auth window can close.

Copilot uses AI. Check for mistakes.
Expand Down
18 changes: 11 additions & 7 deletions public/modules/io/cloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,26 +95,30 @@ window.Cloud = (function () {
reject(new Error("Timeout. No auth for Dropbox"));
}, 120 * 1000);

window.addEventListener("dropboxauth", e => {
const channel = new BroadcastChannel("dropbox-auth");
channel.onmessage = async ({data}) => {
channel.close();
clearTimeout(watchDog);
Comment on lines +98 to 101
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

auth() opens a BroadcastChannel but the timeout path only closes authWindow and rejects; the channel remains open and can still deliver a late message after the Promise is settled. Consider tracking a settled flag and closing the channel / removing the handler in the watchdog (and ignoring messages if already settled) to avoid leaks and racey state updates.

Copilot uses AI. Check for mistakes.
resolve();
});
if (data.type === "token") {
await this.setDropBoxToken(data.token);
Comment on lines +99 to +103
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

channel.onmessage is async and awaits setDropBoxToken(). If setDropBoxToken / connect throws, the exception becomes an unhandled rejection and the outer auth() Promise will never resolve/reject. Wrap the body in try/catch and call reject(err) (and show an error) on failure.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +103
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

Using a fixed channel name ("dropbox-auth") broadcasts the access token to any same-origin context currently listening on that channel (e.g., another open FMG tab doing Dropbox auth). This can cause cross-tab interference (wrong tab resolves, orphaned auth windows) and unnecessarily widens token exposure. Consider generating a per-auth nonce/channel name and passing it to dropbox.html (e.g., via query string) so only the initiating window receives the token/error.

Copilot uses AI. Check for mistakes.
resolve();
} else {
this.returnError(data.description);
reject(new Error(data.description));
}
Comment on lines +102 to +108
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The message handler assumes data.description is always a string. If error_description is missing (or a malformed message is received), returnError will throw on replaceAll and the rejection will be confusing. Guard the message schema here (e.g., default the description, and validate token exists before calling setDropBoxToken).

Copilot uses AI. Check for mistakes.
};
});
},

// Callback function for auth window
async setDropBoxToken(token) {
DEBUG.cloud && console.info("Access token:", token);
setToken(this.name, token);
await this.connect(token);
this.authWindow.close();
window.dispatchEvent(new Event("dropboxauth"));
},

returnError(errorDescription) {
console.error(errorDescription);
tip(errorDescription.replaceAll("+", " "), true, "error", 4000);
this.authWindow.close();
},

async getLink(path) {
Expand Down
Loading