Prólogo
Mi aplicación web está almacenando datos en
localStorage
. Esto era conveniente hasta que quería que el usuario viera lo mismo al acceder al sitio desde diferentes dispositivos. Es decir, se requería almacenamiento remoto.
Pero la aplicación está "alojada" en las páginas de GitHub y no tiene una parte de servidor. Decidí no hacer un servidor, sino almacenar los datos con un tercero. Esto ofrece ventajas significativas:
- No es necesario pagar por el servidor, no perjudica a la cabeza por su estabilidad y disponibilidad.
- Menos código, menos errores.
- El usuario no necesita registrarse en mi aplicación (esto es molesto para muchos).
- La privacidad es mayor, y el usuario sabe que sus datos se almacenan en un lugar en el que probablemente confía más que yo.
Primero, la elección recayó en
remoteStorage.js . Ofrecen un protocolo de intercambio de datos abierto, una API bastante agradable, la capacidad de integrarse con Google Drive y Dropbox, así como con sus servidores. Pero este camino resultó ser un callejón sin salida (por qué, una historia separada).
Al final, decidí usar Google Drive directamente y la
Biblioteca de cliente API de Google (en adelante, GAPI) como biblioteca para acceder a ella.
Desafortunadamente, la documentación de Google es decepcionante, y la biblioteca GAPI parece inacabada, además, tiene varias versiones, y no siempre está claro cuál está en cuestión. Por lo tanto, la solución a mis problemas tuvo que recopilarse en partes de la documentación, preguntas y respuestas en StackOverflow y publicaciones aleatorias en Internet.
Espero que este artículo le ahorre tiempo si decide usar Google Drive en su aplicación.
Preparación
La siguiente es una descripción de cómo obtener claves para trabajar con la API de Google. Si no está interesado, vaya directamente a la siguiente parte.
Recibiendo llavesEn Google Developer Console,
cree un nuevo proyecto, ingrese un nombre.
En el "Panel de control", haga clic en "Habilitar API y servicios" y active Google Drive.
A continuación, vaya a la sección API y Servicios -> Credenciales, haga clic en "Crear credenciales". Hay tres cosas que debes hacer:
- Configure la "Ventana de solicitud de acceso de OAuth". Ingrese el nombre de la aplicación, su dominio en la sección "Dominios autorizados" y un enlace a la página principal de la aplicación. Otros campos son opcionales.
- En la sección "Credenciales", haga clic en "Crear credenciales" -> "Identificador de cliente OAuth". Seleccione el tipo "Aplicación web". En la ventana de configuración, agregue "Fuentes de Javascript permitidas" y "URI de redireccionamiento permitidos":
- Tu dominio (requerido)
http://localhost:8000
(opcional para trabajar localmente).

- En la sección "Credenciales", haga clic en "Crear credenciales" -> "Clave API". En la configuración clave, especifique las restricciones:
- Tipo de aplicación permitida -> referencias HTTP (sitios web)
- Acepte solicitudes http de las siguientes fuentes de referencia (sitios) -> su dominio y localhost (como en el punto 2).
- API válidas -> API de Google Drive

La sección de Credenciales debería verse así:

