如何将Chrome扩展程序中的blob传递到Chrome应用

时间:2014-09-04 15:07:44

标签: javascript google-chrome google-chrome-extension google-chrome-app

小背景

我已经在Chrome扩展程序上工作了几天,每天多次截取给定网页的屏幕截图。我使用this作为指导,事情按预期工作。

但是,有一个小的要求扩展无法满足。用户必须能够访问保存图像(屏幕截图)的文件夹,但Chrome Extensions don't have access to the file system。另一方面,Chrome应用程序可以。因此,经过多次调查,我得出结论,我必须创建Chrome扩展程序和Chrome应用程序。我们的想法是扩展会创建一个屏幕截图,然后将该blob发送到应用程序,然后将其作为图像保存到用户指定的位置。而这正是我正在做的事情 - 我在扩展端创建一个screentshot的blob然后将其发送到应用程序,在该应用程序中要求用户选择保存图像的位置

问题

直到保存部分,一切都按预期工作。 blob在扩展程序上创建,发送到应用程序,由应用程序接收,用户被问到保存位置,图像被保存....这就是事情崩溃的地方。 生成的图片无法使用。当我尝试打开它时,收到一条消息,上面写着"无法确定类型"。以下是我使用的代码:

  1. 首先在EXTENSION方面,我创建一个blob并将其发送出去,如下所示:

     chrome.runtime.sendMessage(
        APP_ID, /* I got this from the app */
        {myMessage: blob}, /* Blob created previously; it's correct */
        function(response) {
          appendLog("response: "+JSON.stringify(response));
        }
     );
    
  2. 然后,在APP方面,我收到blob并试图像这样保存它:

    // listen for external messages
    chrome.runtime.onMessageExternal.addListener(
      function(request, sender, sendResponse) {
        if (sender.id in blacklistedIds) {
          sendResponse({"result":"sorry, could not process your message"});
          return;  // don't allow this extension access
        } else if (request.incomingBlob) {
          appendLog("from "+sender.id+": " + request.incomingBlob);
    
          // attempt to save blob to choosen location
          if (_folderEntry == null) {
             // get a directory to save in if not yet chosen
             openDirectory();
          }
          saveBlobToFile(request.incomingBlob, "screenshot.png");
    
          /*
          // inspect object to try to see what's wrong
          var keys = Object.keys(request.incomingBlob);
          var keyString = "";
          for (var key in keys) {
             keyString += " " + key;
          }
          appendLog("Blob object keys:" + keyString);
          */
    
          sendResponse({"result":"Ok, got your message"});
        } else {
          sendResponse({"result":"Ops, I don't understand this message"});
        }
      }
    );
    

    这是执行实际保存的APP上的功能:

    function saveBlobToFile(blob, fileName) {
      appendLog('entering saveBlobToFile function...');
      chrome.fileSystem.getWritableEntry(_folderEntry, function(entry) {         
        entry.getFile(fileName, {create: true}, function(entry) {         
          entry.createWriter(function(writer) {
            //writer.onwrite = function() {
            //   writer.onwrite = null;
            //   writer.truncate(writer.position);
            //};
            appendLog('calling writer.write...');
            writer.write(blob);                       
            // Also tried writer.write(new Blob([blob], {type: 'image/png'}));
          });
        });
      });
    }
    
  3. 没有错误。没有打嗝。代码可以工作,但图像没用。我到底错过了什么?我哪里错了?我们只能在扩展/应用之间传递字符串吗?斑点在路上被破坏了吗?我的应用程序是否无法访问blob,因为它是在扩展程序上创建的?谁能请一些亮点?

    更新(2014年9月23日) 对于最新的更新很抱歉,但我被分配到了另一个项目,直到2天前才回复。

    因此,经过多次调查,我决定采用@Danniel Herr的建议,建议使用SharedWorker和嵌入在应用程序框架中的页面。我们的想法是扩展程序将blob提供给SharedWorker,后者将blob转发到嵌入应用程序框架中的扩展中的页面。该页面然后使用parent.postMessage(...)将blob转发到应用程序。它有点麻烦,但它似乎是我唯一的选择。

    让我发布一些代码,以便更有意义:

    扩展程序:

    var worker = new SharedWorker(chrome.runtime.getURL('shared-worker.js'));
    worker.port.start();
    worker.postMessage('hello from extension'); // Can send blob here too
    worker.port.addEventListener("message", function(event) {
       $('h1Title').innerHTML = event.data;
    });
    

    的proxy.js

    var worker = new SharedWorker(chrome.runtime.getURL('shared-worker.js'));
    worker.port.start();
    
    worker.port.addEventListener("message",
       function(event) {      
          parent.postMessage(event.data, 'chrome-extension://[extension id]');
       }
    );
    

    proxy.html

    <script src='proxy.js'></script>
    

    共享worker.js

    var ports = [];
    var count = 0;
    onconnect = function(event) {
        count++;
        var port = event.ports[0];
        ports.push(port);
        port.start(); 
    
        /* 
        On both the extension and the app, I get count = 1 and ports.length = 1
        I'm running them side by side. This is so maddening!!!
        What am I missing?
        */
        var msg = 'Hi, you are connection #' + count + ". ";
        msg += " There are " + ports.length + " ports open so far."
        port.postMessage(msg);
    
        port.addEventListener("message",       
          function(event) {
            for (var i = 0; i < ports.length; ++i) {
                //if (ports[i] != port) {
                    ports[i].postMessage(event.data);
                //}
            }
        });
    };
    

    在应用

    context.addEventListener("message", 
        function(event) {
            appendLog("message from proxy: " + event.data);
        } 
    );
    

    所以这是执行流程......在扩展上,我创建了一个共享工作者并向其发送消息。共享工作者应该能够接收blob,但出于测试目的,我只发送一个简单的字符串。

    接下来,共享工作人员收到该消息并将其转发给已连接的所有人。 app中框架内的proxy.html / js确实已经连接,并且应该接收共享工作者转发的任何内容。

    接下来,proxy.js [应该]从共享工作者接收消息并使用parent.postMessage(...)将其发送到应用程序。该应用程序正在通过window.addEventListener(&#34; message&#34;,...)进行侦听。

    要测试此流程,我首先打开应用程序,然后单击扩展按钮。我在应用程序上没有收到任何消息。我也没有错。

    扩展程序可以与共享工作者来回通信。该应用程序可以很好地与共享工作人员进行通信。但是,我从扩展程序&gt; proxy-&gt;应用程序发送的消息未到达该应用程序。我错过了什么?

    对于那些长篇大论的人来说很抱歉,但是我希望有人能说清楚,因为这让我感到疯狂。

    由于

4 个答案:

答案 0 :(得分:8)

感谢您的帮助。我发现解决方案是将blob转换为扩展名上的二进制字符串,然后使用chrome&#39; s消息传递API将字符串发送到应用程序。在应用程序上,我接着做了Francois建议将二进制字符串转换回blob的内容。之前我曾尝试过这个解决方案但是我没有工作,因为我在应用程序上使用了以下代码:

blob = new Blob([blobAsBinString], {type: mimeType});

该代码可能适用于文本文件或简单字符串,但它不适用于图像(可能是由于字符编码问题)。这就是我疯狂的地方。解决方案是使用Francois从一开始就提供的内容:

var bytes = new Uint8Array(blobAsBinString.length);
for (var i=0; i<bytes.length; i++) {
   bytes[i] = blobAsBinString.charCodeAt(i);            
}             
blob = new Blob([bytes], {type: mimeString});

该代码重新训练二进制字符串的完整性,并在应用程序上正确地重新创建blob。

现在我还在这里收集了一些我发现你和其他人建议的东西,这是将blob拆分成块并将其发送过来,以防blob太大。整个解决方案如下:

关于扩展:

function sendBlobToApp() {  

  // read the blob in chunks/chunks and send it to the app
  // Note: I crashed the app using 1 KB chunks. 1 MB chunks work just fine. 
  // I decided to use 256 KB as that seems neither too big nor too small
  var CHUNK_SIZE = 256 * 1024;
  var start = 0;
  var stop = CHUNK_SIZE;      

  var remainder = blob.size % CHUNK_SIZE;
  var chunks = Math.floor(blob.size / CHUNK_SIZE);      

  var chunkIndex = 0;

  if (remainder != 0) chunks = chunks + 1;           

  var fr = new FileReader();
  fr.onload = function() {
      var message = {
          blobAsText: fr.result,
          mimeString: mimeString,                 
          chunks: chunks 
      };          
      // APP_ID was obtained elsewhere
      chrome.runtime.sendMessage(APP_ID, message, function(result) {
          if (chrome.runtime.lastError) {
              // Handle error, e.g. app not installed
              // appendLog is defined elsewhere
              appendLog("could not send message to app");
          } 
      });

      // read the next chunk of bytes
      processChunk();
  };
  fr.onerror = function() { appendLog("An error ocurred while reading file"); };
  processChunk();

  function processChunk() {
     chunkIndex++;         

     // exit if there are no more chunks
     if (chunkIndex > chunks) {
        return;
     }

     if (chunkIndex == chunks && remainder != 0) {
        stop = start + remainder;
     }                           

     var blobChunk = blob.slice(start, stop);

     // prepare for next chunk
     start = stop;
     stop = stop + CHUNK_SIZE;

     // convert chunk as binary string
     fr.readAsBinaryString(blobChunk);
  } 
}

关于APP

chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id in blacklistedIds) {
      return;  // don't allow this extension access
    } else if (request.blobAsText) {                  
       //new chunk received  
      _chunkIndex++;                   

      var bytes = new Uint8Array(request.blobAsText.length);                     
      for (var i=0; i<bytes.length; i++) {
         bytes[i] = request.blobAsText.charCodeAt(i);            
      }         
      // store blob
      _blobs[_chunkIndex-1] = new Blob([bytes], {type: request.mimeString});           

      if (_chunkIndex == request.chunks) {                      
         // merge all blob chunks
         for (j=0; j<_blobs.length; j++) {
            var mergedBlob;
            if (j>0) {                  
               // append blob
               mergedBlob = new Blob([mergedBlob, _blobs[j]], {type: request.mimeString});
            }
            else {                  
               mergedBlob = new Blob([_blobs[j]], {type: request.mimeString});
            }
         }                         

         saveBlobToFile(mergedBlob, "myImage.png", request.mimeString);
      }
    }
 }
);

