Google Drive als Speicher für eine Webanwendung

Vorwort


Meine Webanwendung speichert Daten in localStorage . Dies war praktisch, bis ich wollte, dass der Benutzer beim Zugriff auf die Site von verschiedenen Geräten aus dasselbe sieht. Das heißt, Remote-Speicher war erforderlich.

Die Anwendung wird jedoch auf GitHub-Seiten "gehostet" und hat keinen Serverteil. Ich habe beschlossen, keinen Server zu erstellen, sondern die Daten bei einem Dritten zu speichern. Dies bietet erhebliche Vorteile:

  1. Der Server muss nicht bezahlt werden, er schadet dem Kopf in Bezug auf Stabilität und Verfügbarkeit nicht.
  2. Weniger Code, weniger Fehler.
  3. Der Benutzer muss sich nicht in meiner Anwendung registrieren (dies ist für viele ärgerlich).
  4. Der Datenschutz ist höher und der Benutzer weiß, dass seine Daten an einem Ort gespeichert sind, dem er höchstwahrscheinlich mehr vertraut als mir.

Erstens fiel die Wahl auf remoteStorage.js . Sie bieten ein offenes Datenaustauschprotokoll, eine hübsche API, die Möglichkeit zur Integration in Google Drive und Dropbox sowie deren Server. Aber dieser Weg stellte sich als Sackgasse heraus (warum - eine separate Geschichte).

Am Ende habe ich mich entschieden, Google Drive direkt und die Google API Client Library (im Folgenden GAPI) als Bibliothek für den Zugriff darauf zu verwenden.

Leider ist die Google-Dokumentation enttäuschend und die GAPI-Bibliothek sieht unvollendet aus. Außerdem hat sie mehrere Versionen und es ist nicht immer klar, um welche es sich handelt. Daher musste die Lösung für meine Probleme in Teilen aus der Dokumentation, Fragen und Antworten zu StackOverflow und zufälligen Beiträgen im Internet gesammelt werden.

Ich hoffe, dieser Artikel spart Ihnen Zeit, wenn Sie Google Drive in Ihrer Anwendung verwenden.

Vorbereitung


Im Folgenden wird beschrieben, wie Sie Schlüssel für die Arbeit mit der Google-API erhalten. Wenn Sie nicht interessiert sind, fahren Sie direkt mit dem nächsten Teil fort.

Schlüssel empfangen
Erstellen Sie in der Google Developer Console ein neues Projekt und geben Sie einen Namen ein.

Klicken Sie in der Systemsteuerung auf "API und Dienste aktivieren" und aktivieren Sie Google Drive.

Gehen Sie als Nächstes zum Abschnitt API und Dienste -> Anmeldeinformationen und klicken Sie auf "Anmeldeinformationen erstellen". Es gibt drei Dinge, die Sie tun müssen:

  1. Konfigurieren Sie "OAuth Access Request Window". Geben Sie den Namen der Anwendung, Ihre Domain im Abschnitt "Autorisierte Domains" und einen Link zur Hauptseite der Anwendung ein. Andere Felder sind optional.
  2. Klicken Sie im Abschnitt "Anmeldeinformationen" auf "Anmeldeinformationen erstellen" -> "OAuth-Client-ID". Wählen Sie den Typ "Webanwendung". Fügen Sie im Einstellungsfenster "Zulässige Javascript-Quellen" und "Zulässige Umleitungs-URIs" hinzu:
    • Ihre Domain (erforderlich)
    • http://localhost:8000 (optional, um lokal zu arbeiten).


  3. Klicken Sie im Abschnitt "Anmeldeinformationen" auf "Anmeldeinformationen erstellen" -> "API-Schlüssel". Geben Sie in den Schlüsseleinstellungen die Einschränkungen an:
    • Zulässiger Anwendungstyp -> HTTP-Verweise (Websites)
    • Akzeptieren Sie http-Anfragen von den folgenden Verweisquellen (Sites) -> Ihrer Domain und Ihrem lokalen Host (wie in Punkt 2).
    • Gültige APIs -> Google Drive API



