Skip to content

Commit 719d396

Browse files
Merge remote-tracking branch 'origin/main' into watches
2 parents 83b1289 + 0565a0a commit 719d396

File tree

15 files changed

+188
-47
lines changed

15 files changed

+188
-47
lines changed

.changeset/cool-yaks-allow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/web': minor
3+
---
4+
5+
To support the upstream credentials management changes from `@powersync/common`, the sync worker now communicates credentials invalidation to tabs.

.changeset/green-buckets-talk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/op-sqlite': patch
3+
---
4+
5+
Promoting package to Beta release.

.changeset/honest-melons-laugh.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/web': patch
3+
---
4+
5+
Fixed issue where broadcast logger wasn't being passed to WebRemote, causing worker remote logs not to be broadcasted to the tab's logs.

.changeset/shiny-rules-invent.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': minor
3+
---
4+
5+
Improved credentials management and error handling. Credentials are invalidated when they expire or become invalid based on responses from the PowerSync service. The frequency of credential fetching has been reduced as a result of this work.

demos/react-native-barebones-opsqlite/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# PowerSync React Native Barebones OPSQlite
1+
# PowerSync React Native Barebones OP-SQlite
22

33
## Overview
44

5-
This is a minimal example demonstrating a barebones react native project using OPSQLite . It shows an update to the local SQLite DB on app launch.
5+
This is a minimal example demonstrating a barebones React Native project using OP-SQLite. It shows an update to the local SQLite DB on app launch.
66

77

88
## Getting Started

packages/common/src/client/sync/stream/AbstractRemote.ts

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@ export type BSONImplementation = typeof BSON;
1515

1616
export type RemoteConnector = {
1717
fetchCredentials: () => Promise<PowerSyncCredentials | null>;
18+
invalidateCredentials?: () => void;
1819
};
1920

2021
const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
2122
const POWERSYNC_JS_VERSION = PACKAGE.version;
2223

23-
// Refresh at least 30 sec before it expires
24-
const REFRESH_CREDENTIALS_SAFETY_PERIOD_MS = 30_000;
2524
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
2625

2726
// Keep alive message is sent every period
@@ -130,18 +129,59 @@ export abstract class AbstractRemote {
130129
: fetchImplementation;
131130
}
132131

132+
/**
133+
* Get credentials currently cached, or fetch new credentials if none are
134+
* available.
135+
*
136+
* These credentials may have expired already.
137+
*/
133138
async getCredentials(): Promise<PowerSyncCredentials | null> {
134-
const { expiresAt } = this.credentials ?? {};
135-
if (expiresAt && expiresAt > new Date(new Date().valueOf() + REFRESH_CREDENTIALS_SAFETY_PERIOD_MS)) {
136-
return this.credentials!;
139+
if (this.credentials) {
140+
return this.credentials;
137141
}
138-
this.credentials = await this.connector.fetchCredentials();
139-
if (this.credentials?.endpoint.match(POWERSYNC_TRAILING_SLASH_MATCH)) {
142+
143+
return this.prefetchCredentials();
144+
}
145+
146+
/**
147+
* Fetch a new set of credentials and cache it.
148+
*
149+
* Until this call succeeds, `getCredentials` will still return the
150+
* old credentials.
151+
*
152+
* This may be called before the current credentials have expired.
153+
*/
154+
async prefetchCredentials() {
155+
this.credentials = await this.fetchCredentials();
156+
157+
return this.credentials;
158+
}
159+
160+
/**
161+
* Get credentials for PowerSync.
162+
*
163+
* This should always fetch a fresh set of credentials - don't use cached
164+
* values.
165+
*/
166+
async fetchCredentials() {
167+
const credentials = await this.connector.fetchCredentials();
168+
if (credentials?.endpoint.match(POWERSYNC_TRAILING_SLASH_MATCH)) {
140169
throw new Error(
141-
`A trailing forward slash "/" was found in the fetchCredentials endpoint: "${this.credentials.endpoint}". Remove the trailing forward slash "/" to fix this error.`
170+
`A trailing forward slash "/" was found in the fetchCredentials endpoint: "${credentials.endpoint}". Remove the trailing forward slash "/" to fix this error.`
142171
);
143172
}
144-
return this.credentials;
173+
174+
return credentials;
175+
}
176+
177+
/***
178+
* Immediately invalidate credentials.
179+
*
180+
* This may be called when the current credentials have expired.
181+
*/
182+
invalidateCredentials() {
183+
this.credentials = null;
184+
this.connector.invalidateCredentials?.();
145185
}
146186

147187
getUserAgent() {
@@ -181,6 +221,10 @@ export abstract class AbstractRemote {
181221
body: JSON.stringify(data)
182222
});
183223

224+
if (res.status === 401) {
225+
this.invalidateCredentials();
226+
}
227+
184228
if (!res.ok) {
185229
throw new Error(`Received ${res.status} - ${res.statusText} when posting to ${path}: ${await res.text()}}`);
186230
}
@@ -198,6 +242,10 @@ export abstract class AbstractRemote {
198242
}
199243
});
200244

