Skip to content

Commit bbc1a2e

Browse files
committedJun 8, 2023
Deploy staging play-codecheck
1 parent 04deb75 commit bbc1a2e

File tree

2 files changed

+139
-173
lines changed

2 files changed

+139
-173
lines changed
 

‎public/codecheck2.js

+26-10
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ window.horstmann_config = {
2222
}
2323
else if (horstmann_config.inIframe()) {
2424
const qid = horstmann_config.getInteractiveId(element) // TODO should be done by caller
25-
const param = { state, score, qid }
26-
const data = { query: 'send', param }
27-
window.parent.postMessage(data, '*' )
25+
// const param = { state, score, qid }
26+
// const message = { query: 'send', param }
27+
const message = {
28+
subject: 'SPLICE.reportScoreAndState',
29+
message_id: generateUUID(),
30+
score,
31+
state
32+
}
33+
window.parent.postMessage(message, '*' )
2834
}
2935
},
3036

@@ -68,10 +74,14 @@ window.horstmann_config = {
6874
else if (horstmann_config.inIframe()) {
6975
const nonce = horstmann_config.generateUUID()
7076
horstmann_config.nonceMap[nonce] = callback
71-
const qid = horstmann_config.getInteractiveId(element) // TODO should be done by caller
72-
const param = { qid }
73-
const data = { query: 'retrieve', param, nonce }
74-
window.parent.postMessage(data, '*')
77+
// const qid = horstmann_config.getInteractiveId(element) // TODO should be done by caller
78+
// const param = { qid }
79+
// const message = { query: 'retrieve', param, nonce }
80+
const message = {
81+
subject: 'SPLICE.getState',
82+
message_id: generateUUID()
83+
}
84+
window.parent.postMessage(message, '*')
7585
const MESSAGE_TIMEOUT = 5000
7686
setTimeout(() => {
7787
if ((nonce in horstmann_config.nonceMap)) {
@@ -112,9 +122,15 @@ document.addEventListener('DOMContentLoaded', function () {
112122
setTimeout(() => {
113123
let newDocHeight = document.documentElement.scrollHeight + document.documentElement.offsetTop
114124
if (docHeight != newDocHeight) {
115-
docHeight = newDocHeight
116-
const data = { query: 'docHeight', param: { docHeight } }
117-
window.parent.postMessage(data, '*' )
125+
// docHeight = newDocHeight
126+
// const message = { query: 'docHeight', param: { docHeight } }
127+
const message = {
128+
subject: 'lti.frameResize',
129+
message_id: generateUUID(),
130+
height: newDocHeight,
131+
width: document.documentElement.scrollWidth + document.documentElement.offsetLeft
132+
}
133+
window.parent.postMessage(message, '*' )
118134
}
119135
}, SEND_DOCHEIGHT_DELAY)
120136
}

‎receiveMessage.js

+113-163
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,32 @@
1-
/*
1+
// This file is included by the activities on www.interactivities.ws
22

3-
Protocol:
3+
// The following starts out with splice-iframe.js, and then translates VitalSource EPUB to SPLICE
44

5-
data.
6-
query (request from parent to child) 'docHeight', 'getContent', 'restoreState' (LTIHub v1)
7-
(request from child to parent) 'docHeight', 'send', 'retrieve' (LTIHub v2)
8-
request (response) the request
5+
window.addEventListener('load', event => {
96

7+
const scores = {}
8+
const states = {}
9+
let callbacks = undefined
10+
let docHeight = 0
1011

11-
state (restoreState request from parent to child, v1)
12-
state (getContent response from child to parent, v1)
13-
14-
nonce (request from child to parent) a nonce to be returned with
15-
the response, for non-void requests ('retrieve', v2)
16-
17-
docHeight (docHeight request, response from child to parent, v1)
18-
19-
score (getContent response from child to parent, v1)
20-
21-
param (request or response) parameter object (v2)
22-
23-
24-
v2 details:
25-
26-
Always from child to parent
27-
28-
{ query: 'docHeight', param: { docHeight: ... } }
29-
No qid because applies to whole frame
30-
No response
31-
32-
{ query: 'send', param: { qid: ..., state: ..., score: ... }}
33-
Score between 0 and 1
34-
No response
12+
const sendDocHeight = () => {
13+
if (window.self === window.top) return // not iframe
3514

36-
{ query: 'retrieve', nonce: ..., param: { qid: ... } }
37-
Response: { request: ..., param: state }
38-
39-
TODO: Why not param: { state: ..., score: ... }
40-
If we want that, must adapt workAssignment.js, receiveMessage.js, codecheck.js
41-
42-
*/
43-
44-
if (window.self !== window.top) { // iframe
45-
if (!('EPUB' in window))
46-
window.EPUB = {}
47-
if (!('Education' in window.EPUB)) {
48-
window.EPUB.Education = {
49-
nonceMap: {},
50-
retrieveCallback: undefined, // LTIHub v1
51-
retrieve: (request, callback) => {
52-
window.EPUB.Education.retrieveCallback = callback // LTIHub v1
53-
if ('stateToRestore' in window.EPUB.Education) { // LTIHub v1, restore data already arrived
54-
callback({ data: [ { data: window.EPUB.Education.stateToRestore } ] })
55-
delete window.EPUB.Education.stateToRestore
56-
return
57-
}
58-
// Register callback
59-
const nonce = generateUUID()
60-
window.EPUB.Education.nonceMap[nonce] = callback
61-
if (window.EPUB.Education.version !== 1) { // LTIHub v1
62-
// Pass request and nonce to parent
63-
// TODO: VitalSource format
64-
const qid = request.filters[0].activityIds[0]
65-
const param = { qid }
66-
const data = { query: 'retrieve', param, nonce }
67-
window.parent.postMessage(data, '*' )
68-
}
69-
const MESSAGE_TIMEOUT = 5000
70-
setTimeout(() => {
71-
if ('stateToRestore' in window.EPUB.Education) { // LTIHub v1, restore data already arrived and delivered
72-
delete window.EPUB.Education.stateToRestore
73-
return
74-
}
75-
76-
if (!(nonce in window.EPUB.Education.nonceMap)) return
77-
delete window.EPUB.Education.nonceMap[nonce]
78-
// TODO: VitalSource format
79-
callback({ data: [ { data: null } ] })
80-
}, MESSAGE_TIMEOUT)
81-
},
82-
send: (request, callback) => {
83-
if (window.EPUB.Education.version === 1) return // LTIHub v1
84-
// TODO: VitalSource format
85-
const param = { state: request.data[0].state.data, score: request.data[0].results[0].score, qid: request.data[0].activityId }
86-
const data = { query: 'send', param }
15+
window.scrollTo(0, 0)
16+
const SEND_DOCHEIGHT_DELAY = 100
17+
setTimeout(() => {
18+
let newDocHeight = document.body.scrollHeight + document.body.offsetTop
19+
if (docHeight != newDocHeight) {
20+
docHeight = newDocHeight
21+
const data = { subject: 'lti.frameResize', message_id: generateUUID(), height: docHeight }
8722
window.parent.postMessage(data, '*' )
88-
sendDocHeight()
89-
},
90-
}
23+
if (window.SPLICE.logging) console.log('postMessage to parent', data)
24+
}
25+
}, SEND_DOCHEIGHT_DELAY)
9126
}
9227

9328
// https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
94-
95-
function generateUUID() { // Public Domain/MIT
29+
const generateUUID = () => { // Public Domain/MIT
9630
var d = new Date().getTime()
9731
var d2 = (performance && performance.now && (performance.now() * 1000)) || 0 // Time in microseconds since page-load or 0 if unsupported
9832
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
@@ -108,87 +42,103 @@ if (window.self !== window.top) { // iframe
10842
})
10943
}
11044

111-
let element = undefined
112-
let docHeight = 0
113-
114-
function sendDocHeight() {
115-
window.scrollTo(0, 0)
116-
const SEND_DOCHEIGHT_DELAY = 100
117-
if (window.EPUB.Education.version === 1) return // TODO
118-
setTimeout(() => {
119-
const container = element === undefined ? document.body : element.closest('li').parentNode
120-
// When using container = document.documentElement, the document grows without bound
121-
// TODO: Why does this work in codecheck.js but not here?
122-
let newDocHeight = container.scrollHeight + container.offsetTop
123-
if (docHeight != newDocHeight) {
124-
docHeight = newDocHeight
125-
const data = { query: 'docHeight', param: { docHeight } }
126-
window.parent.postMessage(data, '*' )
127-
}
128-
}, SEND_DOCHEIGHT_DELAY)
45+
if (!('SPLICE' in window)) {
46+
window.SPLICE = {
47+
logging: true,
48+
getScore: (location, callback) => {
49+
if (callbacks === undefined) {
50+
callback(states[location])
51+
} else {
52+
callbacks.push({location, callback})
53+
}
54+
},
55+
reportScoreAndState: (location, score, state) => {
56+
scores[location] = score
57+
states[location] = state
58+
if (window.self === window.top) return // not iframe
59+
let averageScore = 0
60+
let n = 0
61+
for (const location in scores) {
62+
averageScore += scores[location];
63+
n++
64+
}
65+
if (n > 0) averageScore /= n
66+
const message = { subject: 'SPLICE.reportScoreAndState', message_id: generateUUID(), score: averageScore, state: states }
67+
window.parent.postMessage(message, '*' )
68+
if (window.SPLICE.logging) console.log('postMessage to parent', message)
69+
},
70+
sendEvent: (location, name, data) => {
71+
const message = { subject: 'SPLICE.sendEvent', message_id: generateUUID(), name, data }
72+
window.parent.postMessage(message, '*' )
73+
if (window.SPLICE.logging) console.log('postMessage to parent', message)
74+
}
75+
}
12976
}
130-
131-
window.addEventListener('load', event => {
132-
const interactiveElements = [...document.querySelectorAll('div, ol')].
133-
filter(e => {
134-
const ty = e.tagName
135-
const cl = e.getAttribute('class')
136-
return cl && (ty === 'div' && cl.indexOf('horstmann_') == 0 || ty === 'ol' && (cl.indexOf('multiple-choice') == 0 || cl.indexOf('horstmann_ma') == 0))
137-
})
138-
element = interactiveElements[0]
139-
sendDocHeight()
140-
document.body.style.overflow = 'hidden'
141-
// ResizeObserver did not work
142-
const mutationObserver = new MutationObserver(sendDocHeight);
143-
mutationObserver.observe(element === undefined ? document.body : element, { childList: true, subtree: true })
144-
})
14577

146-
window.addEventListener("message", event => {
78+
if (window.self !== window.top) { // iframe
79+
const message = { subject: 'SPLICE.getState', message_id: generateUUID() }
80+
window.parent.postMessage(message, '*' )
81+
if (window.SPLICE.logging) console.log('postMessage to parent', message)
82+
callbacks = []
83+
const MESSAGE_TIMEOUT = 5000
84+
setTimeout(() => {
85+
if (callbacks === undefined) return
86+
for (const { location, callback } of callbacks) {
87+
scores[location] = 0
88+
states[location] = undefined
89+
callback(undefined, { code: 'timeout' })
90+
}
91+
callbacks = undefined
92+
}, MESSAGE_TIMEOUT)
93+
}
94+
95+
window.addEventListener('message', event => {
14796
if (!(event.data instanceof Object)) return
148-
if ('request' in event.data) { // It's a response
149-
const request = event.data.request
150-
if (request.query === 'retrieve') { // LTIHub v2
151-
const state = event.data.param
152-
// TODO Old VitalSource API
153-
// let state = response.data[0].data
154-
const arg = { data: [ { data: state } ] }
155-
if (request.nonce in window.EPUB.Education.nonceMap) {
156-
// If not, already timed out
157-
window.EPUB.Education.nonceMap[request.nonce](arg)
158-
delete window.EPUB.Education.nonceMap[request.nonce]
97+
if (!(event.data.subject instanceof String && event.data.subject.startsWith('SPLICE.'))) return
98+
if (event.data.subject.endsWith('response')) {
99+
if (window.SPLICE.logging) console.log('postmessage response', event)
100+
if (event.data.subject === 'SPLICE.getState.response') {
101+
if (callbacks === undefined) return // Already timed out
102+
for (const location in event.data.state) {
103+
scores[location] = 0
104+
states[location] = event.data.state[location]
159105
}
106+
107+
for (const { location, callback } of callbacks) {
108+
callback(states[location], event.data.error)
109+
}
110+
callbacks = undefined
160111
}
161-
// Handle other responses
162-
} else { // It's a request
163-
if (event.data.query === 'docHeight') { // LTIHub v1
164-
const docHeight = document.body.children[0].scrollHeight
165-
document.documentElement.style.height = docHeight + 'px'
166-
document.body.style.height = docHeight + 'px'
167-
document.body.style.overflow = 'auto'
168-
let response = { request: event.data, docHeight }
169-
event.source.postMessage(response, '*' )
170-
window.EPUB.Education.version = 1
171-
}
172-
else if (event.data.query === 'getContent') { // LTIHub v1
173-
const docHeight = document.body.children[0].scrollHeight
174-
document.documentElement.style.height = docHeight + 'px'
175-
document.body.style.height = docHeight + 'px'
176-
const id = element.closest('li').id
177-
const score = { correct: Math.min(element.correct, element.maxscore), errors: element.errors, maxscore: element.maxscore, activity: id }
178-
let response = { request: event.data, score: score, state: element.state }
179-
event.source.postMessage(response, '*' )
180-
} else if (event.data.query === 'restoreState') { // LTIHub v1
181-
window.EPUB.Education.stateToRestore = event.data.state
182-
/*
183-
It is possible that the element already made a
184-
retrieve request (which goes unanswered by the parent).
185-
*/
186-
if (window.EPUB.Education.retrieveCallback !== undefined) { // retrieve request already made
187-
window.EPUB.Education.retrieve(undefined, window.EPUB.Education.retrieveCallback)
188-
// delete window.EPUB.Education.retrieveCallback
189-
// Not deleting--in the instructor view assignments, called more than once
190-
}
191-
}
112+
// Handle other responses
192113
}
114+
// TODO: SPLICE messages from children
193115
}, false)
194-
}
116+
117+
sendDocHeight()
118+
document.body.style.overflow = 'hidden'
119+
// ResizeObserver did not work
120+
const mutationObserver = new MutationObserver(sendDocHeight);
121+
mutationObserver.observe(document.body, { childList: true, subtree: true })
122+
123+
// Translating EPUB to SPLICE
124+
if (!('EPUB' in window))
125+
window.EPUB = {}
126+
if (!('Education' in window.EPUB)) {
127+
window.EPUB.Education = {
128+
retrieve: (request, callback) => {
129+
const location = request.filters[0].activityIds[0]
130+
SPLICE.getState(location, (location, state) => {
131+
callback({ data: [ { data: state } ] })
132+
})
133+
},
134+
send: (request, callback) => {
135+
const location = request.data[0].activityId
136+
const score = request.data[0].results[0].score
137+
const state = request.data[0].state.data
138+
SPLICE.reportScoreAndState(location, score, state)
139+
},
140+
}
141+
}
142+
143+
})
144+

0 commit comments

Comments
 (0)
Please sign in to comment.