Skip to main content

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()

FeatureMwenClient (direct)MwenProvider + useMwen()
FrameworkAnyReact only
Extension detectionawait client.detectExtension()Automatic on mount
Session restoreclient.getSession()Automatic on mount
Cross-tab syncManual storage listenerAutomatic
Auto-refreshAutomatic (after authenticate())Automatic
Reactive stateManualBuilt-in (isLoading, error, etc.)