245+
if (res.status === 401) {
246+
this.invalidateCredentials();
247+
}
248+
201249
if (!res.ok) {
202250
throw new Error(`Received ${res.status} - ${res.statusText} when getting from ${path}: ${await res.text()}}`);
203251
}
@@ -224,6 +272,10 @@ export abstract class AbstractRemote {
224272
throw ex;
225273
});
226274

275+
if (res.status === 401) {
276+
this.invalidateCredentials();
277+
}
278+
227279
if (!res.ok) {
228280
const text = await res.text();
229281
this.logger.error(`Could not POST streaming to ${path} - ${res.status} - ${res.statusText}: ${text}`);
@@ -260,22 +312,17 @@ export abstract class AbstractRemote {
260312
// automatically as a header.
261313
const userAgent = this.getUserAgent();
262314

263-
let r: (value: Error | null) => void;
264-
let socketError: Promise<Error | null> = new Promise((resolve) => {
265-
r = resolve;
266-
});
315+
let socketCreationError: Error | undefined;
267316

268317
const connector = new RSocketConnector({
269318
transport: new WebsocketClientTransport({
270319
url: this.options.socketUrlTransformer(request.url),
271320
wsCreator: (url) => {
272321
const s = this.createSocket(url);
273-
s.addEventListener('error', (e) => {
274-
// This is a workaround for the fact that the socket error event
275-
// does not provide the error message
276-
r(new Error(`WebSocket error: ${JSON.stringify(e)}`));
322+
s.addEventListener('error', (e: Event) => {
323+
socketCreationError = new Error('Failed to create connection to websocket: ', (e.target as any).url ?? '');
324+
this.logger.warn('Socket error', e);
277325
});
278-
s.addEventListener('open', () => r(null));
279326
return s;
280327
}
281328
}),
@@ -304,8 +351,7 @@ export abstract class AbstractRemote {
304351
* On React native the connection exception can be `undefined` this causes issues
305352
* with detecting the exception inside async-mutex
306353
*/
307-
const e = await socketError;
308-
throw new Error(`Could not connect to PowerSync instance: ${JSON.stringify(ex)} ${e?.message}`);
354+
throw new Error(`Could not connect to PowerSync instance: ${JSON.stringify(ex ?? socketCreationError)}`);
309355
}
310356

311357
const stream = new DataStream({
@@ -350,6 +396,17 @@ export abstract class AbstractRemote {
350396
syncQueueRequestSize, // The initial N amount
351397
{
352398
onError: (e) => {
399+
if (e.message.includes('PSYNC_')) {
400+
if (e.message.includes('PSYNC_S21')) {
401+
this.invalidateCredentials();
402+
}
403+
} else {
404+
// Possible that connection is with an older service, always invalidate to be safe
405+
if (e.message !== 'Closed. ') {
406+
this.invalidateCredentials();
407+
}
408+
}
409+
353410
// Don't log closed as an error
354411
if (e.message !== 'Closed. ') {
355412
this.logger.error(e);

packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,12 +683,19 @@ The next upload iteration will be delayed.`);
683683
if (remaining_seconds == 0) {
684684
// Connection would be closed automatically right after this
685685
this.logger.debug('Token expiring; reconnect');
686+
this.options.remote.invalidateCredentials();
687+
686688
/**
687689
* For a rare case where the backend connector does not update the token
688690
* (uses the same one), this should have some delay.
689691
*/
690692
await this.delayRetry();
691693
return;
694+
} else if (remaining_seconds < 30) {
695+
this.logger.debug('Token will expire soon; reconnect');
696+
// Pre-emptively refresh the token
697+
this.options.remote.invalidateCredentials();
698+
return;
692699
}
693700
this.triggerCrudUpload();
694701
} else {

packages/powersync-op-sqlite/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ This package (`packages/powersync-op-sqlite`) enables using [OP-SQLite](https://
66

77
If you are not yet familiar with PowerSync, please see the [PowerSync React Native SDK README](https://github.com/powersync-ja/powersync-js/tree/main/packages/react-native) for more information.
88

9-
### Alpha release
9+
## Beta Release
1010

11-
This package is currently in an alpha release. If you find a bug or issue, please open a [GitHub issue](https://github.com/powersync-ja/powersync-js/issues). Questions or feedback can be posted on our [community Discord](https://discord.gg/powersync) - we'd love to hear from you.
11+
This package is currently in a beta release.
1212

1313
## Installation
1414

packages/react-native/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# @powersync/react-native
22

3+
## 1.20.2
4+
5+
### Patch Changes
6+
7+
- 84cdd9d: Fixed issue where CRUD uploads could fail with the error
8+
9+
```
10+
Exception: require(_dependencyMap[11], "rea(...)/BlobManager").createFromOptions is not a function (it is undefined)
11+
```
12+
313
## 1.20.1
414

515
### Patch Changes

packages/react-native/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@powersync/react-native",
3-
"version": "1.20.1",
3+
"version": "1.20.2",
44
"publishConfig": {
55
"registry": "https://registry.npmjs.org/",
66
"access": "public"

0 commit comments

Comments
 (0)