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

@safercity/sdk-react

v0.4.0

Published

React hooks and components for SaferCity SDK with TanStack Query integration

Readme

@safercity/sdk-react

React hooks and components for SaferCity SDK with TanStack Query integration.

What's New in v0.4.0

  • 100% Codegen Types - All hook parameter and return types flow from the generated OpenAPI types. Type changes propagate automatically after bun run generate:all.
  • New Panic Hooks - Added useCreatePremiumPanic() for CellFind provider integration.
  • Subscription Realignment - Removed useSubscribeUser() hook (dead route). useCreateSubscription() body shape updated to use isPremium flag.
  • Enhanced Filtering - useSubscriptions() now supports expanded query parameters (status, subscriptionTypeId, sortBy, search).

What's New in v0.3.2

  • Proxy Mode Enhanced - SaferCityProvider in proxy mode now supports tenantId, userId, and custom headers. Perfect for white-label React Native apps that need to pass tenant context through a proxy.
  • useBanner Fix - Fixed useBanner hook parameter from radius to days to match API schema.
// White-label app with proxy mode
<SaferCityProvider
  mode="proxy"
  proxyBaseUrl={`${API_URL}/api/safercity`}
  tenantId={TENANT_ID}
>
  <App />
</SaferCityProvider>

What's New in v0.3.1

  • Simplified Hook Data - Hook data now reflects the API response body directly (no ApiResponse wrapper). Access data.status for health, data.data.firstName for user responses with API envelopes.
  • Typed Hook Errors - All hooks use SaferCityApiError as the error type. Access error.status, error.message, error.error directly in isError blocks or onError callbacks.

What's New in v0.3.0

  • Typed SDK Alignment - Hooks now use the auto-generated types from @safercity/sdk, ensuring 1:1 alignment with the API schema.
  • Correct Field Names - Mutations now use the exact API field names (emailAddress, panicType, phoneNumber).
  • Simplified Return Types - Domain methods now return the API body directly (no more ApiResponse<T> wrapper). Hook data reflects the API response structure directly (e.g., user.data.firstName).

What's New in v0.2.0

  • Panic Information Hooks - New hooks for managing user panic profiles and emergency contacts.
  • User Scoping - SaferCityProvider now supports userId for automatic request scoping.
  • Enhanced Crime Hooks - Added useBanner and useCrimeCategoriesWithTypes.
  • Security Hardening - Removed admin-only hooks (useUsers, useDeleteUser, useSubscriptionStats).
  • Path Alignment - Hooks now use the latest singular API paths.

What's New in v0.1.3

  • Security hardening - Removed usePanics and useSubscriptionStats hooks (client-side listing/stats are now restricted)

Installation

npm install @safercity/sdk-react @tanstack/react-query
# or
bun add @safercity/sdk-react @tanstack/react-query

Authentication Modes

The provider supports three authentication modes. Choose the one that fits your architecture:

Proxy Mode (Default - Most Secure)

Client -> Your Backend -> SaferCity API. Your backend handles credentials.

import { SaferCityProvider } from '@safercity/sdk-react';

function App() {
  return (
    <SaferCityProvider mode="proxy" proxyBaseUrl="/api/safercity">
      <YourApp />
    </SaferCityProvider>
  );
}

Set up the proxy on your backend with createNextHandler or createExpressMiddleware from @safercity/sdk.

Direct Mode

Client -> SaferCity API with an external auth token. For white-label apps using Clerk, Auth0, better-auth, etc.

import { SaferCityProvider } from '@safercity/sdk-react';

function App() {
  return (
    <SaferCityProvider
      mode="direct"
      baseUrl="https://api.safercity.com"
      tenantId="tenant-123"
      userId="user-123" // optional, for auto-scoping
      getAccessToken={() => session?.accessToken}
    >
      <YourApp />
    </SaferCityProvider>
  );
}

Cookie Mode

Browser with credentials: include. For first-party web apps using session cookies.

import { SaferCityProvider } from '@safercity/sdk-react';

function App() {
  return (
    <SaferCityProvider 
      mode="cookie" 
      baseUrl="https://api.safercity.com"
      userId="user-123" // optional
    >
      <YourApp />
    </SaferCityProvider>
  );
}

Session Management (Cookie Mode)

When using cookie mode, the provider exposes session management hooks:

import { useSession, useSessionManager, useAuthMode } from '@safercity/sdk-react';

function AuthStatus() {
  const session = useSession();
  const mode = useAuthMode(); // "proxy" | "direct" | "cookie"

  if (session.isLoading) return <div>Loading...</div>;
  if (session.error) return <div>Error: {session.error.message}</div>;

  return <div>Authenticated: {session.isAuthenticated ? 'Yes' : 'No'}</div>;
}

