@enfin/chat-shared 1.2.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/dist/apikey.d.ts +27 -0
- package/dist/apikey.js +73 -0
- package/dist/esm/apikey.d.ts +27 -0
- package/dist/esm/apikey.js +65 -0
- package/dist/esm/index.d.ts +164 -0
- package/dist/esm/index.js +19 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +37 -0
- package/package.json +29 -0
- package/src/apikey.ts +74 -0
- package/src/index.ts +216 -0
package/dist/apikey.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a random API key for chat platform
|
|
3
|
+
* Format: chat_{tenantId}_{checksum}
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateApiKey(tenantId: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Generate a random tenant ID
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateTenantId(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Validate API key format
|
|
12
|
+
* Returns true if format is correct: chat_{tenantId}_{random}
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateApiKeyFormat(apiKey: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Extract tenant ID from API key
|
|
17
|
+
* Format: chat_{tenantId}_{random}
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractTenantId(apiKey: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a checksum for API key validation
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateChecksum(apiKey: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Validate API key with checksum
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateApiKeyWithChecksum(apiKey: string, expectedChecksum: string): boolean;
|
package/dist/apikey.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// API Key generation and validation utilities
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.generateApiKey = generateApiKey;
|
|
5
|
+
exports.generateTenantId = generateTenantId;
|
|
6
|
+
exports.validateApiKeyFormat = validateApiKeyFormat;
|
|
7
|
+
exports.extractTenantId = extractTenantId;
|
|
8
|
+
exports.generateChecksum = generateChecksum;
|
|
9
|
+
exports.validateApiKeyWithChecksum = validateApiKeyWithChecksum;
|
|
10
|
+
const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
11
|
+
/**
|
|
12
|
+
* Generate a random API key for chat platform
|
|
13
|
+
* Format: chat_{tenantId}_{checksum}
|
|
14
|
+
*/
|
|
15
|
+
function generateApiKey(tenantId) {
|
|
16
|
+
const randomPart = generateRandomString(10);
|
|
17
|
+
return `chat_${tenantId}_${randomPart}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate a random tenant ID
|
|
21
|
+
*/
|
|
22
|
+
function generateTenantId() {
|
|
23
|
+
return generateRandomString(8);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate a random string of specified length using Base62
|
|
27
|
+
*/
|
|
28
|
+
function generateRandomString(length) {
|
|
29
|
+
let result = '';
|
|
30
|
+
for (let i = 0; i < length; i++) {
|
|
31
|
+
result += BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)];
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validate API key format
|
|
37
|
+
* Returns true if format is correct: chat_{tenantId}_{random}
|
|
38
|
+
*/
|
|
39
|
+
function validateApiKeyFormat(apiKey) {
|
|
40
|
+
const parts = apiKey.split('_');
|
|
41
|
+
return (parts.length === 3 &&
|
|
42
|
+
parts[0] === 'chat' &&
|
|
43
|
+
parts[1].length >= 6 &&
|
|
44
|
+
parts[2].length >= 6);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extract tenant ID from API key
|
|
48
|
+
* Format: chat_{tenantId}_{random}
|
|
49
|
+
*/
|
|
50
|
+
function extractTenantId(apiKey) {
|
|
51
|
+
const parts = apiKey.split('_');
|
|
52
|
+
if (parts.length !== 3 || parts[0] !== 'chat')
|
|
53
|
+
return null;
|
|
54
|
+
return parts[1];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Generate a checksum for API key validation
|
|
58
|
+
*/
|
|
59
|
+
function generateChecksum(apiKey) {
|
|
60
|
+
let hash = 0;
|
|
61
|
+
for (let i = 0; i < apiKey.length; i++) {
|
|
62
|
+
const char = apiKey.charCodeAt(i);
|
|
63
|
+
hash = (hash << 5) - hash + char;
|
|
64
|
+
hash = hash & hash;
|
|
65
|
+
}
|
|
66
|
+
return Math.abs(hash).toString(36);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate API key with checksum
|
|
70
|
+
*/
|
|
71
|
+
function validateApiKeyWithChecksum(apiKey, expectedChecksum) {
|
|
72
|
+
return generateChecksum(apiKey) === expectedChecksum;
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a random API key for chat platform
|
|
3
|
+
* Format: chat_{tenantId}_{checksum}
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateApiKey(tenantId: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Generate a random tenant ID
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateTenantId(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Validate API key format
|
|
12
|
+
* Returns true if format is correct: chat_{tenantId}_{random}
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateApiKeyFormat(apiKey: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Extract tenant ID from API key
|
|
17
|
+
* Format: chat_{tenantId}_{random}
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractTenantId(apiKey: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a checksum for API key validation
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateChecksum(apiKey: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Validate API key with checksum
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateApiKeyWithChecksum(apiKey: string, expectedChecksum: string): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// API Key generation and validation utilities
|
|
2
|
+
const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
3
|
+
/**
|
|
4
|
+
* Generate a random API key for chat platform
|
|
5
|
+
* Format: chat_{tenantId}_{checksum}
|
|
6
|
+
*/
|
|
7
|
+
export function generateApiKey(tenantId) {
|
|
8
|
+
const randomPart = generateRandomString(10);
|
|
9
|
+
return `chat_${tenantId}_${randomPart}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate a random tenant ID
|
|
13
|
+
*/
|
|
14
|
+
export function generateTenantId() {
|
|
15
|
+
return generateRandomString(8);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate a random string of specified length using Base62
|
|
19
|
+
*/
|
|
20
|
+
function generateRandomString(length) {
|
|
21
|
+
let result = '';
|
|
22
|
+
for (let i = 0; i < length; i++) {
|
|
23
|
+
result += BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)];
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate API key format
|
|
29
|
+
* Returns true if format is correct: chat_{tenantId}_{random}
|
|
30
|
+
*/
|
|
31
|
+
export function validateApiKeyFormat(apiKey) {
|
|
32
|
+
const parts = apiKey.split('_');
|
|
33
|
+
return (parts.length === 3 &&
|
|
34
|
+
parts[0] === 'chat' &&
|
|
35
|
+
parts[1].length >= 6 &&
|
|
36
|
+
parts[2].length >= 6);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Extract tenant ID from API key
|
|
40
|
+
* Format: chat_{tenantId}_{random}
|
|
41
|
+
*/
|
|
42
|
+
export function extractTenantId(apiKey) {
|
|
43
|
+
const parts = apiKey.split('_');
|
|
44
|
+
if (parts.length !== 3 || parts[0] !== 'chat')
|
|
45
|
+
return null;
|
|
46
|
+
return parts[1];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate a checksum for API key validation
|
|
50
|
+
*/
|
|
51
|
+
export function generateChecksum(apiKey) {
|
|
52
|
+
let hash = 0;
|
|
53
|
+
for (let i = 0; i < apiKey.length; i++) {
|
|
54
|
+
const char = apiKey.charCodeAt(i);
|
|
55
|
+
hash = (hash << 5) - hash + char;
|
|
56
|
+
hash = hash & hash;
|
|
57
|
+
}
|
|
58
|
+
return Math.abs(hash).toString(36);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate API key with checksum
|
|
62
|
+
*/
|
|
63
|
+
export function validateApiKeyWithChecksum(apiKey, expectedChecksum) {
|
|
64
|
+
return generateChecksum(apiKey) === expectedChecksum;
|
|
65
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export interface ApiKeyValidationResponse {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
mode: 'managed' | 'external-db';
|
|
5
|
+
database?: string;
|
|
6
|
+
version: string;
|
|
7
|
+
tenantId: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface Message {
|
|
11
|
+
_id: string;
|
|
12
|
+
roomId: string;
|
|
13
|
+
senderId: string;
|
|
14
|
+
senderName: string;
|
|
15
|
+
senderAvatar?: string;
|
|
16
|
+
content: string;
|
|
17
|
+
fileUrl?: string;
|
|
18
|
+
fileType?: string;
|
|
19
|
+
fileName?: string;
|
|
20
|
+
createdAt: Date;
|
|
21
|
+
updatedAt: Date;
|
|
22
|
+
readBy: ReadReceipt[];
|
|
23
|
+
}
|
|
24
|
+
export interface ReadReceipt {
|
|
25
|
+
userId: string;
|
|
26
|
+
readAt: Date;
|
|
27
|
+
}
|
|
28
|
+
export interface Room {
|
|
29
|
+
_id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
type: 'direct' | 'group';
|
|
32
|
+
members: string[];
|
|
33
|
+
createdBy: string;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
updatedAt: Date;
|
|
36
|
+
lastMessage?: string | Message;
|
|
37
|
+
lastActivity: Date;
|
|
38
|
+
avatar?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface PresenceStatus {
|
|
41
|
+
userId: string;
|
|
42
|
+
status: 'online' | 'offline' | 'away';
|
|
43
|
+
lastSeen: Date;
|
|
44
|
+
typingIn?: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface AudioCall {
|
|
47
|
+
_id: string;
|
|
48
|
+
roomId: string;
|
|
49
|
+
initiatorId: string;
|
|
50
|
+
participantId: string;
|
|
51
|
+
status: 'ringing' | 'active' | 'ended';
|
|
52
|
+
startedAt: Date;
|
|
53
|
+
endedAt?: Date;
|
|
54
|
+
duration?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface SocketMessage {
|
|
57
|
+
type: string;
|
|
58
|
+
data: any;
|
|
59
|
+
timestamp: number;
|
|
60
|
+
}
|
|
61
|
+
export interface SendMessagePayload {
|
|
62
|
+
roomId: string;
|
|
63
|
+
content: string;
|
|
64
|
+
fileUrl?: string;
|
|
65
|
+
fileType?: string;
|
|
66
|
+
fileName?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface TypingPayload {
|
|
69
|
+
roomId: string;
|
|
70
|
+
userId: string;
|
|
71
|
+
userName: string;
|
|
72
|
+
typing?: boolean;
|
|
73
|
+
content?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface PresencePayload {
|
|
76
|
+
userId: string;
|
|
77
|
+
status: 'online' | 'offline' | 'away';
|
|
78
|
+
}
|
|
79
|
+
export interface CallSignalingPayload {
|
|
80
|
+
roomId: string;
|
|
81
|
+
initiatorId: string;
|
|
82
|
+
participantId: string;
|
|
83
|
+
offer?: RTCSessionDescription;
|
|
84
|
+
answer?: RTCSessionDescription;
|
|
85
|
+
candidate?: RTCIceCandidate;
|
|
86
|
+
}
|
|
87
|
+
export interface FileUploadPayload {
|
|
88
|
+
roomId: string;
|
|
89
|
+
fileName: string;
|
|
90
|
+
fileSize: number;
|
|
91
|
+
fileType: string;
|
|
92
|
+
}
|
|
93
|
+
export interface FileUploadResponse {
|
|
94
|
+
success: boolean;
|
|
95
|
+
fileUrl: string;
|
|
96
|
+
fileName: string;
|
|
97
|
+
error?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface ChatConfig {
|
|
100
|
+
apiKey: string;
|
|
101
|
+
mongoUri?: string;
|
|
102
|
+
mode?: 'managed' | 'external-db';
|
|
103
|
+
version?: string;
|
|
104
|
+
validationUrl?: string;
|
|
105
|
+
jwtSecret?: string;
|
|
106
|
+
jwtIssuer?: string;
|
|
107
|
+
jwtAudience?: string;
|
|
108
|
+
iceServers?: RTCIceServer[];
|
|
109
|
+
turnServers?: TurnServerConfig[];
|
|
110
|
+
rateLimit?: {
|
|
111
|
+
messagesPerMinute?: number;
|
|
112
|
+
eventsPerMinute?: number;
|
|
113
|
+
};
|
|
114
|
+
hooks?: ChatHooks;
|
|
115
|
+
ringtoneUrl?: string;
|
|
116
|
+
}
|
|
117
|
+
export interface ChatHooks {
|
|
118
|
+
beforeMessageCreate?: (message: Message, user: AuthContext) => Message;
|
|
119
|
+
afterMessageCreate?: (message: Message) => void;
|
|
120
|
+
beforeRoomCreate?: (room: Room, user: AuthContext) => Room;
|
|
121
|
+
afterRoomCreate?: (room: Room) => void;
|
|
122
|
+
}
|
|
123
|
+
export declare const SDK_VERSION = "1.0.0";
|
|
124
|
+
export declare const SDK_MIN_COMPATIBLE_VERSION = "1.0.0";
|
|
125
|
+
export declare const SDK_MAX_COMPATIBLE_VERSION = "1.9.999";
|
|
126
|
+
export interface TurnServerConfig {
|
|
127
|
+
urls: string;
|
|
128
|
+
username?: string;
|
|
129
|
+
credential?: string;
|
|
130
|
+
}
|
|
131
|
+
export interface AuthContext {
|
|
132
|
+
userId: string;
|
|
133
|
+
tenantId: string;
|
|
134
|
+
roles: string[];
|
|
135
|
+
}
|
|
136
|
+
export interface VersionCheckResult {
|
|
137
|
+
compatible: boolean;
|
|
138
|
+
severity: 'ok' | 'warning' | 'error';
|
|
139
|
+
message: string;
|
|
140
|
+
}
|
|
141
|
+
export interface PaginatedMessages {
|
|
142
|
+
messages: Message[];
|
|
143
|
+
hasMore: boolean;
|
|
144
|
+
nextCursor: string | null;
|
|
145
|
+
}
|
|
146
|
+
export interface GetMessagesPayload {
|
|
147
|
+
roomId: string;
|
|
148
|
+
limit?: number;
|
|
149
|
+
beforeCursor?: string;
|
|
150
|
+
}
|
|
151
|
+
export interface RateLimitExceededPayload {
|
|
152
|
+
code: 'RATE_LIMIT_EXCEEDED';
|
|
153
|
+
message: string;
|
|
154
|
+
retryAfterMs?: number;
|
|
155
|
+
}
|
|
156
|
+
export interface AdminTenantStats {
|
|
157
|
+
tenantId: string;
|
|
158
|
+
totalMessages: number;
|
|
159
|
+
totalRooms: number;
|
|
160
|
+
activeConnections: number;
|
|
161
|
+
lastActivity: Date;
|
|
162
|
+
}
|
|
163
|
+
export declare function deriveDatabaseFromApiKey(apiKey: string): string | null;
|
|
164
|
+
export * from './apikey';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// @enfin/chat-shared - Core Types
|
|
3
|
+
// ============================================
|
|
4
|
+
// SDK Version
|
|
5
|
+
export const SDK_VERSION = '1.0.0';
|
|
6
|
+
export const SDK_MIN_COMPATIBLE_VERSION = '1.0.0';
|
|
7
|
+
export const SDK_MAX_COMPATIBLE_VERSION = '1.9.999';
|
|
8
|
+
// Derive tenant database name from API key
|
|
9
|
+
export function deriveDatabaseFromApiKey(apiKey) {
|
|
10
|
+
const parts = apiKey.split('_');
|
|
11
|
+
if (parts.length !== 3 || parts[0] !== 'chat')
|
|
12
|
+
return null;
|
|
13
|
+
const tenantId = parts[1];
|
|
14
|
+
if (!tenantId)
|
|
15
|
+
return null;
|
|
16
|
+
return `chat_${tenantId}`;
|
|
17
|
+
}
|
|
18
|
+
// Re-export API key utilities
|
|
19
|
+
export * from './apikey';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export interface ApiKeyValidationResponse {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
mode: 'managed' | 'external-db';
|
|
5
|
+
database?: string;
|
|
6
|
+
version: string;
|
|
7
|
+
tenantId: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface Message {
|
|
11
|
+
_id: string;
|
|
12
|
+
roomId: string;
|
|
13
|
+
senderId: string;
|
|
14
|
+
senderName: string;
|
|
15
|
+
senderAvatar?: string;
|
|
16
|
+
content: string;
|
|
17
|
+
fileUrl?: string;
|
|
18
|
+
fileType?: string;
|
|
19
|
+
fileName?: string;
|
|
20
|
+
createdAt: Date;
|
|
21
|
+
updatedAt: Date;
|
|
22
|
+
readBy: ReadReceipt[];
|
|
23
|
+
}
|
|
24
|
+
export interface ReadReceipt {
|
|
25
|
+
userId: string;
|
|
26
|
+
readAt: Date;
|
|
27
|
+
}
|
|
28
|
+
export interface Room {
|
|
29
|
+
_id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
type: 'direct' | 'group';
|
|
32
|
+
members: string[];
|
|
33
|
+
createdBy: string;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
updatedAt: Date;
|
|
36
|
+
lastMessage?: string | Message;
|
|
37
|
+
lastActivity: Date;
|
|
38
|
+
avatar?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface PresenceStatus {
|
|
41
|
+
userId: string;
|
|
42
|
+
status: 'online' | 'offline' | 'away';
|
|
43
|
+
lastSeen: Date;
|
|
44
|
+
typingIn?: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface AudioCall {
|
|
47
|
+
_id: string;
|
|
48
|
+
roomId: string;
|
|
49
|
+
initiatorId: string;
|
|
50
|
+
participantId: string;
|
|
51
|
+
status: 'ringing' | 'active' | 'ended';
|
|
52
|
+
startedAt: Date;
|
|
53
|
+
endedAt?: Date;
|
|
54
|
+
duration?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface SocketMessage {
|
|
57
|
+
type: string;
|
|
58
|
+
data: any;
|
|
59
|
+
timestamp: number;
|
|
60
|
+
}
|
|
61
|
+
export interface SendMessagePayload {
|
|
62
|
+
roomId: string;
|
|
63
|
+
content: string;
|
|
64
|
+
fileUrl?: string;
|
|
65
|
+
fileType?: string;
|
|
66
|
+
fileName?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface TypingPayload {
|
|
69
|
+
roomId: string;
|
|
70
|
+
userId: string;
|
|
71
|
+
userName: string;
|
|
72
|
+
typing?: boolean;
|
|
73
|
+
content?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface PresencePayload {
|
|
76
|
+
userId: string;
|
|
77
|
+
status: 'online' | 'offline' | 'away';
|
|
78
|
+
}
|
|
79
|
+
export interface CallSignalingPayload {
|
|
80
|
+
roomId: string;
|
|
81
|
+
initiatorId: string;
|
|
82
|
+
participantId: string;
|
|
83
|
+
offer?: RTCSessionDescription;
|
|
84
|
+
answer?: RTCSessionDescription;
|
|
85
|
+
candidate?: RTCIceCandidate;
|
|
86
|
+
}
|
|
87
|
+
export interface FileUploadPayload {
|
|
88
|
+
roomId: string;
|
|
89
|
+
fileName: string;
|
|
90
|
+
fileSize: number;
|
|
91
|
+
fileType: string;
|
|
92
|
+
}
|
|
93
|
+
export interface FileUploadResponse {
|
|
94
|
+
success: boolean;
|
|
95
|
+
fileUrl: string;
|
|
96
|
+
fileName: string;
|
|
97
|
+
error?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface ChatConfig {
|
|
100
|
+
apiKey: string;
|
|
101
|
+
mongoUri?: string;
|
|
102
|
+
mode?: 'managed' | 'external-db';
|
|
103
|
+
version?: string;
|
|
104
|
+
validationUrl?: string;
|
|
105
|
+
jwtSecret?: string;
|
|
106
|
+
jwtIssuer?: string;
|
|
107
|
+
jwtAudience?: string;
|
|
108
|
+
iceServers?: RTCIceServer[];
|
|
109
|
+
turnServers?: TurnServerConfig[];
|
|
110
|
+
rateLimit?: {
|
|
111
|
+
messagesPerMinute?: number;
|
|
112
|
+
eventsPerMinute?: number;
|
|
113
|
+
};
|
|
114
|
+
hooks?: ChatHooks;
|
|
115
|
+
ringtoneUrl?: string;
|
|
116
|
+
}
|
|
117
|
+
export interface ChatHooks {
|
|
118
|
+
beforeMessageCreate?: (message: Message, user: AuthContext) => Message;
|
|
119
|
+
afterMessageCreate?: (message: Message) => void;
|
|
120
|
+
beforeRoomCreate?: (room: Room, user: AuthContext) => Room;
|
|
121
|
+
afterRoomCreate?: (room: Room) => void;
|
|
122
|
+
}
|
|
123
|
+
export declare const SDK_VERSION = "1.0.0";
|
|
124
|
+
export declare const SDK_MIN_COMPATIBLE_VERSION = "1.0.0";
|
|
125
|
+
export declare const SDK_MAX_COMPATIBLE_VERSION = "1.9.999";
|
|
126
|
+
export interface TurnServerConfig {
|
|
127
|
+
urls: string;
|
|
128
|
+
username?: string;
|
|
129
|
+
credential?: string;
|
|
130
|
+
}
|
|
131
|
+
export interface AuthContext {
|
|
132
|
+
userId: string;
|
|
133
|
+
tenantId: string;
|
|
134
|
+
roles: string[];
|
|
135
|
+
}
|
|
136
|
+
export interface VersionCheckResult {
|
|
137
|
+
compatible: boolean;
|
|
138
|
+
severity: 'ok' | 'warning' | 'error';
|
|
139
|
+
message: string;
|
|
140
|
+
}
|
|
141
|
+
export interface PaginatedMessages {
|
|
142
|
+
messages: Message[];
|
|
143
|
+
hasMore: boolean;
|
|
144
|
+
nextCursor: string | null;
|
|
145
|
+
}
|
|
146
|
+
export interface GetMessagesPayload {
|
|
147
|
+
roomId: string;
|
|
148
|
+
limit?: number;
|
|
149
|
+
beforeCursor?: string;
|
|
150
|
+
}
|
|
151
|
+
export interface RateLimitExceededPayload {
|
|
152
|
+
code: 'RATE_LIMIT_EXCEEDED';
|
|
153
|
+
message: string;
|
|
154
|
+
retryAfterMs?: number;
|
|
155
|
+
}
|
|
156
|
+
export interface AdminTenantStats {
|
|
157
|
+
tenantId: string;
|
|
158
|
+
totalMessages: number;
|
|
159
|
+
totalRooms: number;
|
|
160
|
+
activeConnections: number;
|
|
161
|
+
lastActivity: Date;
|
|
162
|
+
}
|
|
163
|
+
export declare function deriveDatabaseFromApiKey(apiKey: string): string | null;
|
|
164
|
+
export * from './apikey';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================
|
|
3
|
+
// @enfin/chat-shared - Core Types
|
|
4
|
+
// ============================================
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.SDK_MAX_COMPATIBLE_VERSION = exports.SDK_MIN_COMPATIBLE_VERSION = exports.SDK_VERSION = void 0;
|
|
21
|
+
exports.deriveDatabaseFromApiKey = deriveDatabaseFromApiKey;
|
|
22
|
+
// SDK Version
|
|
23
|
+
exports.SDK_VERSION = '1.0.0';
|
|
24
|
+
exports.SDK_MIN_COMPATIBLE_VERSION = '1.0.0';
|
|
25
|
+
exports.SDK_MAX_COMPATIBLE_VERSION = '1.9.999';
|
|
26
|
+
// Derive tenant database name from API key
|
|
27
|
+
function deriveDatabaseFromApiKey(apiKey) {
|
|
28
|
+
const parts = apiKey.split('_');
|
|
29
|
+
if (parts.length !== 3 || parts[0] !== 'chat')
|
|
30
|
+
return null;
|
|
31
|
+
const tenantId = parts[1];
|
|
32
|
+
if (!tenantId)
|
|
33
|
+
return null;
|
|
34
|
+
return `chat_${tenantId}`;
|
|
35
|
+
}
|
|
36
|
+
// Re-export API key utilities
|
|
37
|
+
__exportStar(require("./apikey"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@enfin/chat-shared",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Shared types and utilities for chat platform SDKs",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"module": "dist/esm/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/esm/index.js",
|
|
11
|
+
"require": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.json --module esnext --outDir dist/esm",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"test": "jest"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.0.0",
|
|
23
|
+
"@types/node": "^20.0.0"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src"
|
|
28
|
+
]
|
|
29
|
+
}
|
package/src/apikey.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// API Key generation and validation utilities
|
|
2
|
+
|
|
3
|
+
const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate a random API key for chat platform
|
|
7
|
+
* Format: chat_{tenantId}_{checksum}
|
|
8
|
+
*/
|
|
9
|
+
export function generateApiKey(tenantId: string): string {
|
|
10
|
+
const randomPart = generateRandomString(10);
|
|
11
|
+
return `chat_${tenantId}_${randomPart}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate a random tenant ID
|
|
16
|
+
*/
|
|
17
|
+
export function generateTenantId(): string {
|
|
18
|
+
return generateRandomString(8);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate a random string of specified length using Base62
|
|
23
|
+
*/
|
|
24
|
+
function generateRandomString(length: number): string {
|
|
25
|
+
let result = '';
|
|
26
|
+
for (let i = 0; i < length; i++) {
|
|
27
|
+
result += BASE62_CHARS[Math.floor(Math.random() * BASE62_CHARS.length)];
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate API key format
|
|
34
|
+
* Returns true if format is correct: chat_{tenantId}_{random}
|
|
35
|
+
*/
|
|
36
|
+
export function validateApiKeyFormat(apiKey: string): boolean {
|
|
37
|
+
const parts = apiKey.split('_');
|
|
38
|
+
return (
|
|
39
|
+
parts.length === 3 &&
|
|
40
|
+
parts[0] === 'chat' &&
|
|
41
|
+
parts[1].length >= 6 &&
|
|
42
|
+
parts[2].length >= 6
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract tenant ID from API key
|
|
48
|
+
* Format: chat_{tenantId}_{random}
|
|
49
|
+
*/
|
|
50
|
+
export function extractTenantId(apiKey: string): string | null {
|
|
51
|
+
const parts = apiKey.split('_');
|
|
52
|
+
if (parts.length !== 3 || parts[0] !== 'chat') return null;
|
|
53
|
+
return parts[1];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a checksum for API key validation
|
|
58
|
+
*/
|
|
59
|
+
export function generateChecksum(apiKey: string): string {
|
|
60
|
+
let hash = 0;
|
|
61
|
+
for (let i = 0; i < apiKey.length; i++) {
|
|
62
|
+
const char = apiKey.charCodeAt(i);
|
|
63
|
+
hash = (hash << 5) - hash + char;
|
|
64
|
+
hash = hash & hash;
|
|
65
|
+
}
|
|
66
|
+
return Math.abs(hash).toString(36);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate API key with checksum
|
|
71
|
+
*/
|
|
72
|
+
export function validateApiKeyWithChecksum(apiKey: string, expectedChecksum: string): boolean {
|
|
73
|
+
return generateChecksum(apiKey) === expectedChecksum;
|
|
74
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// @enfin/chat-shared - Core Types
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
5
|
+
// API Key Validation Types
|
|
6
|
+
export interface ApiKeyValidationResponse {
|
|
7
|
+
valid: boolean;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
mode: 'managed' | 'external-db';
|
|
10
|
+
database?: string;
|
|
11
|
+
version: string;
|
|
12
|
+
tenantId: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Message Types
|
|
17
|
+
export interface Message {
|
|
18
|
+
_id: string;
|
|
19
|
+
roomId: string;
|
|
20
|
+
senderId: string;
|
|
21
|
+
senderName: string;
|
|
22
|
+
senderAvatar?: string;
|
|
23
|
+
content: string;
|
|
24
|
+
fileUrl?: string;
|
|
25
|
+
fileType?: string;
|
|
26
|
+
fileName?: string;
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
updatedAt: Date;
|
|
29
|
+
readBy: ReadReceipt[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ReadReceipt {
|
|
33
|
+
userId: string;
|
|
34
|
+
readAt: Date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Room Types
|
|
38
|
+
export interface Room {
|
|
39
|
+
_id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
type: 'direct' | 'group';
|
|
42
|
+
members: string[];
|
|
43
|
+
createdBy: string;
|
|
44
|
+
createdAt: Date;
|
|
45
|
+
updatedAt: Date;
|
|
46
|
+
lastMessage?: string | Message;
|
|
47
|
+
lastActivity: Date;
|
|
48
|
+
avatar?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// User Presence Types
|
|
52
|
+
export interface PresenceStatus {
|
|
53
|
+
userId: string;
|
|
54
|
+
status: 'online' | 'offline' | 'away';
|
|
55
|
+
lastSeen: Date;
|
|
56
|
+
typingIn?: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Audio Call Types
|
|
60
|
+
export interface AudioCall {
|
|
61
|
+
_id: string;
|
|
62
|
+
roomId: string;
|
|
63
|
+
initiatorId: string;
|
|
64
|
+
participantId: string;
|
|
65
|
+
status: 'ringing' | 'active' | 'ended';
|
|
66
|
+
startedAt: Date;
|
|
67
|
+
endedAt?: Date;
|
|
68
|
+
duration?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Socket Events
|
|
72
|
+
export interface SocketMessage {
|
|
73
|
+
type: string;
|
|
74
|
+
data: any;
|
|
75
|
+
timestamp: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SendMessagePayload {
|
|
79
|
+
roomId: string;
|
|
80
|
+
content: string;
|
|
81
|
+
fileUrl?: string;
|
|
82
|
+
fileType?: string;
|
|
83
|
+
fileName?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface TypingPayload {
|
|
87
|
+
roomId: string;
|
|
88
|
+
userId: string;
|
|
89
|
+
userName: string;
|
|
90
|
+
typing?: boolean;
|
|
91
|
+
content?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface PresencePayload {
|
|
95
|
+
userId: string;
|
|
96
|
+
status: 'online' | 'offline' | 'away';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface CallSignalingPayload {
|
|
100
|
+
roomId: string;
|
|
101
|
+
initiatorId: string;
|
|
102
|
+
participantId: string;
|
|
103
|
+
offer?: RTCSessionDescription;
|
|
104
|
+
answer?: RTCSessionDescription;
|
|
105
|
+
candidate?: RTCIceCandidate;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// File Upload Types
|
|
109
|
+
export interface FileUploadPayload {
|
|
110
|
+
roomId: string;
|
|
111
|
+
fileName: string;
|
|
112
|
+
fileSize: number;
|
|
113
|
+
fileType: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface FileUploadResponse {
|
|
117
|
+
success: boolean;
|
|
118
|
+
fileUrl: string;
|
|
119
|
+
fileName: string;
|
|
120
|
+
error?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Configuration Types
|
|
124
|
+
export interface ChatConfig {
|
|
125
|
+
apiKey: string;
|
|
126
|
+
mongoUri?: string;
|
|
127
|
+
mode?: 'managed' | 'external-db';
|
|
128
|
+
version?: string;
|
|
129
|
+
validationUrl?: string;
|
|
130
|
+
jwtSecret?: string;
|
|
131
|
+
jwtIssuer?: string;
|
|
132
|
+
jwtAudience?: string;
|
|
133
|
+
iceServers?: RTCIceServer[];
|
|
134
|
+
turnServers?: TurnServerConfig[];
|
|
135
|
+
rateLimit?: {
|
|
136
|
+
messagesPerMinute?: number;
|
|
137
|
+
eventsPerMinute?: number;
|
|
138
|
+
};
|
|
139
|
+
hooks?: ChatHooks;
|
|
140
|
+
ringtoneUrl?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Chat Hooks for customization
|
|
144
|
+
export interface ChatHooks {
|
|
145
|
+
beforeMessageCreate?: (message: Message, user: AuthContext) => Message;
|
|
146
|
+
afterMessageCreate?: (message: Message) => void;
|
|
147
|
+
beforeRoomCreate?: (room: Room, user: AuthContext) => Room;
|
|
148
|
+
afterRoomCreate?: (room: Room) => void;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// SDK Version
|
|
152
|
+
export const SDK_VERSION = '1.0.0';
|
|
153
|
+
export const SDK_MIN_COMPATIBLE_VERSION = '1.0.0';
|
|
154
|
+
export const SDK_MAX_COMPATIBLE_VERSION = '1.9.999';
|
|
155
|
+
|
|
156
|
+
// TURN Server Config
|
|
157
|
+
export interface TurnServerConfig {
|
|
158
|
+
urls: string;
|
|
159
|
+
username?: string;
|
|
160
|
+
credential?: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Auth Context
|
|
164
|
+
export interface AuthContext {
|
|
165
|
+
userId: string;
|
|
166
|
+
tenantId: string;
|
|
167
|
+
roles: string[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Version Check Result
|
|
171
|
+
export interface VersionCheckResult {
|
|
172
|
+
compatible: boolean;
|
|
173
|
+
severity: 'ok' | 'warning' | 'error';
|
|
174
|
+
message: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Paginated Messages
|
|
178
|
+
export interface PaginatedMessages {
|
|
179
|
+
messages: Message[];
|
|
180
|
+
hasMore: boolean;
|
|
181
|
+
nextCursor: string | null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface GetMessagesPayload {
|
|
185
|
+
roomId: string;
|
|
186
|
+
limit?: number;
|
|
187
|
+
beforeCursor?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Rate Limit Event
|
|
191
|
+
export interface RateLimitExceededPayload {
|
|
192
|
+
code: 'RATE_LIMIT_EXCEEDED';
|
|
193
|
+
message: string;
|
|
194
|
+
retryAfterMs?: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Admin Tenant Stats
|
|
198
|
+
export interface AdminTenantStats {
|
|
199
|
+
tenantId: string;
|
|
200
|
+
totalMessages: number;
|
|
201
|
+
totalRooms: number;
|
|
202
|
+
activeConnections: number;
|
|
203
|
+
lastActivity: Date;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Derive tenant database name from API key
|
|
207
|
+
export function deriveDatabaseFromApiKey(apiKey: string): string | null {
|
|
208
|
+
const parts = apiKey.split('_');
|
|
209
|
+
if (parts.length !== 3 || parts[0] !== 'chat') return null;
|
|
210
|
+
const tenantId = parts[1];
|
|
211
|
+
if (!tenantId) return null;
|
|
212
|
+
return `chat_${tenantId}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Re-export API key utilities
|
|
216
|
+
export * from './apikey';
|