|
159 | 159 | 'This browser is too old to run the live viewer. Update your OS/browser, or open this link in the latest Safari or Chrome.' |
160 | 160 | ); |
161 | 161 | } |
| 162 | + |
| 163 | + // Report that the page ran before the viewer module completes its handshake. |
| 164 | + try { |
| 165 | + parent.postMessage({ |
| 166 | + type: 'POPCORN_BOOT', |
| 167 | + unsupported: window.__viewerUnsupported || null, |
| 168 | + ua: navigator.userAgent |
| 169 | + }, '*'); |
| 170 | + } catch (e) { /* no parent, or a parent that refuses '*' */ } |
| 171 | + |
| 172 | + // Surface module evaluation failures to cross-origin embedders. |
| 173 | + if (window.addEventListener) { |
| 174 | + window.addEventListener('error', function (event) { |
| 175 | + // Runtime errors after startup belong to the viewer's own lifecycle, |
| 176 | + // not the boot-failure channel. |
| 177 | + if (window.__viewerBooted) return; |
| 178 | + try { |
| 179 | + parent.postMessage({ |
| 180 | + type: 'POPCORN_BOOT_ERROR', |
| 181 | + msg: String((event && event.message) || ''), |
| 182 | + src: String((event && event.filename) || ''), |
| 183 | + line: (event && event.lineno) || 0 |
| 184 | + }, '*'); |
| 185 | + } catch (e) { /* see above */ } |
| 186 | + }); |
| 187 | + } |
| 188 | + |
| 189 | + // Retry the asset, not this document: the primary, a query-busted retry, |
| 190 | + // then a distinct no-cache fallback. Ten seconds avoids restarting slow boots. |
| 191 | + var BOOT_DEADLINE_MS = 10000; |
| 192 | + var bootAttempt = 0; |
| 193 | + var assetPaths = [ |
| 194 | + 'viewer.bundle.js', |
| 195 | + 'viewer.bundle.js?__pcna=1', |
| 196 | + 'viewer-fallback.bundle.js' |
| 197 | + ]; |
| 198 | + var assetScript; |
| 199 | + var assetSettled = false; |
| 200 | + |
| 201 | + // Rewritten by the Docker build with hashed paths and SRI. |
| 202 | + var assetIntegrity = 'sha384-__VIEWER_BUNDLE_SRI__'; |
| 203 | + |
| 204 | + function reportStall(retrying) { |
| 205 | + try { |
| 206 | + parent.postMessage({ |
| 207 | + type: 'POPCORN_BOOT_STALL', stage: 'asset', attempt: bootAttempt, retrying: retrying, |
| 208 | + path: assetPaths[bootAttempt] |
| 209 | + }, '*'); |
| 210 | + } catch (e) { /* no parent, or a parent that refuses '*' */ } |
| 211 | + } |
| 212 | + |
| 213 | + function terminalAssetFailure() { |
| 214 | + reportStall(false); |
| 215 | + window.__viewerOverlay( |
| 216 | + 'Live view couldn\u2019t start', |
| 217 | + 'The viewer didn\u2019t finish loading. Check your connection and try again.', |
| 218 | + function () { location.reload(); } |
| 219 | + ); |
| 220 | + } |
| 221 | + |
| 222 | + function loadViewerAsset() { |
| 223 | + if (window.__viewerBooted || assetSettled) return; |
| 224 | + if (bootAttempt >= assetPaths.length) { |
| 225 | + assetSettled = true; |
| 226 | + terminalAssetFailure(); |
| 227 | + return; |
| 228 | + } |
| 229 | + if (assetScript && assetScript.parentNode) assetScript.parentNode.removeChild(assetScript); |
| 230 | + assetScript = document.createElement('script'); |
| 231 | + assetScript.type = 'module'; |
| 232 | + assetScript.src = assetPaths[bootAttempt]; |
| 233 | + assetScript.integrity = assetIntegrity; |
| 234 | + assetScript.onerror = function () { |
| 235 | + if (window.__viewerBooted || assetSettled) return; |
| 236 | + try { |
| 237 | + parent.postMessage({ |
| 238 | + type: 'POPCORN_BOOT_ERROR', msg: 'Viewer bundle request failed', |
| 239 | + src: assetPaths[bootAttempt], line: 0 |
| 240 | + }, '*'); |
| 241 | + } catch (e) { /* see above */ } |
| 242 | + if (bootAttempt + 1 >= assetPaths.length) { |
| 243 | + assetSettled = true; |
| 244 | + terminalAssetFailure(); |
| 245 | + return; |
| 246 | + } |
| 247 | + reportStall(true); |
| 248 | + bootAttempt++; |
| 249 | + loadViewerAsset(); |
| 250 | + }; |
| 251 | + document.head.appendChild(assetScript); |
| 252 | + } |
| 253 | + |
| 254 | + if (!window.__viewerUnsupported && window.setTimeout) { |
| 255 | + loadViewerAsset(); |
| 256 | + setTimeout(function watchdog() { |
| 257 | + if (window.__viewerBooted || assetSettled) return; |
| 258 | + if (bootAttempt + 1 >= assetPaths.length) { |
| 259 | + assetSettled = true; |
| 260 | + terminalAssetFailure(); |
| 261 | + return; |
| 262 | + } |
| 263 | + reportStall(true); |
| 264 | + bootAttempt++; |
| 265 | + loadViewerAsset(); |
| 266 | + setTimeout(watchdog, BOOT_DEADLINE_MS); |
| 267 | + }, BOOT_DEADLINE_MS); |
| 268 | + } |
162 | 269 | })(); |
163 | 270 | </script> |
164 | 271 |
|
165 | | - <!-- The entire viewer — noVNC core + the kbd/IME layer + this page's |
166 | | - controller — is bundled by esbuild at build time into one file (see |
167 | | - viewer.js + the Dockerfile bundler step), so it loads in a single request |
168 | | - instead of a ~70-module ES-graph waterfall. Served no-store; a module |
169 | | - script is deferred, so the classic preflight above runs first. --> |
170 | | - <script type="module" src="viewer.bundle.js"></script> |
| 272 | + <!-- The asset loader above starts the content-addressed viewer module. --> |
171 | 273 | </body> |
172 | 274 | </html> |
0 commit comments