Skip to content

Commit

Permalink
feat!: use postMessage in embed mode instead of custom events
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasHirt committed Nov 17, 2023
1 parent 263ea12 commit 825ef82
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 48 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/change-embed-actions-post-message
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Change: Use postMessage method instead of emitting custom event in embed mode

We have switched to using `postMessage` method in the embed mode instead of emitting custom events.
This mitigates the issue when running the embed mode on different origin blocked the access to the `dispatchEvent` method.

https://github.com/owncloud/web/pull/9981
32 changes: 22 additions & 10 deletions docs/embed-mode/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ To integrate ownCloud Web into your application, add an iframe element pointing
<iframe src="<web-url>?mode=embed"></iframe>
```

## Events
## Communication

The app is emitting various events depending on the goal of the user. All events are prefixed with `owncloud-embed:` to prevent any naming conflicts with other events.
To establish seamless cross-origin communication between the embedded instance and the parent application, our approach involves emitting events using the `postMessage` method. These events can be conveniently captured by utilizing the standard `window.addEventListener('message', listener)` pattern.

| Event name | Payload | Description |
### Events

To maintain uniformity and ease of handling, each event encapsulates the same structure within its payload: `{ name: string, data: any }`.

| Name | Data | Description |
| --- | --- | --- |
| **owncloud-embed:select** | Resource[] | Gets emitted when user selects resources via the "Attach as copy" action |
| **owncloud-embed:select** | Resource[] | Gets emitted when user selects resources or location via the select action |
| **owncloud-embed:share** | string[] | Gets emitted when user selects resources and shares them via the "Share links" action |
| **owncloud-embed:cancel** | void | Gets emitted when user attempts to close the embedded instance via "Cancel" action |
| **owncloud-embed:cancel** | null | Gets emitted when user attempts to close the embedded instance via "Cancel" action |

### Example

Expand All @@ -37,12 +41,16 @@ The app is emitting various events depending on the goal of the user. All events

<script>
function selectEventHandler(event) {
const resources = event.detail
if (event.data?.name !== 'owncloud-embed:select') {
return
}
const resources = event.data.data
doSomethingWithSelectedResources(resources)
}
window.addEventListener('owncloud-embed:select', selectEventHandler)
window.addEventListener('message', selectEventHandler)
</script>
```
Expand All @@ -57,11 +65,15 @@ By default, the Embed mode allows users to select resources. In certain cases (e
<script>
function selectEventHandler(event) {
const currentFolder = event.detail[0]
if (event.data?.name !== 'owncloud-embed:select') {
return
}
uploadIntoCurrentFolder(currentFolder)
const resources = event.data.data[0]
doSomethingWithSelectedResources(resources)
}
window.addEventListener('owncloud-embed:select', selectEventHandler)
window.addEventListener('message', selectEventHandler)
</script>
```
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,17 @@ export default {
)
const emitSelect = (): void => {
const event: CustomEvent<Resource[]> = new CustomEvent('owncloud-embed:select', {
detail: selectedFiles.value
})
window.parent.dispatchEvent(event)
window.parent.postMessage({ name: 'owncloud-embed:select', data: selectedFiles.value }, '*')
}
const emitCancel = (): void => {
const event: CustomEvent<void> = new CustomEvent('owncloud-embed:cancel')
window.parent.dispatchEvent(event)
window.parent.postMessage({ name: 'owncloud-embed:cancel', data: null }, '*')
}
const emitShare = (links: string[]): void => {
if (!canCreatePublicLinks.value) return
const event: CustomEvent<string[]> = new CustomEvent('owncloud-embed:share', {
detail: links
})
window.parent.dispatchEvent(event)
window.parent.postMessage({ name: 'owncloud-embed:share', data: links }, '*')
}
const sharePublicLinks = async (): Promise<string[]> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,20 @@ describe('EmbedActions', () => {
})

it('should emit select event when the select action is triggered', async () => {
window.parent.dispatchEvent = jest.fn()
window.parent.postMessage = jest.fn()
global.CustomEvent = jest.fn().mockImplementation(mockCustomEvent)

const { wrapper } = getWrapper({ selectedFiles: [{ id: 1 }] })

await wrapper.find(selectors.btnSelect).trigger('click')

expect(window.parent.dispatchEvent).toHaveBeenCalledWith({
name: 'owncloud-embed:select',
payload: { detail: [{ id: 1 }] }
})
expect(window.parent.postMessage).toHaveBeenCalledWith(
{
name: 'owncloud-embed:select',
data: [{ id: 1 }]
},
'*'
)
})

it('should enable select action when embedTarget is set to location', () => {
Expand All @@ -59,7 +62,7 @@ describe('EmbedActions', () => {
})

it('should emit select event with currentFolder as selected resource when select action is triggered', async () => {
window.parent.dispatchEvent = jest.fn()
window.parent.postMessage = jest.fn()
global.CustomEvent = jest.fn().mockImplementation(mockCustomEvent)

const { wrapper } = getWrapper({
Expand All @@ -69,26 +72,32 @@ describe('EmbedActions', () => {

await wrapper.find(selectors.btnSelect).trigger('click')

expect(window.parent.dispatchEvent).toHaveBeenCalledWith({
name: 'owncloud-embed:select',
payload: { detail: [{ id: 1 }] }
})
expect(window.parent.postMessage).toHaveBeenCalledWith(
{
name: 'owncloud-embed:select',
data: [{ id: 1 }]
},
'*'
)
})
})

describe('cancel action', () => {
it('should emit cancel event when the cancel action is triggered', async () => {
window.parent.dispatchEvent = jest.fn()
window.parent.postMessage = jest.fn()
global.CustomEvent = jest.fn().mockImplementation(mockCustomEvent)

const { wrapper } = getWrapper({ selectedFiles: [{ id: 1 }] })

await wrapper.find(selectors.btnCancel).trigger('click')

expect(window.parent.dispatchEvent).toHaveBeenCalledWith({
name: 'owncloud-embed:cancel',
payload: undefined
})
expect(window.parent.postMessage).toHaveBeenCalledWith(
{
name: 'owncloud-embed:cancel',
data: null
},
'*'
)
})
})

Expand All @@ -115,7 +124,7 @@ describe('EmbedActions', () => {
})

it('should emit share event when share action is triggered', async () => {
window.parent.dispatchEvent = jest.fn()
window.parent.postMessage = jest.fn()
global.CustomEvent = jest.fn().mockImplementation(mockCustomEvent)

const { wrapper } = getWrapper({
Expand All @@ -125,14 +134,17 @@ describe('EmbedActions', () => {

await wrapper.find(selectors.btnShare).trigger('click')

expect(window.parent.dispatchEvent).toHaveBeenCalledWith({
name: 'owncloud-embed:share',
payload: { detail: ['link-1'] }
})
expect(window.parent.postMessage).toHaveBeenCalledWith(
{
name: 'owncloud-embed:share',
data: ['link-1']
},
'*'
)
})

it('should ask for password first when required when share action is triggered', async () => {
window.parent.dispatchEvent = jest.fn()
window.parent.postMessage = jest.fn()
global.CustomEvent = jest.fn().mockImplementation(mockCustomEvent)

const { wrapper } = getWrapper({
Expand All @@ -145,10 +157,13 @@ describe('EmbedActions', () => {

await wrapper.find(selectors.btnShare).trigger('click')

expect(window.parent.dispatchEvent).toHaveBeenCalledWith({
name: 'owncloud-embed:share',
payload: { detail: ['password-link-1'] }
})
expect(window.parent.postMessage).toHaveBeenCalledWith(
{
name: 'owncloud-embed:share',
data: ['password-link-1']
},
'*'
)
})

it('should hide share action when embedTarget is set to location', () => {
Expand Down

0 comments on commit 825ef82

Please sign in to comment.