-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathipc.js
187 lines (165 loc) · 4.87 KB
/
ipc.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
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
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
*
* In Chrome Apps, some platform APIs can only be called from the background
* page (e.g. reloading a chrome.app.AppWindow). Likewise, some chrome API's
* must be initiated by user interaction, which can only be called from the
* foreground.
*
* This class provides helper functions to invoke methods on different pages
* using chrome.runtime.sendMessage. Messages are passed in the following
* format:
* {methodName:{string}, params:{Array}}
*
* chrome.runtime.sendMessage allows multiple handlers to be registered on a
* document, but only one handler can send a response.
* This class uniquely identifies a method with the |methodName| and enforces
* that only one handler can be registered per |methodName| in the document.
*
* For example, to call method foo() in the background page from the foreground
* chrome.app.AppWindow, you can do the following.
* In the background page:
* base.Ipc.getInstance().register('my.service.name', foo);
*
* In the AppWindow document:
* base.Ipc.invoke('my.service.name', arg1, arg2, ...).then(
* function(result) {
* console.log('The result is ' + result);
* });
*
* This will invoke foo() with the arg1, arg2, ....
* The return value of foo() will be passed back to the caller in the
* form of a promise.
*/
/** @suppress {duplicate} */
var base = base || {};
(function() {
'use strict';
/**
* @constructor
* @private
*/
base.Ipc = function() {
base.debug.assert(instance_ === null);
/**
* @type {!Object<Function>}
* @private
*/
this.handlers_ = {};
this.onMessageHandler_ = this.onMessage_.bind(this);
chrome.runtime.onMessage.addListener(this.onMessageHandler_);
};
/** @private */
base.Ipc.prototype.dispose_ = function() {
chrome.runtime.onMessage.removeListener(this.onMessageHandler_);
};
/**
* The error strings are only used for debugging purposes and are not localized.
*
* @enum {string}
*/
base.Ipc.Error = {
UNSUPPORTED_REQUEST_TYPE: 'Unsupported method name.',
INVALID_REQUEST_ORIGIN:
'base.Ipc only accept incoming requests from the same extension.'
};
/**
* @constructor
* @param {string} methodName
* @param {?Array} params
* @struct
* @private
*/
base.Ipc.Request_ = function(methodName, params) {
this.methodName = methodName;
this.params = params;
};
/**
* @param {string} methodName
* @param {function(...?)} handler The handler can be invoked by calling
* base.Ipc.invoke(|methodName|, arg1, arg2, ...)
* Async handlers that return promises are currently not supported.
* @return {boolean} Whether the handler is successfully registered.
*/
base.Ipc.prototype.register = function(methodName, handler) {
if (methodName in this.handlers_) {
console.error('service ' + methodName + ' is already registered.');
return false;
}
this.handlers_[methodName] = handler;
return true;
};
/**
* @param {string} methodName
*/
base.Ipc.prototype.unregister = function(methodName) {
delete this.handlers_[methodName];
};
/**
* @param {base.Ipc.Request_} message
* @param {chrome.runtime.MessageSender} sender
* @param {function(*): void} sendResponse
*/
base.Ipc.prototype.onMessage_ = function(message, sender, sendResponse) {
var methodName = message.methodName;
if (typeof methodName !== 'string') {
return;
}
if (sender.id !== chrome.runtime.id) {
sendResponse({error: base.Ipc.Error.INVALID_REQUEST_ORIGIN});
return;
}
var remoteMethod =
/** @type {function(*):void} */ (this.handlers_[methodName]);
if (!remoteMethod) {
sendResponse({error: base.Ipc.Error.UNSUPPORTED_REQUEST_TYPE});
return;
}
try {
sendResponse(remoteMethod.apply(null, message.params));
} catch (/** @type {Error} */ e) {
sendResponse({error: e.message});
}
};
/**
* Invokes a method on a remote page
*
* @param {string} methodName
* @param {...} var_args
* @return A Promise that would resolve to the return value of the handler or
* reject if the handler throws an exception.
*/
base.Ipc.invoke = function(methodName, var_args) {
var params = Array.prototype.slice.call(arguments, 1);
var sendMessage = base.Promise.as(
chrome.runtime.sendMessage,
[null, new base.Ipc.Request_(methodName, params)]);
return sendMessage.then(
/** @param {?{error: Error}} response */
function(response) {
if (response && response.error) {
return Promise.reject(response.error);
} else {
return Promise.resolve(response);
}
});
};
/** @type {base.Ipc} */
var instance_ = null;
/** @return {base.Ipc} */
base.Ipc.getInstance = function() {
if (!instance_) {
instance_ = new base.Ipc();
}
return instance_;
};
base.Ipc.deleteInstance = function() {
if (instance_) {
instance_.dispose_();
instance_ = null;
}
};
})();