Skip to content

Commit e9c5429

Browse files
authored
Merge pull request #616 from brownplt/coop-fix
[ai-assisted] Work around COOP issues with BroadcastChannel and local…
2 parents 07d21a3 + 238e617 commit e9c5429

2 files changed

Lines changed: 97 additions & 8 deletions

File tree

src/web/close.html

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,39 @@
22
<html>
33
<head>
44
<script>
5-
window.opener.postMessage("done", document.location.origin);
6-
window.close()
5+
// Method 1: Traditional postMessage (works when COOP allows window.opener)
6+
if (window.opener) {
7+
try {
8+
window.opener.postMessage("done", document.location.origin);
9+
} catch (e) {
10+
console.warn("postMessage to opener failed:", e);
11+
}
12+
}
13+
else {
14+
console.warn("No window.opener", window.opener);
15+
}
16+
17+
// Method 2: BroadcastChannel (works even when COOP severs window.opener)
18+
// This is the fallback for environments like GoGuardian that inject COOP headers
19+
if (typeof BroadcastChannel !== 'undefined') {
20+
try {
21+
let channel = new BroadcastChannel('pyret_auth');
22+
channel.postMessage({ type: 'auth_complete' });
23+
channel.close();
24+
} catch (e) {
25+
console.warn("BroadcastChannel failed:", e);
26+
}
27+
}
28+
29+
// Method 3: localStorage fallback for very old browsers without BroadcastChannel
30+
// The opener can detect this via the 'storage' event
31+
try {
32+
localStorage.setItem('pyret_auth_complete', Date.now().toString());
33+
} catch (e) {
34+
console.warn("localStorage fallback failed:", e);
35+
}
36+
37+
window.close();
738
</script>
839
</head>
940
</html>

src/web/js/google-apis/api-wrapper.js

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,73 @@ function reauth(immediate, useFullScopes) {
5151
if(useFullScopes) {
5252
path += "&scopes=full";
5353
}
54-
// Need to do a login to get a cookie for this user; do it in a popup
55-
window.addEventListener('message', function(e) {
54+
55+
// Track whether we've already resolved to avoid double-resolution
56+
var resolved = false;
57+
function resolveOnce(method) {
58+
if (!resolved) {
59+
console.log("INFO: Popup login resolved by: ", method);
60+
resolved = true;
61+
// NOTE(joe): A useful thing to do for testing is to comment out this
62+
// cleanup(), and check which of the 3 methods are returning success
63+
// here. cleanup() will stop others from triggering.
64+
cleanup();
65+
d.resolve(reauth(true, useFullScopes));
66+
}
67+
else {
68+
console.log("INFO: Popup login resolved again (ignored): ", method);
69+
}
70+
}
71+
72+
// Cleanup function to remove all listeners
73+
var channel = null;
74+
function cleanup() {
75+
window.removeEventListener('message', messageHandler);
76+
window.removeEventListener('storage', storageHandler);
77+
try { localStorage.removeItem('pyret_auth_complete'); } catch (err) {}
78+
if (channel) {
79+
try { channel.close(); }
80+
finally { channel = null; }
81+
}
82+
}
83+
84+
// Method 1: Traditional postMessage (works when COOP allows window.opener)
85+
function messageHandler(e) {
5686
// e.domain appears to not be defined in Firefox
5787
if ((e.domain || e.origin) === document.location.origin) {
58-
d.resolve(reauth(true, useFullScopes));
59-
} else {
60-
d.resolve(null);
88+
resolveOnce("postMessage");
6189
}
62-
});
90+
}
91+
window.addEventListener('message', messageHandler);
92+
93+
// Method 2: BroadcastChannel (works even when COOP severs window.opener)
94+
// This is the fallback for environments like GoGuardian that inject COOP headers
95+
if (typeof BroadcastChannel !== 'undefined') {
96+
try {
97+
channel = new BroadcastChannel('pyret_auth');
98+
channel.onmessage = function(e) {
99+
if (e.data && e.data.type === 'auth_complete') {
100+
resolveOnce("Broadcast");
101+
}
102+
};
103+
} catch (e) {
104+
console.warn("BroadcastChannel setup failed:", e);
105+
}
106+
}
107+
108+
// Method 3: localStorage fallback for very old browsers without BroadcastChannel
109+
function storageHandler(e) {
110+
if (e.key === 'pyret_auth_complete') {
111+
resolveOnce("localStorage");
112+
// Clean up the flag
113+
try { localStorage.removeItem('pyret_auth_complete'); } catch (err) {}
114+
}
115+
}
116+
// Clear any stale auth flag before opening popup
117+
try { localStorage.removeItem('pyret_auth_complete'); } catch (e) {}
118+
window.addEventListener('storage', storageHandler);
119+
120+
// Need to do a login to get a cookie for this user; do it in a popup
63121
window.open(path);
64122
} else {
65123
// The user is logged in, but needs an access token from our server

0 commit comments

Comments
 (0)