feat: js бриджы для вебапов
This commit is contained in:
@@ -29,6 +29,7 @@ abstract class Opcode {
|
||||
static const int authLoginRestorePassword = 101; // Восстановление пароля
|
||||
static const int auth2faDetails = 104; // Детали 2FA
|
||||
static const int externalCallback = 105; // Внешний коллбэк
|
||||
static const int phoneWebappShare = 106;
|
||||
static const int authValidatePassword = 107; // Валидация пароля
|
||||
static const int authValidateHint = 108; // Валидация подсказки пароля
|
||||
static const int authVerifyEmail = 109; // Верификация email
|
||||
@@ -246,6 +247,7 @@ abstract class Opcode {
|
||||
authLoginRestorePassword: 'AUTH_LOGIN_RESTORE_PASSWORD',
|
||||
auth2faDetails: 'AUTH_2FA_DETAILS',
|
||||
externalCallback: 'EXTERNAL_CALLBACK',
|
||||
phoneWebappShare: 'PHONE_WEBAPP_SHARE',
|
||||
authValidatePassword: 'AUTH_VALIDATE_PASSWORD',
|
||||
authValidateHint: 'AUTH_VALIDATE_HINT',
|
||||
authVerifyEmail: 'AUTH_VERIFY_EMAIL',
|
||||
|
||||
@@ -221,7 +221,7 @@ class AppDatabase {
|
||||
await _migrateLegacyDb(target);
|
||||
return openDatabase(
|
||||
target,
|
||||
version: 21,
|
||||
version: 22,
|
||||
onOpen: (db) => db.execute('PRAGMA foreign_keys = ON'),
|
||||
onCreate: (db, _) => _createTables(db),
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
@@ -349,6 +349,10 @@ class AppDatabase {
|
||||
'TEXT',
|
||||
);
|
||||
}
|
||||
if (oldVersion < 22) {
|
||||
await db.execute(_webAppStorageSchema);
|
||||
await db.execute(_webAppBiometrySchema);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -375,6 +379,8 @@ class AppDatabase {
|
||||
await db.execute(_contactsSchema);
|
||||
await db.execute(_messagesSchema);
|
||||
await db.execute(_chatParticipantsSchema);
|
||||
await db.execute(_webAppStorageSchema);
|
||||
await db.execute(_webAppBiometrySchema);
|
||||
await _createIndexes(db);
|
||||
await _createChatParticipantsIndex(db);
|
||||
}
|
||||
@@ -535,6 +541,26 @@ class AppDatabase {
|
||||
)
|
||||
''';
|
||||
|
||||
static const _webAppStorageSchema = '''
|
||||
CREATE TABLE webapp_storage (
|
||||
account_id INTEGER NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
bot_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
PRIMARY KEY (account_id, bot_id, key)
|
||||
)
|
||||
''';
|
||||
|
||||
static const _webAppBiometrySchema = '''
|
||||
CREATE TABLE webapp_biometry (
|
||||
account_id INTEGER NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
bot_id INTEGER NOT NULL,
|
||||
access_requested INTEGER NOT NULL DEFAULT 0,
|
||||
access_granted INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (account_id, bot_id)
|
||||
)
|
||||
''';
|
||||
|
||||
static Future<void> saveProfile(
|
||||
ProfileData profile, {
|
||||
bool isActive = true,
|
||||
@@ -634,6 +660,104 @@ class AppDatabase {
|
||||
};
|
||||
}
|
||||
|
||||
static Future<void> saveWebAppValue(
|
||||
int accountId,
|
||||
int botId,
|
||||
String key,
|
||||
String value,
|
||||
) async {
|
||||
final db = await _instance;
|
||||
await db.insert('webapp_storage', {
|
||||
'account_id': accountId,
|
||||
'bot_id': botId,
|
||||
'key': key,
|
||||
'value': value,
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
static Future<String?> getWebAppValue(
|
||||
int accountId,
|
||||
int botId,
|
||||
String key,
|
||||
) async {
|
||||
final db = await _instance;
|
||||
final rows = await db.query(
|
||||
'webapp_storage',
|
||||
where: 'account_id = ? AND bot_id = ? AND key = ?',
|
||||
whereArgs: [accountId, botId, key],
|
||||
limit: 1,
|
||||
);
|
||||
if (rows.isEmpty) return null;
|
||||
return rows.first['value'] as String;
|
||||
}
|
||||
|
||||
static Future<void> removeWebAppValue(
|
||||
int accountId,
|
||||
int botId,
|
||||
String key,
|
||||
) async {
|
||||
final db = await _instance;
|
||||
await db.delete(
|
||||
'webapp_storage',
|
||||
where: 'account_id = ? AND bot_id = ? AND key = ?',
|
||||
whereArgs: [accountId, botId, key],
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> clearWebAppValues(int accountId, int botId) async {
|
||||
final db = await _instance;
|
||||
await db.delete(
|
||||
'webapp_storage',
|
||||
where: 'account_id = ? AND bot_id = ?',
|
||||
whereArgs: [accountId, botId],
|
||||
);
|
||||
}
|
||||
|
||||
static Future<int> countWebAppValues(int accountId, int botId) async {
|
||||
final db = await _instance;
|
||||
final rows = await db.rawQuery(
|
||||
'SELECT COUNT(*) AS total FROM webapp_storage '
|
||||
'WHERE account_id = ? AND bot_id = ?',
|
||||
[accountId, botId],
|
||||
);
|
||||
if (rows.isEmpty) return 0;
|
||||
return (rows.first['total'] as num?)?.toInt() ?? 0;
|
||||
}
|
||||
|
||||
static Future<(bool, bool)> getWebAppBiometryAccess(
|
||||
int accountId,
|
||||
int botId,
|
||||
) async {
|
||||
final db = await _instance;
|
||||
final rows = await db.query(
|
||||
'webapp_biometry',
|
||||
where: 'account_id = ? AND bot_id = ?',
|
||||
whereArgs: [accountId, botId],
|
||||
limit: 1,
|
||||
);
|
||||
if (rows.isEmpty) return (false, false);
|
||||
final row = rows.first;
|
||||
return (
|
||||
(row['access_requested'] as int? ?? 0) != 0,
|
||||
(row['access_granted'] as int? ?? 0) != 0,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> setWebAppBiometryAccess(
|
||||
int accountId,
|
||||
int botId, {
|
||||
required bool requested,
|
||||
required bool granted,
|
||||
}) async {
|
||||
final db = await _instance;
|
||||
await db.insert('webapp_biometry', {
|
||||
'account_id': accountId,
|
||||
'bot_id': botId,
|
||||
'access_requested': requested ? 1 : 0,
|
||||
'access_granted': granted ? 1 : 0,
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
static Future<void> savePrivacyConfig(
|
||||
int accountId,
|
||||
String jsonConfig,
|
||||
|
||||
@@ -22,6 +22,11 @@ class TokenStorage {
|
||||
await _secure.delete(key: key);
|
||||
}
|
||||
|
||||
static Future<List<String>> secureKeysWithPrefix(String prefix) async {
|
||||
final all = await _secure.readAll();
|
||||
return all.keys.where((key) => key.startsWith(prefix)).toList();
|
||||
}
|
||||
|
||||
static Future<void> saveToken(String token, int accountId) async {
|
||||
await _secure.write(key: '$_tokenPrefix$accountId', value: token);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import 'app_database.dart';
|
||||
import 'token_storage.dart';
|
||||
|
||||
enum WebAppStorageBackend { device, secure }
|
||||
|
||||
class WebAppStorage {
|
||||
static const int deviceKeyLimit = 512;
|
||||
static const int secureKeyLimit = 128;
|
||||
|
||||
static String _securePrefix(int accountId, int botId) =>
|
||||
'webapp_ss_${accountId}_${botId}_';
|
||||
|
||||
static String _secureKey(int accountId, int botId, String key) =>
|
||||
'${_securePrefix(accountId, botId)}$key';
|
||||
|
||||
static String _biometryTokenKey(int accountId, int botId) =>
|
||||
'webapp_bio_${accountId}_$botId';
|
||||
|
||||
static Future<String?> read(
|
||||
int accountId,
|
||||
int botId,
|
||||
WebAppStorageBackend backend,
|
||||
String key,
|
||||
) {
|
||||
if (backend == WebAppStorageBackend.secure) {
|
||||
return TokenStorage.readSecure(_secureKey(accountId, botId, key));
|
||||
}
|
||||
return AppDatabase.getWebAppValue(accountId, botId, key);
|
||||
}
|
||||
|
||||
static Future<bool> save(
|
||||
int accountId,
|
||||
int botId,
|
||||
WebAppStorageBackend backend,
|
||||
String key,
|
||||
String value,
|
||||
) async {
|
||||
if (await read(accountId, botId, backend, key) == null &&
|
||||
await _count(accountId, botId, backend) >= _limit(backend)) {
|
||||
return false;
|
||||
}
|
||||
if (backend == WebAppStorageBackend.secure) {
|
||||
await TokenStorage.writeSecure(_secureKey(accountId, botId, key), value);
|
||||
} else {
|
||||
await AppDatabase.saveWebAppValue(accountId, botId, key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<void> remove(
|
||||
int accountId,
|
||||
int botId,
|
||||
WebAppStorageBackend backend,
|
||||
String key,
|
||||
) async {
|
||||
if (backend == WebAppStorageBackend.secure) {
|
||||
await TokenStorage.deleteSecure(_secureKey(accountId, botId, key));
|
||||
return;
|
||||
}
|
||||
await AppDatabase.removeWebAppValue(accountId, botId, key);
|
||||
}
|
||||
|
||||
static Future<void> clear(
|
||||
int accountId,
|
||||
int botId,
|
||||
WebAppStorageBackend backend,
|
||||
) async {
|
||||
if (backend == WebAppStorageBackend.secure) {
|
||||
final keys = await TokenStorage.secureKeysWithPrefix(
|
||||
_securePrefix(accountId, botId),
|
||||
);
|
||||
for (final key in keys) {
|
||||
await TokenStorage.deleteSecure(key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await AppDatabase.clearWebAppValues(accountId, botId);
|
||||
}
|
||||
|
||||
static Future<String?> biometryToken(int accountId, int botId) =>
|
||||
TokenStorage.readSecure(_biometryTokenKey(accountId, botId));
|
||||
|
||||
static Future<void> saveBiometryToken(
|
||||
int accountId,
|
||||
int botId,
|
||||
String token,
|
||||
) => TokenStorage.writeSecure(_biometryTokenKey(accountId, botId), token);
|
||||
|
||||
static Future<void> removeBiometryToken(int accountId, int botId) =>
|
||||
TokenStorage.deleteSecure(_biometryTokenKey(accountId, botId));
|
||||
|
||||
static Future<(bool, bool)> biometryAccess(int accountId, int botId) =>
|
||||
AppDatabase.getWebAppBiometryAccess(accountId, botId);
|
||||
|
||||
static Future<void> setBiometryAccess(
|
||||
int accountId,
|
||||
int botId, {
|
||||
required bool requested,
|
||||
required bool granted,
|
||||
}) => AppDatabase.setWebAppBiometryAccess(
|
||||
accountId,
|
||||
botId,
|
||||
requested: requested,
|
||||
granted: granted,
|
||||
);
|
||||
|
||||
static int _limit(WebAppStorageBackend backend) =>
|
||||
backend == WebAppStorageBackend.secure ? secureKeyLimit : deviceKeyLimit;
|
||||
|
||||
static Future<int> _count(
|
||||
int accountId,
|
||||
int botId,
|
||||
WebAppStorageBackend backend,
|
||||
) async {
|
||||
if (backend == WebAppStorageBackend.secure) {
|
||||
final keys = await TokenStorage.secureKeysWithPrefix(
|
||||
_securePrefix(accountId, botId),
|
||||
);
|
||||
return keys.length;
|
||||
}
|
||||
return AppDatabase.countWebAppValues(accountId, botId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user