/**
* Ce fichier est le seul qui devrait être appelé en cross-domain, il est es5 et doit être chargé par un tag script (de type module ou pas, async ou pas).
*
* Il en global toutes nos fonctions de chargement, mais sans rien charger de plus au départ
* - j3pLoad (player v1)
* - editGraphe (éditeur v1)
* - showParcours (afficheur de parcours v1)
* - spEdit (éditeur v2)
* - spPlay (player v2)
* - spView (afficheur de parcours v2)
* - start (interface de la home)
*
* L’appel d’une de ces fonctions globales va déclencher le chargement de son code js,
* cf {@tutorial chargement}
*
* ATTENTION, ce fichier doit rester en ES5 et ne pas avoir d’import statique ni dynamique,
* il est appelé dans un tag script, ordinaire ou de type module
* (les deux sont possibles pour compatibilité ascendante).
*
* Il n’est pas traité par vite mais copié dans build après chaque build de vite,
* pour gérer les chargements depuis un autre domaine (cf scripts/fixLoader.js).
*
* 1) loader.js
* - déclare nos fonctions en global (pour mémoriser d’éventuels appels et les refaire quand lazyLoader sera chargé)
* - ajoute un <script type="module" src="http…/lazyLoader.js"></script>
*
* 2) lazyLoader remplace les fonctions globales précédentes par une fct qui prend les arguments, charge le js de la fct puis l’appelle.
*
* On aurait pu mettre tout le code de lazyLoader dans ce fichier,
* mais ce loader était destiné à orienter entre deux lazyLoader,
* une version moderne et une version legacy (on verra pour le remettre
* quand il n'y aura plus de code v1 pour voir si le build passe avec le
* plugin legacy de vite).
* Et par ailleurs c'est plus confortable d'avoir lazyLoader en ts
* @fileOverview
*/
// une bonne vieille IIFE pour emballer notre code
(function preload () {
'use strict'
/**
* Détection basique de browser obsolète. On ne teste pas toutes les fonctionnalités définie
* comme étant la baseline (cf https://web-platform-dx.github.io/web-features/), mais les plus fréquentes.
* Baseline est la target de la compilation vite, donc les browsers qui n'en font pas partie
* pouraient avoir des problèmes au runtime
* @return {boolean}
*/
function isBrowserInBaseline () {
try {
var s = document.createElement('script')
// Support des modules ES
if (!('noModule' in s)) return false
// Primitifs/objets globaux indispensables
if (typeof globalThis === 'undefined') return false
if (typeof Promise === 'undefined' || typeof Promise.prototype === 'undefined' || typeof Promise.prototype.finally !== 'function') return false
if (typeof Symbol === 'undefined' || typeof Symbol.iterator === 'undefined') return false
if (typeof Map === 'undefined' || typeof Set === 'undefined' || typeof WeakSet === 'undefined') return false
// Réseau / URL modernes
if (typeof fetch !== 'function') return false
if (typeof URL === 'undefined' || typeof URLSearchParams === 'undefined') return false
// Méthodes ES2015+ fréquemment utilisées par les toolchains modernes
if (typeof Object.assign !== 'function') return false
if (typeof Array.from !== 'function') return false
if (!Array.prototype || typeof Array.prototype.includes !== 'function') return false
if (!String.prototype || typeof String.prototype.startsWith !== 'function') return false
if (!String.prototype || typeof String.prototype.endsWith !== 'function') return false
// Microtasks et observer modernes utiles aux libs (facultatif mais discriminant)
// queueMicrotask est largement disponible sur les navigateurs "baseline"
if (typeof queueMicrotask !== 'function') return false
// IntersectionObserver est dans la baseline depuis longtemps et souvent requis par libs UI
if (typeof IntersectionObserver === 'undefined') return false
// On arrête là nos tests en supposant que tout devrait bien se passer
return true
} catch (e) {
return false
}
}
// appelle le loader dès qu'il est chargé, avec les mêmes arguments
function waitForLoader () {
var loader = arguments[0]
// le 1er argument est le loader, imposé par le bind plus bas, les suivants ceux passés au loader
// on met en attente que le timeout soit atteint ou pas (s’il est atteint parce que la connexion est très mauvaise
// l’utilisateur aura le message mais ça finira par être lancé, sinon ça ne sera jamais traité
// au cas où ce serait déjà là
if (typeof window[loader] === 'function') {
if (window.spLoading[loader] === 'preload') {
var originalArgs = arguments
nbWait++
if (nbWait > 200) return console.error(Error('Loader ' + loader + ' toujours pas chargé après 20s, ABANDON'))
setTimeout(function () {
waitForLoader.apply(null, originalArgs)
}, 100)
return
}
// c'est plus notre preLoader, on l'appelle en virant le 1er argument qui est son nom
// avec un try/catch en cas de throw sync du loader
try {
var result = window[loader].apply(null, Array.from(arguments).slice(1))
if (result instanceof Promise) {
result.catch(function (error) { console.error(error) })
}
} catch (error) {
console.error(error)
}
} else {
console.error(Error('Le loader ' + loader + ' n’existe plus'))
}
}
// ajoute le script de chargement du lazyLoader dans le dom
function addScript () {
if (script.inDom) return console.error(Error('tag script déjà dans le dom'))
try {
document.body.appendChild(script)
} catch (error) {
console.error(error)
return setTimeout(addScript, 200)
}
// appendChild a fonctionné
script.inDom = true
script.src = loaderUrl
}
// MAIN code
if (window.spLoading) {
console.warn('loader.js a déjà été chargé dans ce DOM')
return
}
window.spLoading = {}
var script, timeoutId, loaderUrl
var nbWait = 0
try {
script = document.createElement('script')
// on a pas vraiment de moyen fiable de détecter si l'import dynamique est supporté par le navigateur
// (cf https://stackoverflow.com/questions/60317251/how-to-feature-detect-whether-a-browser-supports-dynamic-es6-module-loading)
// on détecte ici les cas triviaux (pas de support du type module ou pas de Promise),
// et pour les autres l'import dynamique du lazyLoader marchera pas et ça va s'arrêter là
if (!isBrowserInBaseline()) {
// eslint-disable-next-line no-alert
return alert('Désolé, ce navigateur ne permet pas d’exécuter les exercices interactifs, essayez avec un navigateur plus récent.')
}
timeoutId = setTimeout(function timeout () {
// eslint-disable-next-line no-alert
alert('Après 30s d’attente le chargement de j3p n’est toujours pas terminé, c’est probablement inutile d’attendre davantage.')
}, 30000)
// pas de for…of en es5
;['editGraphe', 'j3pLoad', 'showParcours', 'spEdit', 'spPlay', 'spView', 'spValidate'].forEach(function (loader) {
if (typeof window[loader] !== 'function') {
window[loader] = waitForLoader.bind(null, loader)
// pour marquer l'étape de chargement (et permettre de vérifier qu’il a bien été écrasé par l’original)
window.spLoading[loader] = 'preload'
} else {
console.error(Error(loader + ' était déjà déclaré en global'))
}
})
// on charge lazyLoader en module (pour qu’il puisse faire des imports)
// NE PAS MODIFIER ces lignes, elles seront complétées par scripts/postBuild.sh
// (j3pVersion sera utile au bugsnag mis par la bibli pour détecter les changements de version de j3p)
loaderUrl = ''
window.j3pVersion = ''
// fin lignes à ne pas modifier
script.type = 'module'
// ça c’est dans tous les cas
script.crossOrigin = 'anonymous'
// quand lazyLoader sera chargé
script.addEventListener('load', function onLazyLoaderLoaded () {
clearTimeout(timeoutId)
})
// c’est mieux de faire dans cet ordre pour une meilleure compatibilité avec tous les navigateurs
// (script elt puis insertion dans le dom puis affectation src)
// mais avant d'ajouter le script dans body, il faut vérifier que le dom est chargé
// (pas forcément le cas si on est chargé dans un <head>)
if (document.readyState === 'interactive' || document.readyState === 'complete') addScript()
else document.addEventListener('DOMContentLoaded', addScript)
} catch (error) {
console.error(error)
}
})()