npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

livesheets-sdk

v0.2.0

Published

LiveSheets SDK for Google Sheets integration with Lovable apps. OAuth onboarding URL: https://livesheets-backend-624058308714.europe-north1.run.app/onboarding

Downloads

43

Readme

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

npm install livesheets-sdk

Quick Start

Using React Hooks (Recommended for Lovable Apps)

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

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:

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:

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:

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:

const { data, loading, error } = useSheetData(jwt);

API Reference

LiveSheetsAuth

class LiveSheetsAuth {
  constructor(config?: {
    backendUrl?: string;
    storageKey?: string;
    userIdKey?: string;
  })
  
  startOAuth(returnUrl?: string): void
  getJWT(): string | null
  isAuthenticated(): boolean
  logout(): void
}

LiveSheetsClient

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

interface SheetRow {
  [key: string]: any;
}

interface SheetMetadata {
  spreadsheetId: string;
  sheetName: string;
  rowCount: number;
  columnCount: number;
}

interface SheetSchema {
  name: string;
  type: string;
}

Complete Example

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:

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:

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:

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:

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:

// ✅ 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:

// ✅ 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