Skip to content

Commit

Permalink
squash
Browse files Browse the repository at this point in the history
  • Loading branch information
Drew-Daniels committed Aug 4, 2024
1 parent e777273 commit b7ac55b
Show file tree
Hide file tree
Showing 134 changed files with 2,848 additions and 1,090 deletions.
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

42 changes: 0 additions & 42 deletions .eslintrc.json

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
# This enables task distribution via Nx Cloud
# Run this command as early as possible, before dependencies are installed
# Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun
- run: yarn dlx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci"
# - run: yarn dlx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci"

# Cache node_modules
- uses: actions/setup-node@v4
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ Thumbs.db

.nx/cache
.nx/workspace-data

.env*
!.env
2 changes: 2 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
node = "22.5.1"
Binary file modified .yarn/install-state.gz
Binary file not shown.
271 changes: 239 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,269 @@
# Liaison
_Liaison_ is a simple library with 0 dependencies that enables easy, secure communication between a browser window and embedded iframes, using the browser [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) API.

<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
> The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
**This workspace has been generated by [Nx, Smart Monorepos · Fast CI.](https://nx.dev)**
### Parent Model
The `Parent` model is used to:
- Define side effects (`effects`) that the `IFrame` model can expect to have the parent window run - _whenever_ it requests the parent window to run them.

## Integrate with editors
For most use cases, these side effects are going to be used for 2 general purposes:
- _Requesting_ the parent window send data to an iframe
- Ex.) Authorization tokens
- _Notifying_ the parent window that some event has occurred within an iframe.
- Ex.) Informing the parent window that an iframe has finished logging a user out of the iframe.

