PageSourceSearch

https://ars-leipzig.de/wp-content/plugins/h5p/h5p-php-library/js/request-queue.js?ver=1.17.9

js ars-leipzig.de collected 2026-10-04 07:39:41 UTC 11,683 bytes, 436 lines download raw bytes

1/**
2 * Queue requests and handle them at your convenience
3 *
4 * @type {RequestQueue}
5 */
6H5P.RequestQueue = (function ($, EventDispatcher) {
7  /**
8   * A queue for requests, will be automatically processed when regaining connection
9   *
10   * @param {boolean} [options.showToast] Show toast when losing or regaining connection
11   * @constructor
12   */
13  const RequestQueue = function (options) {
14    EventDispatcher.call(this);
15    this.processingQueue = false;
16    options = options || {};
17
18    this.showToast = options.showToast;
19    this.itemName = 'requestQueue';
20  };
21
22  /**
23   * Add request to queue. Only supports posts currently.
24   *
25   * @param {string} url
26   * @param {Object} data
27   * @returns {boolean}
28   */
29  RequestQueue.prototype.add = function (url, data) {
30    if (!window.localStorage) {
31      return false;
32    }
33
34    let storedStatements = this.getStoredRequests();
35    if (!storedStatements) {
36      storedStatements = [];
37    }
38
39    storedStatements.push({
40      url: url,
41      data: data,
42    });
43
44    window.localStorage.setItem(this.itemName, JSON.stringify(storedStatements));
45
46    this.trigger('requestQueued', {
47      storedStatements: storedStatements,
48      processingQueue: this.processingQueue,
49    });
50    return true;
51  };
52
53  /**
54   * Get stored requests
55   *
56   * @returns {boolean|Array} Stored requests
57   */
58  RequestQueue.prototype.getStoredRequests = function () {
59    if (!window.localStorage) {
60      return false;
61    }
62
63    const item = window.localStorage.getItem(this.itemName);
64    if (!item) {
65      return [];
66    }
67
68    return JSON.parse(item);
69  };
70
71  /**
72   * Clear stored requests
73   *
74   * @returns {boolean} True if the storage was successfully cleared
75   */
76  RequestQueue.prototype.clearQueue = function () {
77    if (!window.localStorage) {
78      return false;
79    }
80
81    window.localStorage.removeItem(this.itemName);
82    return true;
83  };
84
85  /**
86   * Start processing of requests queue
87   *
88   * @return {boolean} Returns false if it was not possible to resume processing queue
89   */
90  RequestQueue.prototype.resumeQueue = function () {
91    // Not supported
92    if (!H5PIntegration || !window.navigator || !window.localStorage) {
93      return false;
94    }
95
96    // Already processing
97    if (this.processingQueue) {
98      return false;
99    }
100
101    // Attempt to send queued requests
102    const queue = this.getStoredRequests();
103    const queueLength = queue.length;
104
105    // Clear storage, failed requests will be re-added
106    this.clearQueue();
107
108    // No items left in queue
109    if (!queueLength) {
110      this.trigger('emptiedQueue', queue);
111      return true;
112    }
113
114    // Make sure requests are not changed while they're being handled
115    this.processingQueue = true;
116
117    // Process queue in original order
118    this.processQueue(queue);
119    return true
120  };
121
122  /**
123   * Process first item in the request queue
124   *
125   * @param {Array} queue Request queue
126   */
127  RequestQueue.prototype.processQueue = function (queue) {
128    if (!queue.length) {
129      return;
130    }
131
132    this.trigger('processingQueue');
133
134    // Make sure the requests are processed in a FIFO order
135    const request = queue.shift();
136
137    const self = this;
138    $.post(request.url, request.data)
139      .fail(self.onQueuedRequestFail.bind(self, request))
140      .always(self.onQueuedRequestProcessed.bind(self, queue))
141  };
142
143  /**
144   * Request fail handler
145   *
146   * @param {Object} request
147   */
148  RequestQueue.prototype.onQueuedRequestFail = function (request) {
149    // Queue the failed request again if we're offline
150    if (!window.navigator.onLine) {
151      this.add(request.url, request.data);
152    }
153  };
154
155  /**
156   * An item in the queue was processed
157   *
158   * @param {Array} queue Queue that was processed
159   */
160  RequestQueue.prototype.onQueuedRequestProcessed = function (queue) {
161    if (queue.length) {
162      this.processQueue(queue);
163      return;
164    }
165
166    // Finished processing this queue
167    this.processingQueue = false;
168
169    // Run empty queue callback with next request queue
170    const requestQueue = this.getStoredRequests();
171    this.trigger('queueEmptied', requestQueue);
172  };
173
174  /**
175   * Display toast message on the first content of current page
176   *
177   * @param {string} msg Message to display
178   * @param {boolean} [forceShow] Force override showing the toast
179   * @param {Object} [configOverride] Override toast message config
180   */
181  RequestQueue.prototype.displayToastMessage = function (msg, forceShow, configOverride) {
182    if (!this.showToast && !forceShow) {
183      return;
184    }
185
186    const config = H5P.jQuery.extend(true, {}, {
187      position: {
188        horizontal : 'centered',
189        vertical: 'centered',
190        noOverflowX: true,
191      }
192    }, configOverride);
193
194    H5P.attachToastTo(H5P.jQuery('.h5p-content:first')[0], msg, config);
195  };
196
197  return RequestQueue;
198})(H5P.jQuery, H5P.EventDispatcher);
199
200/**
201 * Request queue for retrying failing requests, will automatically retry them when you come online
202 *
203 * @type {offlineRequestQueue}
204 */
205H5P.OfflineRequestQueue = (function (RequestQueue, Dialog) {
206
207  /**
208   * Constructor
209   *
210   * @param {Object} [options] Options for offline request queue
211   * @param {Object} [options.instance] The H5P instance which UI components are placed within
212   */
213  const offlineRequestQueue = function (options) {
214    const requestQueue = new RequestQueue();
215
216    // We could handle requests from previous pages here, but instead we throw them away
217    requestQueue.clearQueue();
218
219    let startTime = null;
220    const retryIntervals = [10, 20, 40, 60, 120, 300, 600];
221    let intervalIndex = -1;
222    let currentInterval = null;
223    let isAttached = false;
224    let isShowing = false;
225    let isLoading = false;
226    const instance = options.instance;
227
228    const offlineDialog = new Dialog({
229      headerText: H5P.t('offlineDialogHeader'),
230      dialogText: H5P.t('offlineDialogBody'),
231      confirmText: H5P.t('offlineDialogRetryButtonLabel'),
232      hideCancel: true,
233      hideExit: true,
234      classes: ['offline'],
235      instance: instance,
236      skipRestoreFocus: true,
237    });
238
239    const dialog = offlineDialog.getElement();
240
241    // Add retry text to body
242    const countDownText = document.createElement('div');
243    countDownText.classList.add('count-down');
244    countDownText.innerHTML = H5P.t('offlineDialogRetryMessage')
245      .replace(':num', '<span class="count-down-num">0</span>');
246
247    dialog.querySelector('.h5p-confirmation-dialog-text').appendChild(countDownText);
248    const countDownNum = countDownText.querySelector('.count-down-num');
249
250    // Create throbber
251    const throbberWrapper = document.createElement('div');
252    throbberWrapper.classList.add('throbber-wrapper');
253    const throbber = document.createElement('div');
254    throbber.classList.add('sending-requests-throbber');
255    throbberWrapper.appendChild(throbber);
256
257    requestQueue.on('requestQueued', function (e) {
258      // Already processing queue, wait until queue has finished processing before showing dialog
259      if (e.data && e.data.processingQueue) {
260        return;
261      }
262
263      if (!isAttached) {
264        const rootContent = document.body.querySelector('.h5p-content');
265        if (!rootContent) {
266          return;
267        }
268        offlineDialog.appendTo(rootContent);
269        rootContent.appendChild(throbberWrapper);
270        isAttached = true;
271      }
272
273      startCountDown();
274    }.bind(this));
275
276    requestQueue.on('queueEmptied', function (e) {
277      if (e.data && e.data.length) {
278        // New requests were added while processing queue or requests failed again. Re-queue requests.
279        startCountDown(true);
280        return;
281      }
282
283      // Successfully emptied queue
284      clearInterval(currentInterval);
285      toggleThrobber(false);
286      intervalIndex = -1;
287      if (isShowing) {
288        offlineDialog.hide();
289        isShowing = false;
290      }
291
292      requestQueue.displayToastMessage(
293        H5P.t('offlineSuccessfulSubmit'),
294        true,
295        {
296          position: {
297            vertical: 'top',
298            offsetVertical: '100',
299          }
300        }
301      );
302
303    }.bind(this));
304
305    offlineDialog.on('confirmed', function () {
306      // Show dialog on next render in case it is being hidden by the 'confirm' button
307      isShowing = false;
308      setTimeout(function () {
309        retryRequests();
310      }, 100);
311    }.bind(this));
312
313    // Initialize listener for when requests are added to queue
314    window.addEventListener('online', function () {
315      retryRequests();
316    }.bind(this));
317
318    // Listen for queued requests outside the iframe
319    window.addEventListener('message', function (event) {
320      const isValidQueueEvent = window.parent === event.source
321        && event.data.context === 'h5p'
322        && event.data.action === 'queueRequest';
323
324      if (!isValidQueueEvent) {
325        return;
326      }
327
328      this.add(event.data.url, event.data.data);
329    }.bind(this));
330
331    /**
332     * Toggle throbber visibility
333     *
334     * @param {boolean} [forceShow] Will force throbber visibility if set
335     */
336    const toggleThrobber = function (forceShow) {
337      isLoading = !isLoading;
338      if (forceShow !== undefined) {
339        isLoading = forceShow;
340      }
341
342      if (isLoading && isShowing) {
343        offlineDialog.hide();
344        isShowing = false;
345      }
346
347      if (isLoading) {
348        throbberWrapper.classList.add('show');
349      }
350      else {
351        throbberWrapper.classList.remove('show');
352      }
353    };
354    /**
355     * Retries the failed requests
356     */
357    const retryRequests = function () {
358      clearInterval(currentInterval);
359      toggleThrobber(true);
360      requestQueue.resumeQueue();
361    };
362
363    /**
364     * Increments retry interval
365     */
366    const incrementRetryInterval = function () {
367      intervalIndex += 1;
368      if (intervalIndex >= retryIntervals.length) {
369        intervalIndex = retryIntervals.length - 1;
370      }
371    };
372
373    /**
374     * Starts counting down to retrying queued requests.
375     *
376     * @param forceDelayedShow
377     */
378    const startCountDown = function (forceDelayedShow) {
379      // Already showing, wait for retry
380      if (isShowing) {
381        return;
382      }
383
384      toggleThrobber(false);
385      if (!isShowing) {
386        if (forceDelayedShow) {
387          // Must force delayed show since dialog may be hiding, and confirmation dialog does not
388          //  support this.
389          setTimeout(function () {
390            offlineDialog.show(0);
391          }, 100);
392        }
393        else {
394          offlineDialog.show(0);
395        }
396      }
397      isShowing = true;
398      startTime = new Date().getTime();
399      incrementRetryInterval();
400      clearInterval(currentInterval);
401      currentInterval = setInterval(updateCountDown, 100);
402    };
403
404    /**
405     * Updates the count down timer. Retries requests when time expires.
406     */
407    const updateCountDown = function () {
408      const time = new Date().getTime();
409      const timeElapsed = Math.floor((time - startTime) / 1000);
410      const timeLeft = retryIntervals[intervalIndex] - timeElapsed;
411      countDownNum.textContent = timeLeft.toString();
412
413      // Retry interval reached, retry requests
414      if (timeLeft <= 0) {
415        retryRequests();
416      }
417    };
418
419    /**
420     * Add request to offline request queue. Only supports posts for now.
421     *
422     * @param {string} url The request url
423     * @param {Object} data The request data
424     */
425    this.add = function (url, data) {
426      // Only queue request if it failed because we are offline
427      if (window.navigator.onLine) {
428        return false;
429      }
430
431      requestQueue.add(url, data);
432    };
433  };
434
435  return offlineRequestQueue;
436})(H5P.RequestQueue, H5P.ConfirmationDialog);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.