-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e777273
commit b7ac55b
Showing
134 changed files
with
2,848 additions
and
1,090 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,3 +40,6 @@ Thumbs.db | |
|
||
.nx/cache | ||
.nx/workspace-data | ||
|
||
.env* | ||
!.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[tools] | ||
node = "22.5.1" |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: {}, | ||
}, | ||
]; |
Oops, something went wrong.