1
- import * as Comlink from 'comlink' ;
2
- import Logger , { type ILogger } from 'js-logger' ;
3
1
import {
4
2
type AbstractStreamingSyncImplementation ,
5
- type StreamingSyncImplementation ,
6
3
type LockOptions ,
4
+ type PowerSyncConnectionOptions ,
5
+ type StreamingSyncImplementation ,
7
6
type StreamingSyncImplementationListener ,
8
7
type SyncStatusOptions ,
9
- type PowerSyncConnectionOptions ,
8
+ AbortOperation ,
10
9
BaseObserver ,
10
+ DBAdapter ,
11
11
SqliteBucketStorage ,
12
- SyncStatus ,
13
- AbortOperation
12
+ SyncStatus
14
13
} from '@powersync/common' ;
14
+ import { Mutex } from 'async-mutex' ;
15
+ import * as Comlink from 'comlink' ;
16
+ import Logger , { type ILogger } from 'js-logger' ;
17
+ import { WebRemote } from '../../db/sync/WebRemote' ;
15
18
import {
16
19
WebStreamingSyncImplementation ,
17
20
WebStreamingSyncImplementationOptions
18
21
} from '../../db/sync/WebStreamingSyncImplementation' ;
19
- import { Mutex } from 'async-mutex' ;
20
- import { WebRemote } from '../../db/sync/WebRemote' ;
21
22
22
23
import { WASQLiteDBAdapter } from '../../db/adapters/wa-sqlite/WASQLiteDBAdapter' ;
23
24
import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider' ;
@@ -66,20 +67,28 @@ export class SharedSyncImplementation
66
67
implements StreamingSyncImplementation
67
68
{
68
69
protected ports : WrappedSyncPort [ ] ;
69
- protected syncStreamClient ? : AbstractStreamingSyncImplementation ;
70
+ protected syncStreamClient : AbstractStreamingSyncImplementation | null ;
70
71
71
72
protected isInitialized : Promise < void > ;
72
73
protected statusListener ?: ( ) => void ;
73
74
74
75
protected fetchCredentialsController ?: RemoteOperationAbortController ;
75
76
protected uploadDataController ?: RemoteOperationAbortController ;
76
77
78
+ protected dbAdapter : DBAdapter | null ;
79
+ protected syncParams : SharedSyncInitOptions | null ;
80
+ protected logger : ILogger ;
81
+
77
82
syncStatus : SyncStatus ;
78
83
broadCastLogger : ILogger ;
79
84
80
85
constructor ( ) {
81
86
super ( ) ;
82
87
this . ports = [ ] ;
88
+ this . dbAdapter = null ;
89
+ this . syncParams = null ;
90
+ this . syncStreamClient = null ;
91
+ this . logger = Logger . get ( 'shared-sync' ) ;
83
92
84
93
this . isInitialized = new Promise ( ( resolve ) => {
85
94
const callback = this . registerListener ( {
@@ -115,82 +124,29 @@ export class SharedSyncImplementation
115
124
* Configures the DBAdapter connection and a streaming sync client.
116
125
*/
117
126
async init ( dbWorkerPort : MessagePort , params : SharedSyncInitOptions ) {
118
- if ( this . syncStreamClient ) {
127
+ if ( this . dbAdapter ) {
119
128
// Cannot modify already existing sync implementation
120
129
return ;
121
130
}
122
131
123
- const logger = params . streamOptions ?. flags ?. broadcastLogs ? this . broadCastLogger : Logger . get ( 'shared-sync' ) ;
132
+ this . dbAdapter = new WASQLiteDBAdapter ( {
133
+ dbFilename : params . dbName ,
134
+ workerPort : dbWorkerPort ,
135
+ flags : { enableMultiTabs : true , useWebWorker : true } ,
136
+ logger : this . logger
137
+ } ) ;
138
+
139
+ this . syncParams = params ;
140
+
141
+ if ( params . streamOptions ?. flags ?. broadcastLogs ) {
142
+ this . logger = this . broadCastLogger ;
143
+ }
124
144
125
145
self . onerror = ( event ) => {
126
146
// Share any uncaught events on the broadcast logger
127
- logger . error ( 'Uncaught exception in PowerSync shared sync worker' , event ) ;
147
+ this . logger . error ( 'Uncaught exception in PowerSync shared sync worker' , event ) ;
128
148
} ;
129
149
130
- this . syncStreamClient = new WebStreamingSyncImplementation ( {
131
- adapter : new SqliteBucketStorage (
132
- new WASQLiteDBAdapter ( {
133
- dbFilename : params . dbName ,
134
- workerPort : dbWorkerPort ,
135
- flags : { enableMultiTabs : true , useWebWorker : true } ,
136
- logger
137
- } ) ,
138
- new Mutex ( ) ,
139
- logger
140
- ) ,
141
- remote : new WebRemote ( {
142
- fetchCredentials : async ( ) => {
143
- const lastPort = this . ports [ this . ports . length - 1 ] ;
144
- return new Promise ( async ( resolve , reject ) => {
145
- const abortController = new AbortController ( ) ;
146
- this . fetchCredentialsController = {
147
- controller : abortController ,
148
- activePort : lastPort
149
- } ;
150
-
151
- abortController . signal . onabort = reject ;
152
- try {
153
- resolve ( await lastPort . clientProvider . fetchCredentials ( ) ) ;
154
- } catch ( ex ) {
155
- reject ( ex ) ;
156
- } finally {
157
- this . fetchCredentialsController = undefined ;
158
- }
159
- } ) ;
160
- }
161
- } ) ,
162
- uploadCrud : async ( ) => {
163
- const lastPort = this . ports [ this . ports . length - 1 ] ;
164
-
165
- return new Promise ( async ( resolve , reject ) => {
166
- const abortController = new AbortController ( ) ;
167
- this . uploadDataController = {
168
- controller : abortController ,
169
- activePort : lastPort
170
- } ;
171
-
172
- // Resolving will make it retry
173
- abortController . signal . onabort = ( ) => resolve ( ) ;
174
- try {
175
- resolve ( await lastPort . clientProvider . uploadCrud ( ) ) ;
176
- } catch ( ex ) {
177
- reject ( ex ) ;
178
- } finally {
179
- this . uploadDataController = undefined ;
180
- }
181
- } ) ;
182
- } ,
183
- ...params . streamOptions ,
184
- // Logger cannot be transferred just yet
185
- logger
186
- } ) ;
187
-
188
- this . syncStreamClient . registerListener ( {
189
- statusChanged : ( status ) => {
190
- this . updateAllStatuses ( status . toJSON ( ) ) ;
191
- }
192
- } ) ;
193
-
194
150
this . iterateListeners ( ( l ) => l . initialized ?.( ) ) ;
195
151
}
196
152
@@ -209,13 +165,27 @@ export class SharedSyncImplementation
209
165
async connect ( options ?: PowerSyncConnectionOptions ) {
210
166
await this . waitForReady ( ) ;
211
167
// This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
212
- return navigator . locks . request ( 'shared-sync-connect' , ( ) => this . syncStreamClient ?. connect ( options ) ) ;
168
+ return navigator . locks . request ( 'shared-sync-connect' , async ( ) => {
169
+ this . syncStreamClient = this . generateStreamingImplementation ( ) ;
170
+
171
+ this . syncStreamClient . registerListener ( {
172
+ statusChanged : ( status ) => {
173
+ this . updateAllStatuses ( status . toJSON ( ) ) ;
174
+ }
175
+ } ) ;
176
+
177
+ await this . syncStreamClient . connect ( options ) ;
178
+ } ) ;
213
179
}
214
180
215
181
async disconnect ( ) {
216
182
await this . waitForReady ( ) ;
217
183
// This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
218
- return navigator . locks . request ( 'shared-sync-connect' , ( ) => this . syncStreamClient ?. disconnect ( ) ) ;
184
+ return navigator . locks . request ( 'shared-sync-connect' , async ( ) => {
185
+ await this . syncStreamClient ?. disconnect ( ) ;
186
+ await this . syncStreamClient ?. dispose ( ) ;
187
+ this . syncStreamClient = null ;
188
+ } ) ;
219
189
}
220
190
221
191
/**
@@ -281,6 +251,62 @@ export class SharedSyncImplementation
281
251
return this . syncStreamClient ! . getWriteCheckpoint ( ) ;
282
252
}
283
253
254
+ protected generateStreamingImplementation ( ) {
255
+ // This should only be called after initialization has completed
256
+ const syncParams = this . syncParams ! ;
257
+
258
+ // Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
259
+ return new WebStreamingSyncImplementation ( {
260
+ adapter : new SqliteBucketStorage ( this . dbAdapter ! , new Mutex ( ) , this . logger ) ,
261
+ remote : new WebRemote ( {
262
+ fetchCredentials : async ( ) => {
263
+ const lastPort = this . ports [ this . ports . length - 1 ] ;
264
+ return new Promise ( async ( resolve , reject ) => {
265
+ const abortController = new AbortController ( ) ;
266
+ this . fetchCredentialsController = {
267
+ controller : abortController ,
268
+ activePort : lastPort
269
+ } ;
270
+
271
+ abortController . signal . onabort = reject ;
272
+ try {
273
+ console . log ( 'calling the last port client provider for credentials' ) ;
274
+ resolve ( await lastPort . clientProvider . fetchCredentials ( ) ) ;
275
+ } catch ( ex ) {
276
+ reject ( ex ) ;
277
+ } finally {
278
+ this . fetchCredentialsController = undefined ;
279
+ }
280
+ } ) ;
281
+ }
282
+ } ) ,
283
+ uploadCrud : async ( ) => {
284
+ const lastPort = this . ports [ this . ports . length - 1 ] ;
285
+
286
+ return new Promise ( async ( resolve , reject ) => {
287
+ const abortController = new AbortController ( ) ;
288
+ this . uploadDataController = {
289
+ controller : abortController ,
290
+ activePort : lastPort
291
+ } ;
292
+
293
+ // Resolving will make it retry
294
+ abortController . signal . onabort = ( ) => resolve ( ) ;
295
+ try {
296
+ resolve ( await lastPort . clientProvider . uploadCrud ( ) ) ;
297
+ } catch ( ex ) {
298
+ reject ( ex ) ;
299
+ } finally {
300
+ this . uploadDataController = undefined ;
301
+ }
302
+ } ) ;
303
+ } ,
304
+ ...syncParams . streamOptions ,
305
+ // Logger cannot be transferred just yet
306
+ logger : this . logger
307
+ } ) ;
308
+ }
309
+
284
310
/**
285
311
* A method to update the all shared statuses for each
286
312
* client.
@@ -296,7 +322,8 @@ export class SharedSyncImplementation
296
322
*/
297
323
private _testUpdateAllStatuses ( status : SyncStatusOptions ) {
298
324
if ( ! this . syncStreamClient ) {
299
- console . warn ( 'no stream client has been initialized yet' ) ;
325
+ // This is just for testing purposes
326
+ this . syncStreamClient = this . generateStreamingImplementation ( ) ;
300
327
}
301
328
302
329
// Only assigning, don't call listeners for this test
0 commit comments