Skip to content

Commit 083ba26

Browse files
committed
Share database updates across tabs
1 parent a62c9e8 commit 083ba26

File tree

4 files changed

+82
-5
lines changed

4 files changed

+82
-5
lines changed

packages/sqlite_async/lib/src/web/database.dart

Lines changed: 6 additions & 1 deletion
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 {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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._(this._channel);
12+
13+
BroadcastUpdates(String name)
14+
: _channel = web.BroadcastChannel('sqlite3_async_updates/$name');
15+
16+
Stream<UpdateNotification> get updates {
17+
return web.EventStreamProviders.messageEvent
18+
.forTarget(_channel)
19+
.map((event) {
20+
final data = event.data as _BroadcastMessage;
21+
if (data.a == 0) {
22+
final payload = data.b as JSArray<JSString>;
23+
return UpdateNotification(
24+
payload.toDart.map((e) => e.toDart).toSet());
25+
} else {
26+
return null;
27+
}
28+
})
29+
.where((e) => e != null)
30+
.cast();
31+
}
32+
33+
void send(UpdateNotification notification) {
34+
_channel.postMessage(_BroadcastMessage.notifications(notification));
35+
}
36+
}
37+
38+
@JS()
39+
@anonymous
40+
extension type _BroadcastMessage._(JSObject _) implements JSObject {
41+
external int get a;
42+
external JSAny get b;
43+
44+
external factory _BroadcastMessage({required int a, required JSAny b});
45+
46+
factory _BroadcastMessage.notifications(UpdateNotification notification) {
47+
return _BroadcastMessage(
48+
a: 0,
49+
b: notification.tables.toList().map((e) => e.toJS).toList().toJS,
50+
);
51+
}
52+
}

packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,20 @@ class SqliteDatabaseImpl
8585
Future<void> _init() async {
8686
_connection = await openFactory.openConnection(SqliteOpenOptions(
8787
primaryConnection: true, readOnly: false, mutex: mutex)) as WebDatabase;
88-
_connection.updates.forEach((update) {
89-
updatesController.add(update);
90-
});
88+
89+
final broadcastUpdates = _connection.broadcastUpdates;
90+
if (broadcastUpdates == null) {
91+
// We can use updates directly from the database.
92+
_connection.updates.forEach((update) {
93+
updatesController.add(update);
94+
});
95+
} else {
96+
// Share local updates with other tabs
97+
_connection.updates.forEach(broadcastUpdates.send);
98+
99+
// Also add updates from other tabs
100+
updatesController.addStream(broadcastUpdates.updates);
101+
}
91102
}
92103

93104
T _runZoned<T>(T Function() callback, {required String debugContext}) {
@@ -132,6 +143,7 @@ class SqliteDatabaseImpl
132143
@override
133144
Future<void> close() async {
134145
await isInitialized;
146+
updatesController.close();
135147
return _connection.close();
136148
}
137149

packages/sqlite_async/lib/src/web/web_sqlite_open_factory.dart

Lines changed: 9 additions & 1 deletion
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)