capacitor-cors-bypass-enhanced
Version:
Enhanced Capacitor plugin for CORS bypass with HTTP/2, HTTP/3, gRPC, GraphQL, file operations, and advanced networking features. Modular TypeScript definitions for better maintainability.
199 lines (172 loc) • 5.68 kB
text/typescript
import type {
SSEOptions,
SSEConnectionOptions,
SSEConnection,
SSEMessageEvent,
SSEConnectionChangeEvent,
} from '../definitions';
import { isCrossOrigin } from './utils';
/**
* SSE Manager
* Handles Server-Sent Events connections with CORS bypass
*/
export class SSEManager {
private proxyServerUrl: string | null;
private sseConnections = new Map<string, EventSource>();
private connectionCounter = 0;
private notifyListeners: (eventName: string, data: any) => void;
constructor(
proxyServerUrl: string | null,
notifyListeners: (eventName: string, data: any) => void
) {
this.proxyServerUrl = proxyServerUrl;
this.notifyListeners = notifyListeners;
}
/**
* Start listening to Server-Sent Events (legacy method)
*/
async startSSE(options: SSEOptions): Promise<{ connectionId: string }> {
const connectionId = `sse_${++this.connectionCounter}`;
const { url, headers = {}, withCredentials = false, reconnectTimeout = 3000 } = options;
// Use proxy server for SSE if available and cross-origin
let sseUrl = url;
if (this.proxyServerUrl && isCrossOrigin(url)) {
console.log(`🔧 Using SSE proxy for: ${url}`);
sseUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`;
}
const eventSource = new EventSource(sseUrl);
this.sseConnections.set(connectionId, eventSource);
eventSource.onopen = () => {
this.notifyListeners('sseOpen', {
connectionId,
status: 'connected',
});
};
eventSource.onmessage = (event) => {
this.notifyListeners('sseMessage', {
connectionId,
type: 'message',
data: event.data,
id: event.lastEventId,
} as SSEMessageEvent);
};
eventSource.onerror = () => {
this.notifyListeners('sseError', {
connectionId,
error: 'Connection error',
});
};
return { connectionId };
}
/**
* Stop listening to Server-Sent Events
*/
async stopSSE(options: { connectionId: string }): Promise<void> {
const { connectionId } = options;
const connection = this.sseConnections.get(connectionId);
if (connection) {
connection.close();
this.sseConnections.delete(connectionId);
this.notifyListeners('sseClose', {
connectionId,
status: 'disconnected',
});
}
}
/**
* Create a Server-Sent Events connection with reconnection support
*/
async createSSEConnection(options: SSEConnectionOptions): Promise<SSEConnection> {
const connectionId = `sse_${++this.connectionCounter}`;
const { url, headers = {}, reconnect = {} } = options;
const {
enabled: reconnectEnabled = true,
initialDelay = 1000,
maxDelay = 30000,
maxAttempts = 10,
} = reconnect;
let retryCount = 0;
let retryDelay = initialDelay;
const createConnection = () => {
// Use proxy server for SSE if available and cross-origin
let sseUrl = url;
if (this.proxyServerUrl && isCrossOrigin(url)) {
console.log(`🔧 Using SSE proxy for: ${url}`);
sseUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`;
}
const eventSource = new EventSource(sseUrl);
this.sseConnections.set(connectionId, eventSource);
eventSource.onopen = () => {
retryCount = 0;
retryDelay = initialDelay;
this.notifyListeners('sseConnectionChange', {
connectionId,
status: 'connected',
} as SSEConnectionChangeEvent);
};
eventSource.onmessage = (event) => {
this.notifyListeners('sseMessage', {
connectionId,
type: 'message',
data: event.data,
id: event.lastEventId,
} as SSEMessageEvent);
};
eventSource.onerror = () => {
this.notifyListeners('sseConnectionChange', {
connectionId,
status: 'error',
error: 'Connection error',
} as SSEConnectionChangeEvent);
if (reconnectEnabled && retryCount < maxAttempts) {
setTimeout(() => {
retryCount++;
retryDelay = Math.min(retryDelay * 2, maxDelay);
eventSource.close();
createConnection();
}, retryDelay);
} else {
this.sseConnections.delete(connectionId);
}
};
// Add custom event listeners
eventSource.addEventListener('error', (event) => {
this.notifyListeners('sseMessage', {
connectionId,
type: 'error',
data: 'Connection error',
} as SSEMessageEvent);
});
};
this.notifyListeners('sseConnectionChange', {
connectionId,
status: 'connecting',
} as SSEConnectionChangeEvent);
createConnection();
return {
connectionId,
status: 'connecting',
};
}
/**
* Close an SSE connection
*/
async closeSSEConnection(options: { connectionId: string }): Promise<void> {
const { connectionId } = options;
const connection = this.sseConnections.get(connectionId);
if (connection) {
connection.close();
this.sseConnections.delete(connectionId);
this.notifyListeners('sseConnectionChange', {
connectionId,
status: 'disconnected',
} as SSEConnectionChangeEvent);
}
}
/**
* Get all active SSE connections
*/
getSSEConnections(): Map<string, EventSource> {
return this.sseConnections;
}
}