Skip to content

Commit e26fb09

Browse files
authored
Broadcast updates across tabs if needed (#74)
* Share database updates across tabs * Avoid unecessary toList() * Remove unused constructor * Also use events locally * Explain duplicate add
1 parent a62c9e8 commit e26fb09

File tree

4 files changed

+90
-5
lines changed

4 files changed

+90
-5
lines changed

Diff for: packages/sqlite_async/lib/src/web/database.dart

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:sqlite3/common.dart';
55
import 'package:sqlite3_web/sqlite3_web.dart';
66
import 'package:sqlite_async/sqlite_async.dart';
77
import 'package:sqlite_async/src/utils/shared_utils.dart';
8+
import 'package:sqlite_async/src/web/database/broadcast_updates.dart';
89
import 'package:sqlite_async/web.dart';
910
import 'protocol.dart';
1011
import 'web_mutex.dart';
@@ -15,10 +16,14 @@ class WebDatabase
1516
final Database _database;
1617
final Mutex? _mutex;
1718

19+
/// For persistent databases that aren't backed by a shared worker, we use
20+
/// web broadcast channels to forward local update events to other tabs.
21+
final BroadcastUpdates? broadcastUpdates;
22+
1823
@override
1924
bool closed = false;
2025

21-
WebDatabase(this._database, this._mutex);
26+
WebDatabase(this._database, this._mutex, {this.broadcastUpdates});
2227

2328
@override
2429
Future<void> close() async {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import 'dart:js_interop';
2+
3+
import 'package:sqlite_async/sqlite_async.dart';
4+
import 'package:web/web.dart' as web;
5+
6+
/// Utility to share received [UpdateNotification]s with other tabs using
7+
/// broadcast channels.
8+
class BroadcastUpdates {
9+
final web.BroadcastChannel _channel;
10+
11+
BroadcastUpdates(String name)
12+
: _channel = web.BroadcastChannel('sqlite3_async_updates/$name');
13+
14+
Stream<UpdateNotification> get updates {
15+
return web.EventStreamProviders.messageEvent
16+
.forTarget(_channel)
17+
.map((event) {
18+
final data = event.data as _BroadcastMessage;
19+
if (data.a == 0) {
20+
final payload = data.b as JSArray<JSString>;
21+
return UpdateNotification(
22+
payload.toDart.map((e) => e.toDart).toSet());
23+
} else {
24+
return null;
25+
}
26+
})
27+
.where((e) => e != null)
28+
.cast();
29+
}
30+
31+
void send(UpdateNotification notification) {
32+
_channel.postMessage(_BroadcastMessage.notifications(notification));
33+
}
34+
}
35+
36+
@JS()
37+
@anonymous
38+
extension type _BroadcastMessage._(JSObject _) implements JSObject {
39+
external int get a;
40+
external JSAny get b;
41+
42+
external factory _BroadcastMessage({required int a, required JSAny b});
43+
44+
factory _BroadcastMessage.notifications(UpdateNotification notification) {
45+
return _BroadcastMessage(
46+
a: 0,
47+
b: notification.tables.map((e) => e.toJS).toList().toJS,
48+
);
49+
}
50+
}

Diff for: packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart

+25-3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class SqliteDatabaseImpl
4545

4646
late final Mutex mutex;
4747
late final WebDatabase _connection;
48+
StreamSubscription? _broadcastUpdatesSubscription;
4849

4950
/// Open a SqliteDatabase.
5051
///
@@ -85,9 +86,28 @@ class SqliteDatabaseImpl
8586
Future<void> _init() async {
8687
_connection = await openFactory.openConnection(SqliteOpenOptions(
8788
primaryConnection: true, readOnly: false, mutex: mutex)) as WebDatabase;
88-
_connection.updates.forEach((update) {
89-
updatesController.add(update);
90-
});
89+
90+
final broadcastUpdates = _connection.broadcastUpdates;
91+
if (broadcastUpdates == null) {
92+
// We can use updates directly from the database.
93+
_connection.updates.forEach((update) {
94+
updatesController.add(update);
95+
});
96+
} else {
97+
_connection.updates.forEach((update) {
98+
updatesController.add(update);
99+
100+
// Share local updates with other tabs
101+
broadcastUpdates.send(update);
102+
});
103+
104+
// Also add updates from other tabs, note that things we send aren't
105+
// received by our tab.
106+
_broadcastUpdatesSubscription =
107+
broadcastUpdates.updates.listen((updates) {
108+
updatesController.add(updates);
109+
});
110+
}
91111
}
92112

93113
T _runZoned<T>(T Function() callback, {required String debugContext}) {
@@ -132,6 +152,8 @@ class SqliteDatabaseImpl
132152
@override
133153
Future<void> close() async {
134154
await isInitialized;
155+
_broadcastUpdatesSubscription?.cancel();
156+
updatesController.close();
135157
return _connection.close();
136158
}
137159

Diff for: packages/sqlite_async/lib/src/web/web_sqlite_open_factory.dart

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:async';
33
import 'package:sqlite3/wasm.dart';
44
import 'package:sqlite3_web/sqlite3_web.dart';
55
import 'package:sqlite_async/sqlite_async.dart';
6+
import 'package:sqlite_async/src/web/database/broadcast_updates.dart';
67
import 'package:sqlite_async/src/web/web_mutex.dart';
78

89
import 'database.dart';
@@ -57,7 +58,14 @@ class DefaultSqliteOpenFactory
5758
? null
5859
: MutexImpl(identifier: path); // Use the DB path as a mutex identifier
5960

60-
return WebDatabase(connection.database, options.mutex ?? mutex);
61+
BroadcastUpdates? updates;
62+
if (connection.access != AccessMode.throughSharedWorker &&
63+
connection.storage != StorageMode.inMemory) {
64+
updates = BroadcastUpdates(path);
65+
}
66+
67+
return WebDatabase(connection.database, options.mutex ?? mutex,
68+
broadcastUpdates: updates);
6169
}
6270

6371
@override

0 commit comments

Comments
 (0)