196 lines
5.2 KiB
TypeScript
196 lines
5.2 KiB
TypeScript
import * as plugins from './bunq.plugins';
|
|
import { BunqHttpClient } from './bunq.classes.httpclient';
|
|
import { BunqCrypto } from './bunq.classes.crypto';
|
|
import {
|
|
IBunqApiContext,
|
|
IBunqInstallationResponse,
|
|
IBunqDeviceServerResponse,
|
|
IBunqSessionServerResponse
|
|
} from './bunq.interfaces';
|
|
|
|
export class BunqSession {
|
|
private httpClient: BunqHttpClient;
|
|
private crypto: BunqCrypto;
|
|
private context: IBunqApiContext;
|
|
private sessionExpiryTime: plugins.smarttime.TimeStamp;
|
|
|
|
constructor(crypto: BunqCrypto, context: IBunqApiContext) {
|
|
this.crypto = crypto;
|
|
this.context = context;
|
|
this.httpClient = new BunqHttpClient(crypto, context);
|
|
}
|
|
|
|
/**
|
|
* Initialize a new bunq API session
|
|
*/
|
|
public async init(deviceDescription: string, permittedIps: string[] = []): Promise<void> {
|
|
// Step 1: Installation
|
|
await this.createInstallation();
|
|
|
|
// Step 2: Device registration
|
|
await this.registerDevice(deviceDescription, permittedIps);
|
|
|
|
// Step 3: Session creation
|
|
await this.createSession();
|
|
}
|
|
|
|
/**
|
|
* Create installation and exchange keys
|
|
*/
|
|
private async createInstallation(): Promise<void> {
|
|
// Generate RSA key pair if not already generated
|
|
if (!this.crypto.getPublicKey()) {
|
|
await this.crypto.generateKeyPair();
|
|
}
|
|
|
|
const response = await this.httpClient.post<IBunqInstallationResponse>('/v1/installation', {
|
|
client_public_key: this.crypto.getPublicKey()
|
|
});
|
|
|
|
// Extract installation token and server public key
|
|
let installationToken: string;
|
|
let serverPublicKey: string;
|
|
|
|
for (const item of response.Response) {
|
|
if (item.Token) {
|
|
installationToken = item.Token.token;
|
|
}
|
|
if (item.ServerPublicKey) {
|
|
serverPublicKey = item.ServerPublicKey.server_public_key;
|
|
}
|
|
}
|
|
|
|
if (!installationToken || !serverPublicKey) {
|
|
throw new Error('Failed to get installation token or server public key');
|
|
}
|
|
|
|
// Update context
|
|
this.context.installationToken = installationToken;
|
|
this.context.serverPublicKey = serverPublicKey;
|
|
this.context.clientPrivateKey = this.crypto.getPrivateKey();
|
|
this.context.clientPublicKey = this.crypto.getPublicKey();
|
|
|
|
// Update HTTP client context
|
|
this.httpClient.updateContext({
|
|
installationToken,
|
|
serverPublicKey
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register the device
|
|
*/
|
|
private async registerDevice(description: string, permittedIps: string[] = []): Promise<void> {
|
|
const response = await this.httpClient.post<IBunqDeviceServerResponse>('/v1/device-server', {
|
|
description,
|
|
secret: this.context.apiKey,
|
|
permitted_ips: permittedIps.length > 0 ? permittedIps : undefined
|
|
});
|
|
|
|
// Device is now registered
|
|
if (!response.Response || !response.Response[0] || !response.Response[0].Id) {
|
|
throw new Error('Failed to register device');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new session
|
|
*/
|
|
private async createSession(): Promise<void> {
|
|
const response = await this.httpClient.post<IBunqSessionServerResponse>('/v1/session-server', {
|
|
secret: this.context.apiKey
|
|
});
|
|
|
|
// Extract session token and user info
|
|
let sessionToken: string;
|
|
let userId: number;
|
|
|
|
for (const item of response.Response) {
|
|
if (item.Token) {
|
|
sessionToken = item.Token.token;
|
|
}
|
|
if (item.UserPerson) {
|
|
userId = item.UserPerson.id;
|
|
} else if (item.UserCompany) {
|
|
userId = item.UserCompany.id;
|
|
} else if (item.UserApiKey) {
|
|
userId = item.UserApiKey.id;
|
|
}
|
|
}
|
|
|
|
if (!sessionToken || !userId) {
|
|
throw new Error('Failed to create session');
|
|
}
|
|
|
|
// Update context
|
|
this.context.sessionToken = sessionToken;
|
|
|
|
// Update HTTP client context
|
|
this.httpClient.updateContext({
|
|
sessionToken
|
|
});
|
|
|
|
// Set session expiry (bunq sessions expire after 10 minutes of inactivity)
|
|
this.sessionExpiryTime = plugins.smarttime.TimeStamp.fromMilliSeconds(Date.now() + 600000);
|
|
}
|
|
|
|
/**
|
|
* Check if session is still valid
|
|
*/
|
|
public isSessionValid(): boolean {
|
|
if (!this.sessionExpiryTime) {
|
|
return false;
|
|
}
|
|
|
|
const now = new plugins.smarttime.TimeStamp();
|
|
return this.sessionExpiryTime.isYoungerThanOtherTimeStamp(now);
|
|
}
|
|
|
|
/**
|
|
* Refresh the session if needed
|
|
*/
|
|
public async refreshSession(): Promise<void> {
|
|
if (!this.isSessionValid()) {
|
|
await this.createSession();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy the current session
|
|
*/
|
|
public async destroySession(): Promise<void> {
|
|
if (this.context.sessionToken) {
|
|
try {
|
|
await this.httpClient.delete('/v1/session/' + this.getSessionId());
|
|
} catch (error) {
|
|
// Ignore errors when destroying session
|
|
}
|
|
|
|
this.context.sessionToken = null;
|
|
this.httpClient.updateContext({ sessionToken: null });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the current session ID from the token
|
|
*/
|
|
private getSessionId(): string {
|
|
// In a real implementation, we would need to store the session ID
|
|
// For now, return a placeholder
|
|
return '0';
|
|
}
|
|
|
|
/**
|
|
* Get the HTTP client for making API requests
|
|
*/
|
|
public getHttpClient(): BunqHttpClient {
|
|
return this.httpClient;
|
|
}
|
|
|
|
/**
|
|
* Get the current context
|
|
*/
|
|
public getContext(): IBunqApiContext {
|
|
return this.context;
|
|
}
|
|
} |