@bernierllc/chat-suite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +26 -0
- package/README.md +542 -0
- package/__tests__/integration/ChatSuite.test.ts +235 -0
- package/__tests__/unit/ConfigManager.test.ts +122 -0
- package/__tests__/unit/MessageOrchestrator.test.ts +223 -0
- package/__tests__/unit/UserManager.test.ts +208 -0
- package/dist/ChatSuite.d.ts +76 -0
- package/dist/ChatSuite.d.ts.map +1 -0
- package/dist/ChatSuite.js +273 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/neverhub/discovery.d.ts +40 -0
- package/dist/neverhub/discovery.d.ts.map +1 -0
- package/dist/neverhub/discovery.js +106 -0
- package/dist/neverhub/event-handlers.d.ts +38 -0
- package/dist/neverhub/event-handlers.d.ts.map +1 -0
- package/dist/neverhub/event-handlers.js +125 -0
- package/dist/neverhub/index.d.ts +4 -0
- package/dist/neverhub/index.d.ts.map +1 -0
- package/dist/neverhub/index.js +26 -0
- package/dist/neverhub/service-registration.d.ts +29 -0
- package/dist/neverhub/service-registration.d.ts.map +1 -0
- package/dist/neverhub/service-registration.js +66 -0
- package/dist/providers/ChatKitProvider.d.ts +53 -0
- package/dist/providers/ChatKitProvider.d.ts.map +1 -0
- package/dist/providers/ChatKitProvider.js +167 -0
- package/dist/providers/ExternalProviders.d.ts +101 -0
- package/dist/providers/ExternalProviders.d.ts.map +1 -0
- package/dist/providers/ExternalProviders.js +153 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +25 -0
- package/dist/services/ConfigManager.d.ts +50 -0
- package/dist/services/ConfigManager.d.ts.map +1 -0
- package/dist/services/ConfigManager.js +95 -0
- package/dist/services/HealthMonitor.d.ts +58 -0
- package/dist/services/HealthMonitor.d.ts.map +1 -0
- package/dist/services/HealthMonitor.js +148 -0
- package/dist/services/MessageOrchestrator.d.ts +63 -0
- package/dist/services/MessageOrchestrator.d.ts.map +1 -0
- package/dist/services/MessageOrchestrator.js +212 -0
- package/dist/services/ProviderManager.d.ts +83 -0
- package/dist/services/ProviderManager.d.ts.map +1 -0
- package/dist/services/ProviderManager.js +204 -0
- package/dist/services/UserManager.d.ts +61 -0
- package/dist/services/UserManager.d.ts.map +1 -0
- package/dist/services/UserManager.js +141 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +28 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +26 -0
- package/dist/types/message-types.d.ts +80 -0
- package/dist/types/message-types.d.ts.map +1 -0
- package/dist/types/message-types.js +32 -0
- package/dist/types/provider-types.d.ts +96 -0
- package/dist/types/provider-types.d.ts.map +1 -0
- package/dist/types/provider-types.js +9 -0
- package/dist/types/suite-types.d.ts +101 -0
- package/dist/types/suite-types.d.ts.map +1 -0
- package/dist/types/suite-types.js +9 -0
- package/dist/utils/config-inheritance.d.ts +24 -0
- package/dist/utils/config-inheritance.d.ts.map +1 -0
- package/dist/utils/config-inheritance.js +167 -0
- package/dist/utils/error-handling.d.ts +37 -0
- package/dist/utils/error-handling.d.ts.map +1 -0
- package/dist/utils/error-handling.js +102 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +27 -0
- package/dist/utils/message-bridge.d.ts +68 -0
- package/dist/utils/message-bridge.d.ts.map +1 -0
- package/dist/utils/message-bridge.js +126 -0
- package/dist/utils/user-mapping.d.ts +63 -0
- package/dist/utils/user-mapping.d.ts.map +1 -0
- package/dist/utils/user-mapping.js +110 -0
- package/jest.config.cjs +30 -0
- package/package.json +67 -0
- package/src/ChatSuite.ts +347 -0
- package/src/index.ts +25 -0
- package/src/neverhub/discovery.ts +137 -0
- package/src/neverhub/event-handlers.ts +141 -0
- package/src/neverhub/index.ts +11 -0
- package/src/neverhub/service-registration.ts +89 -0
- package/src/providers/ChatKitProvider.ts +193 -0
- package/src/providers/ExternalProviders.ts +171 -0
- package/src/providers/index.ts +10 -0
- package/src/services/ConfigManager.ts +112 -0
- package/src/services/HealthMonitor.ts +169 -0
- package/src/services/MessageOrchestrator.ts +271 -0
- package/src/services/ProviderManager.ts +247 -0
- package/src/services/UserManager.ts +176 -0
- package/src/services/index.ts +13 -0
- package/src/types/index.ts +11 -0
- package/src/types/message-types.ts +94 -0
- package/src/types/provider-types.ts +116 -0
- package/src/types/suite-types.ts +117 -0
- package/src/utils/config-inheritance.ts +189 -0
- package/src/utils/error-handling.ts +114 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/message-bridge.ts +164 -0
- package/src/utils/user-mapping.ts +132 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
Provider,
|
|
11
|
+
ProviderType,
|
|
12
|
+
ProviderMessage,
|
|
13
|
+
ProviderMessageResult,
|
|
14
|
+
OrchestratedMessage,
|
|
15
|
+
BroadcastConfig,
|
|
16
|
+
SuiteResult
|
|
17
|
+
} from '../types';
|
|
18
|
+
import { MessagePriority, MessageStatus } from '../types';
|
|
19
|
+
import { MessageBridge } from '../utils/message-bridge';
|
|
20
|
+
import { ChatSuiteError, ChatSuiteErrorType, retryOperation } from '../utils/error-handling';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Orchestrates messages across all providers
|
|
24
|
+
*/
|
|
25
|
+
export class MessageOrchestrator {
|
|
26
|
+
private messageBridge?: MessageBridge;
|
|
27
|
+
private messages: Map<string, OrchestratedMessage> = new Map();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set message bridge for cross-platform sync
|
|
31
|
+
*/
|
|
32
|
+
public setMessageBridge(bridge: MessageBridge): void {
|
|
33
|
+
this.messageBridge = bridge;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Send a message through a specific provider
|
|
38
|
+
*/
|
|
39
|
+
public async sendMessage(
|
|
40
|
+
provider: Provider,
|
|
41
|
+
message: ProviderMessage
|
|
42
|
+
): Promise<SuiteResult<ProviderMessageResult>> {
|
|
43
|
+
try {
|
|
44
|
+
if (!provider.isInitialized()) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: `Provider ${provider.type} is not initialized`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await retryOperation(
|
|
52
|
+
() => provider.sendMessage(message),
|
|
53
|
+
3,
|
|
54
|
+
1000
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return { success: true, data: result };
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: error instanceof Error ? error.message : 'Failed to send message'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Broadcast a message to multiple platforms
|
|
68
|
+
*/
|
|
69
|
+
public async broadcastMessage(
|
|
70
|
+
providers: Map<ProviderType, Provider>,
|
|
71
|
+
config: BroadcastConfig
|
|
72
|
+
): Promise<SuiteResult<ProviderMessageResult[]>> {
|
|
73
|
+
try {
|
|
74
|
+
const results: ProviderMessageResult[] = [];
|
|
75
|
+
const targetProviders = config.platforms
|
|
76
|
+
.map(type => providers.get(type))
|
|
77
|
+
.filter((p): p is Provider => p !== undefined);
|
|
78
|
+
|
|
79
|
+
if (targetProviders.length === 0) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: 'No valid providers found for broadcast'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Create orchestrated message
|
|
87
|
+
const orchestratedMessage: OrchestratedMessage = {
|
|
88
|
+
id: this.generateMessageId(),
|
|
89
|
+
content: config.content,
|
|
90
|
+
priority: config.priority || MessagePriority.NORMAL,
|
|
91
|
+
status: MessageStatus.SENDING,
|
|
92
|
+
targetProviders: config.platforms,
|
|
93
|
+
metadata: config.metadata,
|
|
94
|
+
createdAt: new Date(),
|
|
95
|
+
updatedAt: new Date()
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
this.messages.set(orchestratedMessage.id, orchestratedMessage);
|
|
99
|
+
|
|
100
|
+
// Send to all providers
|
|
101
|
+
for (const provider of targetProviders) {
|
|
102
|
+
const providerMessage: ProviderMessage = {
|
|
103
|
+
content: config.content,
|
|
104
|
+
channelId: config.channels?.[0],
|
|
105
|
+
metadata: config.metadata
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = await this.sendMessage(provider, providerMessage);
|
|
109
|
+
if (result.success && result.data) {
|
|
110
|
+
results.push(result.data);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Update orchestrated message status
|
|
115
|
+
orchestratedMessage.status = results.length > 0 ? MessageStatus.SENT : MessageStatus.FAILED;
|
|
116
|
+
orchestratedMessage.updatedAt = new Date();
|
|
117
|
+
|
|
118
|
+
return { success: true, data: results };
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
error: error instanceof Error ? error.message : 'Failed to broadcast message'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Bridge a message from one provider to another
|
|
129
|
+
*/
|
|
130
|
+
public async bridgeMessage(
|
|
131
|
+
sourceProvider: Provider,
|
|
132
|
+
targetProvider: Provider,
|
|
133
|
+
message: ProviderMessage
|
|
134
|
+
): Promise<SuiteResult<ProviderMessageResult>> {
|
|
135
|
+
try {
|
|
136
|
+
if (!this.messageBridge) {
|
|
137
|
+
throw new ChatSuiteError(
|
|
138
|
+
ChatSuiteErrorType.CONFIGURATION_ERROR,
|
|
139
|
+
'Message bridge not configured'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!this.messageBridge.shouldBridge(sourceProvider.type, targetProvider.type)) {
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
error: `Bridging from ${sourceProvider.type} to ${targetProvider.type} is not enabled`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Transform message for target provider
|
|
151
|
+
const transformedMessage = this.messageBridge.transformMessage(
|
|
152
|
+
message,
|
|
153
|
+
sourceProvider.type,
|
|
154
|
+
targetProvider.type
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Send to target provider
|
|
158
|
+
const result = await this.sendMessage(targetProvider, transformedMessage);
|
|
159
|
+
|
|
160
|
+
if (result.success && result.data) {
|
|
161
|
+
// Record the bridge
|
|
162
|
+
this.messageBridge.recordBridgedMessage({
|
|
163
|
+
originalMessageId: result.data.messageId || 'unknown',
|
|
164
|
+
sourceProvider: sourceProvider.type,
|
|
165
|
+
targetProvider: targetProvider.type,
|
|
166
|
+
targetMessageId: result.data.messageId,
|
|
167
|
+
timestamp: new Date()
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: error instanceof Error ? error.message : 'Failed to bridge message'
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get an orchestrated message by ID
|
|
182
|
+
*/
|
|
183
|
+
public getMessage(messageId: string): OrchestratedMessage | undefined {
|
|
184
|
+
return this.messages.get(messageId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get all messages
|
|
189
|
+
*/
|
|
190
|
+
public getAllMessages(): OrchestratedMessage[] {
|
|
191
|
+
return Array.from(this.messages.values());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get messages by status
|
|
196
|
+
*/
|
|
197
|
+
public getMessagesByStatus(status: MessageStatus): OrchestratedMessage[] {
|
|
198
|
+
return Array.from(this.messages.values()).filter(m => m.status === status);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Update message status
|
|
203
|
+
*/
|
|
204
|
+
public updateMessageStatus(messageId: string, status: MessageStatus): boolean {
|
|
205
|
+
const message = this.messages.get(messageId);
|
|
206
|
+
if (!message) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
message.status = status;
|
|
211
|
+
message.updatedAt = new Date();
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Clear all messages
|
|
217
|
+
*/
|
|
218
|
+
public clearMessages(): void {
|
|
219
|
+
this.messages.clear();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get message count
|
|
224
|
+
*/
|
|
225
|
+
public getMessageCount(): number {
|
|
226
|
+
return this.messages.size;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get statistics
|
|
231
|
+
*/
|
|
232
|
+
public getStats(): {
|
|
233
|
+
total: number;
|
|
234
|
+
byStatus: Record<MessageStatus, number>;
|
|
235
|
+
byPriority: Record<MessagePriority, number>;
|
|
236
|
+
} {
|
|
237
|
+
const byStatus: Record<MessageStatus, number> = {
|
|
238
|
+
[MessageStatus.PENDING]: 0,
|
|
239
|
+
[MessageStatus.ROUTING]: 0,
|
|
240
|
+
[MessageStatus.SENDING]: 0,
|
|
241
|
+
[MessageStatus.SENT]: 0,
|
|
242
|
+
[MessageStatus.FAILED]: 0,
|
|
243
|
+
[MessageStatus.RETRYING]: 0
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const byPriority: Record<MessagePriority, number> = {
|
|
247
|
+
[MessagePriority.LOW]: 0,
|
|
248
|
+
[MessagePriority.NORMAL]: 0,
|
|
249
|
+
[MessagePriority.HIGH]: 0,
|
|
250
|
+
[MessagePriority.URGENT]: 0
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
this.messages.forEach(message => {
|
|
254
|
+
byStatus[message.status]++;
|
|
255
|
+
byPriority[message.priority]++;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
total: this.messages.size,
|
|
260
|
+
byStatus,
|
|
261
|
+
byPriority
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Generate a unique message ID
|
|
267
|
+
*/
|
|
268
|
+
private generateMessageId(): string {
|
|
269
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Provider, ProviderType, SuiteResult } from '../types';
|
|
10
|
+
import { ChatSuiteError, ChatSuiteErrorType } from '../utils/error-handling';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Provider registration information
|
|
14
|
+
*/
|
|
15
|
+
export interface ProviderRegistration {
|
|
16
|
+
provider: Provider;
|
|
17
|
+
priority: number;
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Manages all chat providers
|
|
23
|
+
*/
|
|
24
|
+
export class ProviderManager {
|
|
25
|
+
private providers: Map<ProviderType, ProviderRegistration> = new Map();
|
|
26
|
+
private initializedProviders: Set<ProviderType> = new Set();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register a provider
|
|
30
|
+
*/
|
|
31
|
+
public registerProvider(registration: ProviderRegistration): SuiteResult<void> {
|
|
32
|
+
try {
|
|
33
|
+
if (this.providers.has(registration.provider.type)) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: `Provider ${registration.provider.type} is already registered`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.providers.set(registration.provider.type, registration);
|
|
41
|
+
return { success: true };
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
error: error instanceof Error ? error.message : 'Failed to register provider'
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initialize a provider
|
|
52
|
+
*/
|
|
53
|
+
public async initializeProvider(providerType: ProviderType): Promise<SuiteResult<void>> {
|
|
54
|
+
try {
|
|
55
|
+
const registration = this.providers.get(providerType);
|
|
56
|
+
if (!registration) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: `Provider ${providerType} not registered`
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!registration.enabled) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: `Provider ${providerType} is not enabled`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await registration.provider.initialize();
|
|
71
|
+
this.initializedProviders.add(providerType);
|
|
72
|
+
return { success: true };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
error: error instanceof Error ? error.message : 'Failed to initialize provider'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Initialize all enabled providers
|
|
83
|
+
*/
|
|
84
|
+
public async initializeAll(): Promise<{
|
|
85
|
+
successful: ProviderType[];
|
|
86
|
+
failed: Array<{ type: ProviderType; error: string }>;
|
|
87
|
+
}> {
|
|
88
|
+
const successful: ProviderType[] = [];
|
|
89
|
+
const failed: Array<{ type: ProviderType; error: string }> = [];
|
|
90
|
+
|
|
91
|
+
for (const [type, registration] of this.providers) {
|
|
92
|
+
if (!registration.enabled) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const result = await this.initializeProvider(type);
|
|
97
|
+
if (result.success) {
|
|
98
|
+
successful.push(type);
|
|
99
|
+
} else {
|
|
100
|
+
failed.push({ type, error: result.error || 'Unknown error' });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { successful, failed };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get a provider by type
|
|
109
|
+
*/
|
|
110
|
+
public getProvider(providerType: ProviderType): Provider | undefined {
|
|
111
|
+
return this.providers.get(providerType)?.provider;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if a provider is initialized
|
|
116
|
+
*/
|
|
117
|
+
public isInitialized(providerType: ProviderType): boolean {
|
|
118
|
+
return this.initializedProviders.has(providerType);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get all enabled providers sorted by priority
|
|
123
|
+
*/
|
|
124
|
+
public getEnabledProviders(): Provider[] {
|
|
125
|
+
const enabled = Array.from(this.providers.values())
|
|
126
|
+
.filter(reg => reg.enabled)
|
|
127
|
+
.sort((a, b) => a.priority - b.priority);
|
|
128
|
+
|
|
129
|
+
return enabled.map(reg => reg.provider);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get all initialized providers
|
|
134
|
+
*/
|
|
135
|
+
public getInitializedProviders(): Provider[] {
|
|
136
|
+
return Array.from(this.initializedProviders)
|
|
137
|
+
.map(type => this.providers.get(type)?.provider)
|
|
138
|
+
.filter((p): p is Provider => p !== undefined);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get provider by priority (1 = highest)
|
|
143
|
+
*/
|
|
144
|
+
public getProviderByPriority(priority: number): Provider | undefined {
|
|
145
|
+
const registration = Array.from(this.providers.values())
|
|
146
|
+
.find(reg => reg.priority === priority && reg.enabled);
|
|
147
|
+
|
|
148
|
+
return registration?.provider;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get highest priority provider
|
|
153
|
+
*/
|
|
154
|
+
public getPrimaryProvider(): Provider | undefined {
|
|
155
|
+
const enabled = Array.from(this.providers.values())
|
|
156
|
+
.filter(reg => reg.enabled)
|
|
157
|
+
.sort((a, b) => a.priority - b.priority);
|
|
158
|
+
|
|
159
|
+
return enabled[0]?.provider;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Shutdown a provider
|
|
164
|
+
*/
|
|
165
|
+
public async shutdownProvider(providerType: ProviderType): Promise<SuiteResult<void>> {
|
|
166
|
+
try {
|
|
167
|
+
const provider = this.providers.get(providerType)?.provider;
|
|
168
|
+
if (!provider) {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
error: `Provider ${providerType} not found`
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!this.initializedProviders.has(providerType)) {
|
|
176
|
+
return { success: true }; // Already shutdown
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await provider.shutdown();
|
|
180
|
+
this.initializedProviders.delete(providerType);
|
|
181
|
+
return { success: true };
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
success: false,
|
|
185
|
+
error: error instanceof Error ? error.message : 'Failed to shutdown provider'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Shutdown all providers
|
|
192
|
+
*/
|
|
193
|
+
public async shutdownAll(): Promise<void> {
|
|
194
|
+
const shutdownPromises = Array.from(this.initializedProviders).map(type =>
|
|
195
|
+
this.shutdownProvider(type)
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
await Promise.all(shutdownPromises);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Unregister a provider
|
|
203
|
+
*/
|
|
204
|
+
public unregisterProvider(providerType: ProviderType): SuiteResult<void> {
|
|
205
|
+
if (this.initializedProviders.has(providerType)) {
|
|
206
|
+
throw new ChatSuiteError(
|
|
207
|
+
ChatSuiteErrorType.PROVIDER_ERROR,
|
|
208
|
+
`Cannot unregister initialized provider ${providerType}. Shutdown first.`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const deleted = this.providers.delete(providerType);
|
|
213
|
+
if (!deleted) {
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
error: `Provider ${providerType} not registered`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { success: true };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get provider count
|
|
225
|
+
*/
|
|
226
|
+
public getProviderCount(): {
|
|
227
|
+
total: number;
|
|
228
|
+
enabled: number;
|
|
229
|
+
initialized: number;
|
|
230
|
+
} {
|
|
231
|
+
const enabled = Array.from(this.providers.values()).filter(r => r.enabled).length;
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
total: this.providers.size,
|
|
235
|
+
enabled,
|
|
236
|
+
initialized: this.initializedProviders.size
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Clear all providers
|
|
242
|
+
*/
|
|
243
|
+
public clear(): void {
|
|
244
|
+
this.providers.clear();
|
|
245
|
+
this.initializedProviders.clear();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ProviderType } from '../types';
|
|
10
|
+
import { UserMapper, UserIdentity, createUnifiedId } from '../utils/user-mapping';
|
|
11
|
+
import type { SuiteResult } from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* User registration data
|
|
15
|
+
*/
|
|
16
|
+
export interface UserRegistration {
|
|
17
|
+
provider: ProviderType;
|
|
18
|
+
platformUserId: string;
|
|
19
|
+
displayName?: string;
|
|
20
|
+
email?: string;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Manages users across all chat providers
|
|
26
|
+
*/
|
|
27
|
+
export class UserManager {
|
|
28
|
+
private mapper: UserMapper;
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
this.mapper = new UserMapper();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register a new user or update existing user
|
|
36
|
+
*/
|
|
37
|
+
public registerUser(registration: UserRegistration): SuiteResult<UserIdentity> {
|
|
38
|
+
try {
|
|
39
|
+
// Check if user already exists
|
|
40
|
+
const existingId = this.mapper.getUnifiedId(
|
|
41
|
+
registration.provider,
|
|
42
|
+
registration.platformUserId
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (existingId) {
|
|
46
|
+
// Update existing user
|
|
47
|
+
const identity = this.mapper.getIdentity(existingId);
|
|
48
|
+
if (identity) {
|
|
49
|
+
if (registration.displayName) {
|
|
50
|
+
identity.displayName = registration.displayName;
|
|
51
|
+
}
|
|
52
|
+
if (registration.email) {
|
|
53
|
+
identity.email = registration.email;
|
|
54
|
+
}
|
|
55
|
+
if (registration.metadata) {
|
|
56
|
+
identity.metadata = { ...identity.metadata, ...registration.metadata };
|
|
57
|
+
}
|
|
58
|
+
this.mapper.mapUser(identity);
|
|
59
|
+
return { success: true, data: identity };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create new user
|
|
64
|
+
const platformIds = new Map<ProviderType, string>();
|
|
65
|
+
platformIds.set(registration.provider, registration.platformUserId);
|
|
66
|
+
|
|
67
|
+
const newIdentity: UserIdentity = {
|
|
68
|
+
unifiedId: createUnifiedId(),
|
|
69
|
+
platformIds,
|
|
70
|
+
displayName: registration.displayName,
|
|
71
|
+
email: registration.email,
|
|
72
|
+
metadata: registration.metadata
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.mapper.mapUser(newIdentity);
|
|
76
|
+
return { success: true, data: newIdentity };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: error instanceof Error ? error.message : 'Failed to register user'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Link a platform account to an existing user
|
|
87
|
+
*/
|
|
88
|
+
public linkPlatform(
|
|
89
|
+
unifiedId: string,
|
|
90
|
+
provider: ProviderType,
|
|
91
|
+
platformUserId: string
|
|
92
|
+
): SuiteResult<UserIdentity> {
|
|
93
|
+
try {
|
|
94
|
+
const success = this.mapper.addPlatformId(unifiedId, provider, platformUserId);
|
|
95
|
+
if (!success) {
|
|
96
|
+
return { success: false, error: 'User not found' };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const identity = this.mapper.getIdentity(unifiedId);
|
|
100
|
+
return { success: true, data: identity };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: error instanceof Error ? error.message : 'Failed to link platform'
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get unified user ID from platform-specific ID
|
|
111
|
+
*/
|
|
112
|
+
public getUnifiedId(provider: ProviderType, platformUserId: string): string | undefined {
|
|
113
|
+
return this.mapper.getUnifiedId(provider, platformUserId);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get platform-specific user ID
|
|
118
|
+
*/
|
|
119
|
+
public getPlatformId(unifiedId: string, provider: ProviderType): string | undefined {
|
|
120
|
+
return this.mapper.getPlatformId(unifiedId, provider);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get complete user identity
|
|
125
|
+
*/
|
|
126
|
+
public getUser(unifiedId: string): SuiteResult<UserIdentity> {
|
|
127
|
+
const identity = this.mapper.getIdentity(unifiedId);
|
|
128
|
+
if (!identity) {
|
|
129
|
+
return { success: false, error: 'User not found' };
|
|
130
|
+
}
|
|
131
|
+
return { success: true, data: identity };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get all users
|
|
136
|
+
*/
|
|
137
|
+
public getAllUsers(): UserIdentity[] {
|
|
138
|
+
return this.mapper.getAllUsers();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Remove a user
|
|
143
|
+
*/
|
|
144
|
+
public removeUser(unifiedId: string): SuiteResult<void> {
|
|
145
|
+
const success = this.mapper.removeUser(unifiedId);
|
|
146
|
+
if (!success) {
|
|
147
|
+
return { success: false, error: 'User not found' };
|
|
148
|
+
}
|
|
149
|
+
return { success: true };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get user count
|
|
154
|
+
*/
|
|
155
|
+
public getUserCount(): number {
|
|
156
|
+
return this.mapper.size();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Search users by display name or email
|
|
161
|
+
*/
|
|
162
|
+
public searchUsers(query: string): UserIdentity[] {
|
|
163
|
+
const lowerQuery = query.toLowerCase();
|
|
164
|
+
return this.mapper.getAllUsers().filter(user =>
|
|
165
|
+
user.displayName?.toLowerCase().includes(lowerQuery) ||
|
|
166
|
+
user.email?.toLowerCase().includes(lowerQuery)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Clear all users
|
|
172
|
+
*/
|
|
173
|
+
public clear(): void {
|
|
174
|
+
this.mapper.clear();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './ConfigManager';
|
|
10
|
+
export * from './UserManager';
|
|
11
|
+
export * from './HealthMonitor';
|
|
12
|
+
export * from './ProviderManager';
|
|
13
|
+
export * from './MessageOrchestrator';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './provider-types';
|
|
10
|
+
export * from './message-types';
|
|
11
|
+
export * from './suite-types';
|