Newer
Older
CrypticOreWallet / src / global / legacy / LegacyMigration.tsx
@BuildTools BuildTools on 9 Jun 2021 3 KB im gay
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of TenebraWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/TenebraWeb2/blob/master/LICENSE.txt
import { useState, useEffect } from "react";

import { BackupFormatType, BackupTenebraWebV1 } from "@pages/backup/backupFormats";
import { LegacyMigrationModal } from "./LegacyMigrationModal";

import Debug from "debug";
const debug = Debug("tenebraweb:legacy-migration");

const MIGRATION_CLEANUP_THRESHOLD = 7 * 24 * 60 * 60 * 1000;
const LEGACY_KEY_RE = /^(?:(?:Wallet|Friend)(?:-.+)?|salt|tester)$/;

// 7 days after a legacy migration, the old data can most likely now be removed
// from local storage.
function removeOldData() {
  debug("checking to remove old data");

  const migratedTime = localStorage.getItem("legacyMigratedTime");
  if (!migratedTime) {
    debug("no migrated time, done");
    return;
  }

  const t = new Date(migratedTime);
  const now = new Date();
  const diff = now.getTime() - t.getTime();
  debug("%d ms since migration", diff);

  if (diff <= MIGRATION_CLEANUP_THRESHOLD) {
    debug("migration threshold not enough time, done");
    return;
  }

  // Remove all the old data from local storage, including the old salt and
  // tester, the wallets, and the friends.
  const keysToRemove = [];
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    if (key && LEGACY_KEY_RE.test(key)) {
      // Push the keys to a queue to remove after looping, because otherwise
      // localStorage.length would change during iteration.
      debug("removing key %s", key);
      keysToRemove.push(key);
    }
  }

  // Now actually remove the keys
  keysToRemove.forEach(k => localStorage.removeItem(k));

  // Clean up legacyMigratedTime so this doesn't happen again
  localStorage.removeItem("legacyMigratedTime");

  debug("legacy migration cleanup done");
}

export function LegacyMigration(): JSX.Element | null {
  const [backup, setBackup] = useState<BackupTenebraWebV1>();

  // Check if a legacy migration needs to be performed
  useEffect(() => {
    debug("checking legacy migration status");

    // Check if legacy migration has already been handled
    const legacyMigrated = localStorage.getItem("migrated");
    if (legacyMigrated === "2") {
      debug("migration already at 2, done");
      removeOldData();
      return;
    }

    // Check if there is a v1 master password in local storage
    const salt = localStorage.getItem("salt") || undefined;
    const tester = localStorage.getItem("tester") || undefined;
    if (!salt || !tester) {
      debug("no legacy master password, done");
      return;
    }

    // Check if there are any v1 wallets or contacts in local storage
    const walletIndex = localStorage.getItem("Wallet") || undefined;
    const contactIndex = localStorage.getItem("Friend") || undefined;
    if (!walletIndex && !contactIndex) {
      debug("no wallets or contacts, done");
      return;
    }

    // Fetch all the wallets and contacts, skipping over any that are missing
    const wallets = Object.fromEntries((walletIndex || "")
      .split(",")
      .map(id => [`Wallet-${id}`, localStorage.getItem(`Wallet-${id}`)])
      .filter(([_, v]) => !!v));
    const contacts = Object.fromEntries((contactIndex || "")
      .split(",")
      .map(id => [`Friend-${id}`, localStorage.getItem(`Friend-${id}`)])
      .filter(([_, v]) => !!v));
    debug("found %d wallets and %d contacts",
      Object.keys(wallets).length, Object.keys(contacts).length);

    // Construct the backup object prior to showing the modal
    const backup: BackupTenebraWebV1 = {
      type: BackupFormatType.KRISTWEB_V1,

      salt,
      tester,

      wallets,
      friends: contacts
    };

    setBackup(backup);
  }, []);

  return backup
    ? <LegacyMigrationModal
      backup={backup}
      setVisible={() => setBackup(undefined)}
    />
    : null;
}