forked from quoid/userscripts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.js
More file actions
197 lines (185 loc) · 5.73 KB
/
Copy pathapi.js
File metadata and controls
197 lines (185 loc) · 5.73 KB
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
async function setValue(key, value) {
if (typeof key !== "string" || !key.length) {
return Promise.reject(new Error("setValue invalid key arg"));
}
const sid = this.US_filename;
if (typeof sid !== "string" || !sid.length) {
return Promise.reject(new Error("setValue invalid call"));
}
const item = {};
item[`${sid}---${key}`] = value;
return browser.storage.local.set(item);
}
async function getValue(key, defaultValue) {
if (typeof key !== "string" || !key.length) {
return Promise.reject(new Error("getValue invalid key arg"));
}
const sid = this.US_filename;
if (typeof sid !== "string" || !sid.length) {
return Promise.reject(new Error("getValue invalid call"));
}
const prefixedKey = `${sid}---${key}`;
const results = await browser.storage.local.get(prefixedKey);
if (prefixedKey in results) return results[prefixedKey];
if (defaultValue !== undefined) return defaultValue;
return undefined;
}
async function deleteValue(key) {
if (typeof key !== "string" || !key.length) {
return Promise.reject(new Error("deleteValue missing key arg"));
}
const sid = this.US_filename;
if (typeof sid !== "string" || !sid.length) {
return Promise.reject(new Error("deleteValue invalid call"));
}
const prefixedKey = `${sid}---${key}`;
return browser.storage.local.remove(prefixedKey);
}
async function listValues() {
const sid = this.US_filename;
if (typeof sid !== "string" || !sid.length) {
return Promise.reject(new Error("listValues invalid call"));
}
const prefix = `${sid}---`;
const results = await browser.storage.local.get();
const keys = [];
for (const key in results) {
key.startsWith(prefix) && keys.push(key.slice(prefix.length));
}
return keys;
}
async function openInTab(url, openInBackground = false) {
try {
new URL(url);
} catch (error) {
return Promise.reject(error);
}
return browser.runtime.sendMessage({
name: "API_OPEN_TAB",
url,
active: !openInBackground,
});
}
async function getTab() {
return browser.runtime.sendMessage({ name: "API_GET_TAB" });
}
async function saveTab(tab) {
if (tab == null) {
return Promise.reject(new Error("saveTab invalid arg"));
}
return browser.runtime.sendMessage({ name: "API_SAVE_TAB", tab });
}
async function closeTab(tabId) {
return browser.runtime.sendMessage({ name: "API_CLOSE_TAB", tabId });
}
async function addStyle(css) {
if (typeof css !== "string" || !css.length) {
return Promise.reject(new Error("addStyle invalid css arg"));
}
return browser.runtime.sendMessage({ name: "API_ADD_STYLE", css });
}
async function setClipboard(clipboardData, type) {
return browser.runtime.sendMessage({
name: "API_SET_CLIPBOARD",
clipboardData,
type,
});
}
function xhr(details) {
if (details == null) return console.error("xhr invalid details arg");
if (!details.url) return console.error("xhr details missing url key");
// generate random port name for single xhr
const xhrPortName = Math.random().toString(36).substring(1, 9);
// strip out functions from details
const detailsParsed = JSON.parse(JSON.stringify(details));
// get all the "on" events from XMLHttpRequest object
const events = [];
for (const k in XMLHttpRequest.prototype) {
if (k.slice(0, 2) === "on") events.push(k);
}
// check which functions are included in the original details object
// add a bool to indicate if event listeners should be attached
for (const e of events) {
if (typeof details[e] === "function") detailsParsed[e] = true;
}
// define return method, will be populated after port is established
const response = {
abort: () => console.error("xhr has not yet been initialized"),
};
// port listener, most of the messaging logic goes here
const listener = (port) => {
if (port.name !== xhrPortName) return;
port.onMessage.addListener(async (msg) => {
if (
events.includes(msg.name) &&
typeof details[msg.name] === "function"
) {
// process xhr response
const r = msg.response;
// only process when xhr is complete and data exist
if (r.readyState === 4 && r.response !== null) {
if (r.responseType === "arraybuffer") {
// arraybuffer responses had their data converted in background
// convert it back to arraybuffer
try {
const buffer = new Uint8Array(r.response).buffer;
r.response = buffer;
} catch (err) {
console.error("error parsing xhr arraybuffer", err);
}
} else if (r.responseType === "blob" && r.response.data) {
// blob responses had their data converted in background
// convert it back to blob
const resp = await fetch(r.response.data);
const b = await resp.blob();
r.response = b;
}
}
// call userscript method
details[msg.name](msg.response);
}
// all messages received
// tell background it's safe to close port
if (msg.name === "onloadend") {
port.postMessage({ name: "DISCONNECT" });
}
});
// handle port disconnect and clean tasks
port.onDisconnect.addListener((p) => {
if (p?.error) {
console.error(`port disconnected due to an error: ${p.error.message}`);
}
browser.runtime.onConnect.removeListener(listener);
});
// fill the method returned to the user script
response.abort = () => port.postMessage({ name: "ABORT" });
};
// wait for the background to establish a port connection
browser.runtime.onConnect.addListener(listener);
// pass the basic information to the background through a common message
const message = {
name: "API_XHR",
details: detailsParsed,
xhrPortName,
events,
};
browser.runtime.sendMessage(message);
return response;
}
export default {
setValue,
getValue,
listValues,
deleteValue,
openInTab,
getTab,
saveTab,
closeTab,
addStyle,
setClipboard,
// notification,
// registerMenuCommand,
// getResourceUrl,
xmlHttpRequest: xhr,
GM_xmlhttpRequest: xhr,
};