ndk

Session Management & Persistence (NDK Mobile)

@nostr-dev-kit/ndk-mobile builds upon the session management provided by @nostr-dev-kit/ndk-hooks by adding persistent storage for user sessions and signers using expo-secure-store. This allows your mobile application to remember logged-in users across restarts.

Core Concepts Recap

Automatic Persistence with useSessionMonitor

The easiest way to enable session persistence is by using the useSessionMonitor hook provided by ndk-mobile.

import React, { useEffect, useState } from 'react';
import NDK from '@nostr-dev-kit/ndk';
import { NDKProvider, useNDKStore } from '@nostr-dev-kit/ndk-mobile';
import { useSessionMonitor, NDKCacheAdapterSqlite, bootNDK } from '@nostr-dev-kit/ndk-mobile';

const cacheAdapter = new NDKCacheAdapterSqlite('your-app');
cacheAdapter.initialize();

function initializeNDK() {
    const opts: Record<string, unknown> = {}; // Use a more specific type than any

    const ndk = new NDK({
        cacheAdapter,
        explicitRelayUrls: [ /* your default relays */ ],
        clientName: 'your-app',
    });

    // Boot NDK with the most logged-in accounts that ndk-mobile saves for you
    bootNDK(ndk); // Call synchronous boot function

    ndk.connect();

    return ndk;
}

const ndk = initializeNDK();

function AppRoot() {
    const { setNDK } = useNDKStore();

    useEffect(() => setNDK(ndk), []);
    // Optionally pass options to control how restored sessions are initialized
    useSessionMonitor({
        // Example: Don't automatically fetch profiles for restored sessions
        // profile: false,
        // follows: false,
        // muteList: false,
    });

    return (
        <NDKProvider ndk={ndk}>
            {/* Your App Components */}
        </NDKProvider>
    );
}

How it works:

  1. On Mount: useSessionMonitor retrieves the ndk instance using the useNDK hook. It then calls loadSessionsFromStorage (asynchronously) to retrieve saved sessions.
  2. It iterates through the stored sessions (most recent first):
    • Gets the NDKUser instance using ndk.getUser().
    • If a signerPayload exists, it calls ndkSignerFromPayload (asynchronously) to deserialize the signer.
    • Calls initSession (from ndk-hooks) for each user, passing the ndk instance, user, optional signer, and merging any provided sessionInitOptions with the default autoSetActive: isFirst logic. This populates the useNDKSessions store.
  3. On Active Session Change: The hook subscribes to changes in useNDKSessions.activeSessionPubkey. When the active session changes:
    • It retrieves the full active session data using getActiveSession().
    • It accesses the signer directly from the session data.
    • It serializes the signer using signer.toPayload().
    • It calls addOrUpdateStoredSession (asynchronously) to save the pubkey, serialized signerPayload, and update the lastActive timestamp in secure storage.
  4. On Session Removal: The hook monitors the sessions map from useNDKSessions. When a session is detected as removed (comparing current vs. previous state), it calls removeStoredSession (asynchronously) to delete the session from secure storage.

Logging In / Starting a New Session

This is the standard method for logging a user into your application and establishing their active session. It’s typically performed after a successful login event, such as obtaining credentials via NIP-07, NIP-46 (Nostr Connect), or directly using a private key. This involves:

  1. Getting the NDKUser: Obtain the NDKUser object for the logged-in user, usually via ndk.getUser({ npub }) or ndk.getUser({ hexpubkey }).
  2. Getting the NDKSigner: Obtain the appropriate NDKSigner instance (e.g., Nip07Signer, Nip46Signer, PrivateKeySigner).
  3. Calling initSession: Use the initSession function exported from @nostr-dev-kit/ndk-hooks (and re-exported by ndk-mobile) to add the user and signer to the session store.
import { useNDK } from "@nostr-dev-kit/ndk-hooks";
import { useNDKSessions } from "@nostr-dev-kit/ndk-hooks";
import { NDKUser, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import React from 'react';

// Assume you have obtained the user's private key after login
const userPrivateKey = "nsec..."; // Replace with actual private key logic

function LoginButton() {
    const { ndk } = useNDK();
    const { initSession, setActiveSession } = useNDKSessions();

    const handleLogin = async () => {
        if (!ndk || !userPrivateKey) return;

        try {
            // 1. Create the signer
            const signer = new NDKPrivateKeySigner(userPrivateKey);

            // 2. Get the NDKUser associated with the signer
            const user: NDKUser = await signer.user();
            await user.fetchProfile(); // Optional: Fetch profile details

            // 3. Initialize the session in the store
            //    - Pass ndk, user, and signer.
            //    - Set autoSetActive to true if you want this to be the main session immediately.
            initSession(ndk, user, signer, true);

            // Alternatively, if you initSession with autoSetActive: false,
            // you can activate it later:
            // setActiveSession(user.pubkey);

            console.log(`Session initialized for user: ${user.npub}`);

            // If useSessionMonitor is active, this session will now be persisted.

        } catch (error) {
            console.error("Failed to initialize session:", error);
            // Handle login error
        }
    };

    return <button onClick={handleLogin}>Login with Private Key</button>;
}

Important Notes: