allow lti postMessage on assignment show page

why:
* requested by studio, and it makes sense to respond to these messages
in more places
* possibly down the road monitor for messages on all pages instead
of piecemeal
* make sure only one message listener is active at a time

closes INTEROP-7422
flag=none

test plan:
* edit an assignment and add a deep link to
its description that points to the 1.3 test tool - make sure to include
`{"width":400,"height":400}`
in the iframe box so that it launches the tool
in an iframe instead of a new window
* save the assignment
* open the browser dev tools console
* in the assignment description, scroll to the bottom of the tool's
iframe and send this postMessage:
`{"subject":"lti.fetchWindowSize"}`
* the console should show "message sent" and "message received" once
* repeat the postMessage send while editing the assignment, should get
the same result
* turn on the assignments_2_student and assignments_2_teacher flags
and make sure they are unlocked
* repeat the postMessage while viewing the assignment, both as a
student and as a teacher, should get one response message
* as a teacher/admin, click into the description to edit it, and repeat
the postMessage, should get one response message

Change-Id: I5c8986d248cd1b9a344fba1060094aa97bfd7a86
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/291323
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Tucker Mcknight <tmcknight@instructure.com>
Product-Review: Alexis Nast <alexis.nast@instructure.com>
Reviewed-by: Tucker Mcknight <tmcknight@instructure.com>
This commit is contained in:
Xander Moffatt 2022-05-06 10:53:44 -06:00
parent 6194946c61
commit b9dba671db
6 changed files with 76 additions and 43 deletions

View File

