Created
December 16, 2022 16:08
-
-
Save ca0v/c6be39e22a0418634b7658fbfcf82f1a to your computer and use it in GitHub Desktop.
Consume the Google Photo API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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