From dd525daa9609050ca9528bba58e33764307de99a Mon Sep 17 00:00:00 2001 From: Elisha Nuchi <31550519+enuchi@users.noreply.github.com> Date: Tue, 1 Jun 2021 01:20:36 -0400 Subject: [PATCH] Fix: add way to customize targetOrigin for calls to parent iframe (#18) * fix: allow setting parentTargetOrigin * v0.3.0 * Update README.md --- README.md | 7 +++---- package.json | 2 +- src/index.js | 10 +++++++--- test/index.test.js | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c9bdc6a..903c9a3 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,8 @@ Now we can use familiar Promises in our client-side code and have easy access to ## API The config object takes: -`allowedDevelopmentDomains`: A config to specifiy which domains are permitted for communication with Google Apps Script Webpack Dev Server development tool. This is a security setting, and if not specified, will block functionality in development. - -`allowedDevelopmentDomains` will accept either a space-separated string of allowed subdomains, e.g. `'https://localhost:3000 https://localhost:8080'` (notice no trailing slashes); or a function that takes in the requesting origin and should return `true` to allow communication, e.g. `(origin) => /localhost:\d+$/.test(origin);` +- `allowedDevelopmentDomains`: A config to specifiy which domains are permitted for communication with Google Apps Script Webpack Dev Server development tool. This is a security setting, and if not specified, will block functionality in development. `allowedDevelopmentDomains` will accept either a space-separated string of allowed subdomains, e.g. `'https://localhost:3000 https://localhost:8080'` (notice no trailing slashes); or a function that takes in the requesting origin and should return `true` to allow communication, e.g. `(origin) => /localhost:\d+$/.test(origin);` +- `parentTargetOrigin` An optional string to specify which parent window domain this client can send communication to. Defaults to own domain for backward compatibility with Google Apps Script Webpack Dev Server development tool (default uses domain where the client is running, e.g. localhost). Can be '*' to allow all parent domains if parent is unknown or variable. ### Production mode @@ -98,7 +97,7 @@ In the normal Google Apps Script production environment, `new Server()` will hav - `serverFunctions`: an object containing all publicly exposed server functions (see example above). -Note that the `allowedDevelopmentDomains` configuration will be ignored in production, so the same code can and should be used for development and production. +Note that `allowedDevelopmentDomains` and `parentTargetOrigin` configurations will be ignored in production, so the same code can and should be used for development and production. ### Development mode diff --git a/package.json b/package.json index e050262..1c829d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gas-client", - "version": "0.2.1", + "version": "0.3.0", "description": "A client-side utility class that can call server-side Google Apps Script functions", "main": "build/index.js", "files": [ diff --git a/src/index.js b/src/index.js index d1298db..8f67661 100644 --- a/src/index.js +++ b/src/index.js @@ -21,7 +21,8 @@ export default class Server { /** * Accepts a single `config` object * @param {object} [config] An optional config object for use in development. - * @param {string|function} [config.allowedDevelopmentDomains] An optional config to specify which domains are permitted for communication with Google Apps Script Webpack Dev Server development tool. This is a security setting, and if not specified, this will block functionality in development. Will accept either a space-separated string of allowed subdomains, e.g. `https://localhost:3000 http://localhost:3000` (notice no trailing slash); or a function that takes in the requesting origin should return `true` to allow communication, e.g. `(origin) => /localhost:\d+$/.test(origin)` + * @param {string|function} [config.allowedDevelopmentDomains] An optional config to specify which domains are permitted for receiving communication from a parent window. This is a security setting, and if not specified, will block functionality in development. Will accept either a space-separated string of allowed subdomains, e.g. `https://localhost:3000 http://localhost:3000` (notice no trailing slash); or a function that takes in the requesting origin should return `true` to allow communication, e.g. `(origin) => /localhost:\d+$/.test(origin)` + * @param {string} [config.parentTargetOrigin] An optional config to specify which parent window domain this client can send communication to. Defaults to own domain for backward compatibility with Google Apps Script Webpack Dev Server development tool (domain where the client is running, e.g. localhost). Can be '*' to allow all parent domains. */ constructor(config = {}) { // skip the reserved names: https://developers.google.com/apps-script/guides/html/reference/run @@ -56,6 +57,10 @@ export default class Server { // we'll store and access the resolve/reject functions here by id window.gasStore = {}; + // this domain should be restricted to googleusercontent.com but the subdomain is variable + // supports window.location.origin as default for backward compatibility + let targetOrigin = config.parentTargetOrigin || window.location.origin; + // set up the message 'receive' handler const receiveMessageHandler = (event) => { const { allowedDevelopmentDomains } = config; @@ -101,8 +106,7 @@ export default class Server { functionName, args: [...args], }, - // only send messages to our dev server, which should be running on the same origin - window.location.origin + targetOrigin ); return promise; }; diff --git a/test/index.test.js b/test/index.test.js index 0222fbd..7b38644 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -165,6 +165,44 @@ describe('local development gas-client server', () => { ); }); + test('should default to post message to target origin of window.location.origin if no parentTargetOrigin is defined', () => { + const mockPostMessage = jest.fn(); + window.parent.postMessage = mockPostMessage; + const defaultLocation = window.location.origin; + + const server = new Server({}); + server.serverFunctions.someFunction('arg1', 'arg2'); + + expect(mockPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ + args: ['arg1', 'arg2'], + functionName: 'someFunction', + id: expect.stringMatching(/^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/), // just simple check this is a uuid + type: 'REQUEST', + }), + defaultLocation + ); + }); + + test("should set postMessage's target origin to parentTargetOrigin if defined", () => { + const mockPostMessage = jest.fn(); + window.parent.postMessage = mockPostMessage; + const parentTargetOrigin = '*'; + + const server = new Server({ parentTargetOrigin }); + server.serverFunctions.someFunction('arg1', 'arg2'); + + expect(mockPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ + args: ['arg1', 'arg2'], + functionName: 'someFunction', + id: expect.stringMatching(/^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/), // just simple check this is a uuid + type: 'REQUEST', + }), + parentTargetOrigin + ); + }); + test('should successfully handle received message and resolve successful server function response', () => { const server = new Server({ allowedDevelopmentDomains: 'https://localhost:3000',