Enhance your Nx experience by installing [Nx Console](https://nx.dev/nx-console) for your favorite editor. Nx Console
provides an interactive UI to view your projects, run tasks, generate code, and more! Available for VSCode, IntelliJ and
comes with a LSP for Vim users.

## Start the application
#### Initialization
The Parent factory function specifies the id of the iframe we expect to receive messages from, and the [origin](https://html.spec.whatwg.org/multipage/comms.html#dom-messageevent-origin-dev) we should validate that those messages originate from.
```js
const parent = Parent({
iframe: {
id: 'my-iframe-id',
src: 'https://embedded.com',
}
...
});
```
Both `iframe.id` and `iframe.src` _are required_.

Run `npx nx serve liaison` to start the development server. Happy coding!
#### Lifecycle Methods
The `Parent` model sets all event handlers when it is initially called (`Parent({ ... })`)

## Build for production
The `destroy()` removes all event listeners on the parent window that are listening for signals from the specified iframe:
```js
// initialize event handlers
const parent = Parent({ ... });

Run `npx nx build liaison` to build the application. The build artifacts are stored in the output directory (e.g. `dist/` or `build/`), ready to be deployed.
// remove event handlers if needed
parent.destroy();
```

## Running tasks
#### Message Handling (`Signals`)
When the parent window receives a [MessageEvent](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent), the Parent model checks if:
- If the MessageEvent has an `origin` exactly matching `src`.
- If the message has _any other origin_ than `src`, it is completely ignored.
- If the `origin` matches `src`, the Parent model checks to ensure that the data passed in the `MessageEvent` matches the expected API (i.e., contains a `Signal`)
- If the `MessageEvent` contains a `Signal`, this `Signal` is then used to call a corresponding `Effect` on the IFrame model.

To execute tasks with Nx use the following syntax:
#### Effects
_`Effects`_ (a.k.a., "side effects") are functions defined on the Parent model, that the Parent model can expect to call on the IFrame model.
```js
const parent = Parent({
...
effects: {
// each `effect` can be synchronous
sendToken: () => {
const token = nanoid();
// ...
},
// ... or asynchronous
sendTokenAsync: async () => {
await timeout(3000);
const token = nanoid();
// ...
}
}
});

// ...
function timeout(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
```
npx nx <target> <project> <...options>
Each `effect` has access to an `args` object, which contains all the arguments passed from the `IFrame` model when it requests a certain side effect occur in the parent window:
```js
// ... in parent window
const parent = Parent({
...
effects: {
logMessageFromIFrame: ({ args }) => {
console.log(`Message received from iframe: ${args.message}`)
},
}
});

// ... in iframe window
const iframe = IFrame({ ... });
iframe.callParentEffect({
name: 'logMessageFromIFrame',
args: { message: 'Greetings' },
});

// logs "Message received from iframe: Greetings"
```
Each `effect` defined in the call to `Parent` has access to the `callIFrameEffect` function, allowing it to _call back to the iframe_:

You can also run multiple targets:
```js
// ... in parent window
const parent = Parent({
...
effects: {
sendToken: ({ callIFrameEffect }) => {
const token = nanoid();
callIFrameEffect({
name: 'saveToken',
args: { token }
});
},
}
});

// ... in iframe window
const iframe = IFrame({
...
effects: {
saveToken: ({ token }) => {
localStorage.setItem('authToken', token);
}
}
});
```
npx nx run-many -t <target1> <target2>

You can also use both `args` and `callIFrameEffect` together:
```js
// ... in parent window
const parent = Parent({
...
effects: {
sendToken: ({ args, callIFrameEffect }) => {
if (args.system === 'client1') {
token = 'xyz';
} else {
token = 'zyx';
}
callIFrameEffect({
name: 'saveToken',
args: { token }
});
},
}
});
```
#### All Together:
```js
const parent = Parent({
iframe: {
id: 'my-iframe-id',
src: 'https://embedded.com',
},
effects: {
sendToken: ({ args, callIFrameEffect }) => {
if (args.system === 'client1') {
token = 'xyz';
} else {
token = 'zyx';
}
callIFrameEffect({
name: 'saveToken',
args: { token }
});
},
}
});
```

..or add `-p` to filter specific projects
### IFrame Model
The `IFrame` model is used to:
- Define side effects (`effects`) that the `Parent` model can expect to have the iframe window run - _whenever_ it requests the iframe window to run them.

Similarly to the `Parent` model, these effects can be used to enable the parent window to:
- _Request_ data from the iframe
- _Notify_ the iframe that some event has occurred in the parent window.

#### Configuration
The iframe model will only initiate side effects in response to messages that have been verified to come from a recognized domain (`parentOrigin`):
```js
const iframe = IFrame({
parentOrigin: 'https://parent.com',
...
});
```
npx nx run-many -t <target1> <target2> -p <proj1> <proj2>

#### Effects
Each `effect` defined on the `IFrame` model can be synchronous or asynchronous:
```js
const iframe = IFrame({
...
effects: {
load: () => {
// fetch some data
},
lazyLoad: async () => {
timeout(3000);
// fetch some data
}
}
});

// ...
function timeout(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
```
Each `effect` defined on the `IFrame` model has access to the `args` object containing all the arguments passed from the parent window to be used with this `effect`:
```js
const iframe = IFrame({
parentOrigin: 'https://parent.com'
effects: {
saveData: ({ args }) => {
// save this data to a db
},
}
});
```

Each `effect` defined on the `IFrame` model has access to the `callParentEffect` function so it can _call back to the parent window_:
```js
const iframe = IFrame({
parentOrigin: 'https://parent.com'
effects: {
notifyParent: ({ callParentEffect }) => {
callParentEffect({ name: 'notify', args: { notification: 'Something happened' } })
},
}
});

// ... in window with url of 'https://parent.com'
const parent = Parent({
...
effects: {
notify: ({ args }) => {
console.log(`Notification: ${args.notification}`)
},
}
});

// logs "Notification: Something happened"
```

Targets can be defined in the `package.json` or `projects.json`. Learn more [in the docs](https://nx.dev/features/run-tasks).
## API Glossary:
### Parent window
The browser window that contains an embedded window

## Set up CI!
### Parent model
The function that can be used to define which iframe the parent window expects to receive signals from, and what effects can run when the iframe requests them to be run.
```js
// use named import
import { Parent } from 'liaison-core';
```

Nx comes with local caching already built-in (check your `nx.json`). On CI you might want to go a step further.
### IFrame window
The embedded iframe window within the parent window

- [Set up remote caching](https://nx.dev/features/share-your-cache)
- [Set up task distribution across multiple machines](https://nx.dev/nx-cloud/features/distribute-task-execution)
- [Learn more how to setup CI](https://nx.dev/recipes/ci)
### IFrame model
The function that can be used to define which origin it can expect to receive signals from, and what effects can be run when the that origin requests them to be run.
```js
// use named import
import { IFrame } from 'liaison-core';
```

## Explore the project graph

Run `npx nx graph` to show the graph of the workspace.
It will show tasks that you can run with Nx.
### Signals:
A `Signal` is an object that contains all the data needed for one client to understand what function it needs to run and the arguments it needs to call that function with.

- [Learn more about Exploring the Project Graph](https://nx.dev/core-features/explore-graph)
- The `name` property indicates the _name of the `Effect`_ one client (`Parent` or `IFrame`) wants to _initiate on the other_.

## Connect with us!
- The `args` property is an object containing all of the arguments that the client was the other to include in its call to that effect.

- [Join the community](https://nx.dev/community)
- [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools)
- [Follow us on Twitter](https://twitter.com/nxdevtools)
### Effects
An `Effect` is a function that can be run on one of the clients, whenever the other client requests it be run.
29 changes: 29 additions & 0 deletions apps/iframe-react-app-e2e/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { FlatCompat } = require('@eslint/eslintrc');
const baseConfig = require('../../eslint.config.js');
const js = require('@eslint/js');

const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});

module.exports = [
...baseConfig,
...compat.extends('plugin:playwright/recommended'),
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
rules: {},
},
{
files: ['src/**/*.{ts,js,tsx,jsx}'],
rules: {},
},
];
Loading

0 comments on commit b7ac55b

Please sign in to comment.