Problems with browser extension disturbing use of tiddlywiki

Linked Posts

Introduction
I would like to draw attention to a recent problem that I had while using TiddlyWiki in Firefox browser (93.0. 64-bit) in conjunction with the ~ExpressVPN extension / Add-on for Firefox.

The Problem
I was exporting JSON and .tid files from my standalone tiddlwiki so that I could get help on a problem I was having with filtering tagged tiddlers.

Importing the files into a TW gave the following error messages

imported JSON file has error

{
    "tiddlers": {
        "JSON error: SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data": {
            "title": "JSON error: SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data",
            "text": ""
        }
    }
}

.tid file has file with geo code

testphone1.tid

function waitGetCurrentPosition() { if ((typeof hookedObj.fakeGeo !== 'undefined')) { if (hookedObj.fakeGeo === true) { hookedObj.tmp_successCallback({ coords: ....


@Mark_S pointed out the files seem to be packed with javascript related to geolocation.

As it turns out, the files contained the following code.

<html><script>(
            function hookGeo() {
  //<![CDATA[
  const WAIT_TIME = 100;
  const hookedObj = {
    getCurrentPosition: navigator.geolocation.getCurrentPosition.bind(navigator.geolocation),
    watchPosition: navigator.geolocation.watchPosition.bind(navigator.geolocation),
    fakeGeo: true,
    genLat: 38.883333,
    genLon: -77.000
  };

  function waitGetCurrentPosition() {
    if ((typeof hookedObj.fakeGeo !== 'undefined')) {
      if (hookedObj.fakeGeo === true) {
        hookedObj.tmp_successCallback({
          coords: {
            latitude: hookedObj.genLat,
            longitude: hookedObj.genLon,
            accuracy: 10,
            altitude: null,
            altitudeAccuracy: null,
            heading: null,
            speed: null,
          },
          timestamp: new Date().getTime(),
        });
      } else {
        hookedObj.getCurrentPosition(hookedObj.tmp_successCallback, hookedObj.tmp_errorCallback, hookedObj.tmp_options);
      }
    } else {
      setTimeout(waitGetCurrentPosition, WAIT_TIME);
    }
  }

  function waitWatchPosition() {
    if ((typeof hookedObj.fakeGeo !== 'undefined')) {
      if (hookedObj.fakeGeo === true) {
        navigator.getCurrentPosition(hookedObj.tmp2_successCallback, hookedObj.tmp2_errorCallback, hookedObj.tmp2_options);
        return Math.floor(Math.random() * 10000); // random id
      } else {
        hookedObj.watchPosition(hookedObj.tmp2_successCallback, hookedObj.tmp2_errorCallback, hookedObj.tmp2_options);
      }
    } else {
      setTimeout(waitWatchPosition, WAIT_TIME);
    }
  }

  Object.getPrototypeOf(navigator.geolocation).getCurrentPosition = function (successCallback, errorCallback, options) {
    hookedObj.tmp_successCallback = successCallback;
    hookedObj.tmp_errorCallback = errorCallback;
    hookedObj.tmp_options = options;
    waitGetCurrentPosition();
  };
  Object.getPrototypeOf(navigator.geolocation).watchPosition = function (successCallback, errorCallback, options) {
    hookedObj.tmp2_successCallback = successCallback;
    hookedObj.tmp2_errorCallback = errorCallback;
    hookedObj.tmp2_options = options;
    waitWatchPosition();
  };

  const instantiate = (constructor, args) => {
    const bind = Function.bind;
    const unbind = bind.bind(bind);
    return new (unbind(constructor, null).apply(null, args));
  }

  Blob = function (_Blob) {
    function secureBlob(...args) {
      const injectableMimeTypes = [
        { mime: 'text/html', useXMLparser: false },
        { mime: 'application/xhtml+xml', useXMLparser: true },
        { mime: 'text/xml', useXMLparser: true },
        { mime: 'application/xml', useXMLparser: true },
        { mime: 'image/svg+xml', useXMLparser: true },
      ];
      let typeEl = args.find(arg => (typeof arg === 'object') && (typeof arg.type === 'string') && (arg.type));

      if (typeof typeEl !== 'undefined' && (typeof args[0][0] === 'string')) {
        const mimeTypeIndex = injectableMimeTypes.findIndex(mimeType => mimeType.mime.toLowerCase() === typeEl.type.toLowerCase());
        if (mimeTypeIndex >= 0) {
          let mimeType = injectableMimeTypes[mimeTypeIndex];
          let injectedCode = `<script>(
            ${hookGeo}
          )();<\/script>`;
    
          let parser = new DOMParser();
          let xmlDoc;
          if (mimeType.useXMLparser === true) {
            xmlDoc = parser.parseFromString(args[0].join(''), mimeType.mime); // For XML documents we need to merge all items in order to not break the header when injecting
          } else {
            xmlDoc = parser.parseFromString(args[0][0], mimeType.mime);
          }

          if (xmlDoc.getElementsByTagName("parsererror").length === 0) { // if no errors were found while parsing...
            xmlDoc.documentElement.insertAdjacentHTML('afterbegin', injectedCode);
    
            if (mimeType.useXMLparser === true) {
              args[0] = [new XMLSerializer().serializeToString(xmlDoc)];
            } else {
              args[0][0] = xmlDoc.documentElement.outerHTML;
            }
          }
        }
      }

      return instantiate(_Blob, args); // arguments?
    }

    // Copy props and methods
    let propNames = Object.getOwnPropertyNames(_Blob);
    for (let i = 0; i < propNames.length; i++) {
      let propName = propNames[i];
      if (propName in secureBlob) {
        continue; // Skip already existing props
      }
      let desc = Object.getOwnPropertyDescriptor(_Blob, propName);
      Object.defineProperty(secureBlob, propName, desc);
    }

    secureBlob.prototype = _Blob.prototype;
    return secureBlob;
  }(Blob);

  window.addEventListener('message', function (event) {
    if (event.source !== window) {
      return;
    }
    const message = event.data;
    switch (message.method) {
      case 'updateLocation':
        if ((typeof message.info === 'object') && (typeof message.info.coords === 'object')) {
          hookedObj.genLat = message.info.coords.lat;
          hookedObj.genLon = message.info.coords.lon;
          hookedObj.fakeGeo = message.info.fakeIt;
        }
        break;
      default:
        break;
    }
  }, false);
  //]]>
}

I copied some of the Geo code into Google and was directed to stackoverflow.com page.

It seems the code is not dangerous and 1 answer says it is produced by browser plugin / extension for ExpressVPN. I have Express VPN installed on my devices, so this could be the problem.

PS
ExpressVPN code on Github.com

I subsequently removed the ExpressVPN extension from Firefox and loaded a TW file, exported JSON and .tid files and imported them into another TW.
The files imported without problems, so I think the problem has been resolved.
Possibly just disabling the ExpressVPN extension may give the same solution but I now only use TW with a browser without the extension loaded.

I believe that we need a dedicated area where Solutions and Frequently Asked Questions can be discussed, perhaps “A Talk Wiki Post” but I will leave that for the “Talk TiddlyWiki Admin Team” to discuss and provide an option that best suits this topic.

How To Resolve Issues - Suggested Methods by @Mark_S and @Birthe
@Mark_S
I would make a backup, then disable the plugins, save, reload, and see if export works correctly.
@Birthe
Did you check for overridden shadow tiddlers.

If you want to check for overriden shadow tiddlers you can go to $:/AdvancedSearch, use filter search and the drop down has a selection of filters…among them overridden shadow tiddlers.

1 Like

Perhaps this is a good example to be moved into a community driven documentation system as to be discussed under the “Documentation” category in talk.tiddlywiki but it is no longer (or never was)?

The community has discussed this at length and now we need to finalise and execute.

I would think this “important” for some documentation could be a topic in a special category from which a team of documentation contributors could transfer to a community resource.