import * as plugins from './bunq.plugins.js'; import { BunqCrypto } from './bunq.classes.crypto.js'; import { BunqSession } from './bunq.classes.session.js'; import type { IBunqApiContext, ISessionData } from './bunq.interfaces.js'; export interface IBunqApiContextOptions { apiKey: string; environment: 'SANDBOX' | 'PRODUCTION'; deviceDescription: string; permittedIps?: string[]; isOAuthToken?: boolean; } export class BunqApiContext { private options: IBunqApiContextOptions; private crypto: BunqCrypto; private session: BunqSession; private context: IBunqApiContext; constructor(options: IBunqApiContextOptions) { this.options = options; this.crypto = new BunqCrypto(); // Initialize context this.context = { apiKey: options.apiKey, environment: options.environment, baseUrl: options.environment === 'PRODUCTION' ? 'https://api.bunq.com' : 'https://public-api.sandbox.bunq.com' }; this.session = new BunqSession(this.crypto, this.context); } /** * Initialize the API context (installation, device, session) * @returns The session data that can be persisted by the consumer */ public async init(): Promise { // Create new session await this.session.init( this.options.deviceDescription, this.options.permittedIps || [] ); // Set OAuth mode if applicable (for session expiry handling) if (this.options.isOAuthToken) { this.session.setOAuthMode(true); } return this.exportSession(); } /** * Initialize the API context with existing session data * @param sessionData The session data to restore */ public async initWithSession(sessionData: ISessionData): Promise { // Validate session data if (!sessionData.sessionToken || !sessionData.sessionId) { throw new Error('Invalid session data: missing session token or ID'); } // Restore crypto keys this.crypto.setKeys( sessionData.clientPrivateKey, sessionData.clientPublicKey ); // Update context with session data this.context = { ...this.context, sessionToken: sessionData.sessionToken, sessionId: sessionData.sessionId, installationToken: sessionData.installationToken, serverPublicKey: sessionData.serverPublicKey, clientPrivateKey: sessionData.clientPrivateKey, clientPublicKey: sessionData.clientPublicKey, expiresAt: new Date(sessionData.expiresAt) }; // Create new session instance with restored context this.session = new BunqSession(this.crypto, this.context); // Set OAuth mode if applicable if (this.options.isOAuthToken) { this.session.setOAuthMode(true); } // Check if session is still valid if (!this.session.isSessionValid()) { throw new Error('Session has expired'); } } /** * Export the current session data for persistence * @returns The session data that can be saved by the consumer */ public exportSession(): ISessionData { const context = this.session.getContext(); if (!context.sessionToken || !context.sessionId) { throw new Error('No active session to export'); } return { sessionId: context.sessionId, sessionToken: context.sessionToken, installationToken: context.installationToken!, serverPublicKey: context.serverPublicKey!, clientPrivateKey: context.clientPrivateKey!, clientPublicKey: context.clientPublicKey!, expiresAt: context.expiresAt!, environment: context.environment, baseUrl: context.baseUrl }; } /** * Create a new BunqApiContext with existing session data * @param sessionData The session data to use * @param apiKey The API key (still needed for refresh) * @param deviceDescription Device description * @returns A new BunqApiContext instance */ public static async createWithSession( sessionData: ISessionData, apiKey: string, deviceDescription: string ): Promise { const context = new BunqApiContext({ apiKey, environment: sessionData.environment, deviceDescription, isOAuthToken: false // Set appropriately based on your needs }); await context.initWithSession(sessionData); return context; } /** * Get the current session */ public getSession(): BunqSession { return this.session; } /** * Get the HTTP client for making API requests */ public getHttpClient() { return this.session.getHttpClient(); } /** * Refresh session if needed * @returns Updated session data if session was refreshed, null otherwise */ public async ensureValidSession(): Promise { const wasValid = this.session.isSessionValid(); await this.session.refreshSession(); // Return updated session data only if session was actually refreshed if (!wasValid) { return this.exportSession(); } return null; } /** * Destroy the current session */ public async destroy(): Promise { await this.session.destroySession(); } /** * Get the environment */ public getEnvironment(): 'SANDBOX' | 'PRODUCTION' { return this.options.environment; } /** * Get the base URL */ public getBaseUrl(): string { return this.context.baseUrl; } /** * Check if the context has a valid session */ public hasValidSession(): boolean { return this.session && this.session.isSessionValid(); } /** * Initialize with existing installation and device (for OAuth tokens that already completed these steps) * @param existingInstallation Optional partial session data with just installation/device info * @returns The new session data */ public async initWithExistingInstallation(existingInstallation?: Partial): Promise { // For OAuth tokens that already have installation/device but need a new session if (existingInstallation && existingInstallation.clientPrivateKey && existingInstallation.clientPublicKey) { // Restore crypto keys from previous installation this.crypto.setKeys( existingInstallation.clientPrivateKey, existingInstallation.clientPublicKey ); // Update context with existing installation data this.context = { ...this.context, installationToken: existingInstallation.installationToken, serverPublicKey: existingInstallation.serverPublicKey, clientPrivateKey: existingInstallation.clientPrivateKey, clientPublicKey: existingInstallation.clientPublicKey }; // Create new session instance this.session = new BunqSession(this.crypto, this.context); // Try to create a new session with the OAuth token try { await this.session.init( this.options.deviceDescription, this.options.permittedIps || [], true // skipInstallationAndDevice = true ); if (this.options.isOAuthToken) { this.session.setOAuthMode(true); } console.log('Successfully created new session with existing installation'); return this.exportSession(); } catch (error) { throw new Error(`Failed to create session with OAuth token: ${error.message}`); } } else { // No existing installation, fall back to full init throw new Error('No existing installation provided, full initialization required'); } } }