latest.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2017-2019 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /*=====================================================================*/
  18. (function () {
  19. /**
  20. * The various CDNs and their data for how to obtain versions
  21. */
  22. var CDN = {
  23. 'cdnjs.cloudflare.com': {
  24. api: 'https://api.cdnjs.com/libraries/mathjax?fields=version',
  25. key: 'version',
  26. base: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/'
  27. },
  28. 'rawcdn.githack.com': {
  29. api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
  30. key: 'tag_name',
  31. base: 'https://rawcdn.githack.com/mathjax/MathJax/'
  32. },
  33. 'gitcdn.xyz': {
  34. api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
  35. key: 'tag_name',
  36. base: 'https://gitcdn.xyz/mathjax/MathJax/'
  37. },
  38. 'cdn.statically.io': {
  39. api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
  40. key: 'tag_name',
  41. base: 'https://cdn.statically.io/gh/mathjax/MathJax/'
  42. },
  43. 'unpkg.com': {
  44. api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
  45. key: 'tag_name',
  46. base: 'https://unpkg.com/mathjax@'
  47. },
  48. 'cdn.jsdelivr.net': {
  49. api: 'https://api.github.com/repos/mathjax/mathjax/releases/latest',
  50. key: 'tag_name',
  51. base: 'https://cdn.jsdelivr.net/npm/mathjax@'
  52. }
  53. };
  54. /**
  55. * The data for getting release versions from GitHub
  56. */
  57. var GITHUB = {
  58. api: 'https://api.github.com/repos/mathjax/mathjax/releases',
  59. key: 'tag_name'
  60. };
  61. /**
  62. * The major version number for MathJax (we will load the highest version with this initial number)
  63. */
  64. var MJX_VERSION = 2;
  65. /**
  66. * The name to use for the version in localStorage
  67. */
  68. var MJX_LATEST = 'mjx-latest-version' + MJX_VERSION;
  69. /**
  70. * The amount of time a cached version number is valid
  71. */
  72. var SAVE_TIME = 1000 * 60 * 60 * 24 * 7; // one week
  73. /**
  74. * Data for the script that loaded latest.js
  75. */
  76. var script = null;
  77. /*=====================================================================*/
  78. /**
  79. * Produce an error message on the console
  80. *
  81. * @param {string} message The error message to display
  82. */
  83. function Error(message) {
  84. if (console && console.error) {
  85. console.error('MathJax(latest.js): ' + message);
  86. }
  87. }
  88. /**
  89. * Create a ScriptData object from the given script tag and CDN
  90. *
  91. * @param {HTMLScriptElement} script The script tag whose data is desired
  92. * @param {CdnData} cdn The CDN data already obtained for the script (or null)
  93. * @return {ScriptData} The data for the given script
  94. */
  95. function scriptData(script, cdn) {
  96. script.parentNode.removeChild(script);
  97. var src = script.src;
  98. var config = src.replace(/.*?\/latest\.js(\?|$)/, '$1');
  99. var data = (src.match(/(\d+\.\d+\.\d+)(\/unpacked)?\/latest.js\?/) || ['', '', '']);
  100. return {
  101. tag: script,
  102. src: src,
  103. id: script.id,
  104. version: data[1],
  105. unpacked: data[2] || '',
  106. config: config,
  107. cdn: cdn
  108. };
  109. }
  110. /**
  111. * Check if a script refers to MathJax on one of the CDNs
  112. *
  113. * @param {HTMLScriptElement} script The script tag to check
  114. * @return {boolean} True if the script is from a MathJax CDN
  115. */
  116. function checkScript(script) {
  117. var CDNs = Object.keys(CDN);
  118. for (var i = 0, m = CDNs.length; i < m; i++) {
  119. var cdn = CDN[CDNs[i]];
  120. var url = cdn.base;
  121. var src = script.src;
  122. if (src && src.substr(0, url.length) === url && src.match(/\/latest\.js(\?|$)/)) {
  123. return scriptData(script, cdn);
  124. }
  125. }
  126. return null;
  127. }
  128. /**
  129. * @return {ScriptData} The data for the script tag that loaded latest.js
  130. */
  131. function getScript() {
  132. if (document.currentScript) {
  133. return scriptData(document.currentScript);
  134. }
  135. var script = document.getElementById('MathJax-script');
  136. if (script && script.nodeName.toLowerCase() === 'script') {
  137. return checkScript(script);
  138. }
  139. var scripts = document.getElementsByTagName('script');
  140. for (var i = 0, m = scripts.length; i < m; i++) {
  141. var data = checkScript(scripts[i]);
  142. if (data) {
  143. return data;
  144. }
  145. }
  146. return null;
  147. }
  148. /*=====================================================================*/
  149. /**
  150. * Save the version and date information in localStorage so we don't
  151. * have to contact the CDN for every page that uses MathJax.
  152. *
  153. * @param {string} version The version to save
  154. */
  155. function saveVersion(version) {
  156. try {
  157. var data = version + ' ' + Date.now();
  158. localStorage.setItem(MJX_LATEST, data);
  159. } catch (err) {}
  160. }
  161. /**
  162. * Get the version from localStorage, and make sure it is fresh enough to use
  163. *
  164. * @return {string|null} The version string (if one has been saved) or null (if not)
  165. */
  166. function getSavedVersion() {
  167. try {
  168. var data = localStorage.getItem(MJX_LATEST).split(/ /);
  169. var version = data[0], date = data[0];
  170. if (date && Date.now() - parseInt(date) < SAVE_TIME) {
  171. return version;
  172. }
  173. } catch (err) {}
  174. return null;
  175. }
  176. /*=====================================================================*/
  177. /**
  178. * Create a script tag that loads the given URL
  179. *
  180. * @param {string} url The URL of the javascript file to be loaded
  181. * @param {string} id The id to use for the script tag
  182. */
  183. function loadMathJax(url, id) {
  184. var script = document.createElement('script');
  185. script.type = 'text/javascript';
  186. script.async = true;
  187. script.src = url;
  188. if (id) {
  189. script.id = id;
  190. }
  191. var head = document.head || document.getElementsByTagName('head')[0] || document.body;
  192. if (head) {
  193. head.appendChild(script);
  194. } else {
  195. Error('Can\'t find the document <head> element');
  196. }
  197. }
  198. /**
  199. * When we can't find the current version, use the original URL but remove the "latest.js"
  200. */
  201. function loadDefaultMathJax() {
  202. if (script) {
  203. loadMathJax(script.src.replace(/\/latest\.js/, '/MathJax.js'), script.id);
  204. } else {
  205. Error('Can\'t determine the URL for loading MathJax');
  206. }
  207. }
  208. /**
  209. * Load the given version using the base URL and file to load
  210. * (if the versions differ, run latest.js from the new version
  211. * in case there are important changes there)
  212. *
  213. * @param {string} version The version of MathJax to load from
  214. */
  215. function loadVersion(version) {
  216. var file = 'MathJax.js' + script.config;
  217. if (script.version && script.version !== version) {
  218. file = 'latest.js' + script.config;
  219. }
  220. loadMathJax(script.cdn.base + version + script.unpacked + '/' + file, script.id);
  221. }
  222. /**
  223. * Check if the given version is acceptable and load it if it is.
  224. *
  225. * @param {string} version The version to check if it is the latest (valid) one
  226. * @return {boolean} True if it is the latest version, false if not
  227. */
  228. function checkVersion(version) {
  229. var major = parseInt(version.split(/\./)[0]);
  230. if (major === MJX_VERSION && !version.match(/-(beta|rc)/)) {
  231. saveVersion(version);
  232. loadVersion(version);
  233. return true;
  234. }
  235. return false;
  236. }
  237. /*=====================================================================*/
  238. /**
  239. * Create an XMLHttpRequest object, if possible
  240. *
  241. * @return {XMLHttpRequest} The XMLHttpRequest instance
  242. */
  243. function getXMLHttpRequest() {
  244. if (window.XMLHttpRequest) {
  245. return new XMLHttpRequest();
  246. }
  247. if (window.ActiveXObject) {
  248. try {return new window.ActiveXObject('Msxml2.XMLHTTP')} catch (err) {}
  249. try {return new window.ActiveXObject('Microsoft.XMLHTTP')} catch (err) {}
  250. }
  251. }
  252. /**
  253. * Request JSON data from a CDN. If it loads OK, call the action() function
  254. * on the data. If not, or if the action returns false, run the failure() function.
  255. *
  256. * @param {CdnData} cdn The CDN whose API will be used
  257. * @param {Function} action The function to perform when the data are received
  258. * @param {Function} failure The function to perform if data can't be obtained,
  259. * or if action() returns false
  260. */
  261. function requestXML(cdn, action, failure) {
  262. var request = getXMLHttpRequest();
  263. if (request) {
  264. request.onreadystatechange = function () {
  265. if (request.readyState === 4) {
  266. if (request.status === 200) {
  267. !action(JSON.parse(request.responseText)) && failure();
  268. } else {
  269. Error('Problem acquiring MathJax version: status = ' + request.status);
  270. failure();
  271. }
  272. }
  273. };
  274. request.open('GET', cdn.api, true);
  275. request.send(null);
  276. } else {
  277. Error('Can\'t create XMLHttpRequest object');
  278. failure();
  279. }
  280. }
  281. /**
  282. * Look through the list of versions on GitHub and find the first one that
  283. * has the MJX_VERSION as its major version number, and load that. If none
  284. * is found, run the version from which latest.js was loaded.
  285. */
  286. function loadLatestGitVersion() {
  287. requestXML(GITHUB, function (json) {
  288. if (!(json instanceof Array)) return;
  289. for (var i = 0, m = json.length; i < m; i++) {
  290. if (checkVersion(json[i][GITHUB.key])) {
  291. return true;
  292. }
  293. }
  294. return false;
  295. }, loadDefaultMathJax);
  296. }
  297. /**
  298. * Check the CDN for its latest version, and load that, if it is an
  299. * acceptable version, otherwise, (e.g., the current version has a
  300. * higher major version that MJX_VERSION), find the highest version on
  301. * GitHub with the given major version and use that. If one can't be
  302. * found, use the version where latest.js was loaded.
  303. */
  304. function loadLatestCdnVersion() {
  305. requestXML(script.cdn, function (json) {
  306. if (json instanceof Array) {
  307. json = json[0];
  308. }
  309. if (!checkVersion(json[script.cdn.key])) {
  310. loadLatestGitVersion();
  311. }
  312. return true;
  313. }, loadDefaultMathJax);
  314. }
  315. /*=====================================================================*/
  316. /*
  317. * Find the script that loaded latest.js
  318. * If the script is from a known CDN:
  319. * Retrieve the cached version (if any)
  320. * Load the given version of the file, if the version is cached,
  321. * Otherwise find the latest version and load that.
  322. * Otherwise,
  323. * Load using the version where latest.js was loaded.
  324. */
  325. script = getScript();
  326. if (script && script.cdn) {
  327. var version = getSavedVersion();
  328. version ?
  329. loadVersion(version) :
  330. loadLatestCdnVersion();
  331. } else {
  332. loadDefaultMathJax();
  333. }
  334. })();