Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@acpaas-ui/embeddable-widgets
Advanced tools
Library for embedding parts of apps into other apps
This library enables cross-framework embedding of a part of one application into a page of another application at run-time using iframes.
An application part that can be embedded is called a widget. The app which publishes the widget is called the publisher. The app which embeds the published widget into a page is called the container.
Widgets can declare API's that are available in the container's page, even if container and publisher are implemented in different front-end frameworks.
If you are migrating from v1.x of this framework to v2.x be aware that there are breaking changes, see the migration notes as well as the changelog.
A first step for both publishing and embedding is including the widgets library into the page.
<script src="https://cdn.antwerpen.be/embeddable-widgets_module_js/2.0.9/aui-embeddable-widgets.min.js"></script>
If you don't want to load from CDN, you can also npm install @acpaas-ui/embeddable-widgets
and you will find the library in the node_modules/@acpaas-ui/embeddable-widgets/lib
folder.
You can publish any webpage as an embeddable widget.
First you need to publish the definition of the widget on a web-accessible URL as JSON.
{
"tag": "my-foo-widget",
"url": "/foo-widget",
"dimensions": {
"width": "100%",
"height": "500px"
},
"props": {
"fooData": {
"type": "array",
"required": true
},
"onFoo": {
"type": "function",
"required": false
}
}
}
What is going on here:
The tag
is a unique identifier for the widget in the page (it is not automatically mapped to a HTML tag).
The url
points to where the widget's page is hosted. It can be relative (to the JSON's URL) or absolute.
It is not required that the definition is hosted on the same server as the widget.
The dimensions
specify the initial rendering dimensions, applies as style attributes to the iframe.
The props
specify the properties that the widget can be initialized with
fooData
is an array which will be passed from container to widgetonFoo
is an event handler which will be defined in the container and called by the widgetIf you want to render the same component multiple times on the same page but with different definition values.
You will have to define()
the component with different tags.
See the API section below for more details.
CORS headers need to be set on this JSON (e.g.
Access-Control-Allow-Origin: *
). CORS headers do not need to be set on the widget page itself.
To publish an Angular 6+ app, use the Angular wrapper ngx-embeddable-widgets. It includes an example app.
Initialize the widget:
window.auiEmbeddableWidgets.load('//example.com/path/to/definition.json');
Access the properties passed from the container in window.xprops
.
function doFoo() {
if (window.xprops && window.xprops.fooData) {
const result = window.xprops.fooData.map(...);
if (window.xprops.onFoo) {
window.xprops.onFoo(result);
}
}
}
The widget's page does not need cross-origin headers. It communicates to the container via
window.postMessage
. However, it should allow framing by setting appropriateX-Frame-Options
orContent-Security-Policy
headers if necessary.
To embed into an Angular 6+ app, use the Angular wrapper ngx-embeddable-widgets.
import React from 'react';
import ReactDOM from 'react-dom';
const MyWidget = window.auiEmbeddableWidgets.reactComponent(
// url to the definition
"//example.com/path/to/defintion.json",
{ React, ReactDOM }
)
class App extends Component {
onFoo(result) { ... }
render() {
return (
<MyWidget
fooData={ ['one', 'two'] }
onFoo={ result => this.onFoo(result) }
className="my-widget"
/>
);
}
}
This renders a
<div>
with the (optional)className
applied to it.
Provide a div to render the widget in:
<div id="my-container"></div>
Render the widget into the div, passing it the necessary properties:
window.auiEmbeddableWidgets.renderUrl(
'//example.com/path/to/definition.json',
{ fooData: ['one', 'two'],
onFoo: function(result) { ... } },
document.getElementById('my-container')
);
If you're currently using v1.x of this library in an app or in a widget, care must be taken to upgrade properly. Both app and widget must be running the same major version of the embeddable widgets library.
This library appends the _aui_api_version query parameter to the URL it loads into the iframe. Based on this the appropriate library version should be loaded inside of the widget's page. v1.x
for _aui_api_version=1
, and v2.x
for _aui_api_version=2
.
To load multiple versions of this library inside the widget's app, you can use the npm feature to have multiple versions of the same library.
A suggested upgrade strategy in case widget and app are separately hosted:
_aui_api_version
and load the appropriate library version.window.auiEmbeddableWidgets
define(definition: object, ?overrides: object): object
Defines a widget from the specified definition (same as the JSON described above) and definition overrides and returns a composed object with everything required to instantiate a component. Object has:
options: the definition with processed default values
overrides: the overrides you passed down
component: a function to pass the properties to and instantiate
Each widget has a unique tag. Each tag can only be defined once in the page, but can be rendered multiple times. However if you want to change the dimensions on the same widget, you will have to redefine one with a new tag.
isDefined(tag: string): boolean
Returns true if the widget is already defined in the page.
load(url: string, ?overrides: object): Promise<object>
Loads a widget definition from a URL, applies the optional overrides to the loaded definition, then returns a handle to the widget for instantiating.
render(tag: string|object, props: object, elem: HTMLElement): object
Renders a previously defined widget with the specified props parameters into the specified element and returns a handle to the instance.
tag
can be the widget instance returned from the load operation, or its tag string.
renderUrl(url: string, props: object, elem: HTMLElement, ?overrides: object): Promise<object>
Loads a widget definition from URL (if not yet loaded), applies the optional overrides to the loaded definition, then renders it to the specified element with the given props parameters. Returns a promise for the rendered instance.
reactComponent(url: string, deps: object, ?overrides: object): object
Creates a React component for the widget with definition hosted at url
, with the optional overrides applied to that definition.
The deps object must contain the React
and ReactDOM
objects provided by React.
The possible attributes for the widget definition:
string
[required]A tag-name for the component, used for:
tag: 'my-component-tag'
string
[required]The URL that will be loaded when the widget is rendered. Can be relative to the JSON's URL or absolute.
url: 'https://example.com/foo-widget'
url: '/foo-widget'
{ width : string, height : string }
The initial dimensions for the widget, in css-style units, with support for px
or %
.
dimensions: {
width: '300px',
height: '200px'
}
dimensions: {
width: '80%',
height: '90%'
}
Dimensions are set at
define()
time, not atrender()
time. To override dimensions from the defaults they can be passed as a property of theoverrides
argument torenderUrl
. However, this will be ignored on successive calls torenderUrl()
for the same widget. To render the same widget multiple times with different dimensions it is suggested to use width and height 100% and put each widget in a container div that is appropriately sized.
Object<string, Object>
Props that can be passed to the widget when rendering (data or functions).
props: {
onLogin: {
type: 'function'
},
prefilledEmail: {
type: 'string',
required: false
}
}
Sometimes you need to scroll the page outside the iframe to match an element inside the iframe. In order to do this, from inside the widget the scrollTo prop can be called:
this.props.scrollTo(elementOffset, this.props.tag);
There is a default handler for scrollTo
that is provided by the library. You can override it by passing your own implementation as props.scrollTo
, for example, to compensate for header elements. This is the default implementation:
const scrollTo = (elementOffset, tag) => {
const containerElement = document.querySelector(`[id^='zoid-${tag}-']`);
const newTopOffset = containerElement.offsetParent.offsetTop + elementOffset;
window.scrollTo({
top: newTopOffset,
behavior: 'smooth',
});
};
NOTE:
window.scrollTo
is polyfilled by this library.
type string
The data-type expected for the prop
'string'
'number'
'boolean'
'object'
'function'
'array'
required boolean
Whether or not the prop is mandatory. Defaults to true
.
onLogin: {
type: 'function',
required: false
}
defaultValue
The default value for the prop if not passed at render time. required
must be false.
fooData: {
type: "array",
required: false,
defaultValue: ["one", "two"]
}
This can be any type of value. However if you pass a function, it will be called with props
as the first argument. So if you want to have a function as defaultValue
, make sure you wrap it.
queryParam boolean | string
Should a prop be passed in the url (so it can influence the routing)?
email: {
type: 'string',
queryParam: true // ?email=foo@bar.com
}
If a string is set, this specifies the url param name which will be used.
email: {
type: 'string',
queryParam: 'user-email' // ?user-email=foo@bar.com
}
serialization string
If json
, the prop will be JSON stringified before being inserted into the url
user: {
type: 'object',
serialization: 'json' // ?user={"name":"Zippy","age":34}
}
If dotify
the prop will be converted to dot-notation.
user: {
type: 'object',
serialization: 'dotify' // ?user.name=Zippy&user.age=34
}
If base64
, the prop will be JSON stringified then base64 encoded before being inserted into the url
user: {
type: 'object',
serialization: 'base64' // ?user=eyJuYW1lIjoiWmlwcHkiLCJhZ2UiOjM0fQ==
}
{ height: boolean, width: boolean, element: string }
Makes the container's iframe resize automatically when the child widget window size changes.
autoResize: {
width: false,
height: true,
}
Note that by default it matches the body
element of your content.
You can override this setting by specifying a custom selector as an element
property.
autoResize: {
width: false,
height: true,
element: '.my-selector',
}
Recommended to only use autoResize for height. Width has some strange effects, especially when scroll bars are present.
string
The default logging level for the widget's internals, helpful for debugging. Options are:
'debug'
'info'
'warn'
(default)'error'
defaultLogLevel: 'info'
Note that this value can be overriden by passing
logLevel
as a prop when rendering the component.
Check the Zoid API documentation for additional properties. The function-based properties can only be specified as overrides, not in the JSON.
Run a server publishing the embeddable widgets framework
npm install
npm start
Point your publisher and container apps to this locally hosted version.
See the contribution guide for additional details.
This framework makes use of the Zoid framework, which implements the boilerplate for embedding apps into other apps using iframes and the postMessage API.
The wrapper is necessary to allow for a different developer experience which is more suited to the needs of Digipolis development projects.
url
renderUrl
to determine the URL for the widget page itselfPull requests are always welcome, however keep the following things in mind:
Joeri Sebrechts (joeri.sebrechts@digipolis.be)
Copyright (c) 2019-present, Digipolis
FAQs
Library for embedding parts of apps into other apps
We found that @acpaas-ui/embeddable-widgets demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 8 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.