Der Abschnitt Anmeldeinformationen sollte ungefähr so ​​aussehen:



Hier sind wir fertig. Wir gehen zum Code über.

Initialisierung und Login


Die von Google empfohlene Methode zum Aktivieren von GAPI besteht darin, den folgenden Code in Ihren HTML-Code einzufügen:

 <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> 

Nach dem Laden der Bibliothek wird die initClient Funktion aufgerufen, die wir selbst schreiben müssen. Sein typisches Aussehen ist wie folgt:

 function initClient() { gapi.client.init({ //   API apiKey: GOOGLE_API_KEY, //    clientId: GOOGLE_CLIENT_ID, // ,     Google Drive API v3 discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'], //    application data folder (. ) scope: 'https://www.googleapis.com/auth/drive.appfolder' }).then(() => { //    / (. ) gapi.auth2.getAuthInstance().isSignedIn.listen(onSignIn) //   initApp() }, error => { console.log('Failed to init GAPI client', error) //    initApp({showAlert: 'google-init-failed-alert'}) }) } 

Zur Datenspeicherung verwenden wir den sogenannten Application Data Ordner . Seine Vorteile gegenüber einem normalen Ordner:

  1. Der Benutzer sieht es nicht direkt: Dateien daraus verstopfen seinen persönlichen Bereich nicht und er kann unsere Daten nicht ruinieren.
  2. Andere Anwendungen sehen es nicht und können es auch nicht verderben.
  3. Der oben erwähnte Bereich gewährt der Anwendung Zugriff darauf, jedoch keinen Zugriff auf die übrigen Dateien des Benutzers. Das heißt, wir werden eine Person nicht durch Anfragen nach Zugang zu ihren persönlichen Daten abschrecken.

Nach erfolgreicher Initialisierung der Google-API führt die Funktion Folgendes aus:

  1. Fängt an, Anmelde- / Abmeldeereignisse abzufangen - dies sollte höchstwahrscheinlich immer erfolgen.
  2. Initialisiert die Anwendung. Dies kann vor dem Laden und Initialisieren von GAPI erfolgen - wie Sie es bevorzugen. Mein Initialisierungsverfahren war etwas anders, wenn Google nicht verfügbar ist. Jemand könnte sagen, dass dies nicht der Fall ist :) Aber erstens können Sie in Zukunft mit Schlüsseln und Zugriffsrechten klug sein. Zweitens ist Google beispielsweise in China verboten.

Das An- und Abmelden erfolgt einfach:

 function isGapiLoaded() { return gapi && gapi.auth2 } function logIn() { if (isGapiLoaded()) { //    Google    gapi.auth2.getAuthInstance().signIn() } } function logOut() { if (isGapiLoaded()) { gapi.auth2.getAuthInstance().signOut() } } 

Sie erhalten onSignIn im onSignIn Handler:

 function isLoggedIn() { return isGapiLoaded() && gapi.auth2.getAuthInstance().isSignedIn.get() } function onSignIn() { if (isLoggedIn()) { //   } else { //   } //   .    "" } 

Leider ist die Arbeit mit Dateien nicht so offensichtlich.

Versprich Helfer


GAPI gibt keine normalen Versprechen zurück. Stattdessen wird eine eigene Thennable-Schnittstelle verwendet, die Versprechungen ähnelt, aber nicht ganz. Aus praktischen async/await (hauptsächlich zur Verwendung von async/await ) werden wir daher einen kleinen Helfer erstellen:

 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) }) }) } 

Diese Funktion verwendet die GAPI-Methode und die Parameter als erstes Argument und gibt Promise zurück. Dann werden Sie sehen, wie man es benutzt.

Mit Dateien arbeiten


