Skip to content

Commit ebf84e9

Browse files
authored
Merge pull request #569 from brownplt/vmt-initialState
VMT/embedding API
2 parents dfbd511 + 296e2d5 commit ebf84e9

23 files changed

+6376
-2909
lines changed

.env.example

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ PYRET="http://localhost:4999/js/cpo-main.jarr"
1313
PORT=4999
1414
NODE_ENV=development
1515
POSTMESSAGE_ORIGIN="http://localhost:3000"
16-
16+
SHARED_FETCH_SERVER="https://code.pyret.org"

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ env:
1616
- PORT=5000
1717
- SAUCE_TEST_TARGET="http://localhost:5000"
1818
- SAUCE_USERNAME="pyret-lang"
19+
- POSTMESSAGE_ORIGIN="*"
1920
- secure: e0vpQGmW0d69Ql72Mw6gsGsMxAKgt61ZNqsfQEtNlAGtx7CbTKe3xg8kF1ygwFVEB7GDmTaojTcSGbRwqqZ5GAa8dsTAkefhOtfCDV5KUjvXXbpo9J/8YLiZiIy7ksHyh1yhByvvWmZZpjmdxWwcH8gUqtpae3bGtz7rQX3jLf2v3OR5uVFt8vKRMSR9imCWmq6tpnu+GVoaCFOHjq3U7LS1h78R2LJz+MnTuCbDtYFlxp/sAyS7vSXssFScl6wqOsgH5PHlj+kaHhFWLhzsAzG1RoHftqqIeinXzDcYo8EC/c6O3hMf2KtHm/Hsh1dPR8GnE6WClxPw0xNbh10jVf7b1CXuzKGF8a1JlmtsVztiujsMswZGGLpTkqBvBAiuDsMOwKFNk7UrR5FA/0bVMMsluKxW/0UBzVPN9QyMKAs19ca1t/qT3abxYFyvXrDibKMLuS1uCCsZbwTkjkPwfWBzin8ohK2gLi+y24eQFSd27XWcmk+fJHfgJ458OgvczubK0CsyPAaJtCvRBYKvLQaEtkc4RkoM0yx6NhYYLCZqH1DRneam40rVSrVO41yT/PUnA5U64q16ptbn90Vgw/yKlp9OLtWKheBDTinwbst1kegbCSj4qZtORFLIQIUsZQN6SUm9tCACqFosDpRXAzURc2IOdsSUmtJEqvkijsc=
2021
- secure: "E19TyEFE1iQSRrZI4w6ahWgFGEFZBiUgxn/Pqq5sQwHlMNpai+bVydDRVlcQ0NsrdPmyXY/4+29wMjsAHbSaiBuqgG5PbtpChJK7a+8583G6CrbkWdZgwkMDpqPQ2IBEWVYeybvl36wuXJ4HNCqImPODWvX1Wxzk5RCK/gM4PNu3OdmQm3+6O8y1N6r0Y/lnqeAxhaZ5wv01/6EE3I3rAbnFxnu0wIJ7AfNV64VRk+tqwYuM5fLXrl6yhelSwp9q5CLEwvvYknBkklHAXbvxc2qo8XfZb6Zb701NyGvFW86Acgo1dytqV+BNmxNj4FfnXj+rxi/SDpiKEifzj5UYLIAgAx/qbxWSHD8SHiOL1WMa7N3vN2Dh+O+9vCLDwtpzbC0kJqxVMei8buAR4pLReZO0OsmHGXDPApoqOdxvQ0Z2VtEWojIWaJSIl0gTaWKAPAE6GMT0zRQcmm6GPXmLpWGc/9MZKJ5lTXQloucRaStRhPYV4x2HNF4vVZmJ0RWQexAX4qsUiaVFs0jdnJTDvgyPWj+f7qJaaqNFfXz9TyP2N8LSjPdGBYfkWIbkv8dXjJCufXX29BbOD5Yq1NcRQxgTnQO1cvXs1/MJOrK8tCAQ/rZUqQakBMg71fhznOm2lejiiingfnq846C1v27KxZzFLnGIOfDUbMAgS1sbGUU="
2122
- secure: "W74n+StLe0w+Lm6KNQN3lLzj4BFCy7vVSRCOQ3t5e3uSXDyLubqgRTXTXPoeypzcztbnMsd8Y57FyjJEaUydb2LPATEoJ9VbBy0ohXV/c3bucJSa4ZJxvb/8L62cepsZkRmoMl1HpW2kLWle50UdCLRqE0aGNHwM9FXfBAd279vmjmvPhFtGEiKkDek/ckroebG0n0xAox56ZmnuJM4boG3o0oUNjSE4U4W/RKMoApLJlUcEcdmWzJvOILeNvydFrHzQcSeHx6uH5LJg/N6RCHb++MI+BiUrAeGa0jm0YXFH0mdDmjCACAcRUzlyAGuadb2dAPcgKPV7xGyDJIn6+OD9dhwEGTGzyKr622fUcCa5+t4PERBFq7nYj7BU1CoqWUsYN6jArkH0ye7lA9usNZsHNXoykZkhpJYEoygYQg6mGnO0v0sRiUbv0BFQ4tN+l/ejvLfCiczjhuBgHlm7m0TUsH94dzeHFqGPlNGy75Rq9PM+0kxvN/TLY3udymKzpTJlbcaDvwQ37unBU2h7DpAYzscxyMcPbHo/F2lnmK/vlpSAsSjkdnpVp4Av+urn8G2eVJw+XnLWCQb4GIRsFzLfXOAPdhcQIZHfhiQTUXgFl+ahC2hEdC1sjho0yWWOHIhRoQ/5sLenB4hFTozxhbIKZTlyWI7FwuPnpWnhvAY="

