implemented an E2E-encrypted Nextcloud push-v2 notification system with support for RSA decryption and signature verification; introduced an iOS Notification Service Extension and native AppDelegate handlers for Talk actions (inline reply and mark-as-read); replaced the legacy notification registration with a new lifecycle managing app passwords and secure keypair storage; added background message handling with tray synchronization and a test notification utility in the settings.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import 'package:localstore/localstore.dart';
|
||||
|
||||
/// A rendered notification's bookkeeping, keyed by the Nextcloud notification
|
||||
/// id (`nid`). Lets a later delete-push (which only carries the `nid`) find and
|
||||
/// cancel the exact tray notification that was shown.
|
||||
class NidEntry {
|
||||
final int nid;
|
||||
final int notificationId;
|
||||
final String tag;
|
||||
final String? chatToken;
|
||||
|
||||
const NidEntry({
|
||||
required this.nid,
|
||||
required this.notificationId,
|
||||
required this.tag,
|
||||
this.chatToken,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'nid': nid,
|
||||
'notificationId': notificationId,
|
||||
'tag': tag,
|
||||
if (chatToken != null) 'chatToken': chatToken,
|
||||
};
|
||||
|
||||
factory NidEntry.fromJson(Map<String, dynamic> json) => NidEntry(
|
||||
nid: (json['nid'] as num).toInt(),
|
||||
notificationId: (json['notificationId'] as num).toInt(),
|
||||
tag: json['tag'] as String,
|
||||
chatToken: json['chatToken'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Persists the `nid → tray notification` mapping via [Localstore] so both the
|
||||
/// foreground and background isolates can resolve delete-pushes.
|
||||
class NidStore {
|
||||
static const _collection = 'push_nids';
|
||||
|
||||
final Localstore _db;
|
||||
|
||||
NidStore({Localstore? db}) : _db = db ?? Localstore.instance;
|
||||
|
||||
Future<void> put(NidEntry entry) =>
|
||||
_db.collection(_collection).doc('${entry.nid}').set(entry.toJson());
|
||||
|
||||
Future<NidEntry?> get(int nid) async {
|
||||
final data = await _db.collection(_collection).doc('$nid').get();
|
||||
if (data == null) return null;
|
||||
return NidEntry.fromJson(data);
|
||||
}
|
||||
|
||||
Future<void> delete(int nid) =>
|
||||
_db.collection(_collection).doc('$nid').delete();
|
||||
|
||||
Future<List<NidEntry>> all() async {
|
||||
final docs = await _db.collection(_collection).get();
|
||||
if (docs == null) return const [];
|
||||
return docs.values
|
||||
.map((e) => NidEntry.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
final docs = await _db.collection(_collection).get();
|
||||
if (docs == null) return;
|
||||
for (final id in docs.keys) {
|
||||
// Localstore keys are the full document paths (/push_nids/<nid>).
|
||||
final nid = int.tryParse(id.split('/').last);
|
||||
if (nid != null) await delete(nid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user