lib/amd/src/network.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Poll the server to keep the session alive.
  17. *
  18. * @module core/network
  19. * @copyright 2019 Damyon Wiese
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
  23. function($, Ajax, Config, Notification, Str) {
  24. var started = false;
  25. var warningDisplayed = false;
  26. var keepAliveFrequency = 0;
  27. var requestTimeout = 0;
  28. var keepAliveMessage = false;
  29. var sessionTimeout = false;
  30. // 1/10 of session timeout, max of 10 minutes.
  31. var checkFrequency = Math.min((Config.sessiontimeout / 10), 600) * 1000;
  32. // Check if sessiontimeoutwarning is set or double the checkFrequency.
  33. var warningLimit = (Config.sessiontimeoutwarning > 0) ? (Config.sessiontimeoutwarning * 1000) : (checkFrequency * 2);
  34. // First wait is minimum of remaining time or half of the session timeout.
  35. var firstWait = (Config.sessiontimeoutwarning > 0) ?
  36. Math.min((Config.sessiontimeout - Config.sessiontimeoutwarning) * 1000, checkFrequency * 5) : checkFrequency * 5;
  37. /**
  38. * The session time has expired - we can't extend it now.
  39. * @param {Modal} modal
  40. */
  41. var timeoutSessionExpired = function(modal) {
  42. sessionTimeout = true;
  43. warningDisplayed = false;
  44. closeModal(modal);
  45. displaySessionExpired();
  46. };
  47. /**
  48. * Close modal - this relies on modal object passed from Notification.confirm.
  49. *
  50. * @param {Modal} modal
  51. */
  52. var closeModal = function(modal) {
  53. modal.destroy();
  54. };
  55. /**
  56. * The session time has expired - we can't extend it now.
  57. * @return {Promise}
  58. */
  59. var displaySessionExpired = function() {
  60. // Check again if its already extended before displaying session expired popup in case multiple tabs are open.
  61. var request = {
  62. methodname: 'core_session_time_remaining',
  63. args: { }
  64. };
  65. return Ajax.call([request], true, true, true)[0].then(function(args) {
  66. if (args.timeremaining * 1000 > warningLimit) {
  67. return false;
  68. } else {
  69. return Str.get_strings([
  70. {key: 'sessionexpired', component: 'error'},
  71. {key: 'sessionerroruser', component: 'error'},
  72. {key: 'loginagain', component: 'moodle'},
  73. {key: 'cancel', component: 'moodle'}
  74. ]).then(function(strings) {
  75. Notification.confirm(
  76. strings[0], // Title.
  77. strings[1], // Message.
  78. strings[2], // Login Again.
  79. strings[3], // Cancel.
  80. function() {
  81. location.reload();
  82. return true;
  83. }
  84. );
  85. return true;
  86. }).catch(Notification.exception);
  87. }
  88. });
  89. };
  90. /**
  91. * Ping the server to keep the session alive.
  92. *
  93. * @return {Promise}
  94. */
  95. var touchSession = function() {
  96. var request = {
  97. methodname: 'core_session_touch',
  98. args: { }
  99. };
  100. if (sessionTimeout) {
  101. // We timed out before we extended the session.
  102. return displaySessionExpired();
  103. } else {
  104. return Ajax.call([request], true, true, false, requestTimeout)[0].then(function() {
  105. if (keepAliveFrequency > 0) {
  106. setTimeout(touchSession, keepAliveFrequency);
  107. }
  108. return true;
  109. }).catch(function() {
  110. Notification.alert('', keepAliveMessage);
  111. });
  112. }
  113. };
  114. /**
  115. * Ask the server how much time is remaining in this session and
  116. * show confirm/cancel notifications if the session is about to run out.
  117. *
  118. * @return {Promise}
  119. */
  120. var checkSession = function() {
  121. var request = {
  122. methodname: 'core_session_time_remaining',
  123. args: { }
  124. };
  125. sessionTimeout = false;
  126. return Ajax.call([request], true, true, true)[0].then(function(args) {
  127. if (args.userid <= 0) {
  128. return false;
  129. }
  130. if (args.timeremaining <= 0) {
  131. return displaySessionExpired();
  132. } else if (args.timeremaining * 1000 <= warningLimit && !warningDisplayed) {
  133. warningDisplayed = true;
  134. Str.get_strings([
  135. {key: 'norecentactivity', component: 'moodle'},
  136. {key: 'sessiontimeoutsoon', component: 'moodle'},
  137. {key: 'extendsession', component: 'moodle'},
  138. {key: 'cancel', component: 'moodle'}
  139. ]).then(function(strings) {
  140. return Notification.confirm(
  141. strings[0], // Title.
  142. strings[1], // Message.
  143. strings[2], // Extend session.
  144. strings[3], // Cancel.
  145. function() {
  146. touchSession();
  147. warningDisplayed = false;
  148. // First wait is minimum of remaining time or half of the session timeout.
  149. setTimeout(checkSession, firstWait);
  150. return true;
  151. },
  152. function() {
  153. // User has cancelled notification.
  154. setTimeout(checkSession, checkFrequency);
  155. }
  156. );
  157. }).then(modal => {
  158. // If we don't extend the session before the timeout - warn.
  159. setTimeout(timeoutSessionExpired, args.timeremaining * 1000, modal);
  160. return;
  161. }).catch(Notification.exception);
  162. } else {
  163. setTimeout(checkSession, checkFrequency);
  164. }
  165. return true;
  166. });
  167. // We do not catch the fails from the above ajax call because they will fail when
  168. // we are not logged in - we don't need to take any action then.
  169. };
  170. /**
  171. * Start calling a function to check if the session is still alive.
  172. */
  173. var start = function() {
  174. if (keepAliveFrequency > 0) {
  175. setTimeout(touchSession, keepAliveFrequency);
  176. } else {
  177. // First wait is minimum of remaining time or half of the session timeout.
  178. setTimeout(checkSession, firstWait);
  179. }
  180. };
  181. /**
  182. * Are we in an iframe and the parent page is from the same Moodle site?
  183. *
  184. * @return {boolean} true if we are in an iframe in a page from this Moodle site.
  185. */
  186. const isMoodleIframe = function() {
  187. if (window.parent === window) {
  188. // Not in an iframe.
  189. return false;
  190. }
  191. // We are in an iframe. Is the parent from the same Moodle site?
  192. let parentUrl;
  193. try {
  194. parentUrl = window.parent.location.href;
  195. } catch (e) {
  196. // If we cannot access the URL of the parent page, it must be another site.
  197. return false;
  198. }
  199. return parentUrl.startsWith(M.cfg.wwwroot);
  200. };
  201. /**
  202. * Don't allow more than one of these polling loops in a single page.
  203. */
  204. var init = function() {
  205. // We only allow one concurrent instance of this checker.
  206. if (started) {
  207. return;
  208. }
  209. started = true;
  210. if (isMoodleIframe()) {
  211. window.console.log('Not starting Moodle session timeout warning in this iframe.');
  212. return;
  213. }
  214. window.console.log('Starting Moodle session timeout warning.');
  215. start();
  216. };
  217. /**
  218. * Start polling with more specific values for the frequency, timeout and message.
  219. *
  220. * @param {number} freq How ofter to poll the server.
  221. * @param {number} timeout The time to wait for each request to the server.
  222. * @param {string} identifier The string identifier for the message to show if session is going to time out.
  223. * @param {string} component The string component for the message to show if session is going to time out.
  224. */
  225. var keepalive = async function(freq, timeout, identifier, component) {
  226. // We only allow one concurrent instance of this checker.
  227. if (started) {
  228. window.console.warn('Ignoring session keep-alive. The core/network module was already initialised.');
  229. return;
  230. }
  231. started = true;
  232. if (isMoodleIframe()) {
  233. window.console.warn('Ignoring session keep-alive in this iframe inside another Moodle page.');
  234. return;
  235. }
  236. window.console.log('Starting Moodle session keep-alive.');
  237. keepAliveFrequency = freq * 1000;
  238. keepAliveMessage = await Str.get_string(identifier, component);
  239. requestTimeout = timeout * 1000;
  240. start();
  241. };
  242. return {
  243. keepalive: keepalive,
  244. init: init
  245. };
  246. });