Google Drive sebagai penyimpanan untuk aplikasi web

Kata Pengantar


Aplikasi web saya menyimpan data di localStorage . Ini nyaman sampai saya ingin pengguna melihat hal yang sama ketika mengakses situs dari perangkat yang berbeda. Artinya, penyimpanan jarak jauh diperlukan.

Tetapi aplikasi ini "dihosting" pada Halaman GitHub dan tidak memiliki bagian server. Saya memutuskan untuk tidak membuat server, tetapi untuk menyimpan data dengan pihak ketiga. Ini memberikan keuntungan signifikan:

  1. Tidak perlu membayar untuk server, tidak ada salahnya kepala tentang stabilitas dan ketersediaannya.
  2. Lebih sedikit kode, lebih sedikit kesalahan.
  3. Pengguna tidak perlu mendaftar di aplikasi saya (ini menjengkelkan bagi banyak orang).
  4. Privasi lebih tinggi, dan pengguna tahu bahwa datanya disimpan di tempat yang kemungkinan besar lebih dipercayanya daripada saya.

Pertama, pilihan jatuh pada remoteStorage.js . Mereka menawarkan protokol pertukaran data terbuka, API yang cukup bagus, kemampuan untuk berintegrasi dengan Google Drive dan Dropbox, serta server mereka. Tapi jalan ini ternyata menjadi jalan buntu (mengapa - cerita yang terpisah).

Pada akhirnya, saya memutuskan untuk menggunakan Google Drive secara langsung, dan Perpustakaan Klien Google API (selanjutnya GAPI) sebagai perpustakaan untuk mengaksesnya.

Sayangnya, dokumentasi Google mengecewakan, dan perpustakaan GAPI terlihat belum selesai, apalagi, ia memiliki beberapa versi, dan tidak selalu jelas yang mana yang dimaksud. Oleh karena itu, solusi untuk masalah saya harus dikumpulkan berkeping-keping dari dokumentasi, pertanyaan dan jawaban tentang StackOverflow dan posting acak di Internet.

Saya harap artikel ini menghemat waktu Anda jika Anda memutuskan untuk menggunakan Google Drive di aplikasi Anda.

Persiapan


Berikut ini adalah deskripsi cara mendapatkan kunci untuk bekerja dengan Google API. Jika Anda tidak tertarik, langsung ke bagian selanjutnya.

Menerima kunci
Di Google Developer Console, buat proyek baru, masukkan nama.

Di "Control Panel" klik "Enable API and Services" dan nyalakan Google Drive.

Selanjutnya, buka API dan Layanan -> bagian Kredensial, klik "Buat Kredensial". Ada tiga hal yang perlu Anda lakukan:

  1. Konfigurasikan "Jendela Permintaan Akses OAuth". Masukkan nama aplikasi, domain Anda di bagian "Domain Resmi" dan tautan ke halaman utama aplikasi. Bidang lainnya opsional.
  2. Di bagian "Kredensial", klik "Buat Kredensial" -> "Pengidentifikasi Klien OAuth". Pilih jenis "Aplikasi Web". Di jendela pengaturan, tambahkan "Sumber Javascript yang Diizinkan" dan "URI Pengalihan yang Diizinkan":
    • Domain Anda (wajib)
    • http://localhost:8000 (opsional untuk bekerja secara lokal).


  3. Di bagian "Kredensial", klik "Buat Kredensial" -> "Kunci API". Di pengaturan tombol, tentukan batasan:
    • Jenis aplikasi yang diizinkan -> perujuk HTTP (situs web)
    • Terima permintaan http dari sumber rujukan berikut (situs) -> domain Anda dan hosting lokal (seperti dalam poin 2).
    • API Valid -> Google Drive API



Bagian Kredensial akan terlihat seperti ini:



Di sini kita sudah selesai. Kami lolos ke kode.

Inisialisasi dan login


Cara yang disarankan Google untuk mengaktifkan GAPI adalah dengan menempelkan kode berikut ke dalam HTML Anda:

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

Setelah memuat pustaka, fungsi initClient akan dipanggil, yang harus kita tulis sendiri. Penampilannya yang khas adalah sebagai berikut:

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

Untuk penyimpanan data, kita akan menggunakan folder Data Aplikasi . Keuntungannya dibandingkan folder biasa:

  1. Pengguna tidak melihatnya secara langsung: file darinya tidak menyumbat ruang pribadinya, dan ia tidak dapat merusak data kami.
  2. Aplikasi lain tidak melihatnya dan tidak dapat merusaknya juga.
  3. Lingkup, yang disebutkan di atas, memberikan akses aplikasi ke sana, tetapi tidak memberikan akses ke file pengguna lainnya. Artinya, kami tidak akan menakuti seseorang dengan permintaan akses ke data pribadinya.