答案 1 :(得分:4)

  

我的应用无法访问blob,因为它是在   延期?谁能请一些亮点?

完全!您可能希望传递dataUrl而不是blob。下面这样的东西可以起作用:

/* Chrome Extension */

var blobToDataURL = function(blob, cb) {
  var reader = new FileReader();
  reader.onload = function() {
    var dataUrl = reader.result;
    var base64 = dataUrl.split(',')[1];
    cb(base64);
  };
  reader.readAsDataURL(blob);
  };

blobToDataUrl(blob, function(dataUrl) {
  chrome.runtime.sendMessage(APP_ID, {databUrl: dataUrl}, function() {});
});

/* Chrome App */

function dataURLtoBlob(dataURL) {
    var byteString = atob(dataURL.split(',')[1]),
        mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];

    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    var blob = new Blob([ia], {type: mimeString});
    return blob;
}

chrome.runtime.onMessageExternal.addListener(
    function(request) {  
  var blob =  dataURLtoBlob(request.dataUrl); 
  saveBlobToFile(blob, "screenshot.png");
});

答案 2 :(得分:3)

我对这个问题非常感兴趣,因为我正在努力完成类似的事情。

这些是我发现的相关问题:

  1. How can a Chrome extension save many files to a user-specified directory?
  2. Implement cross extension message passing in chrome extension and app
  3. Does chrome.runtime support posting messages with transferable objects?
  4. Pass File object to background.js from content script or pass createObjectURL (and keep alive after refresh)
  5. 根据Rob W的说法,在第一个链接中:

    &#34; Chrome的fileSystem(app)API可以直接写入用户指定的用户文件系统(例如〜/ Documents或%USERPROFILE%\ Documents)。&#34;

    如果您可以写信给用户的文件系统,您应该能够正确地阅读它吗?

    我没有机会尝试这一点,但是您可以使用chrome扩展程序下载API将项目保存到您的下载中,而不是直接将文件blob传递给应用程序。

    然后您可以使用chrome app filesystem api检索它以获取对它的访问权限。

    编辑:

    我一直在读,api可以访问的文件系统是沙箱。所以我不知道这个解决方案是否可行。它是沙箱和Rob W的描述&#34;直接写入用户的文件系统&#34;听起来像对我一样。

    编辑:

    Rob W在这里修改了答案:Implement cross extension message passing in chrome extension and app

    它不再使用共享工作器,并将文件数据作为字符串传递给后端,后端可以将字符串转换回blob。

    我不确定消息的最大长度是多少,但是Rob W还提到了一种解决方法,即将blob切片以将其分段发送。

    编辑:

    我发送了43 mbs的数据而没有崩溃我的应用。

答案 3 :(得分:2)

这真是一个有趣的问题。从我的观点来看,可以使用这些技术来完成:

  1. 首先,您应该将blob转换为arraybuffer。这可以使用FileReader来完成,它是异步操作
  2. 然后出现Encoding API的一些魔力,目前可在稳定的Chrome上使用。所以你将arraybuffer转换成字符串。此操作是同步
  3. 然后,您可以使用Chrome API like this与其他扩展程序/应用进行通信。我使用这种技术来推广我的一个应用程序(新的打包应用程序)使用另一个着名的遗留应用程序。由于传统打包应用程序实际上是扩展,我认为一切都会好起来的。