Aquí hemos terminado. Pasamos al código.
Inicialización e inicio de sesión
La forma recomendada de Google para habilitar GAPI es pegar el siguiente código en su HTML:
<script src="https://apis.google.com/js/api.js" onload="this.onload=function(){}; gapi.load('client:auth2', initClient)" onreadystatechange="if (this.readyState === 'complete') this.onload()"> </script>
Después de cargar la biblioteca, se
initClient
función
initClient
, que debemos escribir nosotros mismos. Su apariencia típica es la siguiente:
function initClient() { gapi.client.init({
Para el almacenamiento de datos, utilizaremos la llamada
carpeta de datos de la aplicación . Sus ventajas sobre una carpeta normal:
- El usuario no lo ve directamente: los archivos que contiene no obstruyen su espacio personal y no puede arruinar nuestros datos.
- Otras aplicaciones no lo ven y tampoco pueden estropearlo.
- Scope, mencionado anteriormente, le da acceso a la aplicación, pero no le da acceso al resto de los archivos del usuario. Es decir, no asustaremos a una persona mediante solicitudes de acceso a sus datos personales.
Tras la inicialización exitosa de la API de Google, la función hace lo siguiente:
- Comienza a capturar eventos de inicio / cierre de sesión; lo más probable es que esto siempre se haga.
- Inicializa la aplicación. Esto se puede hacer antes de cargar e inicializar GAPI, como prefiera. Mi procedimiento de inicialización fue ligeramente diferente si Google no está disponible. Alguien puede decir que esto no sucede :) Pero, en primer lugar, puede ser inteligente con las claves y los derechos de acceso en el futuro. En segundo lugar, por ejemplo, en China, Google está prohibido.
Iniciar y cerrar sesión se realiza simplemente:
function isGapiLoaded() { return gapi && gapi.auth2 } function logIn() { if (isGapiLoaded()) {
Recibirá resultados de inicio de sesión en el controlador
onSignIn
:
function isLoggedIn() { return isGapiLoaded() && gapi.auth2.getAuthInstance().isSignedIn.get() } function onSignIn() { if (isLoggedIn()) {
Desafortunadamente, trabajar con archivos no es tan obvio.
Promesa de ayuda
GAPI no devuelve las promesas normales. En cambio, se utiliza su propia interfaz Thennable, que es similar a las promesas, pero no del todo. Por lo tanto, para la comodidad del trabajo (principalmente para usar
async/await
), haremos un pequeño ayudante:
function prom(gapiCall, argObj) { return new Promise((resolve, reject) => { gapiCall(argObj).then(resp => { if (resp && (resp.status < 200 || resp.status > 299)) { console.log('GAPI call returned bad status', resp) reject(resp) } else { resolve(resp) } }, err => { console.log('GAPI call failed', err) reject(err) }) }) }
Esta función toma el método GAPI y los parámetros como el primer argumento y devuelve Promise. Entonces verás cómo usarlo.
Trabajar con archivos
Siempre debe recordar que
el nombre del archivo en Google Drive no es único . Puede crear cualquier cantidad de archivos y carpetas con el mismo nombre. Solo el identificador es único.
Para tareas básicas, no necesita trabajar con carpetas, por lo que todas las funciones a continuación funcionan con archivos en la raíz de la carpeta Datos de la aplicación. Los comentarios indican qué se debe cambiar para trabajar con carpetas. La documentación de Google está aquí .
Crea un archivo vacío
async function createEmptyFile(name, mimeType) { const resp = await prom(gapi.client.drive.files.create, { resource: { name: name,
Esta función asincrónica crea un archivo vacío y devuelve su identificador (cadena). Si dicho archivo ya existe, se creará un nuevo archivo con el mismo nombre y se devolverá su ID. Si no desea esto, primero debe verificar que no haya ningún archivo con el mismo nombre (ver más abajo).
Google Drive no es una base de datos completa. Por ejemplo, si desea que varios usuarios trabajen desde la misma cuenta de Google simultáneamente desde diferentes dispositivos, puede haber problemas para resolver conflictos debido a la falta de transacciones. Para tales tareas, es mejor no usar Google Drive.
Trabajar con contenido de archivo
GAPI (para JavaScript basado en navegador) no proporciona métodos para trabajar con el contenido de los archivos (muy extraño, ¿no?). En cambio, hay un método de
request
general (un envoltorio delgado sobre una solicitud AJAX simple).
A través de prueba y error, llegué a las siguientes implementaciones:
async function upload(fileId, content) {
Búsqueda de archivos
async function find(query) { let ret = [] let token do { const resp = await prom(gapi.client.drive.files.list, {
Esta función, si no especifica la
query
, devuelve todos los archivos en la carpeta de la aplicación (una matriz de objetos con campos de
id
y
name
), ordenados por hora de creación.
Si especifica la cadena de
query
(la sintaxis se describe
aquí ), solo devolverá archivos que coincidan con la consulta. Por ejemplo, para verificar si
config.json
un archivo llamado
config.json
, debe hacer
if ((await find('name = "config.json"')).length > 0) {
Eliminar archivos
async function deleteFile(fileId) { try { await prom(gapi.client.drive.files.delete, { fileId: fileId }) return true } catch (err) { if (err.status === 404) { return false } throw err } }
Esta función elimina el archivo por ID y devuelve
true
si se eliminó con éxito, y
false
si no existía dicho archivo.
Sincronización
Es aconsejable que el programa trabaje principalmente con
localStorage
, y Google Drive se usa solo para sincronizar datos de
localStorage
.
La siguiente es una estrategia de sincronización de configuración simple:
- La nueva configuración se descarga de Google Drive con un inicio de sesión, y luego cada 3 minutos, sobrescribiendo la copia local;
- Los cambios locales se vierten en Google Drive, sobrescribiendo lo que estaba allí;
- el
fileID
configuración se almacena en caché en localStorage
para acelerar el trabajo y reducir el número de solicitudes; - Las situaciones manejadas correctamente (erróneas) son cuando Google Drive tiene varios archivos de configuración y cuando alguien eliminó nuestro archivo de configuración o lo arruinó.
- Los detalles de sincronización no afectan el resto del código de la aplicación. Para trabajar con la configuración, utiliza solo dos funciones:
getConfig()
y saveConfig(newConfig)
.
En una aplicación real, probablemente desee implementar un manejo de conflictos más flexible al cargar / descargar una configuración.
Conclusión
Me parece que el almacén de datos para un sitio web en Google Drive es ideal para pequeños proyectos y creación de prototipos. No solo es fácil de implementar y soportar, sino que también ayuda a reducir la cantidad de entidades innecesarias en el universo. Y espero que mi artículo lo ayude a ahorrar tiempo si elige este camino.
PD: El código del proyecto real se
encuentra en GitHub ,
puedes probarlo
aquí .