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

@coderstudiolabs/google-chat-space-finder

v1.0.1

Published

Resolve a Google Chat DM space ID from an email address. Handles OAuth2, People API, and Directory API lookups so you can deep-link, send programmatic messages, or build integrations without touching the Google Chat UI.

Downloads

229

Readme

google-chat-space-finder

Resolve a Google Chat DM space ID from an email address. Handles OAuth2, People API, and Directory API lookups so you can deep-link into conversations, send programmatic messages, or build integrations — without ever touching the Google Chat UI.

Works as a CLI tool or a Node.js library.

Prerequisites

1. Enable Google APIs

Go to Google Cloud ConsoleAPIs & Services > Library and enable the following:

| API | Purpose | | --- | ------- | | Google Chat API | Look up and create DM spaces | | People API | Resolve an email to a Google account ID via the org directory |

Note: You do NOT need to be a Google Workspace admin. The People API searchDirectoryPeople endpoint works for any Workspace user within your org.

2. Configure the OAuth Consent Screen

Go to APIs & Services > OAuth consent screen:

  • User type: Internal (recommended — limits access to your org)
  • Add the following scopes:
    • https://www.googleapis.com/auth/chat.spaces.readonly
    • https://www.googleapis.com/auth/chat.spaces
    • https://www.googleapis.com/auth/directory.readonly

3. Create OAuth 2.0 Credentials

Go to APIs & Services > Credentials > Create Credentials > OAuth client ID:

  • Application type: Desktop app
  • After creation, copy the Client ID and Client Secret
  • Add http://localhost:3003/callback (or your custom port) as an authorized redirect URI

Installation

npm install -g @coderstudiolabs/google-chat-space-finder

Copy the example env file and fill in your credentials:

cp .env.example .env

CLI Usage

export GOOGLE_CLIENT_ID=your_client_id
export GOOGLE_CLIENT_SECRET=your_client_secret

# Interactive
chat-space

# Pass email as argument (scriptable)
chat-space [email protected]

Environment Variables

| Variable | Required | Default | Description | | --------------------------- | -------- | ---------------------------------- | --------------------------------------------------------------------------- | | AUTH_GOOGLE_CLIENT_ID | Yes | — | OAuth 2.0 client ID (also accepted: GOOGLE_CLIENT_ID) | | AUTH_GOOGLE_CLIENT_SECRET | Yes | — | OAuth 2.0 client secret (also accepted: GOOGLE_CLIENT_SECRET) | | AUTH_GOOGLE_CALLBACK_URL | No | http://localhost:3003/callback | OAuth redirect URI (also accepted: GOOGLE_OAUTH_REDIRECT_URI) | | AUTH_GOOGLE_ALLOWED_DOMAINS | No | — | Comma-separated list of allowed email domains, e.g. example.com,acme.com. Rejects emails outside these domains. (also accepted: GOOGLE_ALLOWED_DOMAINS) | | GOOGLE_OAUTH_PORT | No | 3003 | Local port for OAuth callback |


Usage in JavaScript Projects

Important: This package uses the Google OAuth flow, which requires a server-side component to keep your GOOGLE_CLIENT_SECRET safe. Never expose your client secret in frontend or mobile code. The recommended pattern for all UI frameworks is:

  1. Your backend handles authentication and space ID resolution using this package.
  2. Your frontend/mobile app calls your backend API to get the space ID or URLs.

Next.js

Install as a server-side dependency:

npm install @coderstudiolabs/google-chat-space-finder

1. Add environment variables to .env.local:

GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret

2. Create an API route that resolves the space ID:

// app/api/chat-space/route.js  (Next.js App Router)
import { resolveUserId, getDMSpace, buildSpaceUrls, refreshAccessToken } from '@coderstudiolabs/google-chat-space-finder';

export async function POST(request) {
  const { email, accessToken, refreshToken } = await request.json();

  if (!email || !accessToken) {
    return Response.json({ error: 'email and accessToken are required' }, { status: 400 });
  }

  try {
    // Refresh the token if needed before making API calls
    let token = accessToken;
    if (refreshToken) {
      const refreshed = await refreshAccessToken({
        refreshToken,
        clientId: process.env.GOOGLE_CLIENT_ID,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      });
      token = refreshed.access_token;
    }

    const userId = await resolveUserId(email, token);
    const space = await getDMSpace(userId, token);
    const spaceId = space.name.replace('spaces/', '');
    const { webUrl, androidUrl } = buildSpaceUrls(spaceId);

    return Response.json({ spaceId, webUrl, androidUrl });
  } catch (err) {
    return Response.json({ error: err.message }, { status: 500 });
  }
}

3. Create an API route for the OAuth callback:

// app/api/auth/google/callback/route.js
import { exchangeCodeForTokens } from '@coderstudiolabs/google-chat-space-finder';

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get('code');

  if (!code) {
    return Response.json({ error: 'Missing authorization code' }, { status: 400 });
  }

  const tokens = await exchangeCodeForTokens({
    code,
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    redirectUri: process.env.GOOGLE_OAUTH_REDIRECT_URI,
  });

  // Store tokens securely (session, database, etc.) — never send refresh_token to the client
  // Example: set an HTTP-only cookie or save to your database
  return Response.json({ access_token: tokens.access_token, expires_in: tokens.expires_in });
}

4. Call from your React component:

// components/SendMessageButton.jsx
'use client';

