UNPKG

@extend-chrome/messages

Version:

An API for Chrome extension messages that makes sense.

295 lines (282 loc) 10.1 kB
import { merge, fromEventPattern } from 'rxjs'; import { first, filter, map } from 'rxjs/operators'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } const _listeners = new Map(); const _removeListener = (scope, callback) => { const _callbacks = _listeners.get(scope); if (_callbacks) { _callbacks.delete(callback); } }; const _getListener = (scope, callback) => { const _callbacks = _listeners.get(scope); if (_callbacks) { return _callbacks.get(callback); } }; const _setListener = (scope, callback, listener) => { const _callbacks = _listeners.get(scope) || new Map(); _callbacks.set(callback, listener); if (!_listeners.has(scope)) { _listeners.set(scope, _callbacks); } }; const scopeOn = (scope) => (callback) => { const listener = (message, sender) => { if (message.async || message.scope !== scope) { return false; } try { callback(message.payload, sender); } catch (error) { // Log listener error console.error('Uncaught error in chrome.runtime.onMessage listener'); console.error(error); } return false; }; chrome.runtime.onMessage.addListener(listener); _setListener(scope, callback, listener); }; const scopeAsyncOn = (scope) => (callback) => { const listener = (message, sender, sendResponse) => { if (message.async && scope === message.scope) { handleMessage(); return true; } return false; async function handleMessage() { try { const respond = (response) => { const coreResponse = { success: true, payload: response, }; sendResponse(coreResponse); }; await callback(message.payload, sender, respond); } catch (error) { const response = { success: false, payload: { greeting: error.message, }, }; console.error(error); sendResponse(response); } } }; chrome.runtime.onMessage.addListener(listener); _setListener(scope, callback, listener); }; const scopeOff = (scope) => (listener) => { const _listener = _getListener(scope, listener); if (_listener) { _removeListener(scope, listener); chrome.runtime.onMessage.removeListener(_listener); } }; class ChromeMessageError extends Error { constructor(_a) { var _b, _c; var { coreMessage = null, coreResponse = null, message = ((_c = (_b = chrome.runtime) === null || _b === void 0 ? void 0 : _b.lastError) === null || _c === void 0 ? void 0 : _c.message) || (coreResponse === null || coreResponse === void 0 ? void 0 : coreResponse.payload.greeting) || 'chrome.runtime.lastError is undefined', } = _a; super(message); this.coreMessage = coreMessage; this.coreResponse = coreResponse; } } const scopeSend = (scope) => (message, { tabId, frameId } = {}) => new Promise((resolve, reject) => { const coreMessage = { async: false, tabId: tabId || null, payload: message, scope, }; const callback = (response) => { if (chrome.runtime.lastError) { const lastError = chrome.runtime.lastError.message; const noResponse = 'The message port closed before a response was received'; if (lastError && lastError.includes(noResponse)) { resolve(); } else { reject(new ChromeMessageError({ coreMessage })); } } else { if (response && !response.success) { reject(response.payload); } else { resolve(); } } }; if (typeof tabId === 'number' && typeof frameId === 'number') { chrome.tabs.sendMessage(tabId, coreMessage, { frameId }, callback); } else if (typeof tabId === 'number') { chrome.tabs.sendMessage(tabId, coreMessage, callback); } else { chrome.runtime.sendMessage(coreMessage, callback); } }); const scopeAsyncSend = (scope) => (message, { tabId, frameId } = {}) => new Promise((resolve, reject) => { const coreMessage = { async: true, tabId: tabId || null, payload: message, scope, }; const callback = (coreResponse) => { if (chrome.runtime.lastError || coreResponse === null || !coreResponse.success) { reject(new ChromeMessageError({ coreMessage, coreResponse })); } else { resolve(coreResponse.payload); } }; if (typeof tabId === 'number' && typeof frameId === 'number') { chrome.tabs.sendMessage(tabId, coreMessage, { frameId }, callback); } else if (typeof tabId === 'number') { chrome.tabs.sendMessage(tabId, coreMessage, callback); } else { chrome.runtime.sendMessage(coreMessage, callback); } }); const setupWaitForFirst = (stream) => (predicate = (() => true)) => stream.pipe(first(predicate)).toPromise(); /** * Get a messages scope by name. */ function getScope(scope) { const _asyncOn = scopeAsyncOn(scope); const _asyncSend = scopeAsyncSend(scope); const _off = scopeOff(scope); const _on = scopeOn(scope); const _send = scopeSend(scope); async function send(data, options) { const _options = options || {}; const { async = false } = _options, sendOptions = __rest(_options, ["async"]); if (async) { return _asyncSend(data, sendOptions); } else { return _send(data, sendOptions); } } function on(callback) { if (isMessageListener(callback)) { _on(callback); } else { _asyncOn(callback); } function isMessageListener(x) { return x.length < 3; } } /** Remove a message listener from `on`. */ function off(fn) { return _off(fn); } /** Untyped Observable of all messages in scope */ const stream = merge(fromEventPattern(_on, _off), fromEventPattern(_asyncOn, _off)); /* ------------------ GET MESSAGE ----------------- */ const _greetings = new Set(); function getMessage(greeting, options) { if (_greetings.has(greeting)) throw new Error('greeting is not unique'); _greetings.add(greeting); const { async } = options || {}; const _send = (data, _options) => { _options = _options || {}; let tabId; if (typeof _options.tabId === 'number') { tabId = _options.tabId; } let frameId; if (typeof _options.frameId === 'number') { frameId = _options.frameId; } if (async) { return send({ greeting, data }, { async, tabId, frameId }); } else { return send({ greeting, data }, { tabId, frameId }); } }; /** Use this to send a message with no data to a tab */ _send.toTab = ({ tabId }) => { if (async) { return send({ greeting }, { async, tabId }); } else { return send({ greeting }, { tabId }); } }; if (async) { const _stream = stream.pipe( // Filter line messages filter(isMatchingMessage), // Map message to data map(([{ data }, s, r]) => [data, s, r]), filter((x) => x.length === 3)); return [_send, _stream, setupWaitForFirst(stream)]; } else { const _stream = stream.pipe( // Filter line messages filter(isMatchingMessage), // Map message to data map(([{ data }, s]) => [data, s]), filter((x) => x.length < 3)); return [_send, _stream, setupWaitForFirst(_stream)]; } function isMatchingMessage([x]) { return x && typeof x === 'object' && x.greeting === greeting; } } return { send, on, off, stream, getMessage, }; } // Default scope const __defaultScopeName = '@extend-chrome/messages__root'; const messages = getScope(__defaultScopeName); const { getMessage } = messages; export { __defaultScopeName, getMessage, getScope, messages, getScope as useScope }; //# sourceMappingURL=index.esm.js.map