Using MwenClient Without React
MwenClient is a framework-agnostic class. MwenProvider and useMwen() are conveniences built on top of it for React applications. If you are using Vue, Svelte, vanilla JavaScript, or any other framework, use MwenClient directly.
Installation and import
import { MwenClient, VERIFIED_SESSION_KEY } from '@mwen/js-sdk';
import type { MwenClientConfig, VerifiedSession } from '@mwen/js-sdk';
Initialising the client
Create one MwenClient instance per application. Treat it as a singleton.
const client = new MwenClient({
clientId: 'myapp.com',
appName: 'My App',
scopes: ['openid', 'name', 'age', 'email'],
revocationPath: '/revoked',
relayPath: '/api/mwen/relay',
verifyPath: '/api/verify',
});
Extension detection
Always call detectExtension() on page load. It resolves quickly and caches the result.
const hasExtension = await client.detectExtension();
if (!hasExtension) {
showInstallBanner();
}
After the first call, client.hasExtension is available synchronously.
Restoring an existing session
Check for an existing session before showing any sign-in UI:
const session = client.getSession();
if (session) {
renderAuthenticatedState(session);
} else {
renderSignInButton();
}
Triggering authentication
async function handleSignIn() {
try {
const result = await client.authenticate();
// result is AuthenticateResult
renderAuthenticatedState(client.getSession()!);
} catch (err) {
if (err instanceof MwenExtensionNotFoundError) {
showInstallBanner();
} else if (err instanceof MwenUserDeniedError) {
showDeniedMessage();
} else {
showErrorMessage(err instanceof Error ? err.message : String(err));
}
}
}
Import the error classes alongside MwenClient:
import {
MwenClient,
MwenExtensionNotFoundError,
MwenUserDeniedError,
MwenStateMismatchError,
} from '@mwen/js-sdk';
Sign out
function handleSignOut() {
client.signOut();
renderSignInButton();
}
Cross-tab synchronisation
MwenProvider sets up cross-tab storage event listeners automatically. When using MwenClient directly, add the listener yourself:
window.addEventListener('storage', (event) => {
if (event.key === VERIFIED_SESSION_KEY) {
if (event.newValue === null) {
// Session cleared in another tab (sign-out or revocation)
client.clearSession();
renderSignInButton();
} else {
// Session updated in another tab (fresh authenticate)
const session = client.getSession();
if (session) renderAuthenticatedState(session);
}
}
});
Vue example
// stores/mwen.ts (Pinia)
import { defineStore } from 'pinia';
import { ref, onMounted } from 'vue';
import {
MwenClient,
MwenExtensionNotFoundError,
VERIFIED_SESSION_KEY,
type VerifiedSession,
} from '@mwen/js-sdk';
const client = new MwenClient({
clientId: import.meta.env.VITE_CLIENT_ID ?? 'localhost:5173',
appName: 'My Vue App',
scopes: ['openid', 'name', 'age'],
});
export const useMwenStore = defineStore('mwen', () => {
const session = ref<VerifiedSession | null>(null);
const hasExtension = ref(false);
const isLoading = ref(false);
const error = ref<Error | null>(null);
async function init() {
hasExtension.value = await client.detectExtension();
session.value = client.getSession();
}
async function authenticate() {
isLoading.value = true;
error.value = null;
try {
await client.authenticate();
session.value = client.getSession();
} catch (e) {
error.value = e instanceof Error ? e : new Error(String(e));
} finally {
isLoading.value = false;
}
}
function signOut() {
client.signOut();
session.value = null;
}
// Cross-tab sync
window.addEventListener('storage', (e) => {
if (e.key === VERIFIED_SESSION_KEY && e.newValue === null) {
session.value = null;
}
});
return { session, hasExtension, isLoading, error, init, authenticate, signOut };
});
Svelte example
// lib/mwen.ts
import { writable } from 'svelte/store';
import {
MwenClient,
VERIFIED_SESSION_KEY,
type VerifiedSession,
} from '@mwen/js-sdk';
export const client = new MwenClient({
clientId: import.meta.env.PUBLIC_CLIENT_ID ?? 'localhost:5173',
appName: 'My Svelte App',
scopes: ['openid', 'name', 'age'],
});
export const session = writable<VerifiedSession | null>(null);
export const hasExtension = writable(false);
export const isLoading = writable(false);
export async function init() {
hasExtension.set(await client.detectExtension());
session.set(client.getSession());
}
export async function authenticate() {
isLoading.set(true);
try {
await client.authenticate();
session.set(client.getSession());
} finally {
isLoading.set(false);
}
}
export function signOut() {
client.signOut();
session.set(null);
}
// Cross-tab sync
if (typeof window !== 'undefined') {
window.addEventListener('storage', (e) => {
if (e.key === VERIFIED_SESSION_KEY && e.newValue === null) {
session.set(null);
}
});
}
Summary of MwenClient vs. MwenProvider / useMwen()
| Feature | MwenClient (direct) | MwenProvider + useMwen() |
|---|---|---|
| Framework | Any | React only |
| Extension detection | await client.detectExtension() | Automatic on mount |
| Session restore | client.getSession() | Automatic on mount |
| Cross-tab sync | Manual storage listener | Automatic |
| Auto-refresh | Automatic (after authenticate()) | Automatic |
| Reactive state | Manual | Built-in (isLoading, error, etc.) |