@ -1,5 +1,4 @@
Using window.postMessage in LTI Tools
=====================================
# Using window.postMessage in LTI Tools
Canvas listens for events sent through the `window.postMessage` Javascript
API (docs <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage" target="_blank">here</a>)
@ -15,8 +14,10 @@ This token is present in the launch as a custom variable, `$com.instructure.Post
should be passed in postMessage calls if it's present.
If the LTI tool is launched in a iframe, as is most common, then postMessages should be sent to
`window.parent`. However, if the tool is launched in a new tab, window, or popup, then postMessages
should be directed to `window.opener`. The examples will use `window.parent`.
`window.top`. Usually `window.parent` should suffice, but there are some situations where that may
not refer to the Canvas window. `window.top` refers to the topmost parent window, which should always
be Canvas. However, if the tool is launched in a new tab, window, or popup, then postMessages
should be directed to `window.opener`. The examples will use `window.top`.
# Message Types
@ -26,6 +27,7 @@ Launches the tool that sent the event in a full-window context (ie not inside a
Mainly used for Safari launches, since Safari disables setting cookies inside iframes.
**Required properties:**
- subject: "requestFullWindowLaunch"
- data: either a string or an object
- if a string, a url for relaunching the tool
@ -35,6 +37,7 @@ Mainly used for Safari launches, since Safari disables setting cookies inside if
under the custom claim section (`https://www.instructure.com/placement`).
**Optional properties:**
- data.launchType: defaults to "same_window"
- "same_window": launches the tool in the same window, replacing Canvas entirely
- "new_window": launches the tool in a new tab/window, which depends on user preference
@ -43,20 +46,20 @@ Mainly used for Safari launches, since Safari disables setting cookies inside if
- data.launchOptions.height: for launchType: popup, defines the popup window's height. Defaults to 600.
```js
window.parent.postMessage(
window.top.postMessage(
{
subject: "requestFullWindowLaunch",
subject: 'requestFullWindowLaunch',
data: {
url: "https://example-tool.com/launch",
placement: "course_navigation",
launchType: "new_window",
url: 'https://example-tool.com/launch',
placement: 'course_navigation',
launchType: 'new_window',
launchOptions: {
width: 1000,
height: 800
}
}
},
"*"
'*'
)
```
@ -65,10 +68,11 @@ window.parent.postMessage(
Opens and closes the course navigation sidebar, giving more space for the tool to display.
**Required properties:**
- subject: "toggleCourseNavigationMenu"
```js
window.parent.postMessage({ subject: "toggleCourseNavigationMenu" }, "*")
window.top.postMessage({subject: 'toggleCourseNavigationMenu'}, '*')
```
## lti.resourceImported
@ -78,10 +82,11 @@ Canvas will respond by reloading the page, if the tool was present in the extern
apps tray. Used on wiki pages.
**Required properties:**
- subject: "lti.resourceImported"
```js
window.parent.postMessage({ subject: "lti.resourceImported" }, "*")
window.top.postMessage({subject: 'lti.resourceImported'}, '*')
```
## lti.hideRightSideWrapper
@ -89,14 +94,15 @@ window.parent.postMessage({ subject: "lti.resourceImported" }, "*")
Tells Canvas to remove the right side nav in the assignments view.
**Required properties:**
- subject: "lti.hideRightSideWrapper"
```js
window.parent.postMessage(
window.top.postMessage(
{
subject: "lti.hideRightSideWrapper",
subject: 'lti.hideRightSideWrapper'
},
"*"
'*'
)
```
@ -105,19 +111,21 @@ window.parent.postMessage(
Tells Canvas to change the height of the iframe containing the tool.
**Required properties:**
- subject: "lti.frameResize"
- height: integer, in px
**Optional properties:**
- token: postMessage token, discussed above.
```js
window.parent.postMessage(
window.top.postMessage(
{
subject: "lti.frameResize",
subject: 'lti.frameResize',
height: 400
},
"*"
'*'
)
```
@ -127,9 +135,11 @@ Sends a postMessage event back to the tool with details about the window size of
the tool's containing iframe.
**Required properties:**
- subject: "lti.fetchWindowSize"
Returning postMessage includes the following properties:
- subject: "lti.fetchWindowSize.response"
- height: height of the iframe
- width: width of the iframe
@ -138,7 +148,7 @@ Returning postMessage includes the following properties:
- scrollY: the number of px that the iframe is scrolled vertically
```js
window.parent.postMessage({ subject: "lti.fetchWindowSize" }, "*")
window.top.postMessage({subject: 'lti.fetchWindowSize'}, '*')
```
## lti.showModuleNavigation
@ -146,16 +156,17 @@ window.parent.postMessage({ subject: "lti.fetchWindowSize" }, "*")
Toggles the module navigation footer based on the message's content.
**Required properties:**
- subject: "lti.showModuleNavigation"
- show: Boolean, whether to show or hide the footer
```js
window.parent.postMessage(
window.top.postMessage(
{
subject: "lti.frameResize",
subject: 'lti.frameResize',
show: true
},
"*"
'*'
)
```
@ -164,10 +175,11 @@ window.parent.postMessage(
Scrolls the iframe all the way to the top of its container.
**Required properties:**
- subject: "lti.scrollToTop"
```js
window.parent.postMessage({ subject: "lti.scrollToTop" }, "*")
window.top.postMessage({subject: 'lti.scrollToTop'}, '*')
```
## lti.setUnloadMessage
@ -176,16 +188,17 @@ Sets a message to be shown in a browser dialog before page closes (ie
"Do you really want to leave this page?")
**Required properties:**
- subject: "lti.setUnloadMessage"
- message: The message to be shown in the dialog
```js
window.parent.postMessage(
window.top.postMessage(
{
subject: "lti.setUnloadMessage",
message: "Are you sure you want to leave this app?"
subject: 'lti.setUnloadMessage',
message: 'Are you sure you want to leave this app?'
},
"*"
'*'
)
```
@ -194,10 +207,11 @@ window.parent.postMessage(
Clears any set message to be shown on page close.
Required properties
- subject: "lti.removeUnloadMessage"
```js
window.parent.postMessage({ subject: "lti.removeUnloadMessage" }, "*")
window.top.postMessage({subject: 'lti.removeUnloadMessage'}, '*')
```
## lti.screenReaderAlert
@ -205,16 +219,17 @@ window.parent.postMessage({ subject: "lti.removeUnloadMessage" }, "*")
Shows an alert for screen readers.
**Required properties:**
- subject: "lti.screenReaderAlert"
- body: The contents of the alert.
```js
window.parent.postMessage(
window.top.postMessage(
{
subject: "lti.screenReaderAlert",
body: "An alert just for screen readers"
subject: 'lti.screenReaderAlert',
body: 'An alert just for screen readers'
},
"*"
'*'
)
```
@ -224,23 +239,25 @@ Shows an alert using Canvas's alert system, and includes the name of the LTI
tool that sent the message.
**Required properties:**
- subject: "lti.showAlert"
- body: The contents of the alert - can either be a string, or JSON string.
**Optional properties:**
- alertType: "success", "warning", or "error". Defaults to "success".
- title: A display name for the tool. If not provided, Canvas will attempt to
supply the tool name or default to "External Tool".
```js
window.parent.postMessage(
window.top.postMessage(
{
subject: "lti.showAlert",
alertType: "warning",
body: "An warning to be shown",
title: "Tool Name"
subject: 'lti.showAlert',
alertType: 'warning',
body: 'An warning to be shown',
title: 'Tool Name'
},
"*"
'*'
)
```
@ -250,12 +267,14 @@ Sends a debounced postMessage event to the tool every time its containing
iframe is scrolled.
**Required properties:**
- subject: "lti.enableScrollEvents"
Returning postMessage includes the following properties:
- subject: "lti.scroll"
- scrollY: the number of px that the iframe is scrolled vertically
```js
window.parent.postMessage({ subject: "lti.enableScrollEvents" }, "*")
window.top.postMessage({subject: 'lti.enableScrollEvents'}, '*')
```

View File

@ -30,8 +30,10 @@ import GroupCategorySelector from '@canvas/groups/backbone/views/GroupCategorySe
import PeerReviewsSelector from '@canvas/assignments/backbone/views/PeerReviewsSelector.coffee'
import '@canvas/grading-standards'
import LockManager from '@canvas/blueprint-courses/react/components/LockManager/index'
import {monitorLtiMessages} from '@canvas/lti/jquery/messages'
ready(() => {
monitorLtiMessages()
const lockManager = new LockManager()
lockManager.init({itemType: 'assignment', page: 'edit'})
const lockedItems = lockManager.isChildContent() ? lockManager.getItemLocks() : {}

View File

@ -38,6 +38,7 @@ import DirectShareUserModal from '@canvas/direct-sharing/react/components/Direct
import DirectShareCourseTray from '@canvas/direct-sharing/react/components/DirectShareCourseTray'
import {setupSubmitHandler} from '@canvas/assignments/jquery/reuploadSubmissionsHelper'
import ready from '@instructure/ready'
import {monitorLtiMessages} from '@canvas/lti/jquery/messages'
const I18n = useI18nScope('assignment')
@ -45,6 +46,7 @@ ready(() => {
const lockManager = new LockManager()
lockManager.init({itemType: 'assignment', page: 'show'})
renderCoursePacingNotice()
monitorLtiMessages()
})
let studentGroupSelectionRequestTrackers = []

View File

@ -17,8 +17,10 @@
*/
import renderAssignmentsApp from './react/index'
import $ from 'jquery'
import ready from '@instructure/ready'
import {monitorLtiMessages} from '@canvas/lti/jquery/messages'
$(() => {
ready(() => {
monitorLtiMessages()
renderAssignmentsApp(ENV, $('<div/>').appendTo('#content')[0])
})

View File

@ -18,8 +18,10 @@
import renderAssignmentsApp from './react/index'
import ready from '@instructure/ready'
import {monitorLtiMessages} from '@canvas/lti/jquery/messages'
ready(() => {
monitorLtiMessages()
const elt = document.getElementById('content')
renderAssignmentsApp(ENV, elt)
})

View File

@ -114,11 +114,17 @@ async function ltiMessageHandler(e, platformStorageFeatureFlag = false) {
}
}
let hasListener = false
function monitorLtiMessages() {
const platformStorageFeatureFlag = ENV?.FEATURES?.lti_platform_storage
window.addEventListener('message', e => {
const cb = e => {
if (e.data !== '') ltiMessageHandler(e, platformStorageFeatureFlag)
})
}
if (!hasListener) {
window.addEventListener('message', cb)
hasListener = true
}
}
export {ltiState, SUBJECT_ALLOW_LIST, SUBJECT_IGNORE_LIST, ltiMessageHandler, monitorLtiMessages}