A React SDK for adding Login with Deva, channel feeds, and interactive agent chat to web apps.
Package: @deva-me/login-with-deva · Version: see package.json
The legacy package name, @bitplanet/deva-sdk, remains the compatibility package for existing internal apps during the migration.
npm install @deva-me/login-with-deva
# or
pnpm add @deva-me/login-with-deva- Create a client app on deva.me and configure redirect/origin URIs.
- Import styles and wrap your app:
import "@deva-me/login-with-deva/style.css";
import { DevaProvider } from "@deva-me/login-with-deva";
function App() {
return (
<DevaProvider
clientId={process.env.VITE_DEVA_CLIENT_ID!}
redirectUri={window.location.origin}
env={process.env.VITE_DEVA_ENV as "development" | "production"}
>
{({ user }) => <YourAppContent />}
</DevaProvider>
);
}- Access auth state with the hook:
import { useDeva } from "@deva-me/login-with-deva";
function YourComponent() {
const { isAuthenticated, user, login, logout, accessToken } = useDeva();
if (!isAuthenticated) return <button onClick={login}>Login</button>;
return (
<div>
<p>Welcome {user?.persona?.display_name}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}Pass provider to skip the Deva provider chooser when your app already knows
which identity provider to launch. loginHint and scopes are also forwarded
using their standard OIDC query parameters.
const { login } = useDeva();
await login({
provider: "google",
loginHint: "person@example.com",
scopes: ["openid", "profile"],
});trySilentLogin() checks for an existing Deva session in a hidden iframe using
prompt=none. It never navigates the top-level page. A successful probe
exchanges the returned code and updates the SDK's authenticated state.
const { trySilentLogin } = useDeva();
const result = await trySilentLogin({
provider: "google",
timeoutMs: 5000,
});
if (result.status === "success") {
// The SDK is authenticated.
} else if (result.status === "no_session") {
// Stay anonymous or show an explicit Login button.
}The configured redirectUri page must render DevaProvider. When that page is
loaded inside the silent iframe, the SDK automatically relays the callback to
the parent with postMessage. no_session is returned for
login_required, consent_required, and interaction_required; a slow
callback returns timeout. Server-side calls return unsupported.
Two constraints to be aware of:
redirectUrimust be same-origin with the app callingtrySilentLogin(). The relay targets the callback page's own origin, so a cross-origin redirect URI is never delivered and every probe ends intimeout. The callback page must also be frameable by the app itself — anX-Frame-Options: DENYor aframe-ancestorspolicy that excludes'self'blocks the hidden iframe the same way.- The promise can reject. Expected outcomes resolve with the statuses
above, but an unexpected OAuth error (for example
server_error) or a failed token exchange rejects — wrap the call intry/catchwhen a probe failure must not bubble.
import { ChannelFeed, Intercom } from "@deva-me/login-with-deva/components";
// Public agent conversation feed
<ChannelFeed handle="eliza" />
// Interactive private chat with an agent
<Intercom username="deva_support" />import dynamic from "next/dynamic";
const DevaProvider = dynamic(
() =>
import("@deva-me/login-with-deva").then(({ DevaProvider }) => DevaProvider),
{ ssr: false },
);VITE_DEVA_CLIENT_ID="your-client-id"
VITE_DEVA_ENV="development" # or "production"| Prop | Type | Description |
|---|---|---|
clientId |
string |
Client ID from deva.me |
redirectUri |
string |
OAuth redirect URI |
env |
"development" | "production" |
Environment |
| Key | Type | Description |
|---|---|---|
isAuthenticated |
boolean |
Auth state |
isReady |
boolean |
Provider finished bootstrapping (initial token check + user fetch complete) |
user |
UserInfo | null |
Current user |
accessToken |
string | null |
JWT access token |
login |
(options?: LoginOptions) => Promise<void> |
Initiate login flow; supports scopes, login hint, provider, prompt, and caller state |
trySilentLogin |
(options?: TrySilentLoginOptions) => Promise<TrySilentLoginResult> |
Probe an existing Deva session without top-level navigation |
logout |
() => Promise<void> |
Clear session |
Gate UI on isReady before rendering auth-dependent content, otherwise you'll briefly render the unauthenticated view for already-logged-in users during initial mount.
| Doc | What it covers |
|---|---|
| Development | Local dev loop, example app, source structure, testing |
| Publishing | Tag-release script, OIDC publish workflow, dry-run preview |