Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Drew-Daniels committed Aug 6, 2024
1 parent cc436ab commit 5cee3d9
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 223 deletions.
25 changes: 11 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ _Liaison_ is a simple library with 0 dependencies that enables easy, secure comm
### Use Case
The `postMessage` API allows for easy cross-origin resource sharing between windows and embedded applications, however you still need to:
- Create event listeners manually that listen for `MessageEvents` from specific origins
- Manage removal of event listeners where necessary
- Such as in `React` applications, where you need to ensure that they are removed when a component is unmounted
- Manually create event listeners that listen for `MessageEvents` from specific origins
- Manage removal of event listeners where necessary - such as in `React` applications, where you need to ensure that they are removed when a component is unmounted
- Define an API contract between window and iframe code that can be used to reliably transmit data across applications
- This is especially difficult if you only have control over one application's code

Expand All @@ -21,11 +20,11 @@ Often times the user using the parent window application will be associated in s
#### Events
When you want some event to occur in one application as a result of something happening in the other. An example could be that when an application is loaded in an iframe, you want it to request the parent application for the authorization token belonging to the user using the parent window application. You could use `liaison` to dispatch a `MessageEvent` to the parent window that will result in the authorization token being sent back to the iframe application.

### Parent Model
The `Parent` model is used to define functions (`Effects`) that can be run in the parent application whenever the iframe application requests they be run.
### `ParentContact` Model
The `ParentContract` model is used to define functions (`Effects`) that can be run in the parent application whenever the iframe application requests they be run.

### IFrame Model
The `IFrame` model is used to define functions (`Effects`) that can be run the iframe application whenever the parent application requests they be run.
### `IFrameContract` Model
The `IFrameContract` model is used to define functions (`Effects`) that can be run the iframe application whenever the parent application requests they be run.

#### Effects
An `Effect` is a function defined on one model, that the the other model can request be called at anytime. These functions can be synchronous or asynchronous.
Expand All @@ -35,8 +34,8 @@ An `Effect` is a function defined on one model, that the the other model can req
// In the iframe application code, we use the "IFrame" factory function to:
// - initialize event listeners for any MessageEvents that come from the parent window with an origin of "https://my-application.com"
// - get a reference to a callback function, "cb" so we can dispatch MessageEvents to the parent window with an origin of "https://my-application.com"
const { cb: callParentEffect } = IFrame({
parentOrigin: 'https://my-application.com',
const { cb: callParentEffect } = IFrameContract({
targetOrigin: 'https://my-application.com',
effects: {
onParentLogout: () => {
setUser(null);
Expand All @@ -56,11 +55,9 @@ function logout() {
// - initialize event listeners for any MessageEvents that come from the iframe window with an id of "my-embedded-iframe" and origin of "https://my-iframe-application.com"
// - get a reference to a callback function, "cb" so we can dispatch MessageEvents to this iframe application

const { cb: callIFrameEffect } = Parent({
iframe: {
id: 'my-embedded-iframe',
src: 'https://my-iframe-application.com'
},
const { cb: callIFrameEffect } = ParentContract{
iframeId: 'my-embedded-iframe',
iframeSrc: 'https://my-iframe-application.com',
effects: {
onIFrameLogout: () => {
setUser(null)
Expand Down
6 changes: 3 additions & 3 deletions apps/iframe-react-app/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useState } from 'react';

import { useIFrame } from '@liaison/react';
import { useIFrameContract } from '@liaison/react';
import { Button } from '@liaison/ui';

function App() {
const [email, setEmail] = useState('');

const { cb: callParentEffect } = useIFrame({
parentOrigin: import.meta.env.VITE_PARENT_WINDOW_URL,
const { cb: callParentEffect } = useIFrameContract({
targetOrigin: import.meta.env.VITE_PARENT_WINDOW_URL,
// define the effects that the parent window can call on the iframe
effects: {
// onParentWindowLogin will be called by the parent window when we submit the login form in the parent window
Expand Down
10 changes: 4 additions & 6 deletions apps/parent-react-app/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useParent } from '@liaison/react';
import { useParentContract } from '@liaison/react';

import { Button } from '@liaison/ui';
import { LoginForm } from '../ui';
Expand All @@ -9,11 +9,9 @@ const IFRAME_ID = 'my-embedded-iframe';
export function App() {
const [email, setEmail] = useState('');

const { cb: callIFrameEffect } = useParent({
iframe: {
id: IFRAME_ID,
src: import.meta.env.VITE_IFRAME_URL,
},
const { cb: callIFrameEffect } = useParentContract({
iframeId: IFRAME_ID,
iframeSrc: import.meta.env.VITE_IFRAME_URL,
// define effects that the iframe can call on the parent
// this effect will be called by the iframe when we click the logout button within the iframe
effects: {
Expand Down
56 changes: 22 additions & 34 deletions libs/core/src/lib/core.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { MockInstance, vi } from 'vitest';

import { Parent, IFrame } from './core';
import { ParentContract, IFrameContract } from './core';

describe('Parent', () => {
describe('ParentContract', () => {
describe('when an iframe with an id of "id" is not found on the page', () => {
beforeEach(() => {
vi.spyOn(document, 'getElementById').mockReturnValue(null);
Expand All @@ -13,14 +13,12 @@ describe('Parent', () => {
});

it('should throw', () => {
expect(() =>
Parent({
iframe: { id: 'id', src: 'https://example.com' },
effects: {
expect(
() =>
new ParentContract('id', 'https://example.com', {
// eslint-disable-next-line @typescript-eslint/no-empty-function
someEffect: () => {},
},
})
})
).toThrow();
});
});
Expand All @@ -46,31 +44,26 @@ describe('Parent', () => {
});

it('should throw if iframe src is not a valid url', () => {
expect(() =>
Parent({
iframe: { id: 'id', src: 'file://some/file/path' },
effects: {
expect(
() =>
new ParentContract('id', 'file://some/file/path', {
// eslint-disable-next-line @typescript-eslint/no-empty-function
someEffect: () => {},
},
})
})
).toThrow();
});

it('should add an event listener to the window', () => {
Parent({
iframe: { id: 'id', src: 'https://example.com' },
effects: {
// eslint-disable-next-line @typescript-eslint/no-empty-function
someEffect: () => {},
},
new ParentContract('id', 'https://example.com', {
// eslint-disable-next-line @typescript-eslint/no-empty-function
someEffect: () => {},
});
expect(addEventListenerSpy).toHaveBeenCalled();
});
});
});

describe('IFrame', () => {
describe('IFrameContract', () => {
let addEventListenerSpy: MockInstance;

beforeEach(() => {
Expand All @@ -90,25 +83,20 @@ describe('IFrame', () => {
vi.resetAllMocks();
});

it('should throw when parentOrigin is not a valid url', () => {
expect(() =>
IFrame({
parentOrigin: 'file://some/file/path',
effects: {
it('should throw when targetOrigin is not a valid url', () => {
expect(
() =>
new IFrameContract('file://some/file/path', {
// eslint-disable-next-line @typescript-eslint/no-empty-function
someEffect: () => {},
},
})
})
).toThrow();
});

it('should add an event listener to the window', () => {
IFrame({
parentOrigin: 'https://example.com',
effects: {
// eslint-disable-next-line @typescript-eslint/no-empty-function
someEffect: () => {},
},
new IFrameContract('https://example.com', {
// eslint-disable-next-line @typescript-eslint/no-empty-function
someEffect: () => {},
});
expect(addEventListenerSpy).toHaveBeenCalled();
});
Expand Down
Loading

0 comments on commit 5cee3d9

Please sign in to comment.