build/web/js/cpo-main.jarr.gz.js

4.87 MB
Binary file not shown.

cpo-config.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"pyret-base/js/post-load-hooks": "pyret/build/phaseA/js/post-load-hooks.js",
2222

2323
"cpo/gdrive-locators": "src/web/js/gdrive-locators.js",
24+
"cpo/file-locator": "src/web/js/file-locator.js",
2425
"cpo/http-imports": "src/web/js/http-imports.js",
2526
"cpo/guess-gas": "src/web/js/guess-gas.js",
2627
"cpo/cpo-builtin-modules": "src/web/js/cpo-builtin-modules.js",

cpo-standalone.js

+2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ requirejs(["pyret-base/js/runtime", "pyret-base/js/post-load-hooks", "pyret-base
157157
$("#runDropdown").attr("disabled", false);
158158
clearInterval($("#loader").data("intervalID"));
159159
$("#loader").hide();
160+
CPO.events.triggerOnLoad();
160161
CPO.say('Pyret loaded and ready.');
162+
161163
console.log("REPL ready.");
162164
});
163165
}

package-lock.json

+5,387-2,753
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"body-parser": "^1.20.2",
1515
"bootstrap": "^4.6.2",
1616
"browserify": "^14.5.0",
17+
"chromedriver": "^131.0.5",
1718
"codemirror": "~5.58.2",
1819
"colorspaces": "0.1.4",
1920
"cookie-parser": "^1.4.6",

src/run.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ var res = Q.fcall(function(db) {
3737
redirect: "/oauth2callback"
3838
},
3939
version: process.env["CURRENT_PYRET_RELEASE"],
40-
pyret: process.env["PYRET"]
40+
pyret: process.env["PYRET"],
41+
sharedFetchServer: process.env["SHARED_FETCH_SERVER"]
4142
}, function(app) {
4243
console.log("Server ready.");
4344
});

src/server.js