function LoginButton() {
  const { createSession, clearSession, refreshCsrf, isAuthenticated } = useSessionManager();

  const handleLogin = async (externalToken: string) => {
    await createSession(externalToken, 'tenant-123');
  };

  const handleLogout = async () => {
    await clearSession();
  };

  return isAuthenticated
    ? <button onClick={handleLogout}>Logout</button>
    : <button onClick={() => handleLogin('...')}>Login</button>;
}

SessionState

interface SessionState {
  isAuthenticated: boolean;
  isLoading: boolean;
  userId?: string;
  tenantId?: string;
  expiresAt?: number;
  error?: Error;
}

Using Hooks

import { useUser, useCreatePanic } from '@safercity/sdk-react';

function Dashboard({ userId }: { userId: string }) {
  const { data: user, isLoading } = useUser(userId);
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <div>
      <h1>Welcome, {user?.data.firstName}</h1>
    </div>
  );
}

function PanicButton({ userId }: { userId: string }) {
  const createPanic = useCreatePanic();
  
  const handlePanic = () => {
    createPanic.mutate({
      userId,
      panicType: 'emergency',
      latitude: -26.2041,
      longitude: 28.0473,
    });
  };
  
  return (
    <button onClick={handlePanic} disabled={createPanic.isPending}>
      {createPanic.isPending ? 'Creating...' : 'Trigger Panic'}
    </button>
  );
}

Available Hooks

Provider Hooks

  • useSaferCity() - Full context (client, mode, session, session management)
  • useSaferCityClient() - SaferCity client instance
  • useSession() - Session state (cookie mode)
  • useAuthMode() - Current auth mode ("proxy" | "direct" | "cookie")
  • useSessionManager() - Session management functions (cookie mode)

Health and Auth

  • useHealthCheck() - API health status
  • useWhoAmI() - Current auth context

Users

  • useUser(userId?) - Get user by ID (defaults to client's userId)
  • useCreateUser() - Create user mutation
  • useUpdateUser() - Update user mutation

Panics

  • usePanic(panicId, query?) - Get panic by ID
  • useCreatePanic() - Create panic mutation
  • useCreatePremiumPanic() - Create premium panic mutation
  • useUpdatePanicLocation() - Update location mutation
  • useCancelPanic() - Cancel panic mutation
  • usePanicTypes(userId?) - Get available panic types for a user
  • usePanicStream(panicId, options?) - Stream panic updates

Panic Information

  • usePanicInformation(id) - Get profile by ID
  • usePanicInformationByUser(userId?) - Get profile by user ID
  • usePanicEligibility(userId?) - Check user eligibility
  • useCreatePanicInformation() - Create profile mutation
  • useUpdatePanicInformation() - Update profile mutation
  • useDeletePanicInformation() - Delete profile mutation

Subscriptions

  • useSubscriptionTypes() - List subscription types
  • useSubscriptions(filters?) - List subscriptions for a user
  • useCreateSubscription() - Create subscription mutation

Notifications

  • useBulkTriggerNotifications() - Bulk trigger notifications mutation

Location Safety

  • useLocationSafety(lat, lng, radius?) - Check location safety

Banner

  • useBanner(body) - Get crime banner data for a location

Crimes

  • useCrimes(filters?) - List crimes
  • useCrimeCategories() - Get crime categories
  • useCrimeTypes() - Get crime types
  • useCrimeCategoriesWithTypes() - Get categories with nested types

Streaming Hook

import { usePanicStream } from '@safercity/sdk-react';

function PanicTracker({ panicId }: { panicId: string }) {
  const { data, isConnected, error, events } = usePanicStream(panicId, {
    keepHistory: true,
    onEvent: (event) => console.log('Update:', event),
  });
  
  if (error) return <div>Error: {error.message}</div>;
  if (!isConnected) return <div>Connecting...</div>;
  
  return (
    <div>
      <p>Latest: {data?.data}</p>
      <p>Total events: {events.length}</p>
    </div>
  );
}

Query Keys

Use query keys for manual cache management:

import { saferCityKeys } from '@safercity/sdk-react';
import { useQueryClient } from '@tanstack/react-query';

function RefreshButton() {
  const queryClient = useQueryClient();
  
  const refresh = () => {
    // Invalidate all user queries
    queryClient.invalidateQueries({ queryKey: saferCityKeys.users() });
    
    // Invalidate specific user
    queryClient.invalidateQueries({ queryKey: saferCityKeys.usersDetail('user-123') });
  };
  
  return <button onClick={refresh}>Refresh</button>;
}

Custom QueryClient

import { SaferCityProvider } from '@safercity/sdk-react';
import { QueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
    },
  },
});

function App() {
  return (
    <SaferCityProvider mode="proxy" proxyBaseUrl="/api/safercity" queryClient={queryClient}>
      <YourApp />
    </SaferCityProvider>
  );
}

Access Raw Client

import { useSaferCityClient } from '@safercity/sdk-react';

function CustomComponent() {
  const client = useSaferCityClient();
  
  const customRequest = async () => {
    const response = await client._client.get('/custom-endpoint');
    return response.data;
  };
}

License

MIT