Neste artigo, continuo a série de publicações nas quais quero falar sobre minha experiência na criação de uma extensão da Web para navegadores. Eu já tinha experiência na criação de uma extensão da Web, instalada por cerca de 100.000 usuários do Chrome, que funcionavam de forma autônoma, mas nesta série de artigos eu decidi me aprofundar no processo de desenvolvimento da extensão da Web, integrando-a firmemente ao lado do servidor.




Parte 1 ,
Parte 2 ,
Parte 4Armadilhas na implementação da interação de extensões da Web e do lado do servidor
Como já descrito anteriormente, o lado do servidor usa o Meteor.js. Para implementar a API RESTful, o pacote
github.com/kahmali/meteor-restivus é usado. Ele já possui parte implementada para cobrir os mecanismos do usuário relacionados à autorização.
Por exemplo, basta especificar
authRequired: true , como no exemplo abaixo, para que o ponto da API funcione apenas para usuários autorizados.
Api.addRoute('clientScript/:id_script', {authRequired: true}, {get: { action: function() {
Assim, foram adicionados três pontos de API para registro, recebimento e atualização de dados de perfil e redefinição de senha.
Na própria extensão da web, ao chamar métodos que requerem autorização, o seguinte código é usado:
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) {
Um exemplo de uma solicitação de autorização é claramente visível aqui. Os cabeçalhos incluem X-Auth-Token e X-User-Id, que foram obtidos como resultado do processo de registro ou autorização. Esses dados são armazenados no armazenamento local da extensão da web e estão sempre disponíveis no script content.js.
O download de arquivos na extensão da Web é feito lendo o arquivo no lado do navegador e enviando via 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") {
É importante observar a linha
event.target.result.replace (/ ^ data: [^;] *; base64, /, "") . O arquivo no lado do navegador é codificado em base64, mas para compatibilidade no lado do servidor ao usar essa codificação na linha
Buffer.from (nova String (this.bodyParams.content), "base64") , precisamos cortar o prefixo de codificação e ler apenas o "corpo" do arquivo . Também é necessário observar o acondicionamento no encodeURIComponent, pois o mesmo
+ é freqüentemente encontrado nos nomes de arquivos e base64.
Ao editar scripts, é necessário considerar a codificação de caracteres no corpo do script ao transferir conteúdo. Em alguns casos, a codificação base64 não produziu os resultados corretos ao decodificar no lado do servidor usando encodeURIComponent. Portanto, a codificação forçada no utf8 é usada preliminarmente com utf8.encode (str); onde
mths.be/utf8js v3.0.0 de @mathias
Os downloads de arquivos são implementados usando a bem estabelecida biblioteca FileSaver. Os dados recebidos através do XHR são simplesmente transferidos para a entrada do construtor File e, em seguida, o download do arquivo é inicializado:
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);
Biblioteca interna para scripts personalizados
Para que o script, a extensão da web e a parte do servidor interajam, é necessário ter um link intermediário que permita que você receba rapidamente dados do arquivo baixado, salve os dados após a execução do script, etc.
Para esse propósito, uma biblioteca interna foi gravada, inicializada antes de qualquer script começar a funcionar, adicionando-se ao código da página. Aqui você precisa adicionar informações sobre a política de proteção de origem para carregar recursos, ou seja, a política de segurança de conteúdo.
Muitos sites usam cabeçalhos CSP para se proteger contra a execução de código javascript arbitrário em suas páginas de serviços da web, protegendo-se do XSS no lado do navegador da web.
Como o usuário instala a extensão da Web por conta própria, ele pode alterar os cabeçalhos e o conteúdo do recurso baixado. Devido a um erro no Mozilla Firefox, este é um problema para alguns sites. Ou seja, na extensão da Web do Firefox, não será possível modificar os cabeçalhos ou adicionar uma metatag para cancelar a política de CSP dos sites nos quais eles são usados. Esse bug não está fechado há vários anos, embora os padrões estipulem claramente as provisões para extensões da Web, que afirmam que a política referente aos recursos baixados do lado do servidor de aplicativos não pode ser dominante em relação às extensões da Web instaladas pelo próprio usuário.
Uma restrição de política CSP pode ser implementada usando a estrutura kango da seguinte maneira:
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"]);
Nesse caso, não se esqueça de adicionar linhas no manifesto de extensão da Web que permitem trabalhar com o objeto webRequest no modo de bloqueio:
"permissions": { ... "webRequest": true, "webRequestBlocking": true, ... }
Depois de resolver o problema com as restrições impostas pelo CSP, o usuário pode usar os scripts escritos por ele em qualquer página da Internet.
As funções de chamada da biblioteca interna estão disponíveis através do objeto Gc global.
Funções atualmente implementadas:
- GC.saveRow (nome, conteúdo, [reescrever = 0, assíncrono = falso]); , onde name é o nome das linhas a serem gravadas na coleção, o conteúdo é a própria linha de dados para gravação, rewrite é o sinalizador para substituir a coleção inteira, usada na chamada para Gc.saveRow (name, 'clear', 1); que exclui todas as entradas na coleção de cadeias, async é um sinalizador para trabalhar no modo assíncrono.
- GC.getRows (nome, número, [count = 1, assíncrono = false]); onde name é o nome das linhas na coleção, number é o número de série da linha para receber dados, count é o número de dados recebidos começando com number, async é o sinalizador para trabalhar no modo assíncrono.
- GC.console (string); , em que string é uma sequência de saída para o console do GC na página em que o script é executado. Por exemplo, para demonstrar o progresso de uma tarefa.
- GC.clearConsole (); , a função limpa o console do GC na página em que o script é executado.
- GC.stopScript (); , função para interromper a execução do script.
- GC.loadFile (nome, [parseAs = texto]); , em que name é o nome do arquivo com a extensão cujo conteúdo você deseja obter, parseAs é o formato do pré-processador de dados, atualmente são suportados json e text.
No próximo artigo, falarei sobre "
tarefas agendadas ".