mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
Refactor http-server implemention
This commit is contained in:
12
.claude/settings.local.json
Normal file
12
.claude/settings.local.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(find:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(yarn install)",
|
||||
"Bash(yarn lint)",
|
||||
"Bash(yarn prebuild-tv:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
4
.eslintrc.js
Normal file
4
.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// https://docs.expo.dev/guides/using-eslint/
|
||||
module.exports = {
|
||||
extends: 'expo',
|
||||
};
|
||||
107
CLAUDE.md
Normal file
107
CLAUDE.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
OrionTV is a React Native TVOS application for streaming video content, built with Expo and designed specifically for TV platforms (Apple TV and Android TV). The project includes both a frontend React Native app and a backend Express service.
|
||||
|
||||
## Key Commands
|
||||
|
||||
### Development Commands
|
||||
- `yarn start-tv` - Start Metro bundler in TV mode
|
||||
- `yarn ios-tv` - Build and run on Apple TV
|
||||
- `yarn android-tv` - Build and run on Android TV
|
||||
- `yarn prebuild-tv` - Generate native project files for TV (run this after dependency changes)
|
||||
- `yarn lint` - Run linting checks
|
||||
- `yarn test` - Run Jest tests with watch mode
|
||||
- `yarn build-local` - Build Android APK locally
|
||||
|
||||
### Backend Commands (from `/backend` directory)
|
||||
- `yarn dev` - Start backend development server with hot reload
|
||||
- `yarn build` - Build TypeScript backend
|
||||
- `yarn start` - Start production backend server
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Frontend Structure
|
||||
- **Expo Router**: File-based routing with screens in `/app` directory
|
||||
- **State Management**: Zustand stores for global state (`/stores`)
|
||||
- **TV-Specific Components**: Components optimized for TV remote control interaction
|
||||
- **Services**: API layer, storage management, and remote control service
|
||||
|
||||
### Key Technologies
|
||||
- React Native TVOS (0.74.x) - TV-optimized React Native
|
||||
- Expo SDK 51 - Development platform and tooling
|
||||
- TypeScript - Type safety throughout
|
||||
- Zustand - Lightweight state management
|
||||
- Expo AV - Video playback functionality
|
||||
|
||||
### State Management (Zustand Stores)
|
||||
- `homeStore.ts` - Home screen content, categories, and play records
|
||||
- `playerStore.ts` - Video player state and controls
|
||||
- `settingsStore.ts` - App settings and configuration
|
||||
- `remoteControlStore.ts` - Remote control server functionality
|
||||
|
||||
### TV-Specific Features
|
||||
- Remote control navigation (`useTVRemoteHandler` hook)
|
||||
- TV-optimized UI components with focus management
|
||||
- Remote control server for external control via HTTP bridge
|
||||
- Gesture handling for TV remote interactions
|
||||
|
||||
### Backend Architecture
|
||||
- Express.js server providing API endpoints
|
||||
- Routes for search, video details, and Douban integration
|
||||
- Image proxy service for handling external images
|
||||
- CORS enabled for cross-origin requests
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### TV Development Notes
|
||||
- Always use TV-specific commands (`*-tv` variants)
|
||||
- Run `yarn prebuild-tv` after adding new dependencies
|
||||
- Test on both Apple TV and Android TV simulators
|
||||
- TV components require focus management and remote control support
|
||||
|
||||
### State Management Patterns
|
||||
- Use Zustand stores for global state
|
||||
- Stores follow a consistent pattern with actions and state
|
||||
- API calls are centralized in the `/services` directory
|
||||
- Storage operations use AsyncStorage wrapper in `storage.ts`
|
||||
|
||||
### Component Structure
|
||||
- TV-specific components have `.tv.tsx` extensions
|
||||
- Common components in `/components` directory
|
||||
- Custom hooks in `/hooks` directory for reusable logic
|
||||
- TV remote handling is centralized in `useTVRemoteHandler`
|
||||
|
||||
## Testing
|
||||
|
||||
- Uses Jest with `jest-expo` preset
|
||||
- Run tests with `yarn test`
|
||||
- Component tests in `__tests__` directories
|
||||
- Snapshot testing for UI components
|
||||
|
||||
## Common Issues
|
||||
|
||||
### TV Platform Specifics
|
||||
- TV apps require special focus management
|
||||
- Remote control events need careful handling
|
||||
- TV-specific assets and icons required
|
||||
- Platform-specific build configurations
|
||||
|
||||
### Development Environment
|
||||
- Ensure Xcode is installed for Apple TV development
|
||||
- Android Studio required for Android TV development
|
||||
- Metro bundler must run in TV mode (`EXPO_TV=1`)
|
||||
- Backend server must be running on port 3001 for full functionality
|
||||
|
||||
## File Structure Notes
|
||||
|
||||
- `/app` - Expo Router screens and navigation
|
||||
- `/components` - Reusable UI components
|
||||
- `/stores` - Zustand state management
|
||||
- `/services` - API, storage, and external service integrations
|
||||
- `/hooks` - Custom React hooks
|
||||
- `/backend` - Express.js backend service
|
||||
- `/constants` - App constants and theme definitions
|
||||
@@ -4,7 +4,6 @@
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.1.1",
|
||||
"scripts": {
|
||||
"postinstall": "node ./scripts/patch-http-bridge.js",
|
||||
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||
"android": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo run:android",
|
||||
@@ -27,7 +26,6 @@
|
||||
"react-native": "npm:react-native-tvos@~0.74.2-0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-native-http-bridge": "^0.4.0",
|
||||
"@expo/vector-icons": "^14.0.0",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@react-native-community/netinfo": "^11.3.2",
|
||||
@@ -54,6 +52,7 @@
|
||||
"react-native-safe-area-context": "4.10.1",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-svg": "^15.12.0",
|
||||
"react-native-tcp-socket": "^6.0.6",
|
||||
"react-native-toast-message": "^2.3.3",
|
||||
"react-native-web": "~0.19.10",
|
||||
"zustand": "^5.0.6"
|
||||
@@ -64,6 +63,8 @@
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "~18.2.45",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-expo": "~7.1.2",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~51.0.1",
|
||||
"react-test-renderer": "18.2.0",
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function patchFile(filePath, patches) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.log(`File not found, skipping patch: ${filePath}`);
|
||||
return;
|
||||
}
|
||||
console.log(`Patching ${filePath}...`);
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
patches.forEach(patch => {
|
||||
content = content.replace(patch.find, patch.replace);
|
||||
});
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
}
|
||||
|
||||
// --- Patch build.gradle ---
|
||||
const gradleFile = path.resolve(__dirname, '..', 'node_modules', 'react-native-http-bridge', 'android', 'build.gradle');
|
||||
patchFile(gradleFile, [
|
||||
{
|
||||
find: /jcenter\(\)/g,
|
||||
replace: 'google()\n mavenCentral()'
|
||||
},
|
||||
{
|
||||
find: "classpath 'com.android.tools.build:gradle:2.2.0'",
|
||||
replace: "classpath 'com.android.tools.build:gradle:7.3.1'"
|
||||
},
|
||||
{
|
||||
find: 'compileSdkVersion 23',
|
||||
replace: 'compileSdkVersion 33'
|
||||
},
|
||||
{
|
||||
find: 'buildToolsVersion "23.0.1"',
|
||||
replace: 'buildToolsVersion "33.0.0"'
|
||||
},
|
||||
{
|
||||
find: /compile /g,
|
||||
replace: 'implementation '
|
||||
},
|
||||
{
|
||||
find: /android {/,
|
||||
replace: 'android {\n namespace "me.alwx.HttpServer"'
|
||||
}
|
||||
]);
|
||||
|
||||
// --- Patch AndroidManifest.xml ---
|
||||
const manifestFile = path.resolve(__dirname, '..', 'node_modules', 'react-native-http-bridge', 'android', 'src', 'main', 'AndroidManifest.xml');
|
||||
patchFile(manifestFile, [
|
||||
{
|
||||
find: /package="me.alwx.HttpServer"/,
|
||||
replace: ''
|
||||
}
|
||||
]);
|
||||
|
||||
// --- Patch Server.java ---
|
||||
const serverJavaFile = path.resolve(__dirname, '..', 'node_modules', 'react-native-http-bridge', 'android', 'src', 'main', 'java', 'me', 'alwx', 'HttpServer', 'Server.java');
|
||||
patchFile(serverJavaFile, [
|
||||
{
|
||||
find: 'import android.support.annotation.Nullable;',
|
||||
replace: 'import androidx.annotation.Nullable;'
|
||||
}
|
||||
]);
|
||||
|
||||
// --- Patch HttpServerReactPackage.java ---
|
||||
const packageJavaFile = path.resolve(__dirname, '..', 'node_modules', 'react-native-http-bridge', 'android', 'src', 'main', 'java', 'me', 'alwx', 'HttpServer', 'HttpServerReactPackage.java');
|
||||
patchFile(packageJavaFile, [
|
||||
{
|
||||
find: '@Override\n public List<Class<? extends JavaScriptModule>> createJSModules()',
|
||||
replace: 'public List<Class<? extends JavaScriptModule>> createJSModules()'
|
||||
}
|
||||
]);
|
||||
|
||||
// --- Patch HttpServerModule.java for better logging ---
|
||||
const moduleJavaFile = path.resolve(__dirname, '..', 'node_modules', 'react-native-http-bridge', 'android', 'src', 'main', 'java', 'me', 'alwx', 'HttpServer', 'HttpServerModule.java');
|
||||
patchFile(moduleJavaFile, [
|
||||
{
|
||||
find: 'Log.e(MODULE_NAME, e.getMessage());',
|
||||
replace: 'Log.e(MODULE_NAME, "Failed to start server", e);'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Finished patching react-native-http-bridge.');
|
||||
@@ -1,7 +1,4 @@
|
||||
import httpBridge from 'react-native-http-bridge';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
|
||||
const PORT = 12346;
|
||||
import TCPHttpServer from './tcpHttpServer';
|
||||
|
||||
const getRemotePageHTML = () => {
|
||||
return `
|
||||
@@ -49,10 +46,71 @@ const getRemotePageHTML = () => {
|
||||
};
|
||||
|
||||
class RemoteControlService {
|
||||
private isRunning = false;
|
||||
private httpServer: TCPHttpServer;
|
||||
private onMessage: (message: string) => void = () => {};
|
||||
private onHandshake: () => void = () => {};
|
||||
|
||||
constructor() {
|
||||
this.httpServer = new TCPHttpServer();
|
||||
this.setupRequestHandler();
|
||||
}
|
||||
|
||||
private setupRequestHandler() {
|
||||
this.httpServer.setRequestHandler((request) => {
|
||||
console.log('[RemoteControl] Received request:', request.method, request.url);
|
||||
|
||||
try {
|
||||
if (request.method === 'GET' && request.url === '/') {
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: { 'Content-Type': 'text/html' },
|
||||
body: getRemotePageHTML()
|
||||
};
|
||||
} else if (request.method === 'POST' && request.url === '/message') {
|
||||
try {
|
||||
const parsedBody = JSON.parse(request.body || '{}');
|
||||
const message = parsedBody.message;
|
||||
if (message) {
|
||||
this.onMessage(message);
|
||||
}
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: 'ok' })
|
||||
};
|
||||
} catch (parseError) {
|
||||
console.error('[RemoteControl] Failed to parse message body:', parseError);
|
||||
return {
|
||||
statusCode: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ error: 'Invalid JSON' })
|
||||
};
|
||||
}
|
||||
} else if (request.method === 'POST' && request.url === '/handshake') {
|
||||
this.onHandshake();
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: 'ok' })
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
statusCode: 404,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
body: 'Not Found'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[RemoteControl] Request handler error:', error);
|
||||
return {
|
||||
statusCode: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ error: 'Internal Server Error' })
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public init(actions: { onMessage: (message: string) => void; onHandshake: () => void }) {
|
||||
this.onMessage = actions.onMessage;
|
||||
this.onHandshake = actions.onHandshake;
|
||||
@@ -60,72 +118,29 @@ class RemoteControlService {
|
||||
|
||||
public async startServer(): Promise<string> {
|
||||
console.log('[RemoteControl] Attempting to start server...');
|
||||
if (this.isRunning) {
|
||||
|
||||
if (this.httpServer.getIsRunning()) {
|
||||
console.log('[RemoteControl] Server is already running.');
|
||||
throw new Error('Server is already running.');
|
||||
}
|
||||
|
||||
const netState = await NetInfo.fetch();
|
||||
console.log('[RemoteControl] NetInfo state:', JSON.stringify(netState, null, 2));
|
||||
let ipAddress: string | null = null;
|
||||
if (netState.type === 'wifi' || netState.type === 'ethernet') {
|
||||
ipAddress = (netState.details as any)?.ipAddress ?? null;
|
||||
}
|
||||
|
||||
if (!ipAddress) {
|
||||
console.error('[RemoteControl] Could not get IP address.');
|
||||
throw new Error('无法获取IP地址,请确认设备已连接到WiFi或以太网。');
|
||||
}
|
||||
console.log(`[RemoteControl] Got IP address: ${ipAddress}`);
|
||||
|
||||
try {
|
||||
// The third argument to start() is the request handler, not a startup callback.
|
||||
httpBridge.start(
|
||||
PORT,
|
||||
'OrionTVRemoteService',
|
||||
(request: { url: string; type: string; requestId: string; postData: string }) => {
|
||||
const { url, type: method, requestId, postData: body } = request;
|
||||
|
||||
if (method === 'GET' && url === '/') {
|
||||
const html = getRemotePageHTML();
|
||||
httpBridge.respond(requestId, 200, 'text/html', html);
|
||||
} else if (method === 'POST' && url === '/message') {
|
||||
try {
|
||||
const parsedBody = JSON.parse(body);
|
||||
const message = parsedBody.message;
|
||||
if (message) {
|
||||
this.onMessage(message);
|
||||
}
|
||||
httpBridge.respond(requestId, 200, 'application/json', JSON.stringify({ status: 'ok' }));
|
||||
} catch (e) {
|
||||
httpBridge.respond(requestId, 400, 'application/json', JSON.stringify({ error: 'Bad Request' }));
|
||||
}
|
||||
} else if (method === 'POST' && url === '/handshake') {
|
||||
this.onHandshake();
|
||||
httpBridge.respond(requestId, 200, 'application/json', JSON.stringify({ status: 'ok' }));
|
||||
} else {
|
||||
httpBridge.respond(requestId, 404, 'text/plain', 'Not Found');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('[RemoteControl] http-bridge start command issued.');
|
||||
this.isRunning = true;
|
||||
const url = `http://${ipAddress}:${PORT}`;
|
||||
console.log(`[RemoteControl] Server should be running at: ${url}`);
|
||||
const url = await this.httpServer.start();
|
||||
console.log(`[RemoteControl] Server started successfully at: ${url}`);
|
||||
return url;
|
||||
} catch (error) {
|
||||
console.error('[RemoteControl] Failed to issue start command to http-bridge.', error);
|
||||
this.isRunning = false;
|
||||
console.error('[RemoteControl] Failed to start server:', error);
|
||||
throw new Error(error instanceof Error ? error.message : 'Failed to start server');
|
||||
}
|
||||
}
|
||||
|
||||
public stopServer() {
|
||||
if (this.isRunning) {
|
||||
httpBridge.stop();
|
||||
this.isRunning = false;
|
||||
}
|
||||
console.log('[RemoteControl] Stopping server...');
|
||||
this.httpServer.stop();
|
||||
}
|
||||
|
||||
public isRunning(): boolean {
|
||||
return this.httpServer.getIsRunning();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
198
services/tcpHttpServer.ts
Normal file
198
services/tcpHttpServer.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import TcpSocket from 'react-native-tcp-socket';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
|
||||
const PORT = 12346;
|
||||
|
||||
interface HttpRequest {
|
||||
method: string;
|
||||
url: string;
|
||||
headers: { [key: string]: string };
|
||||
body: string;
|
||||
}
|
||||
|
||||
interface HttpResponse {
|
||||
statusCode: number;
|
||||
headers: { [key: string]: string };
|
||||
body: string;
|
||||
}
|
||||
|
||||
type RequestHandler = (request: HttpRequest) => HttpResponse | Promise<HttpResponse>;
|
||||
|
||||
class TCPHttpServer {
|
||||
private server: TcpSocket.Server | null = null;
|
||||
private isRunning = false;
|
||||
private requestHandler: RequestHandler | null = null;
|
||||
|
||||
constructor() {
|
||||
this.server = null;
|
||||
}
|
||||
|
||||
private parseHttpRequest(data: string): HttpRequest | null {
|
||||
try {
|
||||
const lines = data.split('\r\n');
|
||||
const requestLine = lines[0].split(' ');
|
||||
|
||||
if (requestLine.length < 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const method = requestLine[0];
|
||||
const url = requestLine[1];
|
||||
const headers: { [key: string]: string } = {};
|
||||
|
||||
let bodyStartIndex = -1;
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line === '') {
|
||||
bodyStartIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
const colonIndex = line.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = line.substring(0, colonIndex).trim().toLowerCase();
|
||||
const value = line.substring(colonIndex + 1).trim();
|
||||
headers[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
const body = bodyStartIndex > 0 ? lines.slice(bodyStartIndex).join('\r\n') : '';
|
||||
|
||||
return { method, url, headers, body };
|
||||
} catch (error) {
|
||||
console.error('[TCPHttpServer] Error parsing HTTP request:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private formatHttpResponse(response: HttpResponse): string {
|
||||
const statusTexts: { [key: number]: string } = {
|
||||
200: 'OK',
|
||||
400: 'Bad Request',
|
||||
404: 'Not Found',
|
||||
500: 'Internal Server Error'
|
||||
};
|
||||
|
||||
const statusText = statusTexts[response.statusCode] || 'Unknown';
|
||||
const headers = {
|
||||
'Content-Length': new TextEncoder().encode(response.body).length.toString(),
|
||||
'Connection': 'close',
|
||||
...response.headers
|
||||
};
|
||||
|
||||
let httpResponse = `HTTP/1.1 ${response.statusCode} ${statusText}\r\n`;
|
||||
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
httpResponse += `${key}: ${value}\r\n`;
|
||||
}
|
||||
|
||||
httpResponse += '\r\n';
|
||||
httpResponse += response.body;
|
||||
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
public setRequestHandler(handler: RequestHandler) {
|
||||
this.requestHandler = handler;
|
||||
}
|
||||
|
||||
public async start(): Promise<string> {
|
||||
if (this.isRunning) {
|
||||
throw new Error('Server is already running');
|
||||
}
|
||||
|
||||
const netState = await NetInfo.fetch();
|
||||
let ipAddress: string | null = null;
|
||||
|
||||
if (netState.type === 'wifi' || netState.type === 'ethernet') {
|
||||
ipAddress = (netState.details as any)?.ipAddress ?? null;
|
||||
}
|
||||
|
||||
if (!ipAddress) {
|
||||
throw new Error('无法获取IP地址,请确认设备已连接到WiFi或以太网。');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.server = TcpSocket.createServer((socket: TcpSocket.Socket) => {
|
||||
console.log('[TCPHttpServer] Client connected');
|
||||
|
||||
let requestData = '';
|
||||
|
||||
socket.on('data', async (data: string | Buffer) => {
|
||||
requestData += data.toString();
|
||||
|
||||
// Check if we have a complete HTTP request
|
||||
if (requestData.includes('\r\n\r\n')) {
|
||||
try {
|
||||
const request = this.parseHttpRequest(requestData);
|
||||
if (request && this.requestHandler) {
|
||||
const response = await this.requestHandler(request);
|
||||
const httpResponse = this.formatHttpResponse(response);
|
||||
socket.write(httpResponse);
|
||||
} else {
|
||||
// Send 400 Bad Request for malformed requests
|
||||
const errorResponse = this.formatHttpResponse({
|
||||
statusCode: 400,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
body: 'Bad Request'
|
||||
});
|
||||
socket.write(errorResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TCPHttpServer] Error handling request:', error);
|
||||
const errorResponse = this.formatHttpResponse({
|
||||
statusCode: 500,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
body: 'Internal Server Error'
|
||||
});
|
||||
socket.write(errorResponse);
|
||||
}
|
||||
|
||||
socket.end();
|
||||
requestData = '';
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (error: Error) => {
|
||||
console.error('[TCPHttpServer] Socket error:', error);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
console.log('[TCPHttpServer] Client disconnected');
|
||||
});
|
||||
});
|
||||
|
||||
this.server.listen({ port: PORT, host: '0.0.0.0' }, () => {
|
||||
console.log(`[TCPHttpServer] Server listening on ${ipAddress}:${PORT}`);
|
||||
this.isRunning = true;
|
||||
resolve(`http://${ipAddress}:${PORT}`);
|
||||
});
|
||||
|
||||
this.server.on('error', (error: Error) => {
|
||||
console.error('[TCPHttpServer] Server error:', error);
|
||||
this.isRunning = false;
|
||||
reject(error);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('[TCPHttpServer] Failed to start server:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.server && this.isRunning) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
this.isRunning = false;
|
||||
console.log('[TCPHttpServer] Server stopped');
|
||||
}
|
||||
}
|
||||
|
||||
public getIsRunning(): boolean {
|
||||
return this.isRunning;
|
||||
}
|
||||
}
|
||||
|
||||
export default TCPHttpServer;
|
||||
13
types/react-native-http-bridge.d.ts
vendored
13
types/react-native-http-bridge.d.ts
vendored
@@ -1,13 +0,0 @@
|
||||
declare module 'react-native-http-bridge' {
|
||||
import { EmitterSubscription } from 'react-native';
|
||||
|
||||
interface HttpBridge {
|
||||
start(port: number, serviceName: string, callback: (request: { url: string; type: string; requestId: string; postData: string }) => void): void;
|
||||
stop(): void;
|
||||
on(event: 'request', callback: (request: any) => void): EmitterSubscription;
|
||||
respond(requestId: string, code: number, type: string, body: string): void;
|
||||
}
|
||||
|
||||
const httpBridge: HttpBridge;
|
||||
export default httpBridge;
|
||||
}
|
||||
Reference in New Issue
Block a user