Ekstensi web lintas browser untuk skrip khusus Bagian 3

Pada artikel ini, saya melanjutkan serangkaian publikasi yang ingin saya bicarakan tentang pengalaman saya menulis ekstensi web untuk browser. Saya sudah memiliki pengalaman membuat ekstensi web, yang dipasang oleh sekitar 100.000 pengguna Chrome, yang bekerja secara otonom, tetapi dalam seri artikel ini saya memutuskan untuk mempelajari proses pengembangan ekstensi web dengan mengintegrasikannya secara erat dengan sisi server.

gambargambargambargambargambar

Bagian 1 , Bagian 2 , Bagian 4

Jebakan dalam mengimplementasikan interaksi ekstensi web dan sisi server


Seperti yang sudah dijelaskan sebelumnya, sisi server menggunakan Meteor.js. Untuk mengimplementasikan API RESTful, paket github.com/kahmali/meteor-restivus digunakan. Itu sudah memiliki beberapa bagian yang diterapkan di dalamnya untuk mencakup mekanisme pengguna yang terkait dengan otorisasi.

Misalnya, cukup menentukan authRequired: true , seperti pada contoh di bawah ini, sehingga titik API hanya berfungsi untuk pengguna yang diotorisasi.

Api.addRoute('clientScript/:id_script', {authRequired: true}, {get: { action: function() { //method for GET on htts://example.com/api/v1/clientScript/:id_script } }); 

Dengan demikian, tiga poin API ditambahkan untuk pendaftaran, untuk menerima data profil dan memperbaruinya, untuk pengaturan ulang kata sandi.

Di ekstensi web itu sendiri, saat memanggil metode yang membutuhkan otorisasi, kode berikut digunakan:

 var details = { url: API_URL + '/api/v1/clientDataRowDownload/' + dataRowId + '/download', method: 'GET', contentType: 'json', headers: {'X-Auth-Token': kango.storage.getItem("authToken"), 'X-User-Id': kango.storage.getItem("userId")} }; kango.xhr.send(details, function(data) { //code for response handler }) 

Contoh permintaan otorisasi terlihat jelas di sini. Header termasuk X-Auth-Token dan X-User-Id, yang diperoleh sebagai hasil dari proses registrasi atau otorisasi. Data ini disimpan di penyimpanan lokal ekstensi web dan selalu tersedia di skrip content.js.

Mengunduh file dalam ekstensi web dilakukan dengan membaca file di sisi browser dan mengirim melalui XHR:

 $("form#uploadFile").on("submit", function(event, template) { event.preventDefault(); var reader = new FileReader(); reader.onload = function(evt) { var details = { url: API_URL + '/api/v1/clientFileAdd/' + kango.storage.getItem("userId"), method: 'POST', contentType: 'json', params: {"content": encodeURIComponent(evt.target.result.replace(/^data:[^;]*;base64,/, "")), "name": encodeURIComponent(event.currentTarget.fileInput.files[0].name), "size": event.currentTarget.fileInput.files[0].size, "type": event.currentTarget.fileInput.files[0].type, "lastModified": event.currentTarget.fileInput.files[0].lastModified }, headers: {'X-Auth-Token': kango.storage.getItem("authToken"), 'X-User-Id': kango.storage.getItem("userId")} }; kango.xhr.send(details, function(data) { if (data.status == 200 && data.response != null) { if(data.response.status == "success") { //ok } else { //error } } else { if(data.status == 401) { //notAuth } else { //error } } }); }; if (event.currentTarget.fileInput.files.length != 0) { reader.readAsDataURL(event.currentTarget.fileInput.files[0]); } return false; }); 

Penting untuk mencatat baris event.target.result.replace (/ ^ data: [^;] *; base64, /, "") . File di sisi browser dikodekan dalam base64, tetapi untuk kompatibilitas di sisi server ketika menggunakan pengkodean ini dalam garis Buffer.from (String baru (this.bodyParams.content), "base64") kita harus memotong awalan pengkodean dan hanya membaca "tubuh" file . Penting juga untuk mencatat pembungkus dalam komponen encodeURIC, karena + yang sama sering ditemukan pada base64 dan nama file.

Saat mengedit skrip, Anda perlu mempertimbangkan pengkodean karakter di tubuh skrip saat mentransfer konten. Dalam beberapa kasus, pengkodean base64 tidak menghasilkan hasil yang benar ketika decoding di sisi server menggunakan komponen encodeURIC. Oleh karena itu, pengkodean paksa dalam utf8 awalnya digunakan dengan utf8.encode (str); di mana mths.be/utf8js v3.0.0 dari @mathias

Unduhan file diimplementasikan menggunakan pustaka FileSaver yang sudah mapan. Data yang diterima melalui XHR hanya ditransfer ke input konstruktor File, dan kemudian unduhan file diinisialisasi:

 var file = new File([data.response.data.join("\n")], "data_rows" + date.getFullYear() + "_" + (date.getMonth() + 1) + "_" + date.getDate() + ".csv", {type: "application/vnd.ms-excel"}); saveAs(file); 

Pustaka internal untuk skrip khusus


Agar skrip, ekstensi web, dan bagian server untuk berinteraksi, perlu memiliki tautan perantara yang memungkinkan Anda menerima data dengan cepat dari file yang diunduh, menyimpan data setelah skrip dieksekusi, dll.

Untuk tujuan ini, perpustakaan internal ditulis, yang diinisialisasi sebelum skrip mulai bekerja dengan menambahkan sendiri ke kode halaman. Di sini Anda perlu menambahkan informasi tentang kebijakan perlindungan sumber untuk memuat sumber daya, yaitu kebijakan konten-keamanan.

Banyak situs menggunakan header CSP untuk melindungi terhadap eksekusi kode javascript sewenang-wenang pada halaman layanan web mereka, sehingga melindungi diri mereka dari XSS di sisi browser web.

Karena pengguna memasang ekstensi web sendiri, ia dapat mengubah tajuk dan konten sumber yang diunduh. Karena ada bug di Mozilla Firefox, ini merupakan masalah untuk beberapa situs. Yaitu, dalam ekstensi web untuk Firefox tidak akan mungkin untuk memodifikasi header atau menambahkan tag meta untuk membatalkan kebijakan CSP untuk situs tempat mereka digunakan. Bug ini belum ditutup selama beberapa tahun, meskipun standar dengan jelas menetapkan ketentuan untuk ekstensi web, yang menyatakan bahwa kebijakan mengenai sumber daya yang diunduh dari sisi server aplikasi tidak dapat dominan dalam kaitannya dengan ekstensi web yang dipasang oleh pengguna sendiri.

Pembatasan kebijakan CSP dapat diimplementasikan menggunakan kerangka kerja kango dengan cara berikut:

 var browserObject; if(kango.browser.getName() == 'chrome') { browserObject = chrome; } else { browserObject = browser; } var filter = { urls: ["*://*/*"], types: ["main_frame", "sub_frame"] }; var onHeadersReceived = function(details) { var newHeaders = []; for (var i = 0; i < details.responseHeaders.length; i++) { if ('content-security-policy' !== details.responseHeaders[i].name.toLowerCase() && 'x-xss-protection' !== details.responseHeaders[i].name.toLowerCase() ) { newHeaders.push(details.responseHeaders[i]); } } return { responseHeaders: newHeaders }; }; browserObject.webRequest.onHeadersReceived.addListener(onHeadersReceived, filter, ["blocking", "responseHeaders"]); 

Dalam hal ini, Anda tidak boleh lupa untuk menambahkan baris dalam manifes ekstensi web yang memungkinkan bekerja dengan objek webRequest dalam mode pemblokiran:

 "permissions": { ... "webRequest": true, "webRequestBlocking": true, ... } 

Setelah menyelesaikan masalah dengan pembatasan yang diberlakukan oleh CSP, pengguna dapat menggunakan skrip yang ditulis olehnya di halaman mana pun di Internet.

Fungsi panggilan dari perpustakaan internal tersedia melalui objek global Gc.
Fungsi yang diimplementasikan saat ini:

  • GC.saveRow (nama, konten, [tulis ulang = 0, async = salah]); , di mana nama adalah nama baris untuk menulis ke koleksi, konten adalah baris data itu sendiri untuk menulis, menulis ulang adalah bendera untuk menimpa seluruh koleksi, digunakan dalam panggilan ke Gc.saveRow (nama, 'jelas', 1); yang menghapus semua entri dalam koleksi string, async adalah flag untuk bekerja dalam mode asinkron.
  • GC.getRows (nama, angka, [hitung = 1, async = false]); di mana nama adalah nama garis dalam koleksi, nomor adalah nomor seri dari garis untuk menerima data, hitungan adalah jumlah data yang diterima dimulai dengan angka, async adalah bendera untuk bekerja dalam mode asinkron.
  • GC.console (string); , di mana string adalah string untuk output ke konsol GC pada halaman di mana skrip dieksekusi. Misalnya, untuk menunjukkan kemajuan suatu tugas.
  • GC.clearConsole (); , fungsi ini menghapus konsol GC pada halaman tempat skrip dieksekusi.
  • GC.stopScript (); , berfungsi untuk menghentikan eksekusi skrip.
  • GC.loadFile (nama, [parseAs = teks]); , di mana nama adalah nama file dengan ekstensi yang isinya ingin Anda peroleh, parseAs adalah format preprosesor data, json dan teks saat ini didukung.

Pada artikel selanjutnya saya akan berbicara tentang " tugas terjadwal ".

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


All Articles