diff --git a/.changeset/neat-crabs-lie.md b/.changeset/neat-crabs-lie.md new file mode 100644 index 000000000..c848811a2 --- /dev/null +++ b/.changeset/neat-crabs-lie.md @@ -0,0 +1,5 @@ +--- +'@powersync/web': patch +--- + +Testing diff --git a/demos/example-capacitor/android/app/capacitor.build.gradle b/demos/example-capacitor/android/app/capacitor.build.gradle index 259821da2..6e9cddfc6 100644 --- a/demos/example-capacitor/android/app/capacitor.build.gradle +++ b/demos/example-capacitor/android/app/capacitor.build.gradle @@ -2,13 +2,15 @@ android { compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } } apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':capacitor-filesystem') + implementation project(':capacitor-share') implementation project(':capacitor-splash-screen') } diff --git a/demos/example-capacitor/android/capacitor.settings.gradle b/demos/example-capacitor/android/capacitor.settings.gradle index 68ddb413e..a765310e6 100644 --- a/demos/example-capacitor/android/capacitor.settings.gradle +++ b/demos/example-capacitor/android/capacitor.settings.gradle @@ -2,5 +2,11 @@ include ':capacitor-android' project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor') +include ':capacitor-filesystem' +project(':capacitor-filesystem').projectDir = new File('../../../node_modules/@capacitor/filesystem/android') + +include ':capacitor-share' +project(':capacitor-share').projectDir = new File('../../../node_modules/@capacitor/share/android') + include ':capacitor-splash-screen' project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/@capacitor/splash-screen/android') diff --git a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj index e9273628f..578b355d4 100644 --- a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj +++ b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj @@ -283,7 +283,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -334,7 +334,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -349,12 +349,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZGT7463CVJ; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; - PRODUCT_BUNDLE_IDENTIFIER = com.powersync.example; + PRODUCT_BUNDLE_IDENTIFIER = com.powersync.capacitor; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; @@ -369,11 +370,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZGT7463CVJ; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.powersync.example; + PRODUCT_BUNDLE_IDENTIFIER = com.powersync.capacitor; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_VERSION = 5.0; diff --git a/demos/example-capacitor/ios/App/App/Info.plist b/demos/example-capacitor/ios/App/App/Info.plist index 966c88263..91d4e771a 100644 --- a/demos/example-capacitor/ios/App/App/Info.plist +++ b/demos/example-capacitor/ios/App/App/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion en CFBundleDisplayName - powersync-capacitor + powersync-capacitor CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/demos/example-capacitor/ios/App/Podfile b/demos/example-capacitor/ios/App/Podfile index 70b65a21b..660c5bef9 100644 --- a/demos/example-capacitor/ios/App/Podfile +++ b/demos/example-capacitor/ios/App/Podfile @@ -1,6 +1,6 @@ require_relative '../../../../node_modules/@capacitor/ios/scripts/pods_helpers' -platform :ios, '13.0' +platform :ios, '14.0' use_frameworks! # workaround to avoid Xcode caching of Pods that requires @@ -11,6 +11,8 @@ install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../../../node_modules/@capacitor/ios' + pod 'CapacitorFilesystem', :path => '../../../../node_modules/@capacitor/filesystem' + pod 'CapacitorShare', :path => '../../../../node_modules/@capacitor/share' pod 'CapacitorSplashScreen', :path => '../../../../node_modules/@capacitor/splash-screen' end diff --git a/demos/example-capacitor/ios/App/Podfile.lock b/demos/example-capacitor/ios/App/Podfile.lock index 850243219..098acc197 100644 --- a/demos/example-capacitor/ios/App/Podfile.lock +++ b/demos/example-capacitor/ios/App/Podfile.lock @@ -1,28 +1,47 @@ PODS: - - Capacitor (6.0.0): + - Capacitor (7.4.3): - CapacitorCordova - - CapacitorCordova (6.0.0) - - CapacitorSplashScreen (6.0.0): + - CapacitorCordova (7.4.3) + - CapacitorFilesystem (7.1.4): - Capacitor + - IONFilesystemLib (~> 1.0.1) + - CapacitorShare (7.0.2): + - Capacitor + - CapacitorSplashScreen (7.0.2): + - Capacitor + - IONFilesystemLib (1.0.1) DEPENDENCIES: - "Capacitor (from `../../../../node_modules/@capacitor/ios`)" - "CapacitorCordova (from `../../../../node_modules/@capacitor/ios`)" + - "CapacitorFilesystem (from `../../../../node_modules/@capacitor/filesystem`)" + - "CapacitorShare (from `../../../../node_modules/@capacitor/share`)" - "CapacitorSplashScreen (from `../../../../node_modules/@capacitor/splash-screen`)" +SPEC REPOS: + trunk: + - IONFilesystemLib + EXTERNAL SOURCES: Capacitor: :path: "../../../../node_modules/@capacitor/ios" CapacitorCordova: :path: "../../../../node_modules/@capacitor/ios" + CapacitorFilesystem: + :path: "../../../../node_modules/@capacitor/filesystem" + CapacitorShare: + :path: "../../../../node_modules/@capacitor/share" CapacitorSplashScreen: :path: "../../../../node_modules/@capacitor/splash-screen" SPEC CHECKSUMS: - Capacitor: 559d073c4ca6c27f8e7002c807eea94c3ba435a9 - CapacitorCordova: 8c4bfdf69368512e85b1d8b724dd7546abeb30af - CapacitorSplashScreen: 5431ab8d19c1c6e95777d53bfaa7a36a6c3d94c7 + Capacitor: b4741ca7affb32c1b70debd03df92cbf522d8a80 + CapacitorCordova: 435121e81a2df4d0034f0fb11fcefab5104cfdb5 + CapacitorFilesystem: ac0a386949ed6c295a3dffa129ecd26b881ae5be + CapacitorShare: 051c3ceee0ddf3bb54574851a622bbb317eed5bd + CapacitorSplashScreen: 8d6c8cb0542a8e81585c593815db8785ed8ce454 + IONFilesystemLib: 89258b8e3e85465da93127d25d7ce37f977e8a6f -PODFILE CHECKSUM: 30a5df536d5e7830e635f84e1fe35fa438802eaa +PODFILE CHECKSUM: fbd8888dbdcba79e24ab532c719332d64cd3a89f -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/demos/example-capacitor/package.json b/demos/example-capacitor/package.json index b9609d0e9..ae8d6f8c6 100644 --- a/demos/example-capacitor/package.json +++ b/demos/example-capacitor/package.json @@ -19,23 +19,29 @@ "preview": "vite preview" }, "dependencies": { - "@capacitor/android": "^6.0.0", + "@capacitor/android": "^7.4.3", "@capacitor/core": "latest", - "@capacitor/ios": "^6.0.0", + "@capacitor/filesystem": "^7.1.4", + "@capacitor/ios": "^7.4.3", + "@capacitor/share": "^7.0.2", "@capacitor/splash-screen": "latest", "@journeyapps/wa-sqlite": "^1.2.0", + "@mui/icons-material": "^7.3.1", "@powersync/react": "workspace:*", "@powersync/web": "workspace:*", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.23.0" + "react-router-dom": "^6.30.1", + "react-window": "^1.8.11" }, "devDependencies": { - "@capacitor/cli": "^6.0.0", + "@capacitor/cli": "^7.4.3", "@swc/core": "~1.6.0", "@types/node": "^20.12.12", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", + "@types/react-window": "^1.8.8", "vite": "^5.2.11", "vite-plugin-require": "^1.2.14", "vite-plugin-top-level-await": "^1.4.1", diff --git a/demos/example-capacitor/src/app/LogsPage.tsx b/demos/example-capacitor/src/app/LogsPage.tsx new file mode 100644 index 000000000..fadafdb3a --- /dev/null +++ b/demos/example-capacitor/src/app/LogsPage.tsx @@ -0,0 +1,216 @@ +import { Capacitor } from '@capacitor/core'; +import { Share } from '@capacitor/share'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { + Avatar, + Button, + ButtonGroup, + Checkbox, + Divider, + FormControlLabel, + FormGroup, + Grid, + IconButton, + ListItem, + ListItemAvatar, + ListItemText, + Paper, + styled, + Typography +} from '@mui/material'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; +import { LOG_STORAGE, LogRecord } from '../components/providers/Logging.js'; + +function downloadTextFile(filename: string, content: string) { + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); +} + +async function shareFile(content: string) { + await Share.share({ + title: 'logs', + text: content, + dialogTitle: 'Share your log file' + }); +} + +export const LogDisplay: React.FC<{ logs: ReadonlyArray> }> = React.memo((props) => { + const { logs } = props; + + const Row = ({ index, style }: ListChildComponentProps) => { + const log = logs[index]; + return ( + + + + + {log.level[0].toUpperCase()} + + + + + {log.timestamp} + + + {log.level.toUpperCase()} + + > + } + secondary={ + + {log.message.slice(0, 100)} + {log.message.length > 100 ? '...' : ''} + + } + /> + + {index < logs.length - 1 && } + + ); + }; + + return ( + + + {logs.length === 0 ? ( + No logs + ) : ( + + {Row} + + )} + + + ); +}); + +const LOG_LEVELS = [ + { label: 'Error', value: 'ERROR', color: '#d32f2f' }, + { label: 'Warn', value: 'WARN', color: '#fbc02d' }, + { label: 'Debug', value: 'DEBUG', color: '#1976d2' }, + { label: 'Info', value: 'INFO', color: '#1976d2' } +]; + +const LogsPage = () => { + const [logs, setLogs] = React.useState(LOG_STORAGE.logs); + const [selectedLevels, setSelectedLevels] = React.useState(LOG_LEVELS.map((l) => l.value)); + const navigate = useNavigate(); + + React.useEffect(() => { + return LOG_STORAGE.registerListener({ + logsUpdated: (logs) => setLogs([...logs]) + }); + }, []); + + const handleLevelChange = (level: string) => { + setSelectedLevels((prev) => (prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level])); + }; + + const filteredLogs = logs.filter((log) => selectedLevels.includes(log.level)); + + return ( + + + navigate(-1)} aria-label="Back"> + + + + Logs + + + + + {LOG_LEVELS.map(({ label, value, color }) => ( + handleLevelChange(value)} + sx={{ color, '&.Mui-checked': { color } }} + /> + } + label={label} + /> + ))} + + + + + LOG_STORAGE.clearLogs()}> + Clear Logs + + { + const filename = `logs-${new Date().toISOString()}.json`; + const content = JSON.stringify(logs); + const platform = Capacitor.getPlatform(); + if (platform == 'web') { + downloadTextFile(filename, content); + } else { + await shareFile(content); + } + }}> + Download + + + + + + + + ); +}; + +namespace S { + export const MainGrid = styled(Grid)` + width: 100vw; + `; + + export const LogsListContainer = styled('div')` + width: 100%; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07); + overflow-x: auto; + overflow-y: auto; + max-height: 1000px; + padding: 0.5rem 0; + `; +} + +export default LogsPage; diff --git a/demos/example-capacitor/src/app/index.tsx b/demos/example-capacitor/src/app/index.tsx index c8a2e01f8..8bc064d98 100644 --- a/demos/example-capacitor/src/app/index.tsx +++ b/demos/example-capacitor/src/app/index.tsx @@ -1,15 +1,32 @@ -import React from 'react'; import { createRoot } from 'react-dom/client'; -import EntryPage from './page.jsx'; + +import { createTheme, CssBaseline, ThemeProvider } from '@mui/material'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; import SystemProvider from '../components/providers/SystemProvider.jsx'; +import LogsPage from './LogsPage.jsx'; +import EntryPage from './page.jsx'; + +const theme = createTheme({ + palette: { + mode: 'dark' + } +}); const root = createRoot(document.getElementById('app')!); root.render(); export function App() { return ( - - - + + + + + + } /> + } /> + + + + ); } diff --git a/demos/example-capacitor/src/app/page.tsx b/demos/example-capacitor/src/app/page.tsx index b5f0049f5..35e125025 100644 --- a/demos/example-capacitor/src/app/page.tsx +++ b/demos/example-capacitor/src/app/page.tsx @@ -1,34 +1,15 @@ +import { Button, ButtonGroup, CircularProgress, Grid, ListItem, Paper, styled } from '@mui/material'; +import { usePowerSync, useQuery, useStatus } from '@powersync/react'; import React from 'react'; -import { CircularProgress, Grid, ListItem, styled } from '@mui/material'; -import { useQuery, useStatus } from '@powersync/react'; - -const EntryPage = () => { - const status = useStatus(); - const { data: customers } = useQuery('SELECT id, name FROM customers'); - - const areVariablesSet = import.meta.env.VITE_POWERSYNC_URL && import.meta.env.VITE_PUBLIC_POWERSYNC_TOKEN; - - if (areVariablesSet && !status.hasSynced) { - return ( - - - Syncing down from the backend. This will load indefinitely if you have not set the connection up correctly. - - - - ); - } - - if (!areVariablesSet) { - return ( - - You have not set up a connection to the backend, please connect your backend. - - ); - } +import { useNavigate } from 'react-router-dom'; +import LocalEditWidget from '../components/LocalEditWidget.js'; +import { Customer } from '../library/powersync/AppSchema.js'; +import { BackendConnector } from '../library/powersync/BackendConnector.js'; +const CustomersWidget: React.FC<{ customers: Customer[] }> = (props) => { + const { customers } = props; return ( - + Customers @@ -47,21 +28,74 @@ const EntryPage = () => { )} - + + ); +}; + +const EntryPage = () => { + const status = useStatus(); + const powerSync = usePowerSync(); + + const { data: customers } = useQuery('SELECT id, name FROM customers'); + const navigate = useNavigate(); + + const areVariablesSet = import.meta.env.VITE_POWERSYNC_URL && import.meta.env.VITE_PUBLIC_POWERSYNC_TOKEN; + + return ( + + + + powerSync.connect(new BackendConnector())}> + Connect + + powerSync.disconnect()}> + Disconnect + + navigate('/logs')}> + View Logs + + + + + + + {areVariablesSet && !status.hasSynced && ( + <> + + Syncing down from the backend. This will load indefinitely if you have not set the connection up + correctly. + + + > + )} + {!areVariablesSet && ( + + You have not set up a connection to the backend, please connect your backend. + + )} + {areVariablesSet && status.hasSynced && } + + + + + + + ); }; namespace S { - export const CenteredGrid = styled(Grid)` + export const FlexGrid = styled(Grid)` display: flex; - justify-content: center; - align-items: center; + flex-direction: column; + margin: 10px; `; - export const MainGrid = styled(CenteredGrid)` - min-height: 100vh; - display: flex; - flex-direction: column; + export const CenteredGrid = styled(FlexGrid)` + justify-content: center; + align-items: center; `; } diff --git a/demos/example-capacitor/src/components/LocalEditWidget.tsx b/demos/example-capacitor/src/components/LocalEditWidget.tsx new file mode 100644 index 000000000..864aac62e --- /dev/null +++ b/demos/example-capacitor/src/components/LocalEditWidget.tsx @@ -0,0 +1,61 @@ +import DeleteIcon from '@mui/icons-material/Delete'; +import { Button, IconButton, List, ListItem, ListItemText, Paper, Typography } from '@mui/material'; +import { usePowerSync, useQuery } from '@powersync/react'; +import React from 'react'; +import { Product } from '../library/powersync/AppSchema.js'; + +function getRandomProductName() { + const adjectives = ['Cool', 'Amazing', 'Fresh', 'New', 'Shiny', 'Super', 'Eco', 'Smart']; + const nouns = ['Widget', 'Gadget', 'Device', 'Item', 'Thing', 'Product', 'Tool', 'Object']; + return ( + adjectives[Math.floor(Math.random() * adjectives.length)] + + ' ' + + nouns[Math.floor(Math.random() * nouns.length)] + + ' #' + + Math.floor(Math.random() * 10000) + ); +} + +export default function LocalEditWidget() { + const powerSync = usePowerSync(); + const { data: products } = useQuery('SELECT * FROM products'); + + const addProduct = React.useCallback(() => { + return powerSync.execute('INSERT INTO products (id, name) VALUES (uuid(), ?)', [getRandomProductName()]); + }, []); + + const deleteProduct = React.useCallback((product: Product) => { + return powerSync.execute('DELETE FROM products WHERE id = ?', [product.id]); + }, []); + + return ( + + + Products + + Perform local only edits to Products. These won't be synced. + + Add Random Product + + + {products.length === 0 ? ( + + + + ) : ( + products.map((product) => ( + deleteProduct(product)}> + + + }> + + + )) + )} + + + ); +} diff --git a/demos/example-capacitor/src/components/providers/Logging.ts b/demos/example-capacitor/src/components/providers/Logging.ts new file mode 100644 index 000000000..470efc352 --- /dev/null +++ b/demos/example-capacitor/src/components/providers/Logging.ts @@ -0,0 +1,122 @@ +import { BaseObserver, ControlledExecutor, createBaseLogger, createLogger, LogLevel } from '@powersync/web'; + +export type LogRecord = { + level: string; + timestamp: string; + message: string; +}; + +export type LogListener = { + logsUpdated: (logs: ReadonlyArray>) => void; +}; + +export class LogStorage extends BaseObserver { + protected _logs: LogRecord[]; + protected writeExecutor: ControlledExecutor; + protected dbPromise: Promise; + + static readonly LOGS_DB_NAME = '_ps_logs_db'; + static readonly LOGS_STORE_NAME = 'logs'; + + get logs(): ReadonlyArray> { + return this._logs; + } + + constructor() { + super(); + this._logs = []; + this.writeExecutor = new ControlledExecutor(() => this.saveLogsToIndexedDB()); + this.dbPromise = this.openLogsDB(); + this.readLogsFromIndexedDB(); + } + + clearLogs() { + this._logs = []; + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + } + + writeLog(record: LogRecord) { + this._logs.unshift(record); + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + this.writeExecutor.schedule(); + } + + private async saveLogsToIndexedDB() { + const db = await this.dbPromise; + const tx = db.transaction(LogStorage.LOGS_STORE_NAME, 'readwrite'); + const store = tx.objectStore(LogStorage.LOGS_STORE_NAME); + await new Promise((resolve, reject) => { + const clearReq = store.clear(); + clearReq.onsuccess = () => { + this.logs.forEach((log) => store.add(log)); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }; + clearReq.onerror = () => reject(clearReq.error); + }); + } + + // Read all LogRecord entries from IndexedDB + private async readLogsFromIndexedDB(): Promise { + const db = await this.dbPromise; + const tx = db.transaction(LogStorage.LOGS_STORE_NAME, 'readwrite'); + const store = tx.objectStore(LogStorage.LOGS_STORE_NAME); + return new Promise((resolve, reject) => { + const req = store.getAll(); + req.onsuccess = () => { + this._logs = (req.result as any[]) + .map(({ id, ...rest }) => rest) + // sort in descending order + .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + resolve(this._logs); + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + }; + req.onerror = () => reject(req.error); + }); + } + + // Helper to open the IndexedDB database + private async openLogsDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(LogStorage.LOGS_DB_NAME, 1); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(LogStorage.LOGS_STORE_NAME)) { + db.createObjectStore(LogStorage.LOGS_STORE_NAME, { keyPath: 'id', autoIncrement: true }); + } + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } +} + +export const LOG_STORAGE = new LogStorage(); + +const mapLogParam = (param: any) => { + if (param instanceof Error) { + return ` +Error +Name: ${param.name} +Message: ${param.message} +Cause: ${param.cause} +Stack: ${param.stack} + `.trim(); + } + return JSON.stringify(param); +}; + +// Configure base logger for global settings +export const BASE_LOGGER = createBaseLogger(); +BASE_LOGGER.useDefaults({ + defaultLevel: LogLevel.DEBUG, + formatter: (messages, context) => { + LOG_STORAGE.writeLog({ + level: context.level.name, + message: messages.map(mapLogParam).join(' -- '), + timestamp: new Date().toISOString() + }); + } +}); + +export const LOGGER = createLogger('CapacitorPS'); diff --git a/demos/example-capacitor/src/components/providers/SystemProvider.tsx b/demos/example-capacitor/src/components/providers/SystemProvider.tsx index fc3b9e050..f618bcfd5 100644 --- a/demos/example-capacitor/src/components/providers/SystemProvider.tsx +++ b/demos/example-capacitor/src/components/providers/SystemProvider.tsx @@ -1,30 +1,27 @@ -import { PowerSyncContext } from '@powersync/react'; -import { PowerSyncDatabase, createBaseLogger, LogLevel } from '@powersync/web'; +import { Capacitor } from '@capacitor/core'; import { CircularProgress } from '@mui/material'; +import { PowerSyncContext } from '@powersync/react'; +import { PowerSyncDatabase, WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/web'; import React, { Suspense } from 'react'; import { AppSchema } from '../../library/powersync/AppSchema.js'; import { BackendConnector } from '../../library/powersync/BackendConnector.js'; -import { Capacitor } from '@capacitor/core'; - -const logger = createBaseLogger(); -logger.useDefaults(); -logger.setLevel(LogLevel.DEBUG); +import { LOGGER } from './Logging.js'; const platform = Capacitor.getPlatform(); -const isIOs = platform === 'ios'; -// Web worker implementation does not work on iOS -const useWebWorker = !isIOs; +LOGGER.debug('Initing PowerSync globally '); const powerSync = new PowerSyncDatabase({ - database: { dbFilename: 'powersync2.db' }, + database: new WASQLiteOpenFactory({ + dbFilename: 'ps.db', + vfs: platform == 'ios' ? WASQLiteVFS.AccessHandlePoolVFS : WASQLiteVFS.OPFSCoopSyncVFS, + debugMode: true + }), schema: AppSchema, flags: { - enableMultiTabs: false, - useWebWorker + enableMultiTabs: false } }); const connector = new BackendConnector(); - powerSync.connect(connector); export const SystemProvider = ({ children }: { children: React.ReactNode }) => { diff --git a/demos/example-capacitor/src/index.css b/demos/example-capacitor/src/index.css index 8856f90b3..a89143ed2 100644 --- a/demos/example-capacitor/src/index.css +++ b/demos/example-capacitor/src/index.css @@ -1,7 +1,10 @@ body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; margin: auto; max-width: 38rem; padding: 2rem; + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); } diff --git a/demos/example-capacitor/src/index.html b/demos/example-capacitor/src/index.html index de7a97c29..27e149188 100644 --- a/demos/example-capacitor/src/index.html +++ b/demos/example-capacitor/src/index.html @@ -1,11 +1,15 @@ - - - - - - - - - - + + + + + + + + + + + + + +
- Syncing down from the backend. This will load indefinitely if you have not set the connection up correctly. -
+ Syncing down from the backend. This will load indefinitely if you have not set the connection up + correctly. +