Как передать blob из расширения Chrome в приложение Chrome
Маленький фон
Я работаю пару дней на расширении Chrome, которое делает снимок экрана с заданных веб-страниц несколько раз в день. Я использовал этот в качестве руководства, и все работает так, как ожидалось.
Однако одно небольшое расширение требований не может встретиться. Пользователь должен иметь доступ к папке, в которой сохранены изображения (скриншоты), но Расширения Chrome не имеют доступа к файловой системе. С другой стороны, Chrome Apps. Таким образом, после многого оглядываясь, я пришел к выводу, что должен создать как расширение Chrome, так и приложение Chrome. Идея состоит в том, что расширение создало бы blob скриншота, а затем отправило бы этот blob в приложение, которое затем сохранит его как изображение в указанном пользователем месте. И это именно то, что я делаю - я создаю blob screentshot на стороне расширения, а затем отправляю его в приложение, где пользователю предлагается выбрать, где сохранить изображение.
Проблема
До части сохранения все работает так, как ожидалось. Блаб создается на добавочном номере, отправляется в приложение, полученном приложением, пользователь спрашивает, где сохранить, и изображение сохраняется. Это то, где вещи разваливаются. Результирующее изображение непригодно. Когда я пытаюсь открыть его, я получаю сообщение с надписью "Невозможно определить тип". Ниже приведен код, который я использую:
-
Сначала на стороне расширения, я создаю blob и отправляю его, например:
chrome.runtime.sendMessage(
APP_ID, /* I got this from the app */
{myMessage: blob}, /* Blob created previously; it correct */
function(response) {
appendLog("response: "+JSON.stringify(response));
}
);
-
Затем, на стороне 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 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"});
}
}
);
Здесь функция ON 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'}));
});
});
});
}
Ошибок нет. Никаких икота. Код работает, но изображение бесполезно. Что именно мне не хватает? Где я иду не так? Может быть, мы можем передавать строки между расширениями/приложениями? Является ли blob поврежденным в пути? У моего приложения нет доступа к блобу, потому что он был создан на расширении? Может ли кто-нибудь пролить некоторый свет?
ОБНОВЛЕНИЕ (9/23/14)
Извините за последнее обновление, но мне был назначен другой проект, и я не мог вернуться к нему до 2 дней назад.
Итак, после многого оглядываясь, я решил пойти с предложением @Danniel Herr, которое предлагает использовать SharedWorker и страницу, встроенную в фрейм в приложении. Идея заключается в том, что Extension будет поставлять blob в SharedWorker, который пересылает blob на страницу в расширении, встроенном в фрейм в приложении. Эта страница, а затем переводит blob в приложение, используя parent.postMessage(...). Это немного громоздко, но, похоже, это единственный вариант, который у меня есть.
Позвольте мне опубликовать некоторый код, чтобы он стал более понятным:
Расширение:
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, но для целей тестирования я просто отправляю простую строку.
Затем общий рабочий получает сообщение и пересылает его всем, кто подключился. Прокси .html/js, который находится внутри фрейма в приложении, действительно подключился к этому моменту и должен получить что-либо, переданное общим рабочим.
Затем proxy.js [должен] получает сообщение от общего рабочего и отправляет его в приложение, используя parent.postMessage(...). Приложение прослушивает через window.addEventListener( "сообщение",...).
Чтобы протестировать этот поток, я сначала открываю приложение, затем нажимаю кнопку расширения. Я не получаю сообщения в приложении. У меня тоже нет ошибок.
Расширение может поддерживать связь с совместно используемым рабочим. Приложение может прекрасно общаться с общим рабочим. Однако сообщение, отправленное из приложения extension- > proxy- > , не доходит до приложения. Что мне не хватает?
Извините за длинные ребята, но я надеюсь, что кто-то прольет свет, так как это сводит меня с ума.
Спасибо
Ответы
Ответ 1
Спасибо всем за помощь. Я нашел решение состоять в том, чтобы преобразовать blob в двоичную строку в расширении, а затем отправить строку в приложение с помощью хром-протокола передачи сообщений. В приложении я тогда сделал то, что предложил Франсуа, чтобы преобразовать двоичную строку обратно в 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 воссоздается должным образом в приложении.
Теперь я также добавил то, что нашел некоторые из вас здесь, и RobW в другом месте, которое должно разделить 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);
}
}
НА ПРИЛОЖЕНИИ
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);
}
}
}
);
Ответ 2
У моего приложения нет доступа к блобу, потому что он был создан на расширение? Может ли кто-нибудь пролить некоторый свет?
Точно! Вы можете передать 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");
});
Ответ 3
Меня очень интересует этот вопрос, поскольку я пытаюсь выполнить нечто подобное.
вот те вопросы, которые, как мне показалось, связаны:
Согласно Rob W, в первой ссылке:
API-интерфейс Chrome fileSystem (app) может напрямую записываться в файловую систему пользователя (например, ~/Documents или% USERPROFILE%\Documents), указанную пользователем.
Если вы можете написать файловую систему пользователя, вы должны быть в состоянии прочитать ее правильно?
У меня не было возможности попробовать это, но вместо того, чтобы напрямую передавать файл blob в приложение, вы можете сохранить элемент в своих загрузках с помощью chrome extension downloads api.
Затем вы можете получить его с помощью файловой системы api chrome для получения доступа к ней.
Изменить:
Я продолжаю читать, что файловая система, к которой может обращаться api, изолирована песочницей. Поэтому я не знаю, возможно ли это решение. Это песочница, а описание Роба "прямое обращение к файловой системе пользователя" звучит как противоположность мне.
Изменить:
Rob W пересмотрел свой ответ здесь: Внедрить перекрестное расширение сообщения, проходящего в chrome extension и app.
Он больше не использует общего рабочего и передает данные файла в виде строки на бэкэнд, которая может превратить строку обратно в blob.
Я не уверен, что такое максимальная длина сообщения, но Rob W также упоминает решение для нарезки блобов, чтобы отправить их в куски.
Изменить:
Я отправил 43 мб данных без сбоев моего приложения.
Ответ 4
Это действительно интересный вопрос. С моей точки зрения, это можно сделать с помощью этих методов:
- Прежде всего, вы должны преобразовать свой blob в arraybuffer. Это можно сделать с помощью FileReader, и это асинхронная операция
- Затем придет волшебство Encoding API, который в настоящее время доступен на стабильном Chrome. Таким образом, вы преобразовываете массив в строку. Эта операция синхронизируется
- Затем вы можете общаться с другими расширениями/приложениями с помощью API Chrome как это. Я использую этот метод для продвижения одного из моих приложений (новое упакованное приложение) с использованием другого известного унаследованного приложения. И из-за того, что устаревшие упакованные приложения на самом деле являются расширениями, я думаю, все будет хорошо.