Skip to main content

React Hooks

The SDK provides first-class React integration through a custom hook and session header component.

useMarketplaceSession Hook

import { useEffect, useState } from 'react';
import MarketplaceSDK from '@mission_sciences/provider-sdk';
import type { SessionStartContext, SessionEndContext } from '@mission_sciences/provider-sdk';

interface SessionState {
isActive: boolean;
userId?: string;
email?: string;
expiresAt?: number;
}

export function useMarketplaceSession() {
const [session, setSession] = useState<SessionState>({ isActive: false });
const [sdk, setSdk] = useState<MarketplaceSDK | null>(null);

useEffect(() => {
const initSDK = async () => {
const marketplaceSDK = new MarketplaceSDK({
applicationId: 'my-react-app',
apiKey: 'gwsk_YOUR_API_KEY_HERE',
jwksUri: 'https://api.generalwisdom.com/.well-known/jwks.json',
hooks: {
onSessionStart: async (context: SessionStartContext) => {
console.log('[Marketplace] Session started', context);

setSession({
isActive: true,
userId: context.userId,
email: context.email,
expiresAt: context.expiresAt,
});

await authenticateMarketplaceUser(context);
},

onSessionEnd: async (context: SessionEndContext) => {
console.log('[Marketplace] Session ended', context);

setSession({ isActive: false });
await clearAuthState();
},
},
});

await marketplaceSDK.initialize();
setSdk(marketplaceSDK);
};

initSDK();

return () => {
sdk?.destroy();
};
}, []);

return { session, sdk };
}

// Helper functions
async function authenticateMarketplaceUser(context: SessionStartContext) {
const response = await fetch('/api/auth/marketplace', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jwt: context.jwt,
userId: context.userId,
}),
});

const { token } = await response.json();
localStorage.setItem('auth_token', token);
}

async function clearAuthState() {
localStorage.removeItem('auth_token');
}

Session Header Component

import { useEffect, useRef } from 'react';
import type MarketplaceSDK from '@mission_sciences/provider-sdk';

interface Props {
sdk: MarketplaceSDK | null;
}

export default function SessionHeader({ sdk }: Props) {
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!sdk || !containerRef.current) return;

// Session header auto-mounts by default.
// Optionally customize with:
// sdk.mountSessionHeader({
// mode: 'floating',
// position: 'bottom-right',
// getTime: () => sdk.getFormattedTime(),
// sdk,
// });

return () => {
sdk.unmountSessionHeader();
};
}, [sdk]);

return <div id="session-header-container" ref={containerRef} />;
}

App Component

import { useMarketplaceSession } from '@/hooks/useMarketplaceSession';
import SessionHeader from '@/components/SessionHeader';

export function App() {
const { session, sdk } = useMarketplaceSession();

if (!session.isActive) {
return (
<div>
<h1>Please access via marketplace</h1>
<p>This app requires a valid marketplace session.</p>
</div>
);
}

return (
<div>
<SessionHeader sdk={sdk} />

<main>
<h1>Welcome, {session.email}</h1>
<YourAppContent />
</main>
</div>
);
}

Testing React Integration

Unit Tests

import { describe, it, expect, beforeEach } from 'vitest';
import MarketplaceSDK from '@mission_sciences/provider-sdk';

describe('MarketplaceSDK', () => {
let sdk: MarketplaceSDK;

beforeEach(() => {
sdk = new MarketplaceSDK({
applicationId: 'test-app',
apiKey: 'gwsk_YOUR_API_KEY_HERE',
jwksUri: 'https://test.example.com/.well-known/jwks.json',
hooks: {
onSessionStart: async () => {},
onSessionEnd: async () => {},
},
});
});

it('initializes without errors', async () => {
await expect(sdk.initialize()).resolves.not.toThrow();
});

it('detects JWT in URL', () => {
window.location.href = 'http://test.com?gwSession=test-token';
expect(sdk.hasJWTInURL()).toBe(true);
});

it('extracts JWT from URL', () => {
window.location.href = 'http://test.com?gwSession=test-token';
expect(sdk.getJWTFromURL()).toBe('test-token');
});
});

Integration Tests

import { describe, it, expect } from 'vitest';
import MarketplaceSDK from '@mission_sciences/provider-sdk';

describe('Session Flow', () => {
it('completes full session lifecycle', async () => {
let sessionStarted = false;
let sessionEnded = false;

const sdk = new MarketplaceSDK({
applicationId: 'test-app',
apiKey: 'gwsk_YOUR_API_KEY_HERE',
jwksUri: 'https://test.example.com/.well-known/jwks.json',
hooks: {
onSessionStart: async (context) => {
sessionStarted = true;
expect(context.userId).toBeDefined();
},

onSessionEnd: async (context) => {
sessionEnded = true;
expect(context.reason).toBe('manual');
},
},
});

window.location.href = 'http://test.com?gwSession=' + generateTestJWT();

await sdk.initialize();
expect(sessionStarted).toBe(true);
expect(sdk.hasActiveSession()).toBe(true);

await sdk.endSession('manual');
expect(sessionEnded).toBe(true);
expect(sdk.hasActiveSession()).toBe(false);
});
});

Mock JWT Generation

For testing without real JWTs:

import jwt from 'jsonwebtoken';
import fs from 'fs';

export function generateTestJWT(options: Record<string, unknown> = {}) {
const privateKey = fs.readFileSync('./keys/private-key.pem');

const payload = {
sessionId: options.sessionId || 'test-session-123',
applicationId: options.applicationId || 'test-app',
userId: options.userId || 'test-user-456',
orgId: options.orgId || 'test-org-789',
email: options.email || 'test@example.com',
startTime: Math.floor(Date.now() / 1000),
durationMinutes: options.durationMinutes || 60,
iss: 'generalwisdom.com',
sub: options.userId || 'test-user-456',
exp: Math.floor(Date.now() / 1000) + ((options.durationMinutes as number) || 60) * 60,
iat: Math.floor(Date.now() / 1000),
};

return jwt.sign(payload, privateKey, {
algorithm: 'RS256',
keyid: 'test-key-id',
});
}

E2E Tests (Playwright)

import { test, expect } from '@playwright/test';

test('marketplace session flow', async ({ page }) => {
const jwt = generateTestJWT({ durationMinutes: 5 });

await page.goto(`http://localhost:3000?gwSession=${jwt}`);

// Wait for session to start
await expect(page.locator('.session-timer')).toBeVisible();

// Verify session header shows
const timer = await page.locator('.session-timer').textContent();
expect(timer).toMatch(/\d+:\d{2}/);

// Click end session button
await page.click('button[data-testid="end-session"]');

// Verify redirect or cleanup
await expect(page).toHaveURL(/marketplace/);
});