-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathhelpers.js
91 lines (82 loc) · 3.55 KB
/
helpers.js
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
const nanoid = require('nanoid');
const common = require('./lib/common');
const { responseType, commandType } = common.constants;
const log = common.logger({ prefix: 'Cypress ext helpers' });
const targetWindow = window.top;
const merge = (...objs) => Object.assign({}, ...objs);
// event listener can't reply to window.postMessage, but it can make a window.postMessage
// in return that we can listen to, with a response unique id to identify what it's replying to
function listenForResponse(message, timeout) {
return new Promise((resolve, reject) => {
const windowListener = function responseListener(event) {
const { data } = event;
if (data && data.cypressExtType === responseType && data.responseId === message.responseId) {
if (message.debug) log(`Got ${message.property}.${message.method}() response`, data, 'in response to:', message);
targetWindow.removeEventListener('message', windowListener);
if (data.error) {
reject(data.error);
} else {
resolve(data.response);
}
}
};
targetWindow.addEventListener('message', windowListener);
setTimeout(() => {
targetWindow.removeEventListener('message', windowListener);
reject(new Error(`Timeout after ${timeout}ms waiting for response to command ${message.property}.${message.method}`));
}, timeout);
});
}
// alias is used to identify the extension if there are several
// returnType can be sync, promise or callback (default), after the browser API's method
// property identifies the browser object (chrome) property on which to call a method,
// method identifies the method to be called on the property
// args is the arguments to pass to the method (must be JSONifiable, no function)
// e.g. {property: "storage.local", method: "get", args: [k1, k2]}
// => chrome.storage.local.get(k1, k2)
function sendBrowserCommand({ alias, timeout, debug, returnType }, property, method, args) {
const responseId = nanoid(); // Unique ID to identify response
const message = {
cypressExtType: commandType,
responseId,
alias,
debug,
returnType,
property,
method,
args,
};
const promise = listenForResponse(message, timeout);
if (debug) log(`Sending ${message.property}.${message.method}() command`, message);
targetWindow.postMessage(message, '*');
return promise;
}
function assertPresent(type) { if (typeof type !== 'string') throw new Error('Need to specify extension storage type (local, sync or managed)'); }
function assertArray(args) { if (typeof args !== 'undefined' && !Array.isArray(args)) throw new Error('execCommand arg should be passed as an array of args, even on single value'); }
const defaultContext = {
alias: 'myExtension',
debug: false,
timeout: 2000,
returnType: 'callback', // sync, promise or callback
};
module.exports = function createHelpers(userContext = {}) {
const ctx = merge(defaultContext, userContext);
return {
clearStorage(type, opts = {}) { // type is basically sync or local
assertPresent(type);
return sendBrowserCommand(merge(ctx, opts), `storage.${type}`, 'clear');
},
setStorage(type, obj, opts = {}) {
assertPresent(type);
return sendBrowserCommand(merge(ctx, opts), `storage.${type}`, 'set', [obj]);
},
getStorage(type, keys, opts = {}) {
assertPresent(type);
return sendBrowserCommand(merge(ctx, opts), `storage.${type}`, 'get', [keys]);
},
execCommand(property, method, args, opts = {}) {
assertArray(args);
return sendBrowserCommand(merge(ctx, opts), property, method, args);
},
};
};