@qumra/app-react-router
v1.0.5
Published
Qumra app integration for React Router 7 - Authentication, session management, and embedded app support
Readme
@qumra/app-react-router
Build Qumra apps with React Router 7 — authentication, session management, and embedded app support.
A first-class React Router 7 integration for the Qumra apps platform. Provides server-side factories, authentication middleware, session management, and admin GraphQL client support for building secure, scalable embedded applications.
Installation
Install the package and its peer dependencies:
npm install @qumra/app-react-router react react-dom react-routerOr with yarn:
yarn add @qumra/app-react-router react react-dom react-routerOr with pnpm:
pnpm add @qumra/app-react-router react react-dom react-routerQuick Start
1. Initialize the Qumra App
Create a qumra.server.ts file to configure your app:
import { qumraApp } from '@qumra/app-react-router/server';
import { PrismaSessionStorage } from '@qumra/app-react-router';
export const qumra = qumraApp({
apiKey: process.env.QUMRA_API_KEY!,
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
sessionStorage: new PrismaSessionStorage(prisma),
hooks: {
afterAuth: async (context) => {
// Custom logic after successful authentication
console.log('User authenticated:', context.admin.userId);
},
},
});2. Define a Route with Admin Context
Use the qumra factory in your route loaders to authenticate requests:
// routes/api/dashboard.tsx
import { json, type LoaderFunctionArgs } from 'react-router';
import { qumra } from '../qumra.server';
export const loader = async ({ request }: LoaderFunctionArgs) => {
const admin = await qumra.authenticate(request);
// Fetch admin data using the GraphQL client
const user = await admin.graphql(`
query {
user {
id
email
}
}
`);
return json({ user });
};
export default function Dashboard() {
const { user } = useLoaderData<typeof loader>();
return <div>Welcome, {user.email}!</div>;
}App Provider
Wrap your root route with the QumraAppProvider to enable session management and authentication state across your React tree:
// root.tsx
import { QumraAppProvider } from '@qumra/app-react-router';
import { Outlet } from 'react-router';
export default function Root() {
return (
<QumraAppProvider>
<Outlet />
</QumraAppProvider>
);
}Authentication
Admin Authentication in Loaders
The qumra.authenticate() method validates the request and returns an admin context with full access to platform APIs:
export const loader = async ({ request }: LoaderFunctionArgs) => {
const admin = await qumra.authenticate(request);
// Access admin properties
console.log(admin.userId);
console.log(admin.appInstallationId);
console.log(admin.shopId);
return json({ /* ... */ });
};When authentication fails, an AuthenticationError is thrown with appropriate error details. Use an error boundary to handle these gracefully.
Webhook Handling
Handle incoming webhooks from the Qumra platform:
// routes/webhooks.tsx
import { json, type ActionFunctionArgs } from 'react-router';
import { qumra } from '../qumra.server';
export const action = async ({ request }: ActionFunctionArgs) => {
if (request.method === 'POST') {
const webhook = await qumra.webhooks.process(request);
switch (webhook.topic) {
case 'app/installed':
// Handle app installation
console.log(`App installed for shop: ${webhook.shopId}`);
break;
case 'app/uninstalled':
// Handle app uninstallation
console.log(`App uninstalled from shop: ${webhook.shopId}`);
break;
}
return json({ success: true });
}
};Boundary Utilities
Error Boundary
Catch and handle errors gracefully using the exported error boundary. This automatically renders appropriate error pages for different error types:
// routes/root.tsx
import { boundary } from '@qumra/app-react-router/server';
import { useRouteError } from 'react-router';
export function ErrorBoundary() {
const error = useRouteError();
return boundary.error(error);
}The error boundary handles:
AuthenticationError— Invalid or missing credentialsInvalidSessionError— Session validation failuresSessionStorageError— Database or storage errors- Generic HTTP errors and unexpected exceptions
Headers Management
Automatically set security headers (CSP, X-Frame-Options) on all responses:
// routes/root.tsx
import { boundary } from '@qumra/app-react-router/server';
export const headers = boundary.headers;App Distribution
Control how your app is distributed across the Qumra ecosystem using the AppDistribution enum:
import { AppDistribution } from '@qumra/app-react-router/server';
export const qumra = qumraApp({
// ...
distribution: AppDistribution.AppStore, // or AppDistribution.PrivateTable
});Distribution Types
- AppStore: Your app is publicly listed and installable by any Qumra user. Uses OAuth token exchange for authentication.
- PrivateTable: Your app is restricted to specific tables or organizations. Uses pre-configured access tokens.
Example: Conditional Logic by Distribution
const config = {
apiKey: process.env.QUMRA_API_KEY!,
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
sessionStorage: new PrismaSessionStorage(prisma),
distribution: process.env.APP_DISTRIBUTION === 'public'
? AppDistribution.AppStore
: AppDistribution.PrivateTable,
};
export const qumra = qumraApp(config);Private App Example
For private apps with pre-configured tokens:
const qumra = qumraApp({
apiKey: process.env.QUMRA_API_KEY!,
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.PrivateTable,
adminAccessToken: process.env.QUMRA_ADMIN_TOKEN,
});Package Exports
The @qumra/app-react-router package is organized into three export paths for flexibility and tree-shaking:
@qumra/app-react-router (default)
Main entry point with commonly used exports:
import {
QumraAppProvider,
PrismaSessionStorage,
SessionStorageError,
InvalidSessionError,
getSessionToken,
parseSessionToken,
} from '@qumra/app-react-router';@qumra/app-react-router/server
Server-side utilities for route loaders and actions:
import {
qumraApp,
boundary,
AppDistribution,
} from '@qumra/app-react-router/server';Includes:
qumraApp()— App factory functionboundary.error— Error boundary handlerboundary.headers— Security headers helperAppDistribution— Distribution enum- Type definitions for server contexts
@qumra/app-react-router/react
React-specific exports (hooks, context providers):
import { QumraAppProvider, useQumraApp } from '@qumra/app-react-router/react';GraphQL Client
Execute authenticated GraphQL queries and mutations using the admin GraphQL client. The client is available on the admin context returned by qumra.authenticate():
const admin = await qumra.authenticate(request);
const result = await admin.graphql(`
query GetUser($id: ID!) {
user(id: $id) {
id
email
role
}
}
`, {
variables: {
id: 'user-123',
},
});The GraphQL client automatically includes authentication headers and handles error responses:
try {
const result = await admin.graphql(query);
console.log(result.data);
} catch (error) {
if (error instanceof GraphQLError) {
console.error('GraphQL error:', error.message);
}
}Use GraphQL to fetch store data, manage resources, and trigger platform actions securely.
Hooks
afterAuth
Customize behavior after successful authentication by providing an afterAuth hook in your app configuration. This hook runs on every authenticated request:
export const qumra = qumraApp({
apiKey: process.env.QUMRA_API_KEY!,
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
sessionStorage: new PrismaSessionStorage(prisma),
hooks: {
afterAuth: async (context) => {
const { admin, request } = context;
// Log authentication events
console.log(`User ${admin.userId} authenticated`);
// Sync user data to your database
await syncUserData(admin.userId, admin.shopId);
// Validate permissions or perform initialization
const userRole = await getUserRole(admin.userId);
if (!userRole) {
throw new Error('User role not found');
}
},
},
});The afterAuth hook receives an AfterAuthOptions context with:
admin— The authenticated admin context with API access (userId,shopId,appInstallationId,graphql())request— The original HTTP requestresponse— The HTTP response object for setting headers
Return a rejected promise or throw to stop authentication.
Qumra Ecosystem
This package is part of the comprehensive Qumra apps ecosystem. Here are the core packages:
| Package | Purpose | |---------|---------| | @qumra/app-sdk | Core SDK — auth, session, security | | @qumra/app-react-router | React Router 7 integration | | @qumra/app-session-storage-prisma | Prisma session storage | | @qumra/app-session-storage-mongodb | MongoDB session storage | | @qumra/jisr | App Bridge for iframe communication | | @qumra/manara | Design system & components | | @qumra/riwaq | UI Extensions SDK |
Requirements
- Node.js: 18.x or higher
- React: >=18.0.0
- React DOM: >=18.0.0
- React Router: ^7.0.0
- @qumra/app-sdk: ^0.3.0
Configuration Types
Full TypeScript support with comprehensive type definitions:
interface QumraAppConfigArg {
apiKey: string;
accessTokenSecret: string;
refreshTokenSecret: string;
sessionStorage: SessionStorage;
distribution?: AppDistribution;
adminAccessToken?: string;
hooks?: HooksConfig;
}
interface HooksConfig {
afterAuth?: (context: AfterAuthOptions) => Promise<void>;
}
interface AfterAuthOptions {
admin: AdminContext;
request: Request;
response: Response;
}
interface AdminContext {
userId: string;
shopId: string;
appInstallationId: string;
graphql(query: string, options?: GraphQLOptions): Promise<any>;
}Support
License
ISC © Qumra
