Skip to content

Commit 7be1cbd

Browse files
committed
feat: Implement SignalR service with chat functionality and comprehensive README documentation
1 parent 3974a00 commit 7be1cbd

File tree

10 files changed

+650
-0
lines changed

10 files changed

+650
-0
lines changed

packages/signalr/README.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# @mixcore/signalr
2+
3+
SignalR real-time communication services for Mixcore SDK.
4+
5+
## Features
6+
7+
- **Real-time Communication**: Full SignalR client implementation with connection management
8+
- **Type Safety**: Complete TypeScript support with strongly typed interfaces
9+
- **Chat Services**: High-level chat service abstraction for common messaging patterns
10+
- **Connection Management**: Automatic reconnection, state management, and error handling
11+
- **Security**: Token-based authentication with configurable access token factory
12+
- **Framework Agnostic**: Can be used in any JavaScript/TypeScript environment
13+
14+
## Installation
15+
16+
```bash
17+
npm install @mixcore/signalr
18+
# or
19+
yarn add @mixcore/signalr
20+
# or
21+
pnpm add @mixcore/signalr
22+
```
23+
24+
## Usage
25+
26+
### Basic SignalR Service
27+
28+
```typescript
29+
import { SignalRService } from '@mixcore/signalr';
30+
31+
const service = new SignalRService({
32+
hubUrl: 'https://your-hub-url/hub',
33+
accessTokenFactory: () => localStorage.getItem('access_token'),
34+
automaticReconnect: true,
35+
onConnected: () => console.log('Connected'),
36+
onDisconnected: (error) => console.log('Disconnected', error)
37+
});
38+
39+
// Start connection
40+
await service.start();
41+
42+
// Listen for messages
43+
service.onMessage('receive_message', (message) => {
44+
console.log('Received:', message);
45+
});
46+
47+
// Send messages
48+
await service.invoke('SendMessage', 'Hello World');
49+
```
50+
51+
### Chat Service
52+
53+
```typescript
54+
import { ChatService } from '@mixcore/signalr';
55+
56+
const chatService = new ChatService({
57+
baseUrl: 'https://your-api-url',
58+
hubPath: '/hub/chat',
59+
accessTokenFactory: () => localStorage.getItem('access_token')
60+
});
61+
62+
// Start chat service
63+
await chatService.start();
64+
65+
// Send messages
66+
await chatService.sendMessage('Hello, AI!');
67+
68+
// Listen for responses
69+
chatService.onMessageReceived((message) => {
70+
if (message.data.response) {
71+
console.log('AI Response:', message.data.response);
72+
}
73+
});
74+
```
75+
76+
## API Reference
77+
78+
### SignalRService
79+
80+
Main service class for SignalR connections.
81+
82+
#### Constructor Options
83+
84+
- `hubUrl`: SignalR hub URL
85+
- `accessTokenFactory`: Function that returns the access token
86+
- `logLevel`: SignalR logging level (optional)
87+
- `automaticReconnect`: Enable automatic reconnection (default: true)
88+
- `onConnected`: Connection established callback
89+
- `onDisconnected`: Connection lost callback
90+
- `onReconnecting`: Reconnection started callback
91+
- `onReconnected`: Reconnection completed callback
92+
- `onError`: Error callback
93+
94+
#### Methods
95+
96+
- `start()`: Start the SignalR connection
97+
- `stop()`: Stop the SignalR connection
98+
- `invoke(method, ...args)`: Invoke a server method and wait for response
99+
- `send(method, ...args)`: Send a message to server without waiting for response
100+
- `onMessage(event, handler)`: Register message handler
101+
- `offMessage(event, handler)`: Unregister message handler
102+
- `isConnected()`: Check if connection is active
103+
- `getConnectionState()`: Get current connection state
104+
- `dispose()`: Clean up resources
105+
106+
### ChatService
107+
108+
High-level service for chat functionality.
109+
110+
#### Constructor Options
111+
112+
- `baseUrl`: Base URL for the SignalR hub (optional, defaults to current origin)
113+
- `hubPath`: Hub path (optional, defaults to '/hub/llm_chat')
114+
- All SignalRService options except `hubUrl`
115+
116+
#### Methods
117+
118+
- `sendMessage(content)`: Send a chat message
119+
- `sendChatMessage(message)`: Send a structured chat message
120+
- `onMessageReceived(handler)`: Listen for incoming messages
121+
- `offMessageReceived(handler)`: Stop listening for messages
122+
- All SignalRService methods
123+
124+
## Security Considerations
125+
126+
- Always use HTTPS in production
127+
- Implement proper access token validation
128+
- Sanitize all incoming messages
129+
- Use environment variables for configuration
130+
- Implement rate limiting on the server side
131+
132+
## Framework Integration
133+
134+
This package is framework-agnostic and can be used with:
135+
136+
- Vanilla JavaScript/TypeScript
137+
- React
138+
- Vue
139+
- Angular
140+
- Svelte/SvelteKit
141+
- Node.js
142+
143+
## License
144+
145+
See LICENSE file in the repository root.

