@retrivora-ai/rag-engine 2.2.6 → 2.2.8
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/{ILLMProvider-BWa68XX5.d.mts → ILLMProvider-teTNQ1lh.d.mts} +2 -0
- package/dist/{ILLMProvider-BWa68XX5.d.ts → ILLMProvider-teTNQ1lh.d.ts} +2 -0
- package/dist/{index-BCbeeh74.d.ts → LicenseValidator-CENvo9o2.d.mts} +48 -5
- package/dist/{index-BbJGyNmW.d.mts → LicenseValidator-CsjJp2PP.d.ts} +48 -5
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +56 -4
- package/dist/handlers/index.mjs +55 -4
- package/dist/{index-DvbtNz7m.d.mts → index-BPJ3KDYI.d.ts} +6 -2
- package/dist/{index-B5TTZWkx.d.ts → index-Dmq5lH0j.d.mts} +6 -2
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +475 -104
- package/dist/index.mjs +478 -104
- package/dist/server.d.mts +6 -6
- package/dist/server.d.ts +6 -6
- package/dist/server.js +214 -4
- package/dist/server.mjs +211 -4
- package/package.json +1 -1
- package/src/components/ChatWidget.tsx +30 -35
- package/src/components/ChatWindow.tsx +37 -33
- package/src/core/LicenseValidator.ts +198 -0
- package/src/exceptions/index.ts +25 -5
- package/src/handlers/index.ts +76 -3
- package/src/hooks/useRagChat.ts +29 -2
- package/src/index.ts +4 -0
- package/src/server.ts +6 -0
- package/src/types/chat.ts +2 -0
- package/src/types/props.ts +5 -2
|
@@ -4,6 +4,7 @@ import { ChatWindow } from './ChatWindow';
|
|
|
4
4
|
import { useConfig } from './ConfigProvider';
|
|
5
5
|
import { ChatWidgetProps } from '../types';
|
|
6
6
|
import { SDK_VERSION } from '../version';
|
|
7
|
+
import { LicenseValidator } from '../core/LicenseValidator';
|
|
7
8
|
|
|
8
9
|
const DEFAULT_DIMENSIONS = { width: 380, height: 600 };
|
|
9
10
|
|
|
@@ -45,8 +46,8 @@ const GEMINI_STYLES = `
|
|
|
45
46
|
}
|
|
46
47
|
`;
|
|
47
48
|
|
|
48
|
-
export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, retrivoraApiBase, headers }: ChatWidgetProps) {
|
|
49
|
-
const { ui } = useConfig();
|
|
49
|
+
export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, retrivoraApiBase, licenseKey: licenseKeyProp, headers }: ChatWidgetProps) {
|
|
50
|
+
const { ui, projectId } = useConfig();
|
|
50
51
|
const [isOpen, setIsOpen] = useState(false);
|
|
51
52
|
const [hasUnread, setHasUnread] = useState(false);
|
|
52
53
|
const [dimensions, setDimensions] = useState(DEFAULT_DIMENSIONS);
|
|
@@ -71,44 +72,37 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, ret
|
|
|
71
72
|
const handleOpen = async () => {
|
|
72
73
|
setIsValidating(true);
|
|
73
74
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
headers: {
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
'x-sdk-version': SDK_VERSION,
|
|
82
|
-
...(headers || {})
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (!res.ok) {
|
|
87
|
-
const data = await res.json().catch(() => ({}));
|
|
88
|
-
if (res.status === 426 || data.code === 'SDK_VERSION_OUTDATED') {
|
|
89
|
-
const msg = data.error || 'Your Retrivora SDK package version is outdated. Please update your package to the latest version to continue.';
|
|
90
|
-
setWidgetError(msg);
|
|
91
|
-
setIsOpen(false); // DO NOT OPEN CHAT WIDGET
|
|
92
|
-
return;
|
|
75
|
+
const getHeader = (name: string) => {
|
|
76
|
+
if (!headers) return undefined;
|
|
77
|
+
const target = name.toLowerCase();
|
|
78
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
79
|
+
if (k.toLowerCase() === target) return v;
|
|
93
80
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
81
|
+
return undefined;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const resolvedLicenseKey =
|
|
85
|
+
licenseKeyProp ||
|
|
86
|
+
getHeader('x-license-key') ||
|
|
87
|
+
getHeader('authorization')?.replace(/^Bearer\s+/i, '') ||
|
|
88
|
+
(typeof process !== 'undefined'
|
|
89
|
+
? (process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY)
|
|
90
|
+
: '') ||
|
|
91
|
+
'';
|
|
92
|
+
|
|
93
|
+
await LicenseValidator.getInstance().validate({
|
|
94
|
+
licenseKey: resolvedLicenseKey,
|
|
95
|
+
projectId,
|
|
96
|
+
retrivoraApiBase,
|
|
97
|
+
headers,
|
|
98
|
+
});
|
|
105
99
|
|
|
106
100
|
setWidgetError(null);
|
|
107
101
|
setIsOpen(true);
|
|
108
102
|
setHasUnread(false);
|
|
109
|
-
} catch {
|
|
110
|
-
|
|
111
|
-
setIsOpen(
|
|
103
|
+
} catch (err: any) {
|
|
104
|
+
setWidgetError(err?.message || 'Access denied.');
|
|
105
|
+
setIsOpen(false); // DO NOT OPEN CHAT WIDGET
|
|
112
106
|
} finally {
|
|
113
107
|
setIsValidating(false);
|
|
114
108
|
}
|
|
@@ -190,6 +184,7 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, ret
|
|
|
190
184
|
onAddToCart={onAddToCart}
|
|
191
185
|
apiUrl={apiUrl}
|
|
192
186
|
retrivoraApiBase={retrivoraApiBase}
|
|
187
|
+
licenseKey={licenseKeyProp}
|
|
193
188
|
headers={headers}
|
|
194
189
|
/>
|
|
195
190
|
{/* Pointer (Tail) */}
|
|
@@ -23,6 +23,7 @@ import { useRagChat } from '../hooks/useRagChat';
|
|
|
23
23
|
import { BORDER_RADIUS_MAP, CHAT_SUGGESTIONS } from '../config/uiConstants';
|
|
24
24
|
import { ChatWindowProps } from '../types';
|
|
25
25
|
import { SDK_VERSION } from '../version';
|
|
26
|
+
import { LicenseValidator } from '../core/LicenseValidator';
|
|
26
27
|
|
|
27
28
|
interface SpeechRecognitionEvent {
|
|
28
29
|
resultIndex: number;
|
|
@@ -67,6 +68,7 @@ export function ChatWindow({
|
|
|
67
68
|
onAddToCart,
|
|
68
69
|
apiUrl,
|
|
69
70
|
retrivoraApiBase,
|
|
71
|
+
licenseKey: licenseKeyProp,
|
|
70
72
|
headers
|
|
71
73
|
}: ChatWindowProps) {
|
|
72
74
|
const { ui, projectId } = useConfig();
|
|
@@ -80,6 +82,7 @@ export function ChatWindow({
|
|
|
80
82
|
namespace: projectId,
|
|
81
83
|
apiUrl,
|
|
82
84
|
retrivoraApiBase,
|
|
85
|
+
licenseKey: licenseKeyProp,
|
|
83
86
|
headers,
|
|
84
87
|
});
|
|
85
88
|
|
|
@@ -97,48 +100,49 @@ export function ChatWindow({
|
|
|
97
100
|
let isMounted = true;
|
|
98
101
|
async function validateSessionLicense() {
|
|
99
102
|
try {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
const getHeader = (name: string) => {
|
|
104
|
+
if (!headers) return undefined;
|
|
105
|
+
const target = name.toLowerCase();
|
|
106
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
107
|
+
if (k.toLowerCase() === target) return v;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const resolvedLicenseKey =
|
|
113
|
+
licenseKeyProp ||
|
|
114
|
+
getHeader('x-license-key') ||
|
|
115
|
+
getHeader('authorization')?.replace(/^Bearer\s+/i, '') ||
|
|
116
|
+
(typeof process !== 'undefined'
|
|
117
|
+
? (process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY)
|
|
118
|
+
: '') ||
|
|
119
|
+
'';
|
|
120
|
+
|
|
121
|
+
await LicenseValidator.getInstance().validate({
|
|
122
|
+
licenseKey: resolvedLicenseKey,
|
|
123
|
+
projectId,
|
|
124
|
+
retrivoraApiBase,
|
|
125
|
+
headers,
|
|
110
126
|
});
|
|
111
127
|
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
(
|
|
122
|
-
) {
|
|
123
|
-
if (isMounted) {
|
|
124
|
-
const errMsg = typeof data.error === 'string' ? data.error : data.error?.message || 'Your Retrivora license is no longer valid. Please contact your administrator or obtain a valid license key.';
|
|
125
|
-
setLicenseError(errMsg);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} else {
|
|
129
|
-
if (isMounted) {
|
|
130
|
-
setLicenseError(null);
|
|
131
|
-
setVersionError(null);
|
|
128
|
+
if (isMounted) {
|
|
129
|
+
setLicenseError(null);
|
|
130
|
+
setVersionError(null);
|
|
131
|
+
}
|
|
132
|
+
} catch (err: any) {
|
|
133
|
+
if (isMounted) {
|
|
134
|
+
if (err?.code === 'SDK_VERSION_UNSUPPORTED') {
|
|
135
|
+
setVersionError(err?.message || 'SDK version unsupported.');
|
|
136
|
+
} else {
|
|
137
|
+
setLicenseError(err?.message || 'License validation failed.');
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
|
-
} catch {
|
|
135
|
-
// Non-blocking fallback
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
validateSessionLicense();
|
|
140
144
|
return () => { isMounted = false; };
|
|
141
|
-
}, [retrivoraApiBase, headers]);
|
|
145
|
+
}, [retrivoraApiBase, headers, projectId]);
|
|
142
146
|
|
|
143
147
|
// Initialize SpeechRecognition
|
|
144
148
|
useEffect(() => {
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { SDK_VERSION } from '../version';
|
|
2
|
+
import { SDKVersionUnsupportedError, LicenseValidationError } from '../exceptions';
|
|
3
|
+
import { LicenseVerifier } from './LicenseVerifier';
|
|
4
|
+
|
|
5
|
+
export interface LicenseValidationRequest {
|
|
6
|
+
licenseKey: string;
|
|
7
|
+
projectId?: string;
|
|
8
|
+
sdkVersion?: string;
|
|
9
|
+
sdk?: string;
|
|
10
|
+
platform?: string;
|
|
11
|
+
retrivoraApiBase?: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LicenseValidationResponse {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
licenseStatus: 'ACTIVE' | 'SUSPENDED' | 'TERMINATED' | 'EXPIRED' | 'REVOKED';
|
|
18
|
+
minimumSupportedVersion: string;
|
|
19
|
+
latestVersion: string;
|
|
20
|
+
forceUpgrade: boolean;
|
|
21
|
+
expiresAt: string | null;
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CachedSuccessValidation {
|
|
26
|
+
response: LicenseValidationResponse;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class LicenseValidator {
|
|
31
|
+
private static instance: LicenseValidator;
|
|
32
|
+
private cache: Map<string, CachedSuccessValidation> = new Map();
|
|
33
|
+
private static readonly SUCCESS_CACHE_TTL_MS = 5 * 60 * 1000; // 5-minute TTL for successful validation only
|
|
34
|
+
|
|
35
|
+
private constructor() {}
|
|
36
|
+
|
|
37
|
+
public static getInstance(): LicenseValidator {
|
|
38
|
+
if (!LicenseValidator.instance) {
|
|
39
|
+
LicenseValidator.instance = new LicenseValidator();
|
|
40
|
+
}
|
|
41
|
+
return LicenseValidator.instance;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Invalidate local validation cache for a license key
|
|
46
|
+
*/
|
|
47
|
+
public purgeCache(licenseKey?: string): void {
|
|
48
|
+
if (licenseKey) {
|
|
49
|
+
this.cache.delete(licenseKey);
|
|
50
|
+
} else {
|
|
51
|
+
this.cache.clear();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate license status and SDK version against Retrivora License Service
|
|
57
|
+
*/
|
|
58
|
+
public async validate(request: LicenseValidationRequest): Promise<LicenseValidationResponse> {
|
|
59
|
+
const {
|
|
60
|
+
licenseKey,
|
|
61
|
+
projectId,
|
|
62
|
+
sdkVersion = SDK_VERSION,
|
|
63
|
+
sdk = '@retrivora-ai/rag-engine',
|
|
64
|
+
platform = typeof window !== 'undefined' ? 'browser' : 'node',
|
|
65
|
+
retrivoraApiBase,
|
|
66
|
+
headers: customHeaders,
|
|
67
|
+
} = request;
|
|
68
|
+
|
|
69
|
+
const getHeader = (name: string): string | undefined => {
|
|
70
|
+
if (!customHeaders) return undefined;
|
|
71
|
+
const target = name.toLowerCase();
|
|
72
|
+
for (const [k, v] of Object.entries(customHeaders)) {
|
|
73
|
+
if (k.toLowerCase() === target) return v;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const effectiveLicenseKey =
|
|
79
|
+
licenseKey ||
|
|
80
|
+
getHeader('x-license-key') ||
|
|
81
|
+
getHeader('authorization')?.replace(/^Bearer\s+/i, '') ||
|
|
82
|
+
(typeof process !== 'undefined'
|
|
83
|
+
? (process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY)
|
|
84
|
+
: '') ||
|
|
85
|
+
'';
|
|
86
|
+
|
|
87
|
+
if (!effectiveLicenseKey) {
|
|
88
|
+
this.purgeCache(effectiveLicenseKey);
|
|
89
|
+
throw new LicenseValidationError('Missing RETRIVORA_LICENSE_KEY in request.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 1. Check successful 5-minute cache
|
|
93
|
+
const cached = this.cache.get(effectiveLicenseKey);
|
|
94
|
+
if (cached) {
|
|
95
|
+
const isFresh = Date.now() - cached.timestamp < LicenseValidator.SUCCESS_CACHE_TTL_MS;
|
|
96
|
+
if (isFresh && cached.response.valid && cached.response.licenseStatus === 'ACTIVE' && !cached.response.forceUpgrade) {
|
|
97
|
+
return cached.response;
|
|
98
|
+
} else {
|
|
99
|
+
this.cache.delete(effectiveLicenseKey);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. Fetch live validation from Retrivora backend API
|
|
104
|
+
const base = retrivoraApiBase || (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_RETRIVORA_API_BASE || process.env.RETRIVORA_API_BASE : '') || '/api/retrivora';
|
|
105
|
+
const validateUrl = `${base.replace(/\/$/, '')}/v1/license/validate`;
|
|
106
|
+
|
|
107
|
+
let res: Response | null = null;
|
|
108
|
+
try {
|
|
109
|
+
res = await fetch(validateUrl, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
cache: 'no-store',
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
'x-license-key': effectiveLicenseKey,
|
|
115
|
+
'x-sdk-version': sdkVersion,
|
|
116
|
+
...(customHeaders || {}),
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
licenseKey,
|
|
120
|
+
projectId,
|
|
121
|
+
sdkVersion,
|
|
122
|
+
sdk,
|
|
123
|
+
platform,
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
} catch (err: any) {
|
|
127
|
+
// Fallback: If network error occurs, verify locally via RSA LicenseVerifier
|
|
128
|
+
try {
|
|
129
|
+
const payload = LicenseVerifier.verify(licenseKey, projectId || 'my-rag-app');
|
|
130
|
+
const fallbackResp: LicenseValidationResponse = {
|
|
131
|
+
valid: true,
|
|
132
|
+
licenseStatus: 'ACTIVE',
|
|
133
|
+
minimumSupportedVersion: '2.1.0',
|
|
134
|
+
latestVersion: SDK_VERSION,
|
|
135
|
+
forceUpgrade: false,
|
|
136
|
+
expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1000).toISOString() : null,
|
|
137
|
+
message: 'Local license verification active.',
|
|
138
|
+
};
|
|
139
|
+
this.cache.set(licenseKey, { response: fallbackResp, timestamp: Date.now() });
|
|
140
|
+
return fallbackResp;
|
|
141
|
+
} catch {
|
|
142
|
+
this.purgeCache(licenseKey);
|
|
143
|
+
throw new LicenseValidationError(`License validation request failed: ${err?.message || 'Network error'}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (res && !res.ok) {
|
|
148
|
+
// Attempt local verification fallback if API route returned 404 or non-OK response
|
|
149
|
+
try {
|
|
150
|
+
const payload = LicenseVerifier.verify(licenseKey, projectId || 'my-rag-app');
|
|
151
|
+
const fallbackResp: LicenseValidationResponse = {
|
|
152
|
+
valid: true,
|
|
153
|
+
licenseStatus: 'ACTIVE',
|
|
154
|
+
minimumSupportedVersion: '2.1.0',
|
|
155
|
+
latestVersion: SDK_VERSION,
|
|
156
|
+
forceUpgrade: false,
|
|
157
|
+
expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1000).toISOString() : null,
|
|
158
|
+
message: 'Local license verification active.',
|
|
159
|
+
};
|
|
160
|
+
this.cache.set(licenseKey, { response: fallbackResp, timestamp: Date.now() });
|
|
161
|
+
return fallbackResp;
|
|
162
|
+
} catch { /* proceed to parse error response */ }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const data: LicenseValidationResponse = await res!.json().catch(() => ({
|
|
166
|
+
valid: false,
|
|
167
|
+
licenseStatus: 'REVOKED' as const,
|
|
168
|
+
minimumSupportedVersion: '2.1.0',
|
|
169
|
+
latestVersion: SDK_VERSION,
|
|
170
|
+
forceUpgrade: false,
|
|
171
|
+
expiresAt: null,
|
|
172
|
+
message: 'Failed to parse license validation response.',
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
// 3. Negative validation responses immediately purge any existing cache
|
|
176
|
+
if (!res.ok || !data.valid || data.licenseStatus !== 'ACTIVE' || data.forceUpgrade) {
|
|
177
|
+
this.purgeCache(licenseKey);
|
|
178
|
+
|
|
179
|
+
if (data.forceUpgrade || res.status === 426) {
|
|
180
|
+
throw new SDKVersionUnsupportedError(
|
|
181
|
+
data.message || `This SDK version (${sdkVersion}) is no longer supported. Upgrade to version ${data.latestVersion || '2.2.6'} or later.`,
|
|
182
|
+
data
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const errMsg = data.message || `License validation failed with status: ${data.licenseStatus}. Access denied.`;
|
|
187
|
+
throw new LicenseValidationError(errMsg, data);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 4. Cache SUCCESSFUL validation only (TTL = 5 minutes)
|
|
191
|
+
this.cache.set(licenseKey, {
|
|
192
|
+
response: data,
|
|
193
|
+
timestamp: Date.now(),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return data;
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/exceptions/index.ts
CHANGED
|
@@ -8,7 +8,9 @@ export type RetrivoraErrorCode =
|
|
|
8
8
|
| 'RETRIEVAL_FAILED'
|
|
9
9
|
| 'RATE_LIMITED'
|
|
10
10
|
| 'CONFIGURATION_ERROR'
|
|
11
|
-
| 'AUTHENTICATION_ERROR'
|
|
11
|
+
| 'AUTHENTICATION_ERROR'
|
|
12
|
+
| 'SDK_VERSION_UNSUPPORTED'
|
|
13
|
+
| 'LICENSE_VALIDATION_ERROR';
|
|
12
14
|
|
|
13
15
|
export class RetrivoraError extends Error {
|
|
14
16
|
readonly code: RetrivoraErrorCode;
|
|
@@ -58,6 +60,18 @@ export class AuthenticationException extends RetrivoraError {
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
export class SDKVersionUnsupportedError extends RetrivoraError {
|
|
64
|
+
constructor(message = 'This SDK version is no longer supported.', details?: unknown) {
|
|
65
|
+
super(message, 'SDK_VERSION_UNSUPPORTED', details);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class LicenseValidationError extends RetrivoraError {
|
|
70
|
+
constructor(message = 'License validation failed.', details?: unknown) {
|
|
71
|
+
super(message, 'LICENSE_VALIDATION_ERROR', details);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
/**
|
|
62
76
|
* Wraps any unknown error into an appropriate RetrivoraError subclass.
|
|
63
77
|
*/
|
|
@@ -80,14 +94,20 @@ export function wrapError(
|
|
|
80
94
|
return new RateLimitException(message, err);
|
|
81
95
|
}
|
|
82
96
|
|
|
83
|
-
// 2. Authentication Recognition
|
|
97
|
+
// 2. Authentication / License Recognition
|
|
84
98
|
if (
|
|
85
99
|
status === 401 ||
|
|
86
100
|
status === 403 ||
|
|
87
|
-
/unauthorized|auth|api[- ]?key/i.test(message) ||
|
|
88
|
-
code === 'INVALID_API_KEY'
|
|
101
|
+
/unauthorized|auth|license|revoked|terminated|api[- ]?key/i.test(message) ||
|
|
102
|
+
code === 'INVALID_API_KEY' ||
|
|
103
|
+
code === 'LICENSE_DELETED' ||
|
|
104
|
+
code === 'LICENSE_INACTIVE'
|
|
89
105
|
) {
|
|
90
|
-
|
|
106
|
+
const isLicenseError = /403|license|revoked|terminated/i.test(message) || status === 403;
|
|
107
|
+
const cleanMsg = isLicenseError
|
|
108
|
+
? 'Your Retrivora license key has been terminated, suspended, or revoked. Access denied.'
|
|
109
|
+
: message;
|
|
110
|
+
return new AuthenticationException(cleanMsg, err);
|
|
91
111
|
}
|
|
92
112
|
|
|
93
113
|
// Fallback Mapping based on defaultCode
|
package/src/handlers/index.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { ChatMessage, ChatResponse } from '../types';
|
|
|
5
5
|
import { DocumentParser } from '../utils/DocumentParser';
|
|
6
6
|
import { UITransformer } from '../utils/UITransformer';
|
|
7
7
|
import { DatabaseStorage, HistoryMessage } from '../core/DatabaseStorage';
|
|
8
|
+
import { LicenseVerifier } from '../core/LicenseVerifier';
|
|
9
|
+
import { SDK_VERSION } from '../version';
|
|
8
10
|
|
|
9
11
|
export interface HandlerOptions {
|
|
10
12
|
onAuthorize?: (req: NextRequest) => boolean | Promise<boolean> | Response | Promise<Response>;
|
|
@@ -147,8 +149,6 @@ function getOrCreatePlugin(configOrPlugin: Partial<RagConfig> | VectorPlugin | u
|
|
|
147
149
|
|
|
148
150
|
// ─── Telemetry Reporter Helper ───────────────────────────────────────────────
|
|
149
151
|
|
|
150
|
-
import { SDK_VERSION } from '../version';
|
|
151
|
-
|
|
152
152
|
/**
|
|
153
153
|
* Asynchronously sends telemetry payload to telemetryUrl when telemetry is enabled
|
|
154
154
|
* or when a licenseKey is set. Non-blocking.
|
|
@@ -492,8 +492,18 @@ export function createStreamHandler(
|
|
|
492
492
|
}
|
|
493
493
|
} catch (streamError) {
|
|
494
494
|
if (isActive) {
|
|
495
|
-
|
|
495
|
+
let errorMessage =
|
|
496
496
|
streamError instanceof Error ? streamError.message : String(streamError);
|
|
497
|
+
|
|
498
|
+
if (
|
|
499
|
+
errorMessage.includes('403') ||
|
|
500
|
+
errorMessage.toLowerCase().includes('license') ||
|
|
501
|
+
errorMessage.toLowerCase().includes('revoked') ||
|
|
502
|
+
errorMessage.toLowerCase().includes('terminated')
|
|
503
|
+
) {
|
|
504
|
+
errorMessage = 'Your Retrivora license key has been terminated, suspended, or revoked. Access denied.';
|
|
505
|
+
}
|
|
506
|
+
|
|
497
507
|
console.error('[createStreamHandler] Stream error:', streamError);
|
|
498
508
|
reportTelemetry(req, plugin, 'QUERY_GENERATION', 'error', errorMessage);
|
|
499
509
|
try {
|
|
@@ -586,6 +596,64 @@ export function createHealthHandler(
|
|
|
586
596
|
};
|
|
587
597
|
}
|
|
588
598
|
|
|
599
|
+
/**
|
|
600
|
+
* createLicenseHandler — factory for the license validation endpoint (/v1/license/validate).
|
|
601
|
+
*/
|
|
602
|
+
export function createLicenseHandler(
|
|
603
|
+
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
604
|
+
options?: HandlerOptions
|
|
605
|
+
) {
|
|
606
|
+
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
607
|
+
const onAuthorize = options?.onAuthorize;
|
|
608
|
+
|
|
609
|
+
return async function POST(req: NextRequest) {
|
|
610
|
+
if (req) {
|
|
611
|
+
const authResult = await checkAuth(req, onAuthorize);
|
|
612
|
+
if (authResult) return authResult as any;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
const body = await req.json().catch(() => ({}));
|
|
617
|
+
const config = plugin.getConfig();
|
|
618
|
+
const licenseKey =
|
|
619
|
+
req.headers.get('x-license-key') ||
|
|
620
|
+
body?.licenseKey ||
|
|
621
|
+
req.headers.get('authorization')?.replace(/^Bearer\s+/i, '') ||
|
|
622
|
+
config.licenseKey ||
|
|
623
|
+
process.env.RETRIVORA_LICENSE_KEY ||
|
|
624
|
+
process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY ||
|
|
625
|
+
'';
|
|
626
|
+
|
|
627
|
+
const projectId = body?.projectId || config.projectId || 'my-rag-app';
|
|
628
|
+
|
|
629
|
+
const payload = LicenseVerifier.verify(licenseKey, projectId);
|
|
630
|
+
|
|
631
|
+
return NextResponse.json({
|
|
632
|
+
valid: true,
|
|
633
|
+
licenseStatus: 'ACTIVE',
|
|
634
|
+
minimumSupportedVersion: '2.1.0',
|
|
635
|
+
latestVersion: SDK_VERSION,
|
|
636
|
+
forceUpgrade: false,
|
|
637
|
+
expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1000).toISOString() : null,
|
|
638
|
+
message: 'License key is active and valid.',
|
|
639
|
+
});
|
|
640
|
+
} catch (err: any) {
|
|
641
|
+
return NextResponse.json(
|
|
642
|
+
{
|
|
643
|
+
valid: false,
|
|
644
|
+
licenseStatus: 'REVOKED',
|
|
645
|
+
minimumSupportedVersion: '2.1.0',
|
|
646
|
+
latestVersion: SDK_VERSION,
|
|
647
|
+
forceUpgrade: false,
|
|
648
|
+
expiresAt: null,
|
|
649
|
+
message: err?.message || 'License validation failed.',
|
|
650
|
+
},
|
|
651
|
+
{ status: 400 }
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
589
657
|
/**
|
|
590
658
|
* createUploadHandler — factory for the file upload ingestion endpoint.
|
|
591
659
|
*/
|
|
@@ -922,6 +990,7 @@ export function createRagHandler(
|
|
|
922
990
|
const historyHandler = createHistoryHandler(plugin, { onAuthorize });
|
|
923
991
|
const feedbackHandler = createFeedbackHandler(plugin, { onAuthorize });
|
|
924
992
|
const sessionsHandler = createSessionsHandler(plugin, { onAuthorize });
|
|
993
|
+
const licenseHandler = createLicenseHandler(plugin, { onAuthorize });
|
|
925
994
|
|
|
926
995
|
async function routePostRequest(req: any, segment: string) {
|
|
927
996
|
switch (segment) {
|
|
@@ -940,6 +1009,9 @@ export function createRagHandler(
|
|
|
940
1009
|
return historyHandler(req);
|
|
941
1010
|
case 'feedback':
|
|
942
1011
|
return feedbackHandler(req);
|
|
1012
|
+
case 'license/validate':
|
|
1013
|
+
case 'v1/license/validate':
|
|
1014
|
+
return licenseHandler(req);
|
|
943
1015
|
default:
|
|
944
1016
|
return NextResponse.json({ error: `Not Found: POST segment "${segment}" not supported.` }, { status: 404 });
|
|
945
1017
|
}
|
|
@@ -973,6 +1045,7 @@ export function createRagHandler(
|
|
|
973
1045
|
try {
|
|
974
1046
|
const url = new URL(req.url);
|
|
975
1047
|
const pathname = url.pathname;
|
|
1048
|
+
if (pathname.includes('/license/validate')) return 'v1/license/validate';
|
|
976
1049
|
if (pathname.endsWith('/upload')) return 'upload';
|
|
977
1050
|
if (pathname.endsWith('/chat') || pathname.endsWith('/chat-sync')) return 'chat';
|
|
978
1051
|
if (pathname.endsWith('/health')) return 'health';
|
package/src/hooks/useRagChat.ts
CHANGED
|
@@ -129,8 +129,27 @@ export function useRagChat(
|
|
|
129
129
|
userMessageId: userMsgId,
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
const
|
|
132
|
+
const getHeader = (headersObj: Record<string, string> | undefined, name: string) => {
|
|
133
|
+
if (!headersObj) return undefined;
|
|
134
|
+
const target = name.toLowerCase();
|
|
135
|
+
for (const [k, v] of Object.entries(headersObj)) {
|
|
136
|
+
if (k.toLowerCase() === target) return v;
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const resolvedLicenseKey =
|
|
142
|
+
options.licenseKey ||
|
|
143
|
+
getHeader(options.headers, 'x-license-key') ||
|
|
144
|
+
getHeader(options.headers, 'authorization')?.replace(/^Bearer\s+/i, '') ||
|
|
145
|
+
(typeof process !== 'undefined'
|
|
146
|
+
? (process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY)
|
|
147
|
+
: '') ||
|
|
148
|
+
'';
|
|
149
|
+
|
|
150
|
+
const fetchHeaders: Record<string, string> = {
|
|
133
151
|
'Content-Type': 'application/json',
|
|
152
|
+
...(resolvedLicenseKey ? { 'x-license-key': resolvedLicenseKey } : {}),
|
|
134
153
|
...(options.headers || {})
|
|
135
154
|
};
|
|
136
155
|
|
|
@@ -292,7 +311,15 @@ export function useRagChat(
|
|
|
292
311
|
console.log('[useRagChat] Streaming stopped by user.');
|
|
293
312
|
return;
|
|
294
313
|
}
|
|
295
|
-
|
|
314
|
+
let msg = err instanceof Error ? err.message : 'Something went wrong. Please try again.';
|
|
315
|
+
if (
|
|
316
|
+
msg.includes('403') ||
|
|
317
|
+
msg.toLowerCase().includes('license') ||
|
|
318
|
+
msg.toLowerCase().includes('revoked') ||
|
|
319
|
+
msg.toLowerCase().includes('terminated')
|
|
320
|
+
) {
|
|
321
|
+
msg = 'Your Retrivora license key has been terminated, suspended, or revoked. Access denied.';
|
|
322
|
+
}
|
|
296
323
|
setError(msg);
|
|
297
324
|
onError?.(msg);
|
|
298
325
|
} finally {
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,8 @@ export type { ILLMProvider, EmbedOptions } from './llm/ILLMProvider';
|
|
|
63
63
|
export type { Chunk, ChunkOptions } from './rag/DocumentChunker';
|
|
64
64
|
export type { Retrivora } from './core/Retrivora';
|
|
65
65
|
export type { RetrivoraErrorCode } from './exceptions';
|
|
66
|
+
export { LicenseValidator } from './core/LicenseValidator';
|
|
67
|
+
export type { LicenseValidationRequest, LicenseValidationResponse } from './core/LicenseValidator';
|
|
66
68
|
export {
|
|
67
69
|
RetrivoraError,
|
|
68
70
|
ProviderNotFoundException,
|
|
@@ -71,6 +73,8 @@ export {
|
|
|
71
73
|
RateLimitException,
|
|
72
74
|
ConfigurationException,
|
|
73
75
|
AuthenticationException,
|
|
76
|
+
SDKVersionUnsupportedError,
|
|
77
|
+
LicenseValidationError,
|
|
74
78
|
} from './exceptions';
|
|
75
79
|
export type {
|
|
76
80
|
VectorMatch,
|
package/src/server.ts
CHANGED
|
@@ -90,6 +90,10 @@ export {
|
|
|
90
90
|
sseErrorFrame,
|
|
91
91
|
} from './handlers';
|
|
92
92
|
|
|
93
|
+
// ── License Validator ─────────────────────────────────────────
|
|
94
|
+
export { LicenseValidator } from './core/LicenseValidator';
|
|
95
|
+
export type { LicenseValidationRequest, LicenseValidationResponse } from './core/LicenseValidator';
|
|
96
|
+
|
|
93
97
|
// ── Exceptions ────────────────────────────────────────────────
|
|
94
98
|
export type { RetrivoraErrorCode } from './exceptions';
|
|
95
99
|
export {
|
|
@@ -100,6 +104,8 @@ export {
|
|
|
100
104
|
RateLimitException,
|
|
101
105
|
ConfigurationException,
|
|
102
106
|
AuthenticationException,
|
|
107
|
+
SDKVersionUnsupportedError,
|
|
108
|
+
LicenseValidationError,
|
|
103
109
|
wrapError,
|
|
104
110
|
} from './exceptions';
|
|
105
111
|
|
package/src/types/chat.ts
CHANGED
|
@@ -57,6 +57,8 @@ export interface UseRagChatOptions {
|
|
|
57
57
|
onError?: (error: string) => void;
|
|
58
58
|
/** Optional custom headers to send with the chat request */
|
|
59
59
|
headers?: Record<string, string>;
|
|
60
|
+
/** Optional Retrivora license key override */
|
|
61
|
+
licenseKey?: string;
|
|
60
62
|
/** Session ID for history and feedback tracking (default: 'default') */
|
|
61
63
|
sessionId?: string;
|
|
62
64
|
}
|