1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
'use strict';
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
/** Wrap an API that uses callbacks with Promises
* This expects the pattern function withCallback(arg1, arg2, ... argN, callback)
* @author Keith Henry <keith.henry@evolutionjobs.co.uk>
* @license MIT */
(function () {
'use strict';
/** Wrap a function with a callback with a Promise.
* @param {function} f The function to wrap, should be pattern: withCallback(arg1, arg2, ... argN, callback).
* @param {function} parseCB Optional function to parse multiple callback parameters into a single object.
* @returns {Promise} Promise that resolves when the callback fires. */
function promisify(f, parseCB) {
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var safeArgs = args;
var callback = void 0;
// The Chrome API functions all use arguments, so we can't use f.length to check
// If there is a last arg
if (args && args.length > 0) {
// ... and the last arg is a function
var last = args[args.length - 1];
if (typeof last === 'function') {
// Trim the last callback arg if it's been passed
safeArgs = args.slice(0, args.length - 1);
callback = last;
}
}
// Return a promise
return new Promise(function (resolve, reject) {
try {
// Try to run the original function, with the trimmed args list
f.apply(undefined, _toConsumableArray(safeArgs).concat([function () {
for (var _len2 = arguments.length, cbArgs = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
cbArgs[_key2] = arguments[_key2];
}
// If a callback was passed at the end of the original arguments
if (callback) {
// Don't allow a bug in the callback to stop the promise resolving
try {
callback.apply(undefined, cbArgs);
} catch (cbErr) {
reject(cbErr);
}
}
// Chrome extensions always fire the callback, but populate chrome.runtime.lastError with exception details
if (chrome.runtime.lastError)
// Return as an error for the awaited catch block
reject(new Error(chrome.runtime.lastError.message || 'Error thrown by API ' + chrome.runtime.lastError));else {
if (parseCB) {
var cbObj = parseCB.apply(undefined, cbArgs);
resolve(cbObj);
} else if (!cbArgs || cbArgs.length === 0) resolve();else if (cbArgs.length === 1) resolve(cbArgs[0]);else resolve(cbArgs);
}
}]));
} catch (err) {
reject(err);
}
});
};
}
/** Promisify all the known functions in the map
* @param {object} api The Chrome native API to extend
* @param {Array} apiMap Collection of sub-API and functions to promisify */
function applyMap(api, apiMap) {
if (!api)
// Not supported by current permissions
return;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = apiMap[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var funcDef = _step.value;
var funcName = void 0;
if (typeof funcDef === 'string') funcName = funcDef;else {
funcName = funcDef.n;
}
if (!api.hasOwnProperty(funcName))
// Member not in API
continue;
var m = api[funcName];
if (typeof m === 'function')
// This is a function, wrap in a promise
api[funcName] = promisify(m, funcDef.cb);else
// Sub-API, recurse this func with the mapped props
applyMap(m, funcDef.props);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/** Apply promise-maps to the Chrome native API.
* @param {object} apiMaps The API to apply. */
function applyMaps(apiMaps) {
for (var apiName in apiMaps) {
var callbackApi = chrome[apiName];
if (!callbackApi)
// Not supported by current permissions
continue;
var apiMap = apiMaps[apiName];
applyMap(callbackApi, apiMap);
}
}
// accessibilityFeatures https://developer.chrome.com/extensions/accessibilityFeatures
var knownA11ySetting = ['get', 'set', 'clear'];
// ContentSetting https://developer.chrome.com/extensions/contentSettings#type-ContentSetting
var knownInContentSetting = ['clear', 'get', 'set', 'getResourceIdentifiers'];
// StorageArea https://developer.chrome.com/extensions/storage#type-StorageArea
var knownInStorageArea = ['get', 'getBytesInUse', 'set', 'remove', 'clear'];
/** Map of API functions that follow the callback pattern that we can 'promisify' */
applyMaps({
accessibilityFeatures: [// Todo: this should extend AccessibilityFeaturesSetting.prototype instead
{ n: 'spokenFeedback', props: knownA11ySetting }, { n: 'largeCursor', props: knownA11ySetting }, { n: 'stickyKeys', props: knownA11ySetting }, { n: 'highContrast', props: knownA11ySetting }, { n: 'screenMagnifier', props: knownA11ySetting }, { n: 'autoclick', props: knownA11ySetting }, { n: 'virtualKeyboard', props: knownA11ySetting }, { n: 'animationPolicy', props: knownA11ySetting }],
alarms: ['get', 'getAll', 'clear', 'clearAll'],
bookmarks: ['get', 'getChildren', 'getRecent', 'getTree', 'getSubTree', 'search', 'create', 'move', 'update', 'remove', 'removeTree'],
browser: ['openTab'],
browserAction: ['getTitle', 'setIcon', 'getPopup', 'getBadgeText', 'getBadgeBackgroundColor'],
browsingData: ['settings', 'remove', 'removeAppcache', 'removeCache', 'removeCookies', 'removeDownloads', 'removeFileSystems', 'removeFormData', 'removeHistory', 'removeIndexedDB', 'removeLocalStorage', 'removePluginData', 'removePasswords', 'removeWebSQL'],
commands: ['getAll'],
contentSettings: [// Todo: this should extend ContentSetting.prototype instead
{ n: 'cookies', props: knownInContentSetting }, { n: 'images', props: knownInContentSetting }, { n: 'javascript', props: knownInContentSetting }, { n: 'location', props: knownInContentSetting }, { n: 'plugins', props: knownInContentSetting }, { n: 'popups', props: knownInContentSetting }, { n: 'notifications', props: knownInContentSetting }, { n: 'fullscreen', props: knownInContentSetting }, { n: 'mouselock', props: knownInContentSetting }, { n: 'microphone', props: knownInContentSetting }, { n: 'camera', props: knownInContentSetting }, { n: 'unsandboxedPlugins', props: knownInContentSetting }, { n: 'automaticDownloads', props: knownInContentSetting }],
contextMenus: ['create', 'update', 'remove', 'removeAll'],
cookies: ['get', 'getAll', 'set', 'remove', 'getAllCookieStores'],
debugger: ['attach', 'detach', 'sendCommand', 'getTargets'],
desktopCapture: ['chooseDesktopMedia'],
// TODO: devtools.*
documentScan: ['scan'],
downloads: ['download', 'search', 'pause', 'resume', 'cancel', 'getFileIcon', 'erase', 'removeFile', 'acceptDanger'],
enterprise: [{ n: 'platformKeys', props: ['getToken', 'getCertificates', 'importCertificate', 'removeCertificate'] }],
extension: ['isAllowedIncognitoAccess', 'isAllowedFileSchemeAccess'], // mostly deprecated in favour of runtime
fileBrowserHandler: ['selectFile'],
fileSystemProvider: ['mount', 'unmount', 'getAll', 'get', 'notify'],
fontSettings: ['setDefaultFontSize', 'getFont', 'getDefaultFontSize', 'getMinimumFontSize', 'setMinimumFontSize', 'getDefaultFixedFontSize', 'clearDefaultFontSize', 'setDefaultFixedFontSize', 'clearFont', 'setFont', 'clearMinimumFontSize', 'getFontList', 'clearDefaultFixedFontSize'],
gcm: ['register', 'unregister', 'send'],
history: ['search', 'getVisits', 'addUrl', 'deleteUrl', 'deleteRange', 'deleteAll'],
i18n: ['getAcceptLanguages', 'detectLanguage'],
identity: ['getAuthToken', 'getProfileUserInfo', 'removeCachedAuthToken', 'launchWebAuthFlow', 'getRedirectURL'],
idle: ['queryState'],
input: [{
n: 'ime', props: ['setMenuItems', 'commitText', 'setCandidates', 'setComposition', 'updateMenuItems', 'setCandidateWindowProperties', 'clearComposition', 'setCursorPosition', 'sendKeyEvents', 'deleteSurroundingText']
}],
management: ['setEnabled', 'getPermissionWarningsById', 'get', 'getAll', 'getPermissionWarningsByManifest', 'launchApp', 'uninstall', 'getSelf', 'uninstallSelf', 'createAppShortcut', 'setLaunchType', 'generateAppForLink'],
networking: [{ n: 'config', props: ['setNetworkFilter', 'finishAuthentication'] }],
notifications: ['create', 'update', 'clear', 'getAll', 'getPermissionLevel'],
pageAction: ['getTitle', 'setIcon', 'getPopup'],
pageCapture: ['saveAsMHTML'],
permissions: ['getAll', 'contains', 'request', 'remove'],
platformKeys: ['selectClientCertificates', 'verifyTLSServerCertificate', { n: "getKeyPair", cb: function cb(publicKey, privateKey) {
return { publicKey: publicKey, privateKey: privateKey };
} }],
runtime: ['getBackgroundPage', 'openOptionsPage', 'setUninstallURL', 'restartAfterDelay', 'sendMessage', 'sendNativeMessage', 'getPlatformInfo', 'getPackageDirectoryEntry', { n: "requestUpdateCheck", cb: function cb(status, details) {
return { status: status, details: details };
} }],
scriptBadge: ['getPopup'],
sessions: ['getRecentlyClosed', 'getDevices', 'restore'],
storage: [// Todo: this should extend StorageArea.prototype instead
{ n: 'sync', props: knownInStorageArea }, { n: 'local', props: knownInStorageArea }, { n: 'managed', props: knownInStorageArea }],
socket: ['create', 'connect', 'bind', 'read', 'write', 'recvFrom', 'sendTo', 'listen', 'accept', 'setKeepAlive', 'setNoDelay', 'getInfo', 'getNetworkList'],
sockets: [{ n: 'tcp', props: ['create', 'update', 'setPaused', 'setKeepAlive', 'setNoDelay', 'connect', 'disconnect', 'secure', 'send', 'close', 'getInfo', 'getSockets'] }, { n: 'tcpServer', props: ['create', 'update', 'setPaused', 'listen', 'disconnect', 'close', 'getInfo', 'getSockets'] }, { n: 'udp', props: ['create', 'update', 'setPaused', 'bind', 'send', 'close', 'getInfo', 'getSockets', 'joinGroup', 'leaveGroup', 'setMulticastTimeToLive', 'setMulticastLoopbackMode', 'getJoinedGroups', 'setBroadcast'] }],
system: [{ n: 'cpu', props: ['getInfo'] }, { n: 'memory', props: ['getInfo'] }, { n: 'storage', props: ['getInfo', 'ejectDevice', 'getAvailableCapacity'] }],
tabCapture: ['capture', 'getCapturedTabs'],
tabs: ['get', 'getCurrent', 'sendMessage', 'create', 'duplicate', 'query', 'highlight', 'update', 'move', 'reload', 'remove', 'detectLanguage', 'captureVisibleTab', 'executeScript', 'insertCSS', 'setZoom', 'getZoom', 'setZoomSettings', 'getZoomSettings', 'discard'],
topSites: ['get'],
tts: ['isSpeaking', 'getVoices', 'speak'],
types: ['set', 'get', 'clear'],
vpnProvider: ['createConfig', 'destroyConfig', 'setParameters', 'sendPacket', 'notifyConnectionStateChanged'],
wallpaper: ['setWallpaper'],
webNavigation: ['getFrame', 'getAllFrames', 'handlerBehaviorChanged'],
windows: ['get', 'getCurrent', 'getLastFocused', 'getAll', 'create', 'update', 'remove']
});
})();
|