Files
BalanceMono/BalanceFlutter/lib/services/runtime_lock_web.dart
T
wt 2c278044bb
Android CI / Build and Test (push) Failing after 21m18s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
delete CloudKit and added BalanceFlutter
2026-10-28 15:37:32 +07:00

97 lines
3.0 KiB
Dart

import 'dart:async';
import 'dart:js_interop';
@JS('isSecureContext')
external bool get _secureContext;
@JS('navigator.locks')
external JSObject? get _navigatorLocks;
extension type _LockManager(JSObject _) implements JSObject {
external JSPromise<JSAny?> request(JSString name, JSFunction callback);
}
/// Holds the origin-wide Web Lock until release, including IndexedDB commits
/// and rotated refresh-token persistence. Browser teardown releases the lock.
class BrowserRuntimeLock {
bool _waiting = false;
Completer<JSAny?>? _release;
Future<JSAny?>? _request;
Future<void> acquire() async {
if (_waiting || _release != null) {
throw StateError('Browser ledger lock is already held or pending');
}
if (!_secureContext) {
throw StateError(
'Для безопасного хранения данных в нескольких вкладках требуется HTTPS '
'(или localhost). Откройте приложение по защищённому адресу.',
);
}
final manager = _navigatorLocks;
if (manager == null) {
throw StateError(
'Браузер не поддерживает Web Locks API. Обновите браузер: '
'без блокировки вкладок безопасная синхронизация недоступна.',
);
}
final ready = Completer<void>();
final release = Completer<JSAny?>();
_waiting = true;
try {
final request = _LockManager(manager)
.request(
'balance-ledger-v1'.toJS,
((JSAny? lock) {
_waiting = false;
_release = release;
ready.complete();
return release.future.toJS;
}).toJS,
)
.toDart;
_request = request;
// A rejected request must wake an acquiring caller, not leave it waiting
// forever. Keep a rejection handler attached even before release awaits it.
unawaited(
request.then<void>(
(_) {
if (!ready.isCompleted) {
ready.completeError(
StateError('Browser ledger lock was not granted'),
);
}
},
onError: (Object error, StackTrace stack) {
if (!ready.isCompleted) {
ready.completeError(
StateError('Не удалось получить блокировку данных браузера'),
stack,
);
}
},
),
);
await ready.future;
} catch (_) {
_waiting = false;
_release = null;
_request = null;
rethrow;
}
}
Future<void> release() async {
final release = _release;
if (release == null) throw StateError('Browser ledger lock is not held');
release.complete(null);
try {
// Resolves only after navigator.locks has actually released ownership.
await _request;
} finally {
_release = null;
_request = null;
}
}
}