-
-
Notifications
You must be signed in to change notification settings - Fork 194
Expand file tree
/
Copy pathEventManager.js
More file actions
206 lines (197 loc) · 9.13 KB
/
EventManager.js
File metadata and controls
206 lines (197 loc) · 9.13 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
198
199
200
201
202
203
204
205
206
/*
* GNU AGPL-3.0 License
*
* Copyright (c) 2021 - present core.ai . All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
* for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
*
*/
/*global less, Phoenix */
// jshint ignore: start
// @INCLUDE_IN_API_DOCS
/**
* The global EventManager can be used to register named EventDispatchers so that events
* can be triggered from anywhere without using require context. This should also be used to handle custom
* `window.onmessage` handlers.
*
* A global `window.EventManager` object is made available in phoenix that can be called anytime after AppStart.
*
* ## Usage
* For Eg. Let's say we have an extension `drawImage` installed that wants to expose custom functionality to phoenix.
* The Extension will first register named EventHandler like this:
* @example
* ```js
* // in drawImage/someExtensionModule.js module within the extension, do the following:
* const EventDispatcher = brackets.getModule("utils/EventDispatcher"),
* EventManager = brackets.getModule("utils/EventManager");
* EventDispatcher.makeEventDispatcher(exports);
*
* EventManager.registerEventHandler("drawImage-Handler", exports);
* ```
* Once the event handler is registered, we can trigger events on the named handler anywhere in phoenix
* (inside or outside the extension) by using:
*
* @example
* ```js
* EventManager.triggerEvent("drawImage-Handler", "someEventName", "param1", "param2", ...);
* ```
* @module utils/EventManager
*/
define(function (require, exports, module) {
const _eventHandlerMap = {};
/**
* Registers a named EventHandler. Event handlers are created using the call:
* `EventDispatcher.makeEventDispatcher(Command.prototype);`
*
* To register a close dialogue event handler in an extension:
* // in close-dialogue.js module winthin the extension, do the following:
* const EventDispatcher = brackets.getModule("utils/EventDispatcher"),
* EventDispatcher.makeEventDispatcher(exports);
* const EventManager = brackets.getModule("utils/EventManager");
*
* // Note: for event handler names, please change the `extensionName` to your extension name
* // to prevent collisions. EventHandlers starting with `ph-` and `br-` are reserved as system handlers
* // and not available for use in extensions.
* EventManager.registerEventHandler("`extensionName`-closeDialogueHandler", exports);
* // Once the event handler is registered, see triggerEvent API on how to raise events
*
* @param {string} handlerName a unique name of the handler.
* @param {object} eventDispatcher An EventDispatcher that will be used to trigger events.
* @return {boolean}
* @type {function}
*/
function registerEventHandler(handlerName, eventDispatcher) {
if(_eventHandlerMap[handlerName]){
console.error("Duplicate EventManager registration for event, overwriting event handler: ", handlerName);
}
_eventHandlerMap[handlerName] = eventDispatcher;
}
/**
* Returns true is an EventHandler of the given name exists.
*
* @param {string} handlerName
* @return {boolean}
* @type {function}
*/
function isExistsEventHandler(handlerName) {
return _eventHandlerMap[handlerName] !== undefined;
}
/**
* Triggers an event on the named event handler.
*
* To trigger an event to the `closeDialogue` event handler registered above
* // anywhere in code, do the following:
* const EventManager = brackets.getModule("utils/EventManager");
* EventManager.triggerEvent("closeDialogueHandler", "someEvent", "param1", "param2", ...);
*
* @param {string} handlerName
* @param eventName the event name as recognised by the handler. this is usually a string.
* @param eventParams Can be a comma seperated list of args or a single argument.
* @type {function}
*/
function triggerEvent(handlerName, eventName, ...eventParams) {
let handler = _eventHandlerMap[handlerName];
if(!handler || !eventName){
console.error(`Could not locate handler for: ${handlerName} eventName: ${eventName} event: ${eventParams}`);
return;
}
handler.trigger(eventName, ...eventParams);
}
const eventTrustedOrigins = {};
/**
* This function acts as a secure event handler for all 'message' events targeted at the window object.
* This is useful if you have to send/receive messaged from an embedded cross-domain iframe inside phoenix.
* https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
* Instead of directly overriding window.onmessage, extensions or other elements that need to
* listen to these events should register their named eventHandler with `EventManager`.
*
* By default, only origins part of `window.Phoenix.TRUSTED_ORIGINS` are whitelisted. If your extension is
* bringing in a cross-origin ifrmame say [`http://mydomain.com`], you should add it to the whitelist by setting
* `window.Phoenix.TRUSTED_ORIGINS ["http://mydomain.com"] = true;`
*
* @private
* @function
* @global
* @listens window#message
*
* @param {MessageEvent} event - The 'message' event targeted at the window object. The event's
* 'data' property should have a 'handlerName' and `eventName` property that will be triggered in phcode.
*
* // We will try to communicate within an embedded iframe and an extension
*
* // In your extension in phoenix, register a handlerName to process a new kind of event.
* const EventDispatcher = brackets.getModule("utils/EventDispatcher"),
* EventDispatcher.makeEventDispatcher(exports);
* const EventManager = brackets.getModule("utils/EventManager");
* // Note: for event handler names, please change the `extensionName` to your extension name
* // to prevent collisions. EventHandlers starting with `ph-` and `br-` are reserved as system handlers
* // and not available for use in extensions.
* window.Phoenix.TRUSTED_ORIGINS ["http://mydomain.com"] = true;
* ```js
* EventManager.registerEventHandler("`extensionName`-iframeMessageHandler", exports);
* exports.on("iframeHelloEvent", function(_ev, event){
* console.log(event.data.message);
* });
* ```
*
* // Now from your iframe, send a message to the above event handler using:
* ```js
* window.parent.postMessage({
* handlerName: "`extensionName`-iframeMessageHandler",
* eventName: "iframeHelloEvent",
* message: "hello world"
* }, '*');
* ```
* // `you should replace * with the trusted domains list in production for security.` See how this can be
* // done securely with this example: https://github.com/phcode-dev/phcode.live/blob/6d64386fbb9d671cdb64622bc48ffe5f71959bff/docs/virtual-server-loader.js#L43
* // Abstract is that, pass in the parentOrigin as a query string parameter in iframe, and validate it against
* // a trusted domains list in your iframe.
*/
window.onmessage = function(event) {
if(!(Phoenix.TRUSTED_ORIGINS[event.origin] || eventTrustedOrigins[event.origin])){
// Sandboxed iframes without allow-same-origin send "null" origin —
// silently ignore these as they communicate via their own message handlers.
if(event.origin === "null") {
return;
}
console.error(`Ignoring event from untrusted origin (should be one of `
+ `${Object.keys(Phoenix.TRUSTED_ORIGINS)}, ${Object.keys(eventTrustedOrigins)}) but got: `, event);
console.error('Forgot to set window.Phoenix.TRUSTED_ORIGINS["http://<yourdomain.com>"]=true; ?');
return;
}
const handlerName = event.data.handlerName, eventName = event.data.eventName;
if(!handlerName) {
return;
}
triggerEvent(handlerName, eventName, event);
};
/**
* add or remove a domain, in the list of trusted origin
*
* @param {string} origin - the origin to be added or removed
* @param {boolean} isTrusted - if `true` adds the origin to the list, else removes it.
*/
function setTrustedOrigin(origin, isTrusted) {
if(!isTrusted){
delete eventTrustedOrigins[origin];
return;
}
eventTrustedOrigins[origin] = isTrusted;
}
// Public API
exports.registerEventHandler = registerEventHandler;
exports.isExistsEventHandler = isExistsEventHandler;
exports.setTrustedOrigin = setTrustedOrigin;
exports.triggerEvent = triggerEvent;
});