-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add extension management apis #3978
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
ef4427a
Improved plugin infrastructure -- render custom UI
a-b-r-o-w-n ca4a868
Aligned sample publish plugin with new publish api
tonyanziano efbb8cd
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
tonyanziano 4826f35
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
tonyanziano 92b3fe4
client-plugin-lib improvements
tonyanziano b51221a
Minor fixes for clarity and polish
tonyanziano 3641ebb
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
tonyanziano 6ce7153
Updated sample-ui-plugin package with docs
tonyanziano 294a998
Removed logs
tonyanziano 13d574a
Updated sample-ui-plugin docs
tonyanziano ea5e497
Guarded against removing / disabling built-in plugins
tonyanziano d6965b6
Minor fixes
tonyanziano dad4155
Linting
tonyanziano 02dbfee
Comment update
tonyanziano ac57244
More linting
tonyanziano 6cad8c1
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
tonyanziano 8162927
Added licenses and removed a comment.
tonyanziano e0be76d
minor cleanup
a-b-r-o-w-n 755b35f
Merge branch 'main' into toanzian/feat/extensions
a-b-r-o-w-n a456069
hide plugins page in client
a-b-r-o-w-n 0f37b90
rename extension-manifest to just extensions
a-b-r-o-w-n 2ddcb4a
run npm commands in a safe manner
a-b-r-o-w-n 86f3250
prettify extensions.json
a-b-r-o-w-n a7a496a
make PluginManager a singleton
a-b-r-o-w-n 443462e
Merge branch 'main' into toanzian/feat/extensions
a-b-r-o-w-n 52fc8b9
Merge branch 'main' into toanzian/feat/extensions
a-b-r-o-w-n 03f69bd
only spawn npm commands
a-b-r-o-w-n File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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 |
---|---|---|
@@ -1 +1,2 @@ | ||
build/ | ||
public/*-bundle.js |
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
22 changes: 22 additions & 0 deletions
22
Composer/packages/client/config/webpack-react-dom.config.js
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,22 @@ | ||
const { resolve } = require('path'); | ||
|
||
module.exports = { | ||
entry: { | ||
'react-dom-bundle': 'react-dom', | ||
}, | ||
mode: 'production', | ||
// export react-dom globally under a variable named ReactDOM | ||
output: { | ||
path: resolve(__dirname, '../public'), | ||
library: 'ReactDOM', | ||
libraryTarget: 'var', | ||
}, | ||
externals: { | ||
// ReactDOM depends on React, but we need this to resolve to the globally-exposed React variable in react-bundle.js (created by webpack-react.config.js). | ||
// If we don't do this, ReactDom will bundle its own copy of React and we will have 2 copies which breaks hooks. | ||
react: 'React', | ||
}, | ||
resolve: { | ||
extensions: ['.js'], | ||
}, | ||
}; |
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,17 @@ | ||
const { resolve } = require('path'); | ||
|
||
module.exports = { | ||
entry: { | ||
'react-bundle': 'react', | ||
}, | ||
mode: 'production', | ||
// export react globally under a variable named React | ||
output: { | ||
path: resolve(__dirname, '../public'), | ||
library: 'React', | ||
libraryTarget: 'var', | ||
}, | ||
resolve: { | ||
extensions: ['.js'], | ||
}, | ||
}; |
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 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// add default doc styles | ||
if (!document.getElementById('plugin-host-default-styles')) { | ||
const styles = document.createElement('style'); | ||
styles.id = 'plugin-host-default-styles'; | ||
styles.type = 'text/css'; | ||
styles.appendChild( | ||
document.createTextNode(` | ||
html, body { padding: 0; margin: 0; } | ||
#plugin-root { | ||
display: flex; | ||
flex-flow: column nowrap; | ||
height: 100%; | ||
} | ||
`) | ||
); | ||
document.head.appendChild(styles); | ||
} | ||
// add the react mount point | ||
if (!document.getElementById('plugin-root')) { | ||
const root = document.createElement('div'); | ||
root.id = 'plugin-root'; | ||
document.body.appendChild(root); | ||
} | ||
// initialize the API object | ||
window.Composer = {}; | ||
// init the render function | ||
window.Composer['render'] = function (component) { | ||
ReactDOM.render(component, document.getElementById('plugin-root')); | ||
}; |
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
74 changes: 74 additions & 0 deletions
74
Composer/packages/client/src/components/PluginHost/PluginHost.tsx
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,74 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
/** @jsx jsx */ | ||
tonyanziano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import { jsx, SerializedStyles } from '@emotion/core'; | ||
import * as React from 'react'; | ||
import { useEffect, useRef } from 'react'; | ||
|
||
import { PluginAPI } from '../../plugins/api'; | ||
import { PluginType } from '../../plugins/types'; | ||
|
||
import { iframeStyle } from './styles'; | ||
|
||
interface PluginHostProps { | ||
extraIframeStyles?: SerializedStyles[]; | ||
pluginName?: string; | ||
pluginType?: PluginType; | ||
} | ||
|
||
/** Binds closures around Composer client code to plugin iframe's window object */ | ||
function attachPluginAPI(win: Window, type: PluginType) { | ||
const api = { ...PluginAPI[type], ...PluginAPI.auth }; | ||
for (const method in api) { | ||
win.Composer[method] = (...args) => api[method](...args); | ||
} | ||
} | ||
|
||
function injectScript(doc: Document, id: string, src: string, async: boolean, onload?: () => any) { | ||
if (!doc.getElementById(id)) { | ||
const script = document.createElement('script'); | ||
Object.assign(script, { id, src, async, onload }); | ||
doc.body.appendChild(script); | ||
} | ||
} | ||
|
||
/** Abstraction that will render an iframe injected with all the necessary UI plugin scripts, | ||
* and then serve the plugin's client bundle. | ||
*/ | ||
export const PluginHost: React.FC<PluginHostProps> = (props) => { | ||
const targetRef = useRef<HTMLIFrameElement>(null); | ||
const { extraIframeStyles = [] } = props; | ||
|
||
useEffect(() => { | ||
const { pluginName, pluginType } = props; | ||
// renders the plugin's UI inside of the iframe | ||
const renderPluginView = async () => { | ||
if (pluginName && pluginType) { | ||
const iframeWindow = targetRef.current?.contentWindow as Window; | ||
const iframeDocument = targetRef.current?.contentDocument as Document; | ||
|
||
// inject the react / react-dom bundles | ||
injectScript(iframeDocument, 'react-bundle', '/react-bundle.js', false); | ||
injectScript(iframeDocument, 'react-dom-bundle', '/react-dom-bundle.js', false); | ||
// // load the preload script to setup the plugin API | ||
injectScript(iframeDocument, 'preload-bundle', '/plugin-host-preload.js', false, () => { | ||
attachPluginAPI(iframeWindow, pluginType); | ||
}); | ||
|
||
//load the bundle for the specified plugin | ||
const pluginScriptId = `plugin-${pluginType}-${pluginName}`; | ||
await new Promise((resolve) => { | ||
const cb = () => { | ||
resolve(); | ||
}; | ||
// If plugin bundles end up being too large and block the client thread due to the load, enable the async flag on this call | ||
injectScript(iframeDocument, pluginScriptId, `/api/plugins/${pluginName}/view/${pluginType}`, false, cb); | ||
}); | ||
} | ||
}; | ||
renderPluginView(); | ||
}, [props.pluginName, props.pluginType, targetRef]); | ||
|
||
return <iframe ref={targetRef} css={[iframeStyle, ...extraIframeStyles]} title={`${props.pluginName} host`}></iframe>; | ||
}; |
10 changes: 10 additions & 0 deletions
10
Composer/packages/client/src/components/PluginHost/styles.ts
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,10 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import { css } from '@emotion/core'; | ||
|
||
export const iframeStyle = css` | ||
height: 100%; | ||
width: 100%; | ||
border: 0; | ||
`; |
15 changes: 15 additions & 0 deletions
15
Composer/packages/client/src/pages/plugin/pluginPageContainer.tsx
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,15 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import React from 'react'; | ||
import { RouteComponentProps } from '@reach/router'; | ||
|
||
import { PluginHost } from '../../components/PluginHost/PluginHost'; | ||
|
||
const PluginPageContainer: React.FC<RouteComponentProps<{ pluginId: string }>> = (props) => { | ||
const { pluginId } = props; | ||
|
||
return <PluginHost pluginName={pluginId} pluginType={'page'}></PluginHost>; | ||
}; | ||
|
||
export { PluginPageContainer }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better place in the app to perform these client-side "on startup go and fetch some data" tasks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. This seems to be the right place. We could make an action like
bootstrapApplication()
or something that other initialization code can go.