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 🙏

© 2025 – Pkg Stats / Ryan Hefner

capacitor-google-auth-zeattacker

v1.0.2

Published

Google Auth plugin for capacitor.

Readme


✨ Key Features

  • 🔐 Persistent Sessions - Sign in once, stay authenticated across app restarts
  • Automatic Token Refresh - Tokens refresh transparently without user interaction
  • 📊 Perfect for Data Sync - Optimized for RxDB → Google Sheets/Drive sync scenarios
  • 🌐 Cross-Platform - Works seamlessly on Web, iOS, and Android
  • 📅 Token Expiration Tracking - Know exactly when tokens expire
  • 🛡️ Secure Storage - Platform-native secure session persistence
  • 🎯 Type-Safe - Full TypeScript support with comprehensive type definitions

💡 Perfect For

  • Offline-First Apps with cloud sync (RxDB, PouchDB, etc.)
  • Google Drive/Sheets Integration without OAuth verification hassle
  • Apps requiring persistent authentication without constant re-login
  • Background data synchronization with automatic token management

📦 Installation

npm install @zeattacker/capacitor-google-auth

# or with pnpm
pnpm add @zeattacker/capacitor-google-auth

# or with yarn
yarn add @zeattacker/capacitor-google-auth

Then sync your Capacitor project:

npx cap sync

🚀 Quick Start

Basic Setup with Persistent Sessions

import { GoogleAuth, GoogleAuthScopes } from '@zeattacker/capacitor-google-auth';

// Initialize with auto-restore for persistent sessions
await GoogleAuth.initialize({
  clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
  scopes: [GoogleAuthScopes.DRIVE_FILE],  // Pre-defined scope constants
  grantOfflineAccess: true,                // Enable refresh tokens
  autoRestoreSession: true,                // Auto-restore on app launch
  refreshThreshold: 300,                   // Auto-refresh if expires in 5 min
});

// Try to restore existing session
let user = await GoogleAuth.restoreSession();

if (!user) {
  // No saved session, prompt user to sign in
  user = await GoogleAuth.signIn();
  await GoogleAuth.saveSession();  // Save for next time
}

// User is now authenticated!
console.log('Signed in as:', user.email);
console.log('Access token:', user.authentication.accessToken);

Using with Google Sheets Sync (RxDB Example)

import { GoogleAuth, GoogleAuthScopes } from '@zeattacker/capacitor-google-auth';

class SheetsSync {
  private accessToken: string;

  async initialize() {
    // Initialize auth
    await GoogleAuth.initialize({
      clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
      scopes: [GoogleAuthScopes.DRIVE_FILE],  // Only app-created files
      grantOfflineAccess: true,
      autoRestoreSession: true,
    });

    // Restore or sign in
    let user = await GoogleAuth.restoreSession();
    if (!user) {
      user = await GoogleAuth.signIn();
      await GoogleAuth.saveSession();
    }

    this.accessToken = user.authentication.accessToken;
  }

  async syncRxDBData(documents: any[]) {
    // Check token validity before API call
    const validation = await GoogleAuth.isTokenValid();

    if (!validation.valid) {
      // Auto-refresh if expired
      const auth = await GoogleAuth.refresh();
      this.accessToken = auth.accessToken;
      await GoogleAuth.saveSession();
    }

    // Now safely make Google Sheets API call
    await this.updateSpreadsheet(documents);
  }

  private async updateSpreadsheet(data: any[]) {
    const response = await fetch(
      'https://sheets.googleapis.com/v4/spreadsheets/YOUR_SPREADSHEET_ID/values/Sheet1!A1:append?valueInputOption=RAW',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ values: data }),
      }
    );

    if (!response.ok) {
      throw new Error('Failed to sync to Google Sheets');
    }
  }
}

📖 For complete RxDB + Google Sheets integration examples, see GOOGLE_SHEETS_SYNC_GUIDE.md

🔧 Platform Setup

Web

