From 343b09dcc01c35981f8a823aca7b8fc645f006aa Mon Sep 17 00:00:00 2001 From: Kasey Powers Date: Mon, 27 Aug 2018 15:54:10 -0400 Subject: [PATCH] feat(message-core): subscriber functionality --- packages/message-core/README.md | 22 +++--- packages/message-core/src/index.js | 26 ++++++- .../message-core/src/tests/message.test.js | 72 +++++++++++++------ 3 files changed, 83 insertions(+), 37 deletions(-) diff --git a/packages/message-core/README.md b/packages/message-core/README.md index 192bda431..e2eb79efc 100644 --- a/packages/message-core/README.md +++ b/packages/message-core/README.md @@ -3,21 +3,16 @@ A package wrapping the postMessage function with helper functions and security checks. ## Install -`npm install @availity/message-core` -## Configure +`npm install @availity/message-core` -`AvMessage` requires an `onMessage` function to be defined. It is called with an event and data parameters. +## Methods -```javascript -import AvMessage from '@availity/message-core'; +### subscribe -AvMessage.onMessage = (event, data) => { - // handle postMessage event -} -``` +`const unsubscribe = avMessage.subscribe(event, fn)` when a message event is received and verified, fn will be called with the event data. -## Methods +it returns a function that can be used to unsubscribe from that event ### enabled @@ -31,12 +26,15 @@ returns a string of the windows current domain. ## send -`AvMessage.send(payload, target)` will send the payload to the target if AvMessage is enabled. +`avMessage.send(payload, target)` will send the payload to the target if AvMessage is enabled. target defaults to the parent window. payload will be stringified if not a string. ## Authors + **Kasey Powers** -* [kaseyepowers@gmail.com](kaseyepowers@gmail.com) + +- [kaseyepowers@gmail.com](kaseyepowers@gmail.com) ## License + [MIT](../../LICENSE) diff --git a/packages/message-core/src/index.js b/packages/message-core/src/index.js index 96a1ed7e5..8afd22834 100644 --- a/packages/message-core/src/index.js +++ b/packages/message-core/src/index.js @@ -1,4 +1,6 @@ class AvMessage { + subscribers = {}; + constructor() { this.isEnabled = true; this.DEFAULT_EVENT = 'avMessage'; @@ -15,9 +17,7 @@ class AvMessage { getEventData(event) { if ( - !this.isEnabled || - !this.onMessage || - typeof this.onMessage !== 'function' || // do nothing if not enabled or no onMessage function given + !this.isEnabled || // do nothing if not enabled !event || !event.data || !event.origin || @@ -49,6 +49,26 @@ class AvMessage { this.onMessage(event, data); } + subscribe(event, fn) { + if (!this.subscribers[event]) { + this.subscribers[event] = []; + } + this.subscribers[event].push(fn); + return () => { + this.subscribers[event] = this.subscribers[event].filter( + val => val !== fn + ); + }; + } + + onMessage(event, data) { + if (this.subscribers[event]) { + this.subscribers[event].forEach(fn => { + fn(data); + }); + } + } + // if current domain doesn't match regex DOMAIN, return true. isDomain(url) { return !this.DOMAIN.test(this.domain()) || this.DOMAIN.test(url); diff --git a/packages/message-core/src/tests/message.test.js b/packages/message-core/src/tests/message.test.js index 16fdedbfa..fecbe6ce2 100644 --- a/packages/message-core/src/tests/message.test.js +++ b/packages/message-core/src/tests/message.test.js @@ -21,6 +21,56 @@ describe('AvMessage', () => { expect(avMessage.enabled('hello')).toBe(true); }); + describe('subscribers', () => { + test('onMessage should call all subscribers for event', () => { + const testEvent = 'testEvent'; + const fns = [jest.fn(), jest.fn()]; + avMessage.subscribers = { + [testEvent]: fns, + }; + avMessage.onMessage(`${testEvent}Other`); + fns.forEach(fn => expect(fn).not.toHaveBeenCalled()); + + const data = { testData: 'hello world' }; + avMessage.onMessage(testEvent, data); + fns.forEach(fn => expect(fn).toHaveBeenCalledWith(data)); + }); + + test('subscribe should add function to subscribers', () => { + avMessage.subscribers = {}; + const testEvent = 'testEvent'; + const fn = 'totally a function'; + avMessage.subscribe(testEvent, fn); + expect(avMessage.subscribers).toEqual({ + [testEvent]: [fn], + }); + + const fn2 = 'totally another function'; + avMessage.subscribe(testEvent, fn2); + expect(avMessage.subscribers).toEqual({ + [testEvent]: [fn, fn2], + }); + }); + + test('subscribe should return function to remove subscribers', () => { + avMessage.subscribers = {}; + const testEvent = 'testEvent'; + const fn = 'totally a function'; + const unsubscribe = avMessage.subscribe(testEvent, fn); + + const fn2 = 'totally another function'; + avMessage.subscribe(testEvent, fn2); + expect(avMessage.subscribers).toEqual({ + [testEvent]: [fn, fn2], + }); + + unsubscribe(); + expect(avMessage.subscribers).toEqual({ + [testEvent]: [fn2], + }); + }); + }); + describe('getEventData()', () => { let spyParse; const mockEvent = { @@ -49,20 +99,6 @@ describe('AvMessage', () => { expect(avMessage.onMessage).not.toHaveBeenCalled(); }); - test('should return early when AvMessages.onMessage not defined', () => { - delete avMessage.onMessage; - avMessage.getEventData(mockEvent); - expect(avMessage.isDomain).not.toHaveBeenCalled(); - expect(spyParse).not.toHaveBeenCalled(); - }); - - test('should return early when AvMessages.onMessage not function', () => { - avMessage.onMessage = 'onMessage'; - avMessage.getEventData(mockEvent); - expect(avMessage.isDomain).not.toHaveBeenCalled(); - expect(spyParse).not.toHaveBeenCalled(); - }); - test('should return early when event does not have all fields', () => { const mockEvent1 = Object.assign({}, mockEvent, { data: false }); const mockEvent2 = Object.assign({}, mockEvent, { origin: false }); @@ -153,14 +189,6 @@ describe('AvMessage', () => { test('should return location.origin if exists', () => { expect(avMessage.domain()).toBe(URL); }); - - // test('if no location.origin, should return domain generated with hostname', () => { - // expect(avMessage.domain()).toBe(URL); - // }); - - // test("if no location origin or hostname, should return '*'", () => { - // expect(avMessage.domain()).toBe(URL); - // }); }); test("isDomain should return true if domain() doesn't match regex", () => {