forked from WebMemex/webmemex-extension
-
Notifications
You must be signed in to change notification settings - Fork 0
/
webextensionRPC.js
107 lines (92 loc) · 3.19 KB
/
webextensionRPC.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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import mapValues from 'lodash/fp/mapValues'
// Our secret tokens to recognise our messages
const RPC_CALL = '__RPC_CALL__'
const RPC_ERR_UNKNOWN_FUNCTION = '__RPC_ERROR_UNKNOWN_FUNCTION__'
// === Initiating side ===
const sendMessageError = ({funcName, otherSide}) =>
`Got no response from RPC when calling '${funcName}'. ` +
`Did you enable RPC in ${otherSide}?`
export function remoteFunction(funcName, {tabId}={}) {
const sendMessage = (tabId !== undefined)
? message => {
return browser.tabs.sendMessage(tabId, message).catch(
err => {
throw sendMessageError({
funcName,
otherSide: "the tab's content script",
})
}
)
}
: message => {
return browser.runtime.sendMessage(message).catch(
err => {
throw sendMessageError({
funcName,
otherSide: "the background script",
})
}
)
}
const f = function (...args) {
const message = {
[RPC_CALL]: RPC_CALL,
funcName,
args,
}
return sendMessage(message).then(response => {
if (response.error)
throw response.error
else
return response.returnValue
})
}
// Give it a name, could be helpful in debugging
Object.defineProperty(f, 'name', { value: `${funcName}_RPC` });
return f
}
// === Executing side ===
const noSuchFunctionError = "Received RPC for unknown function: "
const remotelyCallableFunctions = {}
function incomingRPCListener(message, sender) {
if (message && message[RPC_CALL] === RPC_CALL) {
const funcName = message.funcName
const args = message.hasOwnProperty(args) ? message.args : []
const func = remotelyCallableFunctions[funcName]
if (func === undefined) {
console.error(noSuchFunctionError, funcName)
return {
error: `No such function registered for RPC: ${funcName}`
}
}
const extraArg = {
tab: sender.tab,
}
const value = func.call(null, extraArg, ...args)
return Promise.resolve(value).then(
value => ({
returnValue: value
})
)
}
}
let enabled = false
export function makeRemotelyCallable(functions, {insertExtraArg=false}={}) {
// Every function is passed an extra argument with sender information,
// so remove this from the call if this was not desired.
if (!insertExtraArg) {
// Replace each func with...
const wrapFunctions = mapValues(func =>
// ...a function that calls func, but hides the inserted argument.
(extraArg, ...args) => func(...args)
)
functions = wrapFunctions(functions)
}
// Add the functions to our global repetoir.
Object.assign(remotelyCallableFunctions, functions)
// Enable the listener if needed.
if (!enabled) {
browser.runtime.onMessage.addListener(incomingRPCListener)
enabled = true
}
}