Skip to content

Instantly share code, notes, and snippets.

@ca0v
Created December 16, 2022 16:08
Show Gist options
  • Save ca0v/c6be39e22a0418634b7658fbfcf82f1a to your computer and use it in GitHub Desktop.
Save ca0v/c6be39e22a0418634b7658fbfcf82f1a to your computer and use it in GitHub Desktop.
Consume the Google Photo API
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Google API Playground</title>
</head>
<body>
<script src="./index.js"></script>
<button class="signin" disabled>Sign in</button>
</body>
<script>
document.addEventListener("signedin", async () => {
document.querySelector(".signin").disabled = true;
console.log("interactWithPhotosApi")
const albums = await gapi.client.photoslibrary.albums.list({
pageSize: 10,
});
console.log("albums", albums);
});
</script>
</html>
/**
* @param {string} api
*/
function loaded(api) {
const SCOPES = "https://www.googleapis.com/auth/photoslibrary.readonly"
const DISCOVERY_DOC = "https://www.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest";
async function signin() {
await useGapi();
await useGoogle();
// trigger event on the document notifying that the user is signed in
document.dispatchEvent(new CustomEvent("signedin"));
}
const callbackMap = {
"google+gapi": [() => {
const signinButton = document.querySelector(".signin");
if (!signinButton) return;
signinButton.addEventListener("click", signin);
// @ts-ignore
signinButton.disabled = false;
}],
}
/**
* @param {string} key
*/
function getFromLocalStorage(key) {
const result = localStorage.getItem(key);
if (result == null) return null;
return JSON.parse(result);
}
/**
* @param {string} key
* @param {any} value
*/
function setInLocalStorage(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
/**
* @param {string} message
*/
async function promptForValue(message) {
let result = getFromLocalStorage(message);
if (result) return result;
return new Promise((resolve, reject) => {
result = prompt(message);
if (result) {
setInLocalStorage(message, result);
resolve(result);
}
reject(new Error('No value provided'));
})
}
async function useGoogle() {
console.log("useGoogle")
if (!gapi) throw "gapi not loaded"
if (!gapi.client) throw "gapi.client not loaded"
const clientId = await promptForValue('YOUR_CLIENT_ID');
return new Promise((resolve, reject) => {
const tokenClient = google.accounts.oauth2.initTokenClient({
client_id: clientId,
scope: SCOPES,
callback: (response) => {
if (response.error) {
reject(response.error);
return;
}
console.log("tokenClient", response)
// expires_in is in seconds
const expires_at = Math.floor(Date.now() + 1000 * parseInt(response.expires_in));
setInLocalStorage('google', { ...response, expires_at });
resolve(response);
},
error_callback: (error) => {
console.error(error);
setInLocalStorage('YOUR_CLIENT_ID', null);
reject(error);
}
})
// token is always null on refresh but why prompt for identity?
const token = gapi.client.getToken();
if (token === null) {
if (getFromLocalStorage('google') === null) {
tokenClient.requestAccessToken({ prompt: "consent" });
} else {
const accessTokenInfo = getFromLocalStorage('google');
if (accessTokenInfo.expires_at > Date.now()) {
console.log("reusing token", accessTokenInfo)
gapi.client.setToken({ access_token: accessTokenInfo.access_token });
resolve(accessTokenInfo);
} else {
tokenClient.requestAccessToken({ prompt: "" });
}
}
} else {
tokenClient.requestAccessToken({ prompt: "" });
}
});
}
async function useGapi() {
console.log("useGapi")
return new Promise((resolve, reject) => {
gapi.load("client", async () => {
try {
const apiKey = await promptForValue('YOUR_API_KEY');
await gapi.client.init({
apiKey: apiKey,
discoveryDocs: [DISCOVERY_DOC],
})
resolve();
} catch (error) {
console.error(error);
setInLocalStorage('YOUR_API_KEY', null);
reject();
}
});
});
}
const keys = Object.keys(callbackMap).filter(k => k.split("+").includes(api));
if (!keys.length) return;
keys.forEach(async key => {
const requiredApis = key.split("+");
const availableApis = requiredApis.filter(k => !!window[k]);
if (availableApis.length !== requiredApis.length) return;
const callbacks = callbackMap[key];
if (!callbacks) return;
await Promise.all(callbacks.map((/** @type {() => any} */ c) => c()))
})
}
{
// <script async defer src="https://apis.google.com/js/api.js" onload="loaded('gapi')"></script>
// <script async defer src="https://accounts.google.com/gsi/client" onload="loaded('google')"></script>
/**
* @param {string} src
* @param {string} id
*/
function injectScript(src, id) {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.defer = true;
script.onload = () => loaded(id);
document.body.appendChild(script);
}
injectScript('https://apis.google.com/js/api.js', 'gapi');
injectScript('https://accounts.google.com/gsi/client', 'google');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment