import * as plugins from './bunq.plugins.js'; import * as paths from './bunq.paths.js'; import { BunqCrypto } from './bunq.classes.crypto.js'; import { BunqSession } from './bunq.classes.session.js'; import type { IBunqApiContext } from './bunq.interfaces.js'; export interface IBunqApiContextOptions { apiKey: string; environment: 'SANDBOX' | 'PRODUCTION'; deviceDescription: string; permittedIps?: string[]; } export class BunqApiContext { private options: IBunqApiContextOptions; private crypto: BunqCrypto; private session: BunqSession; private context: IBunqApiContext; private contextFilePath: string; 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' }; // Set context file path based on environment this.contextFilePath = options.environment === 'PRODUCTION' ? paths.bunqJsonProductionFile : paths.bunqJsonSandboxFile; this.session = new BunqSession(this.crypto, this.context); } /** * Initialize the API context (installation, device, session) */ public async init(): Promise { // Try to load existing context const existingContext = await this.loadContext(); if (existingContext && existingContext.sessionToken) { // Restore crypto keys this.crypto.setKeys( existingContext.clientPrivateKey, existingContext.clientPublicKey ); // Update context this.context = { ...this.context, ...existingContext }; this.session = new BunqSession(this.crypto, this.context); // Check if session is still valid if (this.session.isSessionValid()) { return; } } // Create new session await this.session.init( this.options.deviceDescription, this.options.permittedIps || [] ); // Save context await this.saveContext(); } /** * Save the current context to file */ private async saveContext(): Promise { await plugins.smartfile.fs.ensureDir(paths.nogitDir); const contextToSave = { ...this.session.getContext(), savedAt: new Date().toISOString() }; await plugins.smartfile.memory.toFs( JSON.stringify(contextToSave, null, 2), this.contextFilePath ); } /** * Load context from file */ private async loadContext(): Promise { try { const exists = await plugins.smartfile.fs.fileExists(this.contextFilePath); if (!exists) { return null; } const contextData = await plugins.smartfile.fs.toStringSync(this.contextFilePath); return JSON.parse(contextData); } catch (error) { return null; } } /** * 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 */ public async ensureValidSession(): Promise { await this.session.refreshSession(); await this.saveContext(); } /** * Destroy the current session and clean up */ public async destroy(): Promise { await this.session.destroySession(); // Remove saved context try { await plugins.smartfile.fs.remove(this.contextFilePath); } catch (error) { // Ignore errors when removing file } } /** * Get the environment */ public getEnvironment(): 'SANDBOX' | 'PRODUCTION' { return this.options.environment; } /** * Get the base URL */ public getBaseUrl(): string { return this.context.baseUrl; } }