Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
},
"dependencies": {
"@journeyapps/wa-sqlite": "^1.2.5",
"@powersync/react": "^1.5.3",
"@powersync/web": "^1.23.0",
"@powersync/react": "^1.7.0",
"@powersync/web": "^1.25.1",
"@supabase/supabase-js": "^2.50.2",
"react": "^19.1.0",
"react-dom": "^19.1.0",
Expand Down
74 changes: 19 additions & 55 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,37 @@
import "./App.css";
import { useQuery, useStatus } from "@powersync/react";
import { COUNTER_TABLE, type CounterRecord } from "./powersync/AppSchema";
import { useState } from "react";
import { useEffect, useState } from "react";
import { powerSync } from "./powersync/System";
import { connector } from "./powersync/SupabaseConnector";

function App() {
const [userID, setUserID] = useState<string | null>(null);
const [isAuthenticating, setIsAuthenticating] = useState(false);
const [authError, setAuthError] = useState<string | null>(null);
const status = useStatus();

// Example of a watch query using useQuery hook
// This demonstrates how to fetch and automatically update data when the underlying table changes
// using an incremental watch query - see here https://docs.powersync.com/usage/use-case-examples/watch-queries#incremental-watch-queries
const { data: counters, isLoading } = useQuery<CounterRecord>(
`SELECT * FROM ${COUNTER_TABLE} ORDER BY created_at ASC`
);

// Function to fetch and set the current user's ID from Supabase auth session
// Handles both existing sessions and new anonymous authentication
const fetchUserID = async () => {
if (isAuthenticating) {
console.log("Already authenticating, skipping...");
return;
}

setIsAuthenticating(true);
setAuthError(null);

try {
console.log("Fetching user ID from Supabase...");

// First check if we already have a session
let session = connector.currentSession;

if (!session) {
// Only sign in anonymously if we don't have a session
session = await connector.signInAnonymously();
`SELECT * FROM ${COUNTER_TABLE} ORDER BY created_at ASC`,
[],
{
rowComparator: {
keyBy: (item) => item.id,
compareBy: (item) => JSON.stringify(item)
}
}
);

const userId = session?.user?.id;
// Get the current authenticated user's ID from Supabase on component mount
useEffect(() => {
const getCurrentUser = async () => {
const { data: { user } } = await connector.client.auth.getUser();
setUserID(user?.id || null);
};

if (userId) {
setUserID(userId);
} else {
const errorMsg = "No user ID found in session";
console.error(errorMsg);
setAuthError(errorMsg);
}
} catch (error) {
const errorMsg = `Authentication failed: ${error}`;
console.error(errorMsg);
setAuthError(errorMsg);
} finally {
setIsAuthenticating(false);
}
};
getCurrentUser();
}, []);

// Example of executing a native SQLite query using PowerSync
// This demonstrates how to directly execute SQL commands for data mutations
Expand All @@ -75,12 +51,8 @@ function App() {
const createCounter = async () => {
// Ensure user is authenticated before creating counter
if (!userID) {
if (isAuthenticating) {
console.log("Authentication in progress, please wait...");
return;
}
console.log("No user ID, attempting to authenticate...");
await fetchUserID();
// await fetchUserID();

// If still no userID after fetch, don't proceed
if (!userID) {
Expand All @@ -100,12 +72,6 @@ function App() {
}
};

// Check for existing session when component mounts
// This runs only once when the app first loads
if (!userID && !isAuthenticating && !authError) {
fetchUserID();
}

return (
<div className="app-container">
{/* Top row with Status, Logo, and Helpful Links in a grid like counter-grid */}
Expand All @@ -131,8 +97,6 @@ function App() {
<div><strong>lastSyncedAt:</strong> {status.lastSyncedAt?.toLocaleString() ?? "N/A"}</div>

<div><strong>User ID:</strong> {userID || "Not authenticated"}</div>
{isAuthenticating && <div><strong>Status:</strong> Authenticating...</div>}
{authError && <div style={{ color: 'red' }}><strong>Auth Error:</strong> {authError}</div>}
</>
)}
</div>
Expand Down
71 changes: 10 additions & 61 deletions src/powersync/SupabaseConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ export class SupabaseConnector
readonly client: SupabaseClient;
readonly config: SupabaseConfig;

ready: boolean;
currentSession: Session | null;
private initializationPromise: Promise<void>;
private signingIn: boolean = false;

constructor() {
super();
Expand All @@ -67,24 +64,6 @@ export class SupabaseConnector
}
);
this.currentSession = null;
this.ready = false;

// Restore session from localStorage
this.initializationPromise = this.initializeSession();
}

private async initializeSession(): Promise<void> {
try {
const { data, error } = await this.client.auth.getSession();
if (error) {
console.error("Failed to restore session:", error);
} else if (data?.session) {
console.log("Restored session:", data.session);
this.updateSession(data.session);
}
} catch (error) {
console.error("Error during session initialization:", error);
}
}

/**
Expand All @@ -108,49 +87,19 @@ export class SupabaseConnector
// }

async signInAnonymously() {
// Wait for initialization to complete first
await this.initializationPromise;

// Check if we already have a valid session
if (this.currentSession) {
return this.currentSession;
}

// Prevent concurrent sign-in attempts
if (this.signingIn) {
console.log("Already signing in, waiting...");
// Wait for the current sign-in to complete
while (this.signingIn) {
await new Promise(resolve => setTimeout(resolve, 100));
}
return this.currentSession;
}
const { data: { user } } = await this.client.auth.getUser();
if (user?.id) return;

try {
this.signingIn = true;

// Double-check session after acquiring lock
if (this.currentSession) {
console.log("Session acquired while waiting");
return this.currentSession;
}

const {
data: { session },
error
} = await this.client.auth.signInAnonymously();

if (error) {
throw error;
}

console.log("Signed in anonymously:", session);
this.updateSession(session);
const {
data: { session },
error
} = await this.client.auth.signInAnonymously();

return session;
} finally {
this.signingIn = false;
if (error) {
throw error;
}

this.updateSession(session);
}

async logout() {
Expand Down
4 changes: 4 additions & 0 deletions src/powersync/System.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,8 @@ export const powerSync = new PowerSyncDatabase({
* 🔧 Quick prototype → Keep default (IndexedDB)
*/

// Sign in the user anonymously to Supabase (creates a temporary user session)
await connector.signInAnonymously();

// Establish connection between PowerSync and the Supabase connector
powerSync.connect(connector);