The web platform uses Google Identity Services (GIS). Initialize after the platform is ready:

import { GoogleAuth } from '@zeattacker/capacitor-google-auth';

// In your app initialization (e.g., Angular AppComponent, React App.tsx)
GoogleAuth.initialize({
  clientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
  scopes: ['profile', 'email'],
  grantOfflineAccess: true,
});

Optional: Use meta tags for client ID:

<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com" />

iOS

  1. Create iOS Client ID in Google Cloud Console

    • Get your Client ID and iOS URL scheme
  2. Add URL Scheme to Info.plist

    • Open Xcode → Select App Target → Info → URL Types
    • Add REVERSED_CLIENT_ID from your iOS URL scheme
    • Example: com.googleusercontent.apps.YOUR_CLIENT_ID
  3. Configure Client ID (priority order):

    1. clientId in initialize() method
    2. iosClientId in capacitor.config.json
    3. clientId in capacitor.config.json
    4. CLIENT_ID in GoogleService-Info.plist

Example capacitor.config.json:

{
  "plugins": {
    "GoogleAuth": {
      "iosClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com",
      "scopes": ["profile", "email"],
      "forceCodeForRefreshToken": true
    }
  }
}

Android

  1. Configure Client ID (priority order):
    1. clientId in initialize() method
    2. androidClientId in capacitor.config.json
    3. clientId in capacitor.config.json
    4. server_client_id in strings.xml

Option 1: Using capacitor.config.json (Recommended)

{
  "plugins": {
    "GoogleAuth": {
      "androidClientId": "YOUR_ANDROID_CLIENT_ID.apps.googleusercontent.com",
      "scopes": ["profile", "email"],
      "forceCodeForRefreshToken": true
    }
  }
}

Option 2: Using strings.xml

<!-- android/app/src/main/res/values/strings.xml -->
<resources>
  <string name="server_client_id">YOUR_WEB_CLIENT_ID.apps.googleusercontent.com</string>
</resources>

Optional: Override Play Services Auth version in variables.gradle:

ext {
  gmsPlayServicesAuthVersion = '21.2.0'
}

📚 Core API

Initialize

await GoogleAuth.initialize(options?: InitOptions);

Options:

| Option | Type | Description | Default | |--------|------|-------------|---------| | clientId | string | Client ID from Google Console | - | | scopes | string[] | OAuth scopes to request | ['email', 'profile', 'openid'] | | grantOfflineAccess | boolean | Request refresh tokens | false | | autoRestoreSession | boolean | Auto-restore saved session on init | false | | refreshThreshold | number | Seconds before expiry to trigger refresh | 300 |

Sign In

const user = await GoogleAuth.signIn();

Returns user information with authentication tokens:

{
  id: string;
  email: string;
  name: string;
  familyName: string;
  givenName: string;
  imageUrl: string;
  serverAuthCode: string;
  authentication: {
    accessToken: string;
    idToken: string;
    refreshToken?: string;
    expiresIn: number;      // ⭐ New: seconds until expiration
    issuedAt: number;       // ⭐ New: timestamp when issued
    expiresAt: number;      // ⭐ New: expiration timestamp
  }
}

Refresh Token

const auth = await GoogleAuth.refresh(options?: RefreshOptions);

Options:

| Option | Type | Description | Default | |--------|------|-------------|---------| | forceRefresh | boolean | Force refresh even if token is valid | false |

Smart refresh logic:

  • If forceRefresh: false and token valid for > 5 min → returns current token
  • If forceRefresh: true → always gets new token
  • On mobile: uses refresh token (no user prompt)
  • On web: may require user interaction

Sign Out

await GoogleAuth.signOut();

Signs out the user and clears saved session.

Token Validation (⭐ New)

const validation = await GoogleAuth.isTokenValid();

Returns:

{
  valid: boolean;           // Is token currently valid?
  expiresIn: number;        // Seconds remaining until expiration
  needsRefresh: boolean;    // Should refresh soon? (< threshold)
  hasRefreshToken: boolean; // Can refresh without user prompt?
}

