forked from cayhorstmann/codecheck2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
receiveMessage.js
194 lines (170 loc) · 7.76 KB
/
receiveMessage.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/*
Protocol:
data.
query (request from parent to child) 'docHeight', 'getContent', 'restoreState' (LTIHub v1)
(request from child to parent) 'docHeight', 'send', 'retrieve' (LTIHub v2)
request (response) the request
state (restoreState request from parent to child, v1)
state (getContent response from child to parent, v1)
nonce (request from child to parent) a nonce to be returned with
the response, for non-void requests ('retrieve', v2)
docHeight (docHeight request, response from child to parent, v1)
score (getContent response from child to parent, v1)
param (request or response) parameter object (v2)
v2 details:
Always from child to parent
{ query: 'docHeight', param: { docHeight: ... } }
No qid because applies to whole frame
No response
{ query: 'send', param: { qid: ..., state: ..., score: ... }}
Score between 0 and 1
No response
{ query: 'retrieve', param: { qid: ... } }
Response: { request: ..., param: state }
TODO: Why not param: { state: ..., score: ... }
If we want that, must adapt workAssignment.js, receiveMessage.js, codecheck.js
*/
if (window.self !== window.top) { // iframe
if (!('EPUB' in window))
window.EPUB = {}
if (!('Education' in window.EPUB)) {
window.EPUB.Education = {
nonceMap: {},
retrieveCallback: undefined, // LTIHub v1
retrieve: (request, callback) => {
window.EPUB.Education.retrieveCallback = callback // LTIHub v1
if ('stateToRestore' in window.EPUB.Education) { // LTIHub v1, restore data already arrived
callback({ data: [ { data: window.EPUB.Education.stateToRestore } ] })
delete window.EPUB.Education.stateToRestore
return
}
// Register callback
const nonce = generateUUID()
window.EPUB.Education.nonceMap[nonce] = callback
if (window.EPUB.Education.version !== 1) { // LTIHub v1
// Pass request and nonce to parent
// TODO: VitalSource format
const qid = request.filters[0].activityIds[0]
const param = { qid }
const data = { query: 'retrieve', param, nonce }
window.parent.postMessage(data, '*' )
}
const MESSAGE_TIMEOUT = 5000
setTimeout(() => {
if ('stateToRestore' in window.EPUB.Education) { // LTIHub v1, restore data already arrived and delivered
delete window.EPUB.Education.stateToRestore
return
}
if (!(nonce in window.EPUB.Education.nonceMap)) return
delete window.EPUB.Education.nonceMap[nonce]
// TODO: VitalSource format
callback({ data: [ { data: null } ] })
}, MESSAGE_TIMEOUT)
},
send: (request, callback) => {
if (window.EPUB.Education.version === 1) return // LTIHub v1
// TODO: VitalSource format
const param = { state: request.data[0].state.data, score: request.data[0].results[0].score, qid: request.data[0].activityId }
const data = { query: 'send', param }
window.parent.postMessage(data, '*' )
sendDocHeight()
},
}
}
// https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
function generateUUID() { // Public Domain/MIT
var d = new Date().getTime()
var d2 = (performance && performance.now && (performance.now() * 1000)) || 0 // Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
let r = Math.random() * 16 // random number between 0 and 16
if(d > 0) { // Use timestamp until depleted
r = (d + r) % 16 | 0
d = Math.floor(d / 16)
} else { // Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0
d2 = Math.floor(d2/16)
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
})
}
let element = undefined
let docHeight = 0
function sendDocHeight() {
window.scrollTo(0, 0)
const SEND_DOCHEIGHT_DELAY = 100
if (window.EPUB.Education.version === 1) return // TODO
setTimeout(() => {
const container = element === undefined ? document.body : element.closest('li').parentNode
// When using container = document.documentElement, the document grows without bound
// TODO: Why does this work in codecheck.js but not here?
let newDocHeight = container.scrollHeight + container.offsetTop
if (docHeight != newDocHeight) {
docHeight = newDocHeight
const data = { query: 'docHeight', param: { docHeight } }
window.parent.postMessage(data, '*' )
}
}, SEND_DOCHEIGHT_DELAY)
}
window.addEventListener('load', event => {
const interactiveElements = [...document.querySelectorAll('div, ol')].
filter(e => {
const ty = e.tagName
const cl = e.getAttribute('class')
return cl && (ty === 'div' && cl.indexOf('horstmann_') == 0 || ty === 'ol' && (cl.indexOf('multiple-choice') == 0 || cl.indexOf('horstmann_ma') == 0))
})
element = interactiveElements[0]
sendDocHeight()
document.body.style.overflow = 'hidden'
// ResizeObserver did not work
const mutationObserver = new MutationObserver(sendDocHeight);
mutationObserver.observe(element === undefined ? document.body : element, { childList: true, subtree: true })
})
window.addEventListener("message", event => {
if (!(event.data instanceof Object)) return
if ('request' in event.data) { // It's a response
const request = event.data.request
if (request.query === 'retrieve') { // LTIHub v2
const state = event.data.param
// TODO Old VitalSource API
// let state = response.data[0].data
const arg = { data: [ { data: state } ] }
if (request.nonce in window.EPUB.Education.nonceMap) {
// If not, already timed out
window.EPUB.Education.nonceMap[request.nonce](arg)
delete window.EPUB.Education.nonceMap[request.nonce]
}
}
// Handle other responses
} else { // It's a request
if (event.data.query === 'docHeight') { // LTIHub v1
const docHeight = document.body.children[0].scrollHeight
document.documentElement.style.height = docHeight + 'px'
document.body.style.height = docHeight + 'px'
document.body.style.overflow = 'auto'
let response = { request: event.data, docHeight }
event.source.postMessage(response, '*' )
window.EPUB.Education.version = 1
}
else if (event.data.query === 'getContent') { // LTIHub v1
const docHeight = document.body.children[0].scrollHeight
document.documentElement.style.height = docHeight + 'px'
document.body.style.height = docHeight + 'px'
const id = element.closest('li').id
const score = { correct: Math.min(element.correct, element.maxscore), errors: element.errors, maxscore: element.maxscore, activity: id }
let response = { request: event.data, score: score, state: element.state }
event.source.postMessage(response, '*' )
} else if (event.data.query === 'restoreState') { // LTIHub v1
window.EPUB.Education.stateToRestore = event.data.state
/*
It is possible that the element already made a
retrieve request (which goes unanswered by the parent).
*/
if (window.EPUB.Education.retrieveCallback !== undefined) { // retrieve request already made
window.EPUB.Education.retrieve(undefined, window.EPUB.Education.retrieveCallback)
// delete window.EPUB.Education.retrieveCallback
// Not deleting--in the instructor view assignments, called more than once
}
}
}
}, false)
}