recoverFrom method

Future<Database> recoverFrom(
  1. String localId,
  2. String phrase, {
  3. required String passphrase,
  4. String deviceLabel = 'recovered device',
})

Regains access to the database localId on a fresh install, using a recovery phrase minted by enableRecovery plus the database passphrase.

The passphrase unlocks the device roster (its key derives from it); the roster then syncs from any reachable peer, and the keypair derived from phrase unwraps the group key from its still-valid wrap. This device is then admitted to the roster as deviceLabel and the returned Database is DeviceState.ready.

Needs at least one reachable peer that holds the roster (there is no server to ask). Throws StateError when recovery is disabled, when the phrase's roster entry has been revoked, or when no usable wrap arrives in time; ArgumentError when phrase is not a valid mnemonic.

Implementation

Future<Database> recoverFrom(
  String localId,
  String phrase, {
  required String passphrase,
  String deviceLabel = 'recovered device',
}) async {
  final derive = _requireRecovery();
  if (_databases.containsKey(localId)) {
    throw StateError('Database "$localId" is already open.');
  }
  if (!_validateMnemonic(phrase)) {
    throw ArgumentError.value(
      phrase,
      'phrase',
      'not a valid recovery mnemonic',
    );
  }
  final recovery = await derive(phrase, _config.salt);

  final entryKey = '${_config.appId}/$localId';
  final key = await _deriveKey(passphrase, _config.salt);
  await _keyStore.setKey(entryKey, key);
  // Never auto-found here: the group exists elsewhere; founding a fresh key
  // on a briefly-alone install would fork it.
  final db = await _finishOpen(
    localId,
    entryKey,
    key,
    _cipherFactory(key),
    allowAutoFound: false,
  );

  var adopted = false;
  for (var i = 0; i < 100 && !adopted; i++) {
    await db.forceSync();
    await Future<void>.delayed(const Duration(milliseconds: 5));
    adopted = await db.adoptRecovery(recovery, label: deviceLabel);
  }
  if (!adopted) {
    throw StateError(
      'Recovery timed out: no usable wrap for this phrase arrived. Is a '
      'peer that holds the roster reachable?',
    );
  }
  return db;
}