Example:

const validation = await GoogleAuth.isTokenValid();

if (!validation.valid) {
  console.log('Token expired, refreshing...');
  await GoogleAuth.refresh();
} else if (validation.needsRefresh) {
  console.log('Token expiring soon, refresh in background');
  GoogleAuth.refresh().catch(console.error);
}

Session Management (⭐ New)

Save Session

await GoogleAuth.saveSession();

Saves current authentication state to secure storage. Call after sign-in or refresh for persistence.

Restore Session

const user = await GoogleAuth.restoreSession();

Restores previously saved session:

  • Returns null if no saved session exists
  • Auto-refreshes token if expired
  • Returns user object if successful

Check Saved Session

const { exists } = await GoogleAuth.hasSavedSession();

Returns true if a saved session exists.

Clear Session

await GoogleAuth.clearSession();

Clears saved session from storage (doesn't sign out from Google).

🎯 Predefined Scopes (⭐ New)

Use GoogleAuthScopes constants for common scopes:

import { GoogleAuthScopes } from '@zeattacker/capacitor-google-auth';

await GoogleAuth.initialize({
  scopes: [
    GoogleAuthScopes.DRIVE_FILE,          // App-created files only ✅ No verification needed!
    GoogleAuthScopes.DRIVE_APPDATA,       // App-specific data folder
    GoogleAuthScopes.SPREADSHEETS,        // Full spreadsheet access (requires verification)
    GoogleAuthScopes.SPREADSHEETS_READONLY, // Read-only spreadsheets
    GoogleAuthScopes.EMAIL,               // Email address
    GoogleAuthScopes.PROFILE,             // Basic profile
    GoogleAuthScopes.OPENID,              // OpenID Connect
  ]
});

💎 Recommended: drive.file Scope

For RxDB/database sync apps, use GoogleAuthScopes.DRIVE_FILE:

Benefits:

  • Access only to files your app creates
  • No OAuth verification required from Google
  • Perfect for sync spreadsheets
  • Better user privacy
  • Faster to production
scopes: [GoogleAuthScopes.DRIVE_FILE]  // 👈 Best choice for sync apps

🔄 Common Patterns

Pattern 1: App Initialization with Auto-Restore

// app.component.ts (Angular) or App.tsx (React)
async function initializeApp() {
  await GoogleAuth.initialize({
    clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
    scopes: [GoogleAuthScopes.DRIVE_FILE],
    grantOfflineAccess: true,
    autoRestoreSession: true,  // ← Auto-restore on launch
  });

  // Session will be restored automatically if available
  // No need to call restoreSession() manually
}

Pattern 2: Manual Session Restore with Fallback

async function ensureAuthenticated() {
  // Try to restore saved session
  let user = await GoogleAuth.restoreSession();

  if (!user) {
    // No saved session, prompt user to sign in
    user = await GoogleAuth.signIn();
    await GoogleAuth.saveSession();
  }

  return user;
}

Pattern 3: Token Validation Before API Calls

async function makeAuthenticatedRequest(url: string) {
  // Always check token validity first
  const validation = await GoogleAuth.isTokenValid();

  if (!validation.valid) {
    // Refresh if expired
    await GoogleAuth.refresh();
    await GoogleAuth.saveSession();
  }

  // Now make the request
  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  return response;
}

Pattern 4: Background Token Refresh

// Refresh tokens periodically in background
setInterval(async () => {
  const validation = await GoogleAuth.isTokenValid();

  if (validation.needsRefresh && validation.hasRefreshToken) {
    try {
      await GoogleAuth.refresh();
      await GoogleAuth.saveSession();
      console.log('Token refreshed in background');
    } catch (error) {
      console.error('Background refresh failed:', error);
    }
  }
}, 5 * 60 * 1000); // Every 5 minutes

🎓 Framework Examples

Angular

// app.component.ts
import { Component } from '@angular/core';
import { Platform } from '@ionic/angular';
import { GoogleAuth } from '@zeattacker/capacitor-google-auth';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
})
export class AppComponent {
  constructor(private platform: Platform) {
    this.initializeApp();
  }

