Как передать blob из расширения Chrome в приложение Chrome
Немного Истории
Я работаю в течение нескольких дней над расширением Chrome, которое делает скриншот данных веб-страниц несколько раз в день. Я использовал этой в качестве руководства и все работает, как ожидалось.
есть одно незначительное требование расширения не может удовлетворить, хотя. Пользователь должен иметь доступ к папке, в которой сохраняются изображения (скриншоты), но расширения Chrome не имеют доступа к файлу система. Приложения Chrome, с другой стороны, сделать. Таким образом, после долгих поисков я пришел к выводу, что я должен создать расширение Chrome и приложение Chrome. Идея заключается в том, что расширение создаст blob скриншота, а затем отправит этот blob в приложение, которое затем сохранит его как изображение в указанное Пользователем место. И это именно то, что я делаю - я создаю blob screentshot на стороне расширения, а затем отправляю его в приложение, где пользователю предлагается выберите, где Сохранить Изображение.
Проблема
до части сохранения все работает так, как ожидалось. Blob создается на расширении, отправляется в приложение, получает приложение, пользователю предлагается, где сохранить, и изображение сохраняется.... Вот где все разваливается. полученное изображение непригодно. когда я пытаюсь открыть его, я получаю сообщение о том, что "не могу определить тип". Ниже приведен код, который я использую:
-
сначала на стороне расширения я создаю 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)); } );
-
затем, на стороне приложения, я получаю 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"}); } } );
вот функция в приложении, которая выполняет фактическое сохранение:
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'})); }); }); }); }
нет ошибок. Никаких икот. Код работает, но изображение бесполезно. Что именно я упускаю? Где я ошибаюсь? Это то, что мы можем передавать только строки между расширениями/приложениями? Капля портится по пути? Мое приложение не имеет доступа к blob, потому что оно было создано в расширении? Кто-нибудь может пролить свет?
обновление (9/23/14) Извините за позднее обновление, но я был назначен на другой проект и не мог вернуться к этому до 2 дней назад.
Итак, после долгого осмотра, я решил пойти с предложением @Danniel Herr, которое предлагает использовать SharedWorker и страницу, встроенную в фрейм в приложении. Идея заключается в том, что расширение будет поставлять blob SharedWorker, который пересылает blob на страницу расширения, встроенного в фрейм в приложении. Эта страница, а затем перенаправляет blob в приложение с помощью parent.метод postMessage(...). Это немного громоздко, но, похоже, это единственный вариант, который у меня есть.
позвольте мне опубликовать код, чтобы он сделал немного больше смысл:
4 ответов
Спасибо за вашу помощь ребята. Я нашел решение преобразовать blob в двоичную строку расширения, а затем отправить строку в приложение, используя API передачи сообщений chrome. В приложении я сделал то, что предложил Франсуа, чтобы преобразовать двоичную строку обратно в blob. Я пробовал это решение раньше, но я не работал, потому что я использовал следующий код в приложении:
blob = new Blob([blobAsBinString], {type: mimeType});
этот код может работать для текстовых файлов или простых строк, но он не работает для картинки (возможно из-за проблем с кодировками). Вот где я сходил с ума. Решение использовать то, что Франсуа с самого начала:
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 воссоздается должным образом в приложении.
НА РАСШИРЕНИЕ:
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);
}
}
НА ПРИЛОЖЕНИЕ
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);
}
}
}
);
мое приложение не имеет доступа к 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");
});
меня очень интересует этот вопрос, так как я пытаюсь выполнить что-то подобное.
вот вопросы, которые я нашел связанными:
- как расширение Chrome может сохранить много файлов в указанный пользователем каталог?
- реализовать перекрестное сообщение расширения, проходящее в расширении chrome и app
- делает chrome.поддержка времени выполнения разноска сообщений с transferable объекты?
- передать объект File в background.js из сценария содержимого или передать createObjectURL (и сохранить в живых после обновления)
согласно Rob W, в первой ссылке:
"API файловой системы Chrome (app) может напрямую писать в файловую систему пользователя (например, ~/Documents или %USERPROFILE%\Documents), указанную пользователем."
Если вы можете писать в файловую систему пользователя, вы должны иметь возможность читать из нее правильно?
У меня не было возможности попробовать это, но вместо того, чтобы напрямую передавать файл blob в приложение, вы можете сохранить элемент в своих загрузках с помощью api загрузки расширений chrome.
затем вы можете получить его с помощью API файловой системы chrome app, чтобы получить к нему доступ.
Edit:
Я продолжаю читать, что файловая система, к которой может получить доступ api, изолирована. Поэтому я понятия не имею, возможно ли это решение. Это песочница и Роб Описание W "запись непосредственно в файловую систему пользователя" звучит для меня как противоположности.
Edit:
Роб W пересмотрел свой ответ здесь:реализовать перекрестное сообщение расширения, проходящее в расширении chrome и app.
Он больше не использует общий рабочий и передает данные файла в виде строки в серверную часть, которая может превратить строку обратно в blob.
Я не уверен, какова максимальная длина сообщения, но Роб W также упоминает решение для нарезки капель, чтобы отправить их по кусочкам.
Edit:
Я отправил 43 Мб данных без сбоев моего приложения.
Это действительно интересный вопрос. С моей точки зрения, это можно сделать, используя следующие техники:
- прежде всего, вы должны преобразовать свой blob в arraybuffer. Это можно сделать с помощью FileReader и это асинхронная операция
- тогда здесь идет какая-то магия кодирование API, который в настоящее время доступен на стабильном Chrome. Таким образом, вы конвертируете свой arraybuffer в строку. Эта операция sync
- затем вы можете общайтесь с другими расширениями / приложениями с помощью Chrome API такой. Я использую этот метод для продвижения одного из моих приложений (новое упакованное приложение) с помощью другого известного устаревшего приложения. И из-за того, что устаревшие упакованные приложения на самом деле являются расширениями, я думаю, что все будет в порядке.