feat: js бриджы для вебапов

This commit is contained in:
Jganenokk
2026-08-15 21:31:38 +07:00
parent d91759a1cf
commit f7a986fec7
17 changed files with 1347 additions and 1532 deletions
+2
View File
@@ -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',
+125 -1
View File
@@ -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,
+5
View File
@@ -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);
}
+123
View File
@@ -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);
}
}