Sie sollten immer daran denken, dass der Dateiname in Google Drive nicht eindeutig ist . Sie können beliebig viele Dateien und Ordner mit demselben Namen erstellen. Nur die Kennung ist eindeutig.
Für grundlegende Aufgaben müssen Sie nicht mit Ordnern arbeiten, daher funktionieren alle folgenden Funktionen mit Dateien im Stammverzeichnis des Ordners Anwendungsdaten. Die Kommentare geben an, was geändert werden muss, um mit Ordnern zu arbeiten. Die Dokumentation von Google finden Sie hier .

Erstellen Sie eine leere Datei


 async function createEmptyFile(name, mimeType) { const resp = await prom(gapi.client.drive.files.create, { resource: { name: name, //     // mimeType = 'application/vnd.google-apps.folder' mimeType: mimeType || 'text/plain', //  'appDataFolder'   ID  parents: ['appDataFolder'] }, fields: 'id' }) //    —    return resp.result.id } 

Diese asynchrone Funktion erstellt eine leere Datei und gibt ihren Bezeichner (String) zurück. Wenn eine solche Datei bereits vorhanden ist, wird eine neue Datei mit demselben Namen erstellt und ihre ID zurückgegeben. Wenn Sie dies nicht möchten, müssen Sie zunächst überprüfen, ob keine Datei mit demselben Namen vorhanden ist (siehe unten).
Google Drive ist keine vollständige Datenbank. Wenn Sie beispielsweise möchten, dass mehrere Nutzer gleichzeitig von verschiedenen Geräten aus mit demselben Google-Konto arbeiten, kann es aufgrund fehlender Transaktionen zu Problemen bei der Lösung von Konflikten kommen. Für solche Aufgaben ist es besser, Google Drive nicht zu verwenden.

Arbeiten Sie mit Dateiinhalten


GAPI (für browserbasiertes JavaScript) bietet keine Methoden zum Arbeiten mit dem Inhalt von Dateien (sehr seltsam, nicht wahr?). Stattdessen gibt es eine allgemeine request (einen Thin Wrapper über eine einfache AJAX-Anforderung).

Durch Versuch und Irrtum bin ich zu folgenden Implementierungen gekommen:

 async function upload(fileId, content) { //    ,  ,     JSON return prom(gapi.client.request, { path: `/upload/drive/v3/files/${fileId}`, method: 'PATCH', params: {uploadType: 'media'}, body: typeof content === 'string' ? content : JSON.stringify(content) }) } async function download(fileId) { const resp = await prom(gapi.client.drive.files.get, { fileId: fileId, alt: 'media' }) // resp.body      // resp.result —    resp.body  JSON. //   ,  resp.result  false // ..    ,   return resp.result || resp.body } 

Dateisuche


 async function find(query) { let ret = [] let token do { const resp = await prom(gapi.client.drive.files.list, { //  'appDataFolder'   ID  spaces: 'appDataFolder', fields: 'files(id, name), nextPageToken', pageSize: 100, pageToken: token, orderBy: 'createdTime', q: query }) ret = ret.concat(resp.result.files) token = resp.result.nextPageToken } while (token) // :    [{id: '...', name: '...'}], //     return ret } 

Wenn Sie keine query angeben, gibt diese Funktion alle Dateien im Anwendungsordner (ein Array von Objekten mit id und Namensfeldern) sortiert nach Erstellungszeit zurück.

Wenn Sie die query angeben (die Syntax wird hier beschrieben), werden nur Dateien zurückgegeben, die der Abfrage entsprechen. Um beispielsweise zu überprüfen, ob eine Datei mit dem Namen config.json , müssen Sie dies tun

  if ((await find('name = "config.json"')).length > 0) { // ()  } 

Dateien löschen


 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 } } 

Diese Funktion löscht die Datei nach ID und gibt true zurück true wenn sie erfolgreich gelöscht wurde, und false wenn keine solche Datei vorhanden war.

Synchronisieren


Es wird empfohlen, dass das Programm hauptsächlich mit localStorage arbeitet und Google Drive nur zum Synchronisieren von Daten aus localStorage .

Das Folgende ist eine einfache Konfigurationssynchronisationsstrategie:

  1. Die neue Konfiguration wird mit einem Login von Google Drive heruntergeladen und anschließend alle 3 Minuten die lokale Kopie überschrieben.
  2. Lokale Änderungen werden auf Google Drive übertragen und überschreiben, was dort vorhanden war.
  3. Die Konfigurationsdatei- fileID wird in localStorage zwischengespeichert, um die Arbeit zu beschleunigen und die Anzahl der Anforderungen zu verringern.
  4. Richtig gehandhabte (fehlerhafte) Situationen sind, wenn Google Drive über mehrere Konfigurationsdateien verfügt und jemand unsere Konfigurationsdatei gelöscht oder ruiniert hat.
  5. Synchronisierungsdetails wirken sich nicht auf den Rest des Anwendungscodes aus. Um mit der Konfiguration zu arbeiten, verwenden Sie nur zwei Funktionen: getConfig() und saveConfig(newConfig) .

In einer realen Anwendung möchten Sie wahrscheinlich eine flexiblere Konfliktbehandlung beim Laden / Entladen einer Konfiguration implementieren.

Code anzeigen
 //     const SYNC_PERIOD = 1000 * 60 * 3 // 3  //    const DEFAULT_CONFIG = { // ... } //  ID  ,      let configSyncTimeoutId async function getConfigFileId() { //  configFileId let configFileId = localStorage.getItem('configFileId') if (!configFileId) { //     Google Drive const configFiles = await find('name = "config.json"') if (configFiles.length > 0) { //   (  )  configFileId = configFiles[0].id } else { //   configFileId = await createEmptyFile('config.json') } //  ID localStorage.setItem('configFileId', configFileId) } return configFileId } async function onSignIn() { //   / (. ) if (isLoggedIn()) { //   //  (  -?)    scheduleConfigSync(0) } else { //   //          //   config file ID localStorage.removeItem('configFileId') //  localStorage   ,    } } function getConfig() { let ret try { ret = JSON.parse(localStorage.getItem('config')) } catch(e) {} //    ,    return ret || {...DEFAULT_CONFIG} } async function saveConfig(newConfig) { //    ,     localStorage.setItem('config', JSON.stringify(newConfig)) if (isLoggedIn()) { //  config file ID const configFileId = await getConfigFileId() //     Google Drive upload(configFileId, newConfig) } } async function syncConfig() { if (!isLoggedIn()) { return } //  config file ID const configFileId = await getConfigFileId() try { //   const remoteConfig = await download(configFileId) if (!remoteConfig || typeof remoteConfig !== 'object') { //    ,   upload(configFileId, getConfig()) } else { //  ,    localStorage.setItem('config', JSON.stringify(remoteConfig)) } //  ,  localStorage   } catch(e) { if (e.status === 404) { // -   ,   fileID     localStorage.removeItem('configFileId') syncConfig() } else { throw e } } } function scheduleConfigSync(delay) { //   ,    if (configSyncTimeoutId) { clearTimeout(configSyncTimeoutId) } configSyncTimeoutId = setTimeout(() => { //      syncConfig() .catch(e => console.log('Failed to synchronize config', e)) .finally(() => scheduleSourcesSync()) }, typeof delay === 'undefined' ? SYNC_PERIOD : delay) } function initApp() { //      scheduleConfigSync() } 


Fazit


Meiner Meinung nach eignet sich der Datenspeicher für eine Website in Google Drive hervorragend für kleine Projekte und Prototypen. Es ist nicht nur einfach zu implementieren und zu unterstützen, sondern trägt auch dazu bei, die Anzahl unnötiger Entitäten im Universum zu reduzieren. Ich hoffe, mein Artikel hilft Ihnen, Zeit zu sparen, wenn Sie diesen Weg wählen.

PS Der Code des realen Projekts liegt auf GitHub . Sie können ihn hier ausprobieren.

Source: https://habr.com/ru/post/de440844/


All Articles