livesheets-sdk
Version:
LiveSheets SDK for Google Sheets integration with Lovable apps. OAuth onboarding URL: https://livesheets-backend-624058308714.europe-north1.run.app/onboarding
538 lines (434 loc) • 11.9 kB
Markdown
# LiveSheets SDK
Official SDK for integrating Google Sheets with Lovable applications. Provides OAuth authentication, sheet selection, and data access with built-in React hooks support.
## Installation
```bash
npm install livesheets-sdk
```
## Quick Start
### Using React Hooks (Recommended for Lovable Apps)
```tsx
import { useLiveSheets } from 'livesheets-sdk/react';
function GoogleSheetsConnector() {
const {
isAuthenticated,
hasSheet,
data,
isLoading,
error,
connect,
selectSheet,
disconnect,
refresh
} = useLiveSheets();
if (!isAuthenticated) {
return <button onClick={connect}>Connect Google Sheets</button>;
}
if (!hasSheet) {
return <button onClick={selectSheet}>Select a Sheet</button>;
}
return (
<div>
<h3>Sheet Data ({data.length} rows)</h3>
<button onClick={refresh}>Refresh</button>
<button onClick={disconnect}>Disconnect</button>
{/* Render your data */}
</div>
);
}
```
### Using the Core SDK
```typescript
import { LiveSheetsClient, LiveSheetsAuth } from 'livesheets-sdk';
// Initialize auth
const auth = new LiveSheetsAuth();
// Check if authenticated
if (!auth.isAuthenticated()) {
// Start OAuth flow
auth.startOAuth();
}
// Get JWT token
const jwt = auth.getJWT();
// Create client and fetch data
const client = new LiveSheetsClient(jwt);
const rows = await client.listRows();
const metadata = await client.getMetadata();
const schema = await client.getSchema();
```
## Features
### 🔐 OAuth Authentication
Built-in OAuth flow management with automatic JWT handling:
```typescript
import { auth } from 'livesheets-sdk';
// Start OAuth flow (redirects to Google)
auth.startOAuth();
// Check authentication status
if (auth.isAuthenticated()) {
const jwt = auth.getJWT();
// Use JWT for API calls
}
// Logout
auth.logout();
```
### 📊 Sheet Selection
Interactive Google Picker integration:
```typescript
import { LiveSheetsClient } from 'livesheets-sdk';
await LiveSheetsClient.openSheetPicker({
jwt: 'your-jwt-token',
onSuccess: (result) => {
console.log('Selected:', result.spreadsheetId, result.sheetName);
},
onCancel: () => {
console.log('Selection cancelled');
}
});
```
### 🎣 React Hooks
Complete hook with all features:
```typescript
const {
// Auth state
isAuthenticated, // boolean
isConnecting, // boolean
// Sheet state
hasSheet, // boolean
isSelectingSheet, // boolean
sheetMetadata, // { spreadsheetId, sheetName, rowCount, columnCount }
// Data state
data, // SheetRow[]
isLoading, // boolean
error, // Error | null
// Actions
connect, // () => void
disconnect, // () => void
selectSheet, // () => Promise<void>
refresh, // () => Promise<void>
// Direct access
client // LiveSheetsClient | null
} = useLiveSheets(options);
```
Simple data-only hook:
```typescript
const { data, loading, error } = useSheetData(jwt);
```
## API Reference
### LiveSheetsAuth
```typescript
class LiveSheetsAuth {
constructor(config?: {
backendUrl?: string;
storageKey?: string;
userIdKey?: string;
})
startOAuth(returnUrl?: string): void
getJWT(): string | null
isAuthenticated(): boolean
logout(): void
}
```
### LiveSheetsClient
```typescript
class LiveSheetsClient {
constructor(jwt?: string, baseUrl?: string)
// Data methods
async listRows(): Promise<SheetRow[]>
async getMetadata(): Promise<SheetMetadata>
async getSchema(): Promise<SheetSchema[]>
// Static methods
static getAuth(config?: AuthConfig): LiveSheetsAuth
static async openSheetPicker(config: SheetPickerConfig): Promise<void>
}
```
### Types
```typescript
interface SheetRow {
[key: string]: any;
}
interface SheetMetadata {
spreadsheetId: string;
sheetName: string;
rowCount: number;
columnCount: number;
}
interface SheetSchema {
name: string;
type: string;
}
```
## Complete Example
```tsx
import React from 'react';
import { useLiveSheets } from 'livesheets-sdk/react';
export default function DataTable() {
const {
isAuthenticated,
hasSheet,
data,
isLoading,
error,
connect,
selectSheet,
refresh,
sheetMetadata
} = useLiveSheets({
autoLoadData: true,
onDataLoaded: (rows) => {
console.log(`Loaded ${rows.length} rows`);
}
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (!isAuthenticated) {
return (
<button onClick={connect} disabled={isLoading}>
{isLoading ? 'Connecting...' : 'Connect Google Sheets'}
</button>
);
}
if (!hasSheet) {
return (
<button onClick={selectSheet} disabled={isLoading}>
{isLoading ? 'Opening picker...' : 'Select a Sheet'}
</button>
);
}
return (
<div>
<h2>{sheetMetadata?.sheetName}</h2>
<p>{data.length} rows × {sheetMetadata?.columnCount} columns</p>
<button onClick={refresh} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Refresh'}
</button>
<table>
<thead>
<tr>
{data[0] && Object.keys(data[0]).map(key => (
<th key={key}>{key}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i}>
{Object.values(row).map((val, j) => (
<td key={j}>{String(val)}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
```
## Lovable Integration Instructions
### Simple Component for Lovable
Here's a complete GoogleSheetsConnector component that works perfectly with Lovable:
```tsx
import React from 'react';
import { useLiveSheets } from 'livesheets-sdk/react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Loader2 } from 'lucide-react';
export default function GoogleSheetsConnector() {
const {
isAuthenticated,
hasSheet,
data,
isLoading,
error,
connect,
selectSheet,
disconnect,
refresh,
sheetMetadata
} = useLiveSheets();
if (!isAuthenticated) {
return (
<Card>
<CardHeader>
<CardTitle>Connect Google Sheets</CardTitle>
</CardHeader>
<CardContent className="text-center">
<Button onClick={connect} disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Connecting...
</>
) : (
'Connect Google Sheets'
)}
</Button>
</CardContent>
</Card>
);
}
if (!hasSheet) {
return (
<Card>
<CardHeader>
<CardTitle>Select a Spreadsheet</CardTitle>
</CardHeader>
<CardContent className="text-center space-y-4">
<Button onClick={selectSheet} disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Opening picker...
</>
) : (
'Select Sheet'
)}
</Button>
<Button variant="ghost" onClick={disconnect}>
Disconnect
</Button>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>{sheetMetadata?.sheetName || 'Sheet Data'}</CardTitle>
</CardHeader>
<CardContent>
<div className="mb-4 flex gap-2">
<Button onClick={refresh} disabled={isLoading} size="sm">
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Loading...
</>
) : (
'Refresh'
)}
</Button>
<Button onClick={selectSheet} variant="outline" size="sm">
Change Sheet
</Button>
<Button onClick={disconnect} variant="ghost" size="sm">
Disconnect
</Button>
</div>
{error && (
<div className="text-red-500 mb-4">{error.message}</div>
)}
{data.length > 0 && (
<div className="overflow-x-auto">
<table className="w-full border">
<thead>
<tr>
{Object.keys(data[0]).map(key => (
<th key={key} className="border p-2 text-left">
{key}
</th>
))}
</tr>
</thead>
<tbody>
{data.slice(0, 10).map((row, i) => (
<tr key={i}>
{Object.values(row).map((val, j) => (
<td key={j} className="border p-2">
{String(val)}
</td>
))}
</tr>
))}
</tbody>
</table>
{data.length > 10 && (
<p className="text-center mt-2 text-sm text-muted-foreground">
Showing 10 of {data.length} rows
</p>
)}
</div>
)}
</CardContent>
</Card>
);
}
```
### Integration Steps for Lovable
1. **Install the SDK**: Add `livesheets-sdk` to your dependencies
2. **Copy the component above** into your project
3. **Add it to your page**: Just drop `<GoogleSheetsConnector />` anywhere
4. **That's it!** The component handles everything else
## Configuration
### Backend URL
Default: `https://livesheets-backend-624058308714.europe-north1.run.app`
To use a custom backend:
```typescript
const auth = new LiveSheetsAuth({
backendUrl: 'https://your-backend-url.com'
});
// Or with hooks
const { connect } = useLiveSheets({
backendUrl: 'https://your-backend-url.com'
});
```
### Storage Keys
Customize localStorage keys:
```typescript
const auth = new LiveSheetsAuth({
storageKey: 'my_app_jwt',
userIdKey: 'my_app_user_id'
});
```
## OAuth Setup
The SDK handles the complete OAuth flow:
1. User clicks "Connect" → Redirects to Google OAuth
2. User authorizes → Redirects back with JWT
3. SDK stores JWT in localStorage
4. All subsequent API calls use the JWT
### Required Google OAuth Scopes
- `https://www.googleapis.com/auth/spreadsheets.readonly`
- `https://www.googleapis.com/auth/drive.readonly`
### OAuth Onboarding URL
For manual integration or testing:
```
https://livesheets-backend-624058308714.europe-north1.run.app/onboarding
```
## Alternative Usage: readSheet Helper
For simple use cases, you can use the `readSheet` helper:
```javascript
import { readSheet } from 'livesheets-sdk';
// Option 1: Pass JWT directly
const rows = await readSheet(jwt);
// Option 2: Auto-detect JWT from localStorage
const rows = await readSheet();
// Option 3: Pass options
const rows = await readSheet({
jwt: jwt, // Optional if stored in localStorage
sheetId: 'abc123', // For future use
sheet: 'Sheet1' // For future use
});
```
## Common Issues
### Import Errors
Always use named imports with the `new` keyword:
```typescript
// ✅ CORRECT
import { LiveSheetsClient } from 'livesheets-sdk';
const client = new LiveSheetsClient(jwt);
// ❌ WRONG - Missing 'new' keyword
import { LiveSheetsClient } from 'livesheets-sdk';
const client = LiveSheetsClient(jwt); // Error!
```
### TypeScript Errors
When rendering data, convert to strings:
```tsx
// ✅ CORRECT
<td>{String(cell)}</td>
// ❌ WRONG - Type error
<td>{cell}</td>
```
## Browser Support
- Chrome, Firefox, Safari, Edge (latest versions)
- Requires localStorage and postMessage support
- Popup blockers must allow the sheet picker
## License
MIT