  async initializeApp() {
    await this.platform.ready();

    await GoogleAuth.initialize({
      scopes: ['profile', 'email'],
      grantOfflineAccess: true,
      autoRestoreSession: true,
    });
  }

  async signIn() {
    const user = await GoogleAuth.signIn();
    await GoogleAuth.saveSession();
    console.log('Signed in:', user.email);
  }

  async signOut() {
    await GoogleAuth.signOut();
    console.log('Signed out');
  }
}

React

// App.tsx
import { useEffect, useState } from 'react';
import { GoogleAuth, User } from '@zeattacker/capacitor-google-auth';

function App() {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    initAuth();
  }, []);

  const initAuth = async () => {
    await GoogleAuth.initialize({
      clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
      scopes: ['profile', 'email'],
      grantOfflineAccess: true,
      autoRestoreSession: true,
    });

    // Try to restore session
    const restoredUser = await GoogleAuth.restoreSession();
    if (restoredUser) {
      setUser(restoredUser);
    }
  };

  const signIn = async () => {
    const user = await GoogleAuth.signIn();
    await GoogleAuth.saveSession();
    setUser(user);
  };

  const signOut = async () => {
    await GoogleAuth.signOut();
    setUser(null);
  };

  return (
    <div>
      {user ? (
        <div>
          <p>Welcome, {user.name}!</p>
          <button onClick={signOut}>Sign Out</button>
        </div>
      ) : (
        <button onClick={signIn}>Sign In with Google</button>
      )}
    </div>
  );
}

export default App;

Vue 3

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { GoogleAuth } from '@zeattacker/capacitor-google-auth';

const user = ref(null);

onMounted(async () => {
  await GoogleAuth.initialize({
    scopes: ['profile', 'email'],
    grantOfflineAccess: true,
    autoRestoreSession: true,
  });

  // Try to restore session
  user.value = await GoogleAuth.restoreSession();
});

const signIn = async () => {
  user.value = await GoogleAuth.signIn();
  await GoogleAuth.saveSession();
};

const signOut = async () => {
  await GoogleAuth.signOut();
  user.value = null;
};
</script>

<template>
  <div>
    <div v-if="user">
      <p>Welcome, {{ user.name }}!</p>
      <button @click="signOut">Sign Out</button>
    </div>
    <button v-else @click="signIn">Sign In with Google</button>
  </div>
</template>

🔍 Troubleshooting

Sessions not persisting across app restarts

Solution: Ensure you're calling saveSession() after sign-in and token refresh:

const user = await GoogleAuth.signIn();
await GoogleAuth.saveSession();  // ← Don't forget this!

Token expired errors when making API calls

Solution: Always validate tokens before API calls:

const validation = await GoogleAuth.isTokenValid();
if (!validation.valid) {
  await GoogleAuth.refresh();
}
// Now make your API call

"Refresh token not available" on web

Explanation: Web platform doesn't support automatic refresh without user interaction. Users must re-authenticate.

Solution: Handle web platform differently or accept re-authentication:

import { Capacitor } from '@capacitor/core';

if (Capacitor.getPlatform() === 'web') {
  // Web: re-authenticate
  await GoogleAuth.signIn();
} else {
  // Mobile: can refresh silently
  await GoogleAuth.refresh();
}

iOS build errors about GoogleSignIn

Solution: Ensure you've added the URL scheme to Info.plist:

  1. Open Xcode
  2. Select your app target
  3. Go to Info → URL Types
  4. Add REVERSED_CLIENT_ID from Google Console

📖 Additional Resources

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Guidelines:

  • Follow existing code style
  • Add tests for new features
  • Update documentation
  • Keep features aligned with official Google Auth library

📄 License

MIT