Skip to content

Commit 329ddc7

Browse files
committed
Merge branch 'main' of https://github.com/jpolitz/pyret
2 parents 38e9638 + 4f0cd4b commit 329ddc7

23 files changed

Lines changed: 377 additions & 180 deletions

code.pyret.org/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>

code.pyret.org/src/web/editor.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ <h2 id="menutitle" class="screenreader-only">Navigation Controls</h2>
156156
aria-label="Contribute detailed usage information"/>
157157
<label for="detailed-logging" id="detailed-logging-label">
158158
Contribute detailed usage information.</label>
159-
<a href="https://www.pyret.org/cpo-faq#(part._logging)" target="_blank" rel="noopener noreferrer" class="focusable info-btn" role="menuitem" tabindex="-1"
159+
<a href="https://code.pyret.org/faq/" target="_blank" rel="noopener noreferrer" class="focusable info-btn" role="menuitem" tabindex="-1"
160160
id="detailed-logging-learn-more"
161161
title="Learn More" aria-label="Learn More">?</a>
162162
</span>

code.pyret.org/src/web/faq.html

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,7 @@ <h3>What permissions does code.pyret.org ask for and why?</h3>
4242
<li><em>Know who you are on Google</em> and <em>View your email address</em>:
4343
The site needs to know your Google identity because this allows us to give
4444
persistent access to saving to Drive that doesn't expire or require popping up
45-
new windows while you're editing. </li>
46-
47-
<li><em>Manage your photos and videos</em> and <em>View the photos, videos, and
48-
albums in your Google Drive</em>: This is used to import images into programs
49-
from your Drive (which can be useful for customizing games, for example).
50-
</li>
51-
52-
<li><em>View and manage your spreadsheets on Google Drive</em>: This enables
53-
importing tables and working with data sources in your Drive.</li>
45+
new windows while you're editing.</li>
5446

5547
<li><em>Add itself to Google Drive</em>: This lets you right-click on Pyret
5648
programs in Google Drive and "Open with Pyret."</li>

code.pyret.org/src/web/js/cpo-main.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@
150150
// NOTE(joe): this function just allocates a closure, so it's stack-safe
151151
var onCompile = gmf(cpo, "make-on-compile").app(runtime.makeFunction(saveGDriveCachedFile, "save-gdrive-cached-file"));
152152

153+
function maybeAppendSlash(s) {
154+
if(s.endsWith("/")) { return s; }
155+
return s + "/";
156+
}
157+
158+
function urlResolve(path, base) {
159+
return new URL(path, base).href;
160+
}
161+
153162
// NOTE(joe/ben): this function _used_ to be trivially stack safe, but files
154163
// need to resolve their absolute path to calculate their URI, which
155164
// requires an RPC, so this function is no-longer trivially flat
@@ -198,7 +207,7 @@
198207
return arr[0];
199208
}
200209
else if (protocol === "url-file") {
201-
return arr[0] + "/" + arr[1];
210+
return urlResolve(arr[1], maybeAppendSlash(arr[0]));
202211
}
203212
else {
204213
console.error("Unknown import: ", dependency);
@@ -256,7 +265,7 @@
256265
return runtime.getField(runtime.getField(urlLoc, "values"), "url-locator").app(arr[0], replGlobals);
257266
}
258267
else if (protocol === "url-file") {
259-
const fullUrl = arr[0] + "/" + arr[1];
268+
const fullUrl = urlResolve(arr[1], maybeAppendSlash(arr[0]));
260269
switch(urlFileMode) {
261270
case "all-remote":
262271
fetch(fullUrl).then(async (response) => {

code.pyret.org/src/web/js/dashboard/StudentDashboard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ class StudentDashboard extends Component {
228228
</div>
229229
<div className='footer middle'>
230230
<p className='right'>
231-
<a target="_blank" href="https://www.pyret.org">pyret.org</a> | <a target="_blank" href="https://pyret.org/cpo-faq/">Privacy</a> | <a target="_blank" href="https://www.github.com/brownplt/code.pyret.org">Software</a></p>
231+
<a target="_blank" href="https://www.pyret.org">pyret.org</a> | <a target="_blank" href="https://code.pyret.org/faq/">Policies</a> | <a target="_blank" href="https://www.github.com/brownplt/code.pyret.org">Software</a></p>
232232
</div>
233233

234234
</div>

code.pyret.org/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

code.pyret.org/src/web/js/trove/world.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,7 @@
532532
}
533533
var ctx = reusableCanvas.getContext("2d");
534534
ctx.save();
535-
ctx.fillStyle = "rgba(255,255,255,0)";
536-
ctx.fillRect(0, 0, width, height);
535+
ctx.clearRect(0, 0, width, height);
537536
ctx.restore();
538537
theImage.render(ctx, 0, 0);
539538
success([toplevelNode, reusableCanvasNode]);

embed/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

embed/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pyret-embed",
3-
"version": "0.0.46",
3+
"version": "0.0.57",
44
"description": "A library for embedding Pyret into webpages",
55
"main": "dist/pyret.js",
66
"files": [

lang/src/arr/compiler/cli-module-loader.arr

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import filesystem as Filesystem
1313
import file as F
1414
import error as ERR
1515
import system as SYS
16+
import url as URL
1617
import file("js-ast.arr") as J
1718
import file("concat-lists.arr") as C
1819
import file("compile-lib.arr") as CL
@@ -308,6 +309,13 @@ fun get-real-path(current-load-path :: String, this-path :: String):
308309
end
309310
end
310311

312+
fun maybe-add-slash(s):
313+
last-index = string-length(s) - 1
314+
if string-char-at(s, last-index) == "/": s
315+
else: s + "/"
316+
end
317+
end
318+
311319
fun locate-file(ctxt :: CLIContext, rel-path :: String):
312320
clp = ctxt.current-load-path
313321
real-path = get-real-path(clp, rel-path)
@@ -329,7 +337,8 @@ fun module-finder(ctxt :: CLIContext, dep :: CS.Dependency):
329337
else if protocol == "url":
330338
CL.located(UL.url-locator(dep.arguments.get(0), CS.standard-globals), ctxt)
331339
else if protocol == "url-file":
332-
full-url = args.get(0) + "/" + args.get(1)
340+
base = maybe-add-slash(args.get(0))
341+
full-url = URL.resolve(args.get(1), base)
333342
cases(CS.UrlFileMode) ctxt.url-file-mode:
334343
| all-remote =>
335344
CL.located(UL.url-locator(full-url, CS.standard-globals), ctxt)
@@ -551,11 +560,9 @@ fun build-runnable-standalone(path, require-config-path, outfile, options) block
551560
| left(problems) =>
552561
handle-compilation-errors(problems, options)
553562
| right(program) =>
554-
shadow require-config-path = if not( Filesystem.is-absolute( require-config-path ) ):
555-
Filesystem.resolve(Filesystem.join(options.base-dir, require-config-path))
556-
else: require-config-path
557-
end
558-
config.set-now("out", JSON.j-str(Filesystem.resolve(Filesystem.join(options.base-dir, outfile))))
563+
shadow require-config-path = get-real-path(options.base-dir, require-config-path)
564+
565+
config.set-now("out", JSON.j-str(get-real-path(options.base-dir, outfile)))
559566
when not(config.has-key-now("baseUrl")):
560567
config.set-now("baseUrl", JSON.j-str(options.compiled-cache))
561568
end

0 commit comments

Comments
 (0)