Skip to content

Commit

Permalink
Merge pull request #47 from asolove/asolove
Browse files Browse the repository at this point in the history
Start adding Google Drive integration
  • Loading branch information
Emmanuel Schanzer authored Jan 31, 2023
2 parents 68115f2 + e1e1978 commit c39f05d
Show file tree
Hide file tree
Showing 6 changed files with 17,192 additions and 5,564 deletions.
62 changes: 62 additions & 0 deletions NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Goal

This repo started off as a proof-of-concept that the WeScheme compiler
and runtime could be separated from its UI in an easy-to-embed way with
a simple React wrapper UI.

My intention is to add back in the Google Drive integration so that this
can form the foundation of a new version of the public WeScheme editor.

# Scope

- Core Google Drive Integration
- log in
- read/write Scheme files on Drive
- grant WeScheme access to images on Drive

---

# Notes

For the sake of future maintainers, I am keeping this liveblog of notes
of assumptions I make and docs I read as I try to get things working.

- Ok, so how does Google want us to do authe/authn/API calls now?
- Reading Overview: https://developers.google.com/drive/api/guides/about-sdk
- Reading Quickstart: https://developers.google.com/drive/api/quickstart/js

- Project setup
- Make a new project in Google Cloud Console
- Search for "Google Drive API" and click "Enable"
- Set up authorization
- Create app and pick scopes
- In APIS & Services > OAuth Consent Screen: "Create app"
- Enter name, support email, domain, etc.
- Pick Scopes: "Google Drive API .. ./auth/docs"
- Add Test users by email (added myself and [email protected])
- Create OAuth client id
- APIs & Services > Credentials > + Create Credentials > OAuth Client Id
- Create new web client id. Add allowlisted domains.
- Note down client id and secret.
- Create api key
- APIs & Services > Credentials > + Create Credentials > API Key
- Note down API key.

- Auth code
- Mostly stealing the code from the JS Quickstart
- Because we're using webpack, I had to rewrite all the callback functions from `function name() {...` to `window.name = function(){...`.
- At this point, Google code runs and correctly shows logged-out state.
- But, clicking to log in leads to an oauth redirect error (because we're on localhost)
- Let's fix this so we have a public URL in dev:
- Install ngrok (give a public https endpoint for local dev)
- Run `ngrok http https://localhost:8080` to expose local dev
- Find forwarding URL (some-long-id.ngrok.io) and use that
- Now webpack-dev-server fails with "Invalid Host Header"
- It doesn't like being proxied.
- So I had to add `disableHostCheck` in webpack.config.js
- Need to add ngrok url to both JS origin and redirect domains allowed on project (on the OAuth Client Id)

- At this point, the page appears, I can do the oauth redirect flow, accept permissions, and arrive back at a logged-in state.


---
6 changes: 6 additions & 0 deletions example/example.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,10 @@
.blocks-white-space.blocks-editing {
font-weight: normal;
margin: 15px;
}

nav {
display: flex;
justify-content: flex-end;
gap: 1em;
}
128 changes: 128 additions & 0 deletions example/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,132 @@ function runBytecode() {
} finally {
reportIfNoOutput();
}
}



///////////////////////////////////////////////////////////////////////////////
// Google Drive experiment

// TODO(developer): Set to client ID and API key from the Developer Console
const CLIENT_ID = '887605636141-c61b646981pdr0j8u5t4i0l9lehdqrb6.apps.googleusercontent.com';
const API_KEY = 'AIzaSyA7Wj4JqauQI8eTJ4nbCc-EkomT3gJ2sn0';

// Discovery doc URL for APIs used by the quickstart
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';

// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
const SCOPES = 'https://www.googleapis.com/auth/drive.metadata.readonly';

let tokenClient;
let gapiInited = false;
let gisInited = false;

document.getElementById('authorize_button').style.visibility = 'hidden';
document.getElementById('signout_button').style.visibility = 'hidden';

/**
* Callback after api.js is loaded.
*/
window.gapiLoaded = function() {
gapi.load('client', initializeGapiClient);
}

/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
window.initializeGapiClient = async function() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}

/**
* Callback after Google Identity Services are loaded.
*/
window.gisLoaded = function() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
});
gisInited = true;
maybeEnableButtons();
}

/**
* Enables user interaction after all libraries are loaded.
*/
function maybeEnableButtons() {
if (gapiInited && gisInited) {
document.getElementById('authorize_button').style.visibility = 'visible';
}
}

/**
* Sign in the user upon button click.
*/
window.handleAuthClick = function() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) {
throw (resp);
}
document.getElementById('signout_button').style.visibility = 'visible';
document.getElementById('authorize_button').innerText = 'Refresh';
await listFiles();
};

if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({prompt: ''});
}
}

/**
* Print metadata for first 10 files.
*/
async function listFiles() {
let response;
try {
response = await gapi.client.drive.files.list({
'pageSize': 10,
'fields': 'files(id, name)',
});
} catch (err) {
document.getElementById('content').innerText = err.message;
return;
}
const files = response.result.files;
if (!files || files.length == 0) {
document.getElementById('content').innerText = 'No files found.';
return;
}
// Flatten to string to display
const output = files.reduce(
(str, file) => `${str}${file.name} (${file.id})\n`,
'Files:\n');
document.getElementById('files').innerText = output;
}

/**
* Sign out the user upon button click.
*/
window.handleSignoutClick = function() {
const token = gapi.client.getToken();
if (token !== null) {
google.accounts.oauth2.revoke(token.access_token);
gapi.client.setToken('');
document.getElementById('content').innerText = '';
document.getElementById('authorize_button').innerText = 'Authorize';
document.getElementById('signout_button').style.visibility = 'hidden';
}
}
12 changes: 11 additions & 1 deletion example/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<html>
<body>
<h1>WeScheme as a Node Module</h1>
<nav>
<!--Google Auth buttons-->
<button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
<button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>
</nav>

<div class="container">
<div id="sourcecode" class="col">
<p>Write scheme code here...</p>
Expand All @@ -16,6 +21,11 @@ <h1>WeScheme as a Node Module</h1>
</div>
</div>

<div id="files"></div>

<script src="build/example.js"></script>

<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
</body>
</html>
Loading

0 comments on commit c39f05d

Please sign in to comment.