packages/signalr/jest.config.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export default {
2+
preset: 'ts-jest',
3+
testEnvironment: 'jsdom',
4+
extensionsToTreatAsEsm: ['.ts'],
5+
transform: {
6+
'^.+\\.ts$': ['ts-jest', {
7+
useESM: true
8+
}]
9+
},
10+
moduleNameMapping: {
11+
'^@mixcore/(.*)$': '<rootDir>/../$1/src'
12+
},
13+
testMatch: [
14+
'<rootDir>/tests/**/*.test.ts'
15+
]
16+
};

packages/signalr/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@mixcore/signalr",
3+
"version": "0.0.1",
4+
"main": "dist/index.js",
5+
"module": "dist/index.esm.js",
6+
"types": "dist/index.d.ts",
7+
"description": "SignalR real-time communication services for Mixcore SDK",
8+
"scripts": {
9+
"build": "tsup src/index.ts --dts --format esm,cjs --out-dir dist",
10+
"test": "jest",
11+
"lint": "eslint src --ext .ts --config ../../eslint.config.js"
12+
},
13+
"dependencies": {
14+
"@mixcore/base": "workspace:*",
15+
"@mixcore/shared": "workspace:*",
16+
"@microsoft/signalr": "^9.0.6"
17+
},
18+
"devDependencies": {
19+
"@types/node": "^20.0.0",
20+
"jest": "^29.0.0",
21+
"typescript": "^5.0.0"
22+
},
23+
"license": "SEE LICENSE IN LICENSE",
24+
"repository": "https://github.com/mixcore/javascript-sdk",
25+
"publishConfig": {
26+
"access": "public"
27+
},
28+
"keywords": ["mixcore", "sdk", "signalr", "realtime", "chat", "typescript"],
29+
"engines": {
30+
"node": ">=18.0.0"
31+
}
32+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { SignalRService } from './signalr-service';
2+
import { SignalRConfig, SignalRMessage, ChatMessage } from './types';
3+
4+
export interface ChatServiceConfig extends Omit<SignalRConfig, 'hubUrl'> {
5+
baseUrl?: string;
6+
hubPath?: string;
7+
}
8+
9+
export class ChatService {
10+
private signalRService: SignalRService;
11+
private messageQueue: string[] = [];
12+
private isProcessing = false;
13+
14+
constructor(config: ChatServiceConfig) {
15+
const baseUrl = config.baseUrl || (typeof window !== 'undefined' ? window.location.origin : 'https://mixcore.net');
16+
const hubPath = config.hubPath || '/hub/llm_chat';
17+
18+
const signalRConfig: SignalRConfig = {
19+
...config,
20+
hubUrl: `${baseUrl}${hubPath}`,
21+
};
22+
23+
this.signalRService = new SignalRService(signalRConfig);
24+
this.setupMessageHandlers();
25+
}
26+
27+
private setupMessageHandlers(): void {
28+
this.signalRService.onMessage('receive_message', (message: SignalRMessage) => {
29+
// Process the message and convert to ChatMessage if needed
30+
this.handleIncomingMessage(message);
31+
});
32+
}
33+
34+
private handleIncomingMessage(message: SignalRMessage): void {
35+
// Override this method in subclasses or provide callbacks for custom handling
36+
console.log('Received SignalR message:', message);
37+
}
38+
39+
public async start(): Promise<void> {
40+
await this.signalRService.start();
41+
}
42+
43+
public async stop(): Promise<void> {
44+
await this.signalRService.stop();
45+
}
46+
47+
public async sendMessage(content: string): Promise<void> {
48+
if (!content.trim()) {
49+
throw new Error('Message content cannot be empty');
50+
}
51+
52+
try {
53+
await this.signalRService.invoke('AskAI', content);
54+
} catch (error) {
55+
console.error('Failed to send message:', error);
56+
throw error;
57+
}
58+
}
59+
60+
public async sendChatMessage(message: ChatMessage): Promise<void> {
61+
await this.sendMessage(message.content);
62+
}
63+
64+
public onMessageReceived(handler: (message: SignalRMessage) => void): void {
65+
this.signalRService.onMessage('receive_message', handler);
66+
}
67+
68+
public offMessageReceived(handler: (message: SignalRMessage) => void): void {
69+
this.signalRService.offMessage('receive_message', handler);
70+
}
71+
72+
public onConnectionStateChange(handler: (state: string) => void): void {
73+
this.signalRService.onConnectionStateChange(handler);
74+
}
75+
76+
public offConnectionStateChange(handler: (state: string) => void): void {
77+
this.signalRService.offConnectionStateChange(handler);
78+
}
79+
80+
public onError(handler: (error: Error) => void): void {
81+
this.signalRService.onError(handler);
82+
}
83+
84+
public offError(handler: (error: Error) => void): void {
85+
this.signalRService.offError(handler);
86+
}
87+
88+
public isConnected(): boolean {
89+
return this.signalRService.isConnected();
90+
}
91+
92+
public getConnectionState(): string {
93+
return this.signalRService.getConnectionState();
94+
}
95+
96+
public dispose(): void {
97+
this.signalRService.dispose();
98+
}
99+
}

packages/signalr/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './types';
2+
export * from './signalr-service';
3+
export * from './chat-service';

0 commit comments

Comments
 (0)