diff options
-rw-r--r-- | templates/web/base/offline/service_worker.html | 27 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 6 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/offline.js | 52 |
3 files changed, 76 insertions, 9 deletions
diff --git a/templates/web/base/offline/service_worker.html b/templates/web/base/offline/service_worker.html index 3c480f9b1..71e5c282a 100644 --- a/templates/web/base/offline/service_worker.html +++ b/templates/web/base/offline/service_worker.html @@ -52,15 +52,30 @@ addEventListener('fetch', fetchEvent => { } catch { fetchEvent.waitUntil(async function() { - var text = await request.text(); - let formData = new URLSearchParams(text); - formData.set('save', 2); - formData.set('saved_at', Math.floor(+new Date() / 1000)); - formData = formData.toString(); + var request_buffer = await request.arrayBuffer(); + var headers = request.headers; + let formData = {}; + formData.contentType = headers.get('Content-Type'); + let boundary_re = /.*boundary=(.*)/; + let bound = formData.contentType.match(boundary_re); + + let saved = '--' + bound[1] + "\r\nContent-Disposition: form-data; name=\"saved_at\"\r\n\r\n" + Math.floor(+new Date() / 1000) + "\r\n"; + var savedBuffer = new ArrayBuffer(saved.length); + var bufView = new Uint8Array(savedBuffer); + for (var i=0; i<saved.length; i++) { + bufView[i] = saved.charCodeAt(i); + } + + var tmp = new Uint8Array(request_buffer.byteLength + savedBuffer.byteLength); + tmp.set(new Uint8Array(request_buffer), 0); + tmp.set(bufView, 0); + tmp.set(new Uint8Array(request_buffer), savedBuffer.byteLength); + + formData.text = tmp.buffer; var data = await idbKeyval.get('offlineData') || { cachedReports: {}, forms: [] }; var forms = data.forms; - if (!forms.length || formData != forms[forms.length - 1][1]) { + if (!forms.length || tmp.toString() != new Uint8Array(forms[forms.length - 1][1].text).toString()) { forms.push([request.url, formData]); } return idbKeyval.set('offlineData', data); diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index 8073530c6..ad29ab470 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -666,6 +666,12 @@ $.extend(fixmystreet.set_up, { return; } + // we don't want to create this if we're offline (e.g using the inspector + // panel to add a photo) as the server side bit does not work. + if (!navigator.onLine) { + return; + } + // Pass a jQuery element, eg $('.foobar'), into this function // to limit all the selectors to that element. Handy if you want // to only bind/detect Dropzones in a particular part of the page, diff --git a/web/cobrands/fixmystreet/offline.js b/web/cobrands/fixmystreet/offline.js index 908326a69..c6609fe1a 100644 --- a/web/cobrands/fixmystreet/offline.js +++ b/web/cobrands/fixmystreet/offline.js @@ -28,6 +28,23 @@ fixmystreet.offlineBanner = (function() { $('.top_banner--offline').slideUp(); } + // Compare two typed arrays for equality + function isEqual(view1, view2) { + for (var i=0; i != view1.byteLength; i++) { + if (view1[i] != view2[i]) return false; + } + return true; + } + + // Create a Uint8Array of a string + function makeView(str) { + var view = new Uint8Array(str.length); + for (var i=0; i<str.length; i++) { + view[i] = str.charCodeAt(i); + } + return view; + } + return { make: function(offline) { fixmystreet.offlineData.getFormsLength().then(function(num) { @@ -61,7 +78,13 @@ fixmystreet.offlineBanner = (function() { } function postForm(url, data) { - return $.ajax({ url: url, data: data, type: 'POST' }).done(nextForm); + return $.ajax({ + url: url, + contentType: data.contentType, + data: data.text, + type: 'POST', + processData: false + }).done(nextForm); } $(document).on('click', '#oFN', function(e) { @@ -83,8 +106,23 @@ fixmystreet.offlineBanner = (function() { if (!token) { return nextForm(); } - var param = form[1].replace(/&token=[^&]*/, '&token=' + token); - return postForm(form[0], param).fail(nextForm); + + var tokenView = makeView(token); + var tokenName = makeView('name="token"\r\n\r\n'); + + // Make a typed array to update the request body with + // This only works because tokens are always the same length + var curView = new Uint8Array(form[1].text); + + // Find the spot at which the token is in the buffer + var idxS = curView.findIndex(function isToken(element, i, array) { + var sl = array.slice(i, i+tokenName.byteLength); + return isEqual(sl, tokenName); + }); + // Replace the old token with the new one in the right spot + curView.set(tokenView, idxS + tokenName.byteLength); + + return postForm(form[0], form[1]).fail(nextForm); }); }); }); @@ -302,6 +340,14 @@ fixmystreet.offline = (function() { $('.moderate-display.segmented-control, .shadow-wrap, #update_form, #report-cta, .mysoc-footer, .nav-wrapper').hide(); $('.js-back-to-report-list').attr('href', '/my/planned'); + // On iOS we want to hide the photo fields on the offline inspector + // form because including a photo entirely breaks the form submission. + if (/iPad|iPhone|iPod/.test(navigator.platform) || + (/Mac/.test(navigator.userAgent) && 'ontouchend' in document)) // iPadOS 13 pretends to be a desktop Mac + { + $("#form_photos, label[for=form_photo]").hide(); + } + // Refill form with saved data if there is any fixmystreet.offlineData.getForms().then(function(forms) { var savedForm; |