+97-61
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ function start(config, onServerReady) {
7373

7474
app.use(cookieSession({
7575
secret: config.sessionSecret,
76-
key: "code.pyret.org"
76+
key: "code.pyret.org",
77+
78+
sameSite: 'lax'
7779
}));
7880
app.use(cookieParser());
7981

@@ -380,7 +382,6 @@ function start(config, onServerReady) {
380382
var folderId = decodeURIComponent(parsed.query["folderId"]);
381383

382384
lookForProjectOrCopyStructure(serverClient, drive, folderId).then(target => {
383-
console.log("target: ", target);
384385
res.redirect(`/parley?folder=${target.projectDir.id}`);
385386
}).catch((err) => {
386387
console.error(err);
@@ -432,13 +433,11 @@ function start(config, onServerReady) {
432433
}
433434
})
434435
.then(copyResult => {
435-
console.log("New directory: ", copyResult);
436436
serverDrive.files.list({
437437
key: config.google.serverApiKey,
438438
q: `"${fileInfo.id}" in parents and not trashed`,
439439
fields: 'files(id, name, mimeType, modifiedTime, modifiedByMeTime, webContentLink, iconLink, thumbnailLink)',
440440
}).then(files => {
441-
console.log("Directory contents: ", files);
442441
// NOTE(joe): deliberately parallel
443442
Promise.all(files.data.files.map(f => copyFileOrDir(serverDrive, clientDrive, copyResult.data.id, f, serverEmailAddress)))
444443
.then(copiedFiles => {
@@ -471,7 +470,6 @@ function start(config, onServerReady) {
471470
}
472471
})
473472
.then(copiedFile => {
474-
console.log("File copied: ", copiedFile);
475473
resolve(copiedFile.data);
476474
})
477475
})
@@ -497,11 +495,9 @@ function start(config, onServerReady) {
497495
clientDrive.files.list({
498496
q: `properties has {key='${PROJECT_BACKREF}' and value='${fileId}'} and trashed=false`
499497
}).then(files => {
500-
console.log("Files with key result: ", files);
501498
if(files.data.files.length === 0) {
502499
return serverDrive.files.get({ fileId, key: config.google.serverApiKey }).then((dirInfo) => {
503500
return copyFileOrDir(serverDrive, clientDrive, false, dirInfo.data).then(copied => {
504-
console.log("made a full copy of the directory");
505501
resolve({
506502
copied: true,
507503
projectDir: copied
@@ -510,7 +506,6 @@ function start(config, onServerReady) {
510506
});
511507
}
512508
else {
513-
console.log("Directory existed, so not copying");
514509
resolve({
515510
copied: false,
516511
projectDir: files.data.files[0]
@@ -773,70 +768,111 @@ function start(config, onServerReady) {
773768
return ret.promise;
774769
}
775770

771+
// NOTE(joe): this is a hack for making share URLs work on another server.
772+
// The config variable here shouldn't be set to the same URL as the deployment
773+
// URL (since this will just make self request). But since Google credentials
774+
// lock in files to a particular server, and we want share links from CPO
775+
// to work on our testing or other deployments, we need this.
776+
// If set, we first try to fetch from the indicated shared server (usually
777+
// this will be code.pyret.org).
778+
// This in fact doesn't matter *that* much for embedding clients that embed
779+
// code.pyret.org directly. However, for *other* deployments, or test
780+
// deployments, or development copies, it saves a ton of headaches.
781+
function sharedPrefetch(url) {
782+
var ret = Q.defer();
783+
if(config.sharedFetchServer) {
784+
let response = request({url: url});
785+
response.on("error", (error) => { ret.reject(error); });
786+
response.on("response", (resp) => ret.resolve(response));
787+
}
788+
else {
789+
ret.reject("No fallback server configured");
790+
}
791+
792+
return ret.promise;
793+
}
794+
776795
app.get("/shared-program-contents", function(req, res) {
777-
var contents = getSharedContents(req.query.sharedProgramId);
778-
contents.fail(function(err) {
779-
res.status(400);
780-
res.send("Unable to fetch shared file");
781-
res.end();
782-
});
783-
contents.then(function(response) {
784-
if(!response.headers['content-type'] === "text/plain") {
796+
const requestURL = `${config.sharedFetchServer}/shared-program-contents?sharedProgramId=${req.query.sharedProgramId}`;
797+
const shared = sharedPrefetch(requestURL);
798+
shared.then((resp) => resp.pipe(res));
799+
shared.fail(() => {
800+
var contents = getSharedContents(req.query.sharedProgramId);
801+
contents.fail(function(err) {
785802
res.status(400);
786-
res.send("Expected a text file, but got: " + response.headers["content-type"]);
803+
res.send("Unable to fetch shared file");
787804
res.end();
788-
}
789-
else {
790-
response
791-
.on("response", (r) => r.headers["content-disposition"] = `inline; filename="${req.query.sharedProgramId}"`)
792-
.pipe(res);
793-
}
805+
});
806+
contents.then(function(response) {
807+
if(!response.headers['content-type'] === "text/plain") {
808+
res.status(400);
809+
res.send("Expected a text file, but got: " + response.headers["content-type"]);
810+
res.end();
811+
}
812+
else {
813+
response
814+
.on("response", (r) => r.headers["content-disposition"] = `inline; filename="${req.query.sharedProgramId}"`)
815+
.pipe(res);
816+
}
817+
});
794818
});
819+
795820
});
796821

797822
app.get("/shared-image-contents", function(req, res) {
798-
var contents = getSharedContents(req.query.sharedImageId);
799-
contents.then(function(response) {
800-
response
801-
.on("response", (r) => r.headers["content-disposition"] = `inline; filename="${req.query.sharedImageId}"`)
802-
.pipe(res);
803-
})
804-
.fail(function(err) {
805-
res.status(400);
806-
res.send("Could not access shared file, or shared file was not an image.");
807-
res.end();
823+
const requestURL = `${config.sharedFetchServer}/shared-image-contents?sharedImageId=${req.query.sharedImageId}`;
824+
const shared = sharedPrefetch(requestURL);
825+
shared.then((resp) => resp.pipe(res));
826+
shared.fail(() => {
827+
var contents = getSharedContents(req.query.sharedImageId);
828+
contents.then(function(response) {
829+
response
830+
.on("response", (r) => r.headers["content-disposition"] = `inline; filename="${req.query.sharedImageId}"`)
831+
.pipe(res);
832+
})
833+
.fail(function(err) {
834+
res.status(400);
835+
res.send("Could not access shared file, or shared file was not an image.");
836+
res.end();
837+
});
808838
});
809839
});
810840

811841
app.get("/shared-file", function(req, res) {
812-
var sharedProgramId = req.query.sharedProgramId;
813-
var both = fileAndToken(sharedProgramId);
814-
both.fail(function(err) {
815-
console.error(err);
816-
res.status(404).send("No share information found for " + sharedProgramId);
817-
res.end();
818-
});
819-
both.then(function(both) {
820-
var prog = both[0];
821-
var refreshToken = both[1];
822-
auth.refreshAccess(refreshToken, function(err, newToken) {
823-
if(err) { res.status(403).send("Couldn't access shared file " + sharedProgramId); res.end(); return; }
824-
else {
825-
var drive = getDriveClient(newToken, 'v2');
826-
drive.files.get({fileId: sharedProgramId}, function(err, response) {
827-
if(err) { res.status(400).send("Couldn't access shared file " + sharedProgramId); }
828-
else {
829-
res.send({
830-
id: response.data.id,
831-
modifiedDate: response.data.modifiedDate,
832-
title: response.data.title,
833-
selfLink: response.data.selfLink
834-
});
835-
res.status(200);
836-
res.end();
837-
}
838-
});
839-
}
842+
const requestURL = `${config.sharedFetchServer}/shared-file?sharedProgramId=${req.query.sharedProgramId}`;
843+
const shared = sharedPrefetch(requestURL);
844+
shared.then((resp) => resp.pipe(res));
845+
shared.fail((e) => {
846+
console.error("Fallback failed: ", e);
847+
var sharedProgramId = req.query.sharedProgramId;
848+
var both = fileAndToken(sharedProgramId);
849+
both.fail(function(err) {
850+
console.error(err);
851+
res.status(404).send("No share information found for " + sharedProgramId);
852+
res.end();
853+
});
854+
both.then(function(both) {
855+
var prog = both[0];
856+
var refreshToken = both[1];
857+
auth.refreshAccess(refreshToken, function(err, newToken) {
858+
if(err) { res.status(403).send("Couldn't access shared file " + sharedProgramId); res.end(); return; }
859+
else {
860+
var drive = getDriveClient(newToken, 'v2');
861+
drive.files.get({fileId: sharedProgramId}, function(err, response) {
862+
if(err) { res.status(400).send("Couldn't access shared file " + sharedProgramId); }
863+
else {
864+
res.send({
865+
id: response.data.id,
866+
modifiedDate: response.data.modifiedDate,
867+
title: response.data.title,
868+
selfLink: response.data.selfLink
869+
});
870+
res.status(200);
871+
res.end();
872+
}
873+
});
874+
}
875+
});
840876
});
841877
});
842878
});

src/web/editor.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
55
<title>code.pyret.org</title>
66
<link rel="preload" href="{{&PYRET}}" as="script">
7+
<script>window.PYRET = "{{&PYRET}}";</script>
78
<link rel="stylesheet" href="/css/reset.css" />
89
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css" />
910
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Fira+Mono:400,700" />
@@ -458,7 +459,6 @@ <h2>Announcements</h2>
458459
var POSTMESSAGE_ORIGIN = "{{{ POSTMESSAGE_ORIGIN }}}";
459460
console.log(window.performance.now());
460461
</script>
461-
<script src="js/events.js"></script>
462462
<script src="js/beforePyret.js"></script>
463463
<script>
464464
var APP_BASE_URL = "{{{ BASE_URL }}}";
@@ -471,6 +471,7 @@ <h2>Announcements</h2>
471471
}
472472
});
473473
</script>
474+
<script src="js/events.js"></script>
474475

475476
</main>
476477
</body>

src/web/js/authenticate-storage.js

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ window.storageAPI = storageAPIDeferred.promise;
55
window.sheetsAPI = sheetsAPIDeferred.promise;
66

77
window.handleClientLoad = function handleClientLoad(apiKey) {
8+
if(!gapi || !gapi.client) {
9+
storageAPIDeferred.reject("no gapi.client");
10+
sheetsAPIDeferred.reject("no gapi.client");
11+
console.log("Not logged in; proceeding without login info");
12+
return;
13+
}
814
gapi.client.setApiKey(apiKey);
915
var api = createProgramCollectionAPI("code.pyret.org", true);
1016

0 commit comments

Comments
 (0)