export default function SendMessageButton({ email }) {
  async function handleClick() {
    const res = await fetch('/api/chat-space', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, accessToken: 'stored_access_token' }),
    });
    const { webUrl, error } = await res.json();
    if (error) return alert(error);
    window.open(webUrl, '_blank');
  }

  return <button onClick={handleClick}>Send Message via Google Chat</button>;
}

React (Vite / Create React App)

Same pattern as Next.js — you need a separate backend. React itself runs in the browser and cannot securely hold your client secret.

Backend (Express):

npm install @coderstudiolabs/google-chat-space-finder express cors
// server.js
const express = require('express');
const cors = require('cors');
const { resolveUserId, getDMSpace, buildSpaceUrls } = require('@coderstudiolabs/google-chat-space-finder');

const app = express();
app.use(cors());
app.use(express.json());

app.post('/api/chat-space', async (req, res) => {
  const { email, accessToken } = req.body;
  try {
    const userId = await resolveUserId(email, accessToken);
    const space = await getDMSpace(userId, accessToken);
    const spaceId = space.name.replace('spaces/', '');
    res.json(buildSpaceUrls(spaceId));
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(4000, () => console.log('Server running on http://localhost:4000'));

React component:

// SendMessageButton.jsx
import { useState } from 'react';

export default function SendMessageButton({ email }) {
  const [loading, setLoading] = useState(false);

  async function handleClick() {
    setLoading(true);
    try {
      const res = await fetch('http://localhost:4000/api/chat-space', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, accessToken: 'stored_access_token' }),
      });
      const { webUrl, error } = await res.json();
      if (error) throw new Error(error);
      window.open(webUrl, '_blank');
    } finally {
      setLoading(false);
    }
  }

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? 'Loading...' : 'Send Message via Google Chat'}
    </button>
  );
}

React Native

React Native cannot use this package directly — it runs on mobile and has no access to Node.js APIs. The space ID lookup must go through your backend.

Backend: Use the Express example above (or any server), then call it from React Native:

// SendMessageButton.jsx
import { Linking, Platform, TouchableOpacity, Text } from 'react-native';

async function openGoogleChat(email) {
  // 1. Ask your backend for the space URLs
  const res = await fetch('https://your-api.com/api/chat-space', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, accessToken: 'stored_access_token' }),
  });
  const { webUrl, androidUrl, error } = await res.json();
  if (error) throw new Error(error);

  // 2. Open the correct URL for the platform
  const url = Platform.OS === 'android' ? androidUrl : webUrl;
  const supported = await Linking.canOpenURL(url);

  if (supported) {
    await Linking.openURL(url);
  } else {
    // Fallback to web if the app is not installed
    await Linking.openURL(webUrl);
  }
}

export default function SendMessageButton({ email }) {
  return (
    <TouchableOpacity onPress={() => openGoogleChat(email)}>
      <Text>Send Message via Google Chat</Text>
    </TouchableOpacity>
  );
}

Android note: The androidUrl is an intent URL that opens the Google Chat app directly. On iOS, webUrl opens Google Chat in the browser or the app via universal links.


Plain Node.js / Backend Scripts

const { findDMSpace } = require('@coderstudiolabs/google-chat-space-finder');

const result = await findDMSpace({
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  email: '[email protected]',
});

console.log(result.spaceId);    // e.g. "pMtH38AAAAE"
console.log(result.webUrl);     // https://chat.google.com/room/pMtH38AAAAE
console.log(result.androidUrl); // intent://...
console.log(result.tokens);     // { access_token, refresh_token, expires_in, ... }

Domain Filtering (Optional)

Restrict lookups to specific email domains. Any email outside the allowed list is rejected before any API call is made.

CLI — set via environment variable:

# Single domain
AUTH_GOOGLE_ALLOWED_DOMAINS=example.com

# Multiple domains (comma-separated)
AUTH_GOOGLE_ALLOWED_DOMAINS=example.com,acme.com

Or in your .env file:

AUTH_GOOGLE_ALLOWED_DOMAINS=example.com

When set:

  • Passing email as a CLI argument rejects it immediately, before OAuth
  • Entering email interactively rejects it after OAuth, before any API calls

Library — pass allowedDomains array:

const result = await findDMSpace({
  clientId: process.env.AUTH_GOOGLE_CLIENT_ID,
  clientSecret: process.env.AUTH_GOOGLE_CLIENT_SECRET,
  email: '[email protected]',
  allowedDomains: ['example.com', 'acme.com'],
});
// throws: Email "[email protected]" is not from an allowed domain. Allowed: example.com, acme.com

If allowedDomains is not set or is an empty array, all domains are accepted.

Token Refresh

The tokens object returned by findDMSpace and exchangeCodeForTokens includes a refresh_token. Save it and use it to get a new access token without re-authenticating:

const { refreshAccessToken } = require('@coderstudiolabs/google-chat-space-finder');

const newTokens = await refreshAccessToken({
  refreshToken: savedRefreshToken,
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
});

API Reference

| Export | Description | | ------------------------- | ------------------------------------------------------------------ | | findDMSpace(opts) | Full auth → resolve → lookup flow | | buildAuthUrl() | Build the Google OAuth2 authorization URL | | waitForAuthCode(port) | Start local server and wait for OAuth callback | | exchangeCodeForTokens() | Exchange auth code for access/refresh tokens | | refreshAccessToken() | Get a new access token using a refresh token | | resolveUserId() | Resolve a Google account ID from an email address | | getDMSpace() | Find or create a DM space by Google account ID | | buildSpaceUrls() | Build web and Android deep-link URLs for a space | | isValidEmail() | Validate an email address format | | isAllowedDomain() | Check if an email belongs to one of the allowed domains |

License

MIT