Use a token to ensure that messages are coming from Yomichan

This commit is contained in:
toasted-nutbread 2020-02-17 11:02:21 -05:00
parent aee16c4431
commit 0f46e3a093
4 changed files with 66 additions and 10 deletions

View File

@ -46,6 +46,8 @@ class Backend {
this.popupWindow = null; this.popupWindow = null;
this.apiForwarder = new BackendApiForwarder(); this.apiForwarder = new BackendApiForwarder();
this.messageToken = yomichan.generateId(16);
} }
async prepare() { async prepare() {
@ -614,6 +616,10 @@ class Backend {
}); });
} }
async _onApiGetMessageToken() {
return this.messageToken;
}
// Command handlers // Command handlers
async _onCommandSearch(params) { async _onCommandSearch(params) {
@ -875,7 +881,8 @@ Backend._messageHandlers = new Map([
['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)], ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)],
['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)], ['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)],
['getQueryParserTemplatesHtml', (self, ...args) => self._onApiGetQueryParserTemplatesHtml(...args)], ['getQueryParserTemplatesHtml', (self, ...args) => self._onApiGetQueryParserTemplatesHtml(...args)],
['getZoom', (self, ...args) => self._onApiGetZoom(...args)] ['getZoom', (self, ...args) => self._onApiGetZoom(...args)],
['getMessageToken', (self, ...args) => self._onApiGetMessageToken(...args)]
]); ]);
Backend._commandHandlers = new Map([ Backend._commandHandlers = new Map([

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/*global popupNestedInitialize, apiForward, Display*/ /*global popupNestedInitialize, apiForward, apiGetMessageToken, Display*/
class DisplayFloat extends Display { class DisplayFloat extends Display {
constructor() { constructor() {
@ -30,6 +30,8 @@ class DisplayFloat extends Display {
this._orphaned = false; this._orphaned = false;
this._prepareInvoked = false; this._prepareInvoked = false;
this._messageToken = null;
this._messageTokenPromise = null;
yomichan.on('orphaned', () => this.onOrphaned()); yomichan.on('orphaned', () => this.onOrphaned());
window.addEventListener('message', (e) => this.onMessage(e), false); window.addEventListener('message', (e) => this.onMessage(e), false);
@ -75,11 +77,23 @@ class DisplayFloat extends Display {
} }
onMessage(e) { onMessage(e) {
const {action, params} = e.data; const data = e.data;
const handler = DisplayFloat._messageHandlers.get(action); if (typeof data !== 'object' || data === null) { return; } // Invalid data
if (typeof handler !== 'function') { return; }
handler(this, params); const token = data.token;
if (typeof token !== 'string') { return; } // Invalid data
if (this._messageToken === null) {
// Async
this.getMessageToken()
.then(
() => { this.handleAction(token, data); },
() => {}
);
} else {
// Sync
this.handleAction(token, data);
}
} }
onKeyDown(e) { onKeyDown(e) {
@ -94,6 +108,30 @@ class DisplayFloat extends Display {
return super.onKeyDown(e); return super.onKeyDown(e);
} }
async getMessageToken() {
// this._messageTokenPromise is used to ensure that only one call to apiGetMessageToken is made.
if (this._messageTokenPromise === null) {
this._messageTokenPromise = apiGetMessageToken();
}
const messageToken = await this._messageTokenPromise;
if (this._messageToken === null) {
this._messageToken = messageToken;
}
this._messageTokenPromise = null;
}
handleAction(token, {action, params}) {
if (token !== this._messageToken) {
// Invalid token
return;
}
const handler = DisplayFloat._messageHandlers.get(action);
if (typeof handler !== 'function') { return; }
handler(this, params);
}
getOptionsContext() { getOptionsContext() {
return this.optionsContext; return this.optionsContext;
} }

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/*global apiInjectStylesheet*/ /*global apiInjectStylesheet, apiGetMessageToken*/
class Popup { class Popup {
constructor(id, depth, frameIdPromise) { constructor(id, depth, frameIdPromise) {
@ -34,6 +34,7 @@ class Popup {
this._contentScale = 1.0; this._contentScale = 1.0;
this._containerSizeContentScale = null; this._containerSizeContentScale = null;
this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, '');
this._messageToken = null;
this._container = document.createElement('iframe'); this._container = document.createElement('iframe');
this._container.className = 'yomichan-float'; this._container.className = 'yomichan-float';
@ -198,6 +199,10 @@ class Popup {
// NOP // NOP
} }
if (this._messageToken === null) {
this._messageToken = await apiGetMessageToken();
}
return new Promise((resolve) => { return new Promise((resolve) => {
const parentFrameId = (typeof this._frameId === 'number' ? this._frameId : null); const parentFrameId = (typeof this._frameId === 'number' ? this._frameId : null);
this._container.setAttribute('src', chrome.runtime.getURL('/fg/float.html')); this._container.setAttribute('src', chrome.runtime.getURL('/fg/float.html'));
@ -349,9 +354,11 @@ class Popup {
} }
_invokeApi(action, params={}) { _invokeApi(action, params={}) {
if (this._container.contentWindow) { const token = this._messageToken;
this._container.contentWindow.postMessage({action, params}, this._targetOrigin); const contentWindow = this._container.contentWindow;
} if (token === null || contentWindow === null) { return; }
contentWindow.postMessage({action, params, token}, this._targetOrigin);
} }
static _getFullscreenElement() { static _getFullscreenElement() {

View File

@ -113,6 +113,10 @@ function apiGetZoom() {
return _apiInvoke('getZoom'); return _apiInvoke('getZoom');
} }
function apiGetMessageToken() {
return _apiInvoke('getMessageToken');
}
function _apiInvoke(action, params={}) { function _apiInvoke(action, params={}) {
const data = {action, params}; const data = {action, params};
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {