freenet 0.2.108

Freenet core software
Documentation
(function () {
  var acct = document.getElementById('fnacct');
  var pop = document.getElementById('fnpop');
  var ok = document.getElementById('fnok');
  function setOk(m) {
    ok.textContent = m;
    setTimeout(function () {
      if (ok.textContent === m) ok.textContent = '';
    }, 2500);
  }
  document.getElementById('fnacctbtn').addEventListener('click', function (e) {
    e.stopPropagation();
    pop.classList.toggle('open');
  });
  document.addEventListener('click', function (e) {
    if (!acct.contains(e.target)) pop.classList.remove('open');
  });
  // Clicking into the sandboxed iframe (the app, which fills most of the page)
  // does NOT fire the document click above — the click lands in the iframe's
  // own document. It does blur the shell window, so dismiss the popover on
  // focus loss too, otherwise it stays open until you click the bar again.
  window.addEventListener('blur', function () {
    pop.classList.remove('open');
  });
  document.getElementById('fncopy').addEventListener('click', function () {
    var t =
      typeof __freenet_user_token !== 'undefined' ? __freenet_user_token : null;
    if (!t) {
      setOk('No key on this connection');
      return;
    }
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(t).then(
        function () {
          setOk('Copied to clipboard');
        },
        function () {
          window.prompt('Copy your access key:', t);
        },
      );
    } else {
      window.prompt('Copy your access key:', t);
    }
  });
  document.getElementById('fnrestore').addEventListener('click', function () {
    var v = window.prompt(
      'Paste your saved access key to restore access to your data:',
    );
    if (!v) {
      return;
    }
    v = v.trim();
    if (!v) {
      return;
    }
    try {
      localStorage.setItem('__freenet_user_token__', v);
      location.reload();
    } catch (e) {
      setOk('Storage unavailable');
    }
  });
  document.getElementById('fnnewid').addEventListener('click', function () {
    // Start over with a fresh identity. Deleting the stored token is enough:
    // on reload SHELL_USER_TOKEN_JS mints a new random token because the key is
    // absent. Destructive (the old data on this server is unreachable without
    // the old key), so confirm first and point the user at "Copy key".
    if (
      !window.confirm(
        'Start over with a new identity? Your current data on this server ' +
          'stays under your old access key — copy that key first if you want ' +
          'to get back to it. This browser will switch to a fresh, empty ID.',
      )
    ) {
      return;
    }
    try {
      localStorage.removeItem('__freenet_user_token__');
      location.reload();
    } catch (e) {
      setOk('Storage unavailable');
    }
  });
  document.getElementById('fnexport').addEventListener('click', function () {
    var t =
      typeof __freenet_user_token !== 'undefined' ? __freenet_user_token : null;
    if (!t) {
      setOk('No key on this connection');
      return;
    }
    setOk('Preparing download...');
    // Read the token from the shell-only global and send it in the header the
    // export endpoint requires (never a query param, so it stays out of logs).
    // fetch->blob->download because a plain navigation cannot set the header.
    fetch('/v1/hosted/export', { headers: { 'X-Freenet-User-Token': t } })
      .then(function (r) {
        if (!r.ok) {
          throw new Error('HTTP ' + r.status);
        }
        return r.blob();
      })
      .then(function (blob) {
        var url = URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = 'freenet-data.fnsx';
        document.body.appendChild(a);
        a.click();
        a.remove();
        URL.revokeObjectURL(url);
        setOk('Downloaded freenet-data.fnsx');
      })
      .catch(function (e) {
        setOk('Export failed (' + e.message + ')');
      });
  });

  // "Move to my peer" — the zero-friction magic-link migration (#4592). Mints a
  // one-time pull token on this hosted node (authorized by the shell-only user
  // token) and sends the user to the import confirmation page on their OWN peer,
  // which then pulls the data over HTTPS and imports it. The durable access key
  // never leaves this browser: the bundle is sealed under a fresh ephemeral key
  // held server-side.
  //
  // The DEFAULT is one click: we navigate straight to the peer's import page
  // (top-level navigation to the loopback peer is exempt from mixed-content and
  // CORS, and that page still requires an explicit "Import" click, so there is
  // no drive-by risk). Copy-the-URL stays as a SECONDARY option for a peer on a
  // different computer/browser/profile. Not everyone's peer is on this machine,
  // but making the common case one click is the whole point — "why create
  // friction for a behavior we want the user to do?".
  var migrateOut = document.getElementById('fnmigrateout');
  var migrateLink = document.getElementById('fnmigratelink');
  var migrateOpen = document.getElementById('fnmigrateopen');
  var migrateMsg = document.getElementById('fnmigratemsg');
  // Default local-peer web/ws-api port (the freenet config default, see
  // config.rs default_ws_api_port). Overriding a non-default local port is a
  // future refinement.
  var LOCAL_PEER_PORT = 7509;

  function setMigrateMsg(m) {
    if (migrateMsg) migrateMsg.textContent = m || '';
  }

  function buildMigrateLink(pt) {
    return (
      'http://127.0.0.1:' +
      LOCAL_PEER_PORT +
      '/hosted/import?source=' +
      encodeURIComponent(window.location.origin) +
      '&pt=' +
      encodeURIComponent(pt)
    );
  }

  document.getElementById('fnmigrate').addEventListener('click', function () {
    var t =
      typeof __freenet_user_token !== 'undefined' ? __freenet_user_token : null;
    if (!t) {
      setOk('No key on this connection');
      return;
    }
    // Open the destination tab SYNCHRONOUSLY inside the click gesture so the
    // popup isn't blocked, then point it at the link once minted. (window.open
    // after the async mint resolves would be treated as programmatic and
    // blocked.) If the browser still blocks it, peerWin is null and the
    // revealed "Open on my peer" link is a direct one-click fallback.
    var peerWin = null;
    try {
      peerWin = window.open('', '_blank');
      // sever reverse-tabnabbing back-ref; persists across the later navigation
      if (peerWin) peerWin.opener = null;
    } catch (e) {
      peerWin = null;
    }
    setMigrateMsg('Preparing your one-time migration link...');
    fetch('/v1/hosted/migrate/mint', {
      method: 'POST',
      headers: { 'X-Freenet-User-Token': t },
    })
      .then(function (r) {
        if (!r.ok) {
          throw new Error('HTTP ' + r.status);
        }
        return r.json();
      })
      .then(function (data) {
        var pt = data && data.pull_token;
        if (!pt) {
          throw new Error('no token');
        }
        var link = buildMigrateLink(pt);
        if (migrateLink) migrateLink.value = link;
        if (migrateOpen) migrateOpen.href = link;
        if (migrateOut) migrateOut.hidden = false;
        if (peerWin && !peerWin.closed) {
          // One click: send the tab we opened to the peer's import page.
          peerWin.location = link;
          setMigrateMsg(
            "Opening the import page on your peer. Didn't open? Use " +
              '"Open on my peer" below.',
          );
        } else {
          setMigrateMsg('One-time link ready. Click "Open on my peer".');
        }
      })
      .catch(function (e) {
        // Don't strand a blank tab if minting failed.
        if (peerWin && !peerWin.closed) {
          peerWin.close();
        }
        setMigrateMsg('Could not prepare a link (' + e.message + ')');
      });
  });

  document
    .getElementById('fnmigratecopy')
    .addEventListener('click', function () {
      if (!migrateLink || !migrateLink.value) {
        return;
      }
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(migrateLink.value).then(
          function () {
            setMigrateMsg('Link copied to clipboard');
          },
          function () {
            migrateLink.focus();
            migrateLink.select();
          },
        );
      } else {
        migrateLink.focus();
        migrateLink.select();
      }
    });
})();