diff --git a/.changeset/strong-moose-applaud.md b/.changeset/strong-moose-applaud.md new file mode 100644 index 00000000..5d83a093 --- /dev/null +++ b/.changeset/strong-moose-applaud.md @@ -0,0 +1,6 @@ +--- +'@journeyapps/powersync-sdk-react-native': patch +'@journeyapps/powersync-sdk-common': patch +--- + +Added better logging of streaming errors. Added warnings if Polyfills are not correctly configured. diff --git a/packages/powersync-sdk-common/src/client/sync/stream/AbstractRemote.ts b/packages/powersync-sdk-common/src/client/sync/stream/AbstractRemote.ts index 1c1a50ee..d87dd54a 100644 --- a/packages/powersync-sdk-common/src/client/sync/stream/AbstractRemote.ts +++ b/packages/powersync-sdk-common/src/client/sync/stream/AbstractRemote.ts @@ -1,23 +1,26 @@ +import Logger, { ILogger } from 'js-logger'; import { PowerSyncCredentials } from '../../connection/PowerSyncCredentials'; -export type RemoteOptions = { +export type RemoteConnector = { fetchCredentials: () => Promise; }; //Refresh at least 30 sec before it expires const REFRESH_CREDENTIALS_SAFETY_PERIOD_MS = 30_000; +export const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote'); + export abstract class AbstractRemote { protected credentials?: PowerSyncCredentials; - constructor(protected options: RemoteOptions) {} + constructor(protected connector: RemoteConnector, protected logger: ILogger = DEFAULT_REMOTE_LOGGER) {} async getCredentials(): Promise { const { expiresAt } = this.credentials ?? {}; if (expiresAt && expiresAt > new Date(new Date().valueOf() + REFRESH_CREDENTIALS_SAFETY_PERIOD_MS)) { return this.credentials!; } - this.credentials = await this.options.fetchCredentials(); + this.credentials = await this.connector.fetchCredentials(); return this.credentials; } diff --git a/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts b/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts index 2dd7fa8c..f2e8df6c 100644 --- a/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +++ b/packages/powersync-sdk-common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts @@ -123,6 +123,7 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver setTimeout(resolve, this.options.retryDelayMs)); diff --git a/packages/powersync-sdk-react-native/README.md b/packages/powersync-sdk-react-native/README.md index d1b0c2cb..2500780f 100644 --- a/packages/powersync-sdk-react-native/README.md +++ b/packages/powersync-sdk-react-native/README.md @@ -491,6 +491,15 @@ export const TodoListDisplay = () => { } ``` +### Logging +The default logger uses js-logger. Debug logs can be displayed with: + +```JavaScript +import Logger from 'js-logger'; +Logger.useDefaults(); +Logger.setLevel(Logger.DEBUG); +``` + # Known Issues diff --git a/packages/powersync-sdk-react-native/package.json b/packages/powersync-sdk-react-native/package.json index 51864c30..b7f7d99d 100644 --- a/packages/powersync-sdk-react-native/package.json +++ b/packages/powersync-sdk-react-native/package.json @@ -27,6 +27,7 @@ "@journeyapps/react-native-quick-sqlite": "0.0.2", "base-64": "^1.0.0", "react": "*", + "react-native": "*", "react-native-fetch-api": "^3.0.0", "react-native-get-random-values": "^1.9.0", "react-native-polyfill-globals": "^3.1.0", @@ -42,6 +43,7 @@ "devDependencies": { "@journeyapps/react-native-quick-sqlite": "0.0.2", "@types/async-lock": "^1.4.0", + "react-native": "0.72.4", "react": "18.2.0", "typescript": "^4.1.3" }, diff --git a/packages/powersync-sdk-react-native/src/sync/stream/ReactNativeRemote.ts b/packages/powersync-sdk-react-native/src/sync/stream/ReactNativeRemote.ts index 8af575d8..b7b73a3f 100644 --- a/packages/powersync-sdk-react-native/src/sync/stream/ReactNativeRemote.ts +++ b/packages/powersync-sdk-react-native/src/sync/stream/ReactNativeRemote.ts @@ -1,4 +1,7 @@ import { AbstractRemote } from '@journeyapps/powersync-sdk-common'; +import { Platform } from 'react-native'; + +export const STREAMING_POST_TIMEOUT_MS = 30_000; export class ReactNativeRemote extends AbstractRemote { async post(path: string, data: any, headers: Record = {}): Promise { @@ -43,8 +46,30 @@ export class ReactNativeRemote extends AbstractRemote { headers: Record = {}, signal?: AbortSignal ): Promise { + // Ensure polyfills are present + if ( + typeof ReadableStream == 'undefined' || + typeof TextEncoder == 'undefined' || + typeof TextDecoder == 'undefined' + ) { + const errorMessage = `Polyfills are undefined. Please ensure React Native polyfills are installed and imported in the app entrypoint. + "import 'react-native-polyfill-globals/auto';" + `; + this.logger.error(errorMessage); + throw new Error(errorMessage); + } + const credentials = await this.getCredentials(); + let timeout = + Platform.OS == 'android' + ? setTimeout(() => { + this.logger.warn( + `HTTP Streaming POST is taking longer than 30 seconds to resolve. If using a debug build, please ensure Flipper Network plugin is disabled.` + ); + }, STREAMING_POST_TIMEOUT_MS) + : null; + const res = await fetch(credentials.endpoint + path, { method: 'POST', headers: { ...headers, ...(await this.getHeaders()) }, @@ -63,9 +88,11 @@ export class ReactNativeRemote extends AbstractRemote { throw ex; }); + clearTimeout(timeout); + if (!res.ok) { const text = await res.text(); - console.error(`Could not POST streaming to ${path} - ${res.status} - ${res.statusText}: ${text}`); + this.logger.error(`Could not POST streaming to ${path} - ${res.status} - ${res.statusText}: ${text}`); const error: any = new Error(`HTTP ${res.statusText}: ${text}`); error.status = res.status; throw error;