Setelah inisialisasi sukses Google API, fungsi melakukan hal berikut:

  1. Mulai menangkap peristiwa masuk / keluar - kemungkinan besar, ini harus selalu dilakukan.
  2. Menginisialisasi aplikasi. Ini dapat dilakukan sebelum memuat dan menginisialisasi GAPI - seperti yang Anda inginkan. Prosedur inisialisasi saya sedikit berbeda jika Google tidak tersedia. Seseorang mungkin mengatakan bahwa ini tidak terjadi :) Tapi, pertama, Anda bisa pintar dengan kunci dan hak akses di masa depan. Kedua, misalnya, di Cina, Google dilarang.

Masuk dan keluar hanya dilakukan:

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

Anda akan menerima hasil masuk pada penangan onSignIn :

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

Sayangnya, bekerja dengan file tidak begitu jelas.

Pembantu janji


GAPI tidak mengembalikan Janji normal. Sebaliknya, antarmuka Thennable-nya sendiri digunakan, yang mirip dengan janji-janji, tetapi tidak cukup. Oleh karena itu, untuk kenyamanan kerja (terutama menggunakan async/await ), kami akan membuat pembantu kecil:

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

Fungsi ini mengambil metode dan parameter GAPI sebagai argumen pertama dan mengembalikan Janji. Maka Anda akan melihat cara menggunakannya.

Bekerja dengan file


Anda harus selalu ingat bahwa nama file di Google Drive tidak unik . Anda dapat membuat sejumlah file dan folder dengan nama yang sama. Hanya pengenal yang unik.
Untuk tugas-tugas dasar, Anda tidak perlu bekerja dengan folder, jadi semua fungsi di bawah ini berfungsi dengan file di root folder Data Aplikasi. Komentar menunjukkan apa yang perlu diubah untuk bekerja dengan folder. Dokumentasi dari Google ada di sini .

Buat file kosong


 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 } 

Fungsi asinkron ini membuat file kosong dan mengembalikan pengenalnya (string). Jika file seperti itu sudah ada, file baru dengan nama yang sama akan dibuat dan ID-nya akan dikembalikan. Jika Anda tidak menginginkan ini, Anda harus terlebih dahulu memeriksa apakah tidak ada file dengan nama yang sama (lihat di bawah).
Google Drive bukan basis data lengkap. Misalnya, jika Anda ingin beberapa pengguna bekerja dari akun Google yang sama secara bersamaan dari perangkat yang berbeda, mungkin ada masalah dengan menyelesaikan konflik karena kurangnya transaksi. Untuk tugas seperti itu, lebih baik tidak menggunakan Google Drive.

Bekerja dengan konten file


GAPI (untuk JavaScript berbasis browser) tidak menyediakan metode untuk bekerja dengan konten file (sangat aneh, bukan?). Sebagai gantinya, ada metode request umum (pembungkus tipis di atas permintaan AJAX sederhana).

Melalui trial and error, saya sampai pada implementasi berikut:

 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 } 

Pencarian file


 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 } 

Fungsi ini, jika Anda tidak menentukan query , mengembalikan semua file di folder aplikasi (array objek dengan bidang id dan name ), diurutkan berdasarkan waktu pembuatan.

Jika Anda menentukan string query (sintaksnya dijelaskan di sini ), itu hanya akan mengembalikan file yang cocok dengan kueri. Misalnya, untuk memeriksa apakah file bernama config.json , Anda perlu melakukannya

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

Menghapus File


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

Fungsi ini menghapus file dengan ID dan mengembalikan true jika berhasil dihapus, dan false jika tidak ada file seperti itu.

Sinkronkan


Dianjurkan agar program bekerja terutama dengan localStorage , dan Google Drive hanya digunakan untuk menyinkronkan data dari localStorage .

Berikut ini adalah strategi sinkronisasi konfigurasi sederhana:

  1. Konfigurasi baru diunduh dari Google Drive dengan login, dan kemudian setiap 3 menit, menimpa salinan lokal;
  2. Perubahan lokal dituangkan ke Google Drive, menimpa apa yang ada di sana;
  3. fileID konfigurasi fileID -cache di localStorage untuk mempercepat pekerjaan dan mengurangi jumlah permintaan;
  4. Situasi yang ditangani dengan benar (salah) adalah ketika Google Drive memiliki beberapa file konfigurasi, dan ketika seseorang menghapus file konfigurasi kami atau merusaknya.
  5. Detail sinkronisasi tidak memengaruhi kode aplikasi lainnya. Untuk bekerja dengan konfigurasi, Anda hanya menggunakan dua fungsi: getConfig() dan saveConfig(newConfig) .

Dalam aplikasi nyata, Anda mungkin ingin menerapkan penanganan konflik yang lebih fleksibel saat memuat / membongkar konfigurasi.

Lihat kode
 //     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() } 


Kesimpulan


Menurut saya, penyimpanan data untuk situs web di Google Drive cocok untuk proyek kecil dan pembuatan prototipe. Ini tidak hanya mudah diimplementasikan dan didukung, tetapi juga membantu mengurangi jumlah entitas yang tidak perlu di alam semesta. Dan artikel saya, saya harap, akan membantu Anda menghemat waktu jika Anda memilih jalur ini.

PS Kode proyek sebenarnya ada di GitHub , Anda dapat mencobanya di sini .

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


All Articles