@retrivora-ai/rag-engine 2.2.7 → 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/server.js CHANGED
@@ -4823,7 +4823,7 @@ var ConfigValidator = class {
4823
4823
  // package.json
4824
4824
  var package_default = {
4825
4825
  name: "@retrivora-ai/rag-engine",
4826
- version: "2.2.7",
4826
+ version: "2.2.8",
4827
4827
  description: "Retrivora AI is a plug-and-play AI engine for RAG chat experiences \u2014 generic vector DB + LLM provider, embeddable or standalone.",
4828
4828
  author: "Abhinav Alkuchi",
4829
4829
  license: "UNLICENSED",
@@ -11012,6 +11012,46 @@ function createHealthHandler(configOrPlugin, options) {
11012
11012
  }
11013
11013
  };
11014
11014
  }
11015
+ function createLicenseHandler(configOrPlugin, options) {
11016
+ const plugin = getOrCreatePlugin(configOrPlugin);
11017
+ const onAuthorize = options == null ? void 0 : options.onAuthorize;
11018
+ return async function POST(req) {
11019
+ var _a2;
11020
+ if (req) {
11021
+ const authResult = await checkAuth(req, onAuthorize);
11022
+ if (authResult) return authResult;
11023
+ }
11024
+ try {
11025
+ const body = await req.json().catch(() => ({}));
11026
+ const config = plugin.getConfig();
11027
+ const licenseKey = req.headers.get("x-license-key") || (body == null ? void 0 : body.licenseKey) || ((_a2 = req.headers.get("authorization")) == null ? void 0 : _a2.replace(/^Bearer\s+/i, "")) || config.licenseKey || process.env.RETRIVORA_LICENSE_KEY || process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || "";
11028
+ const projectId = (body == null ? void 0 : body.projectId) || config.projectId || "my-rag-app";
11029
+ const payload = LicenseVerifier.verify(licenseKey, projectId);
11030
+ return import_server.NextResponse.json({
11031
+ valid: true,
11032
+ licenseStatus: "ACTIVE",
11033
+ minimumSupportedVersion: "2.1.0",
11034
+ latestVersion: SDK_VERSION,
11035
+ forceUpgrade: false,
11036
+ expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1e3).toISOString() : null,
11037
+ message: "License key is active and valid."
11038
+ });
11039
+ } catch (err) {
11040
+ return import_server.NextResponse.json(
11041
+ {
11042
+ valid: false,
11043
+ licenseStatus: "REVOKED",
11044
+ minimumSupportedVersion: "2.1.0",
11045
+ latestVersion: SDK_VERSION,
11046
+ forceUpgrade: false,
11047
+ expiresAt: null,
11048
+ message: (err == null ? void 0 : err.message) || "License validation failed."
11049
+ },
11050
+ { status: 400 }
11051
+ );
11052
+ }
11053
+ };
11054
+ }
11015
11055
  function createUploadHandler(configOrPlugin, options) {
11016
11056
  const plugin = getOrCreatePlugin(configOrPlugin);
11017
11057
  const onAuthorize = options == null ? void 0 : options.onAuthorize;
@@ -11263,6 +11303,7 @@ function createRagHandler(configOrPlugin, options) {
11263
11303
  const historyHandler = createHistoryHandler(plugin, { onAuthorize });
11264
11304
  const feedbackHandler = createFeedbackHandler(plugin, { onAuthorize });
11265
11305
  const sessionsHandler = createSessionsHandler(plugin, { onAuthorize });
11306
+ const licenseHandler = createLicenseHandler(plugin, { onAuthorize });
11266
11307
  async function routePostRequest(req, segment) {
11267
11308
  switch (segment) {
11268
11309
  case "chat":
@@ -11280,6 +11321,9 @@ function createRagHandler(configOrPlugin, options) {
11280
11321
  return historyHandler(req);
11281
11322
  case "feedback":
11282
11323
  return feedbackHandler(req);
11324
+ case "license/validate":
11325
+ case "v1/license/validate":
11326
+ return licenseHandler(req);
11283
11327
  default:
11284
11328
  return import_server.NextResponse.json({ error: `Not Found: POST segment "${segment}" not supported.` }, { status: 404 });
11285
11329
  }
@@ -11309,6 +11353,7 @@ function createRagHandler(configOrPlugin, options) {
11309
11353
  try {
11310
11354
  const url = new URL(req.url);
11311
11355
  const pathname = url.pathname;
11356
+ if (pathname.includes("/license/validate")) return "v1/license/validate";
11312
11357
  if (pathname.endsWith("/upload")) return "upload";
11313
11358
  if (pathname.endsWith("/chat") || pathname.endsWith("/chat-sync")) return "chat";
11314
11359
  if (pathname.endsWith("/health")) return "health";
@@ -11370,6 +11415,7 @@ var _LicenseValidator = class _LicenseValidator {
11370
11415
  * Validate license status and SDK version against Retrivora License Service
11371
11416
  */
11372
11417
  async validate(request) {
11418
+ var _a2;
11373
11419
  const {
11374
11420
  licenseKey,
11375
11421
  projectId,
@@ -11379,29 +11425,38 @@ var _LicenseValidator = class _LicenseValidator {
11379
11425
  retrivoraApiBase,
11380
11426
  headers: customHeaders
11381
11427
  } = request;
11382
- if (!licenseKey) {
11383
- this.purgeCache(licenseKey);
11428
+ const getHeader = (name) => {
11429
+ if (!customHeaders) return void 0;
11430
+ const target = name.toLowerCase();
11431
+ for (const [k, v] of Object.entries(customHeaders)) {
11432
+ if (k.toLowerCase() === target) return v;
11433
+ }
11434
+ return void 0;
11435
+ };
11436
+ const effectiveLicenseKey = licenseKey || getHeader("x-license-key") || ((_a2 = getHeader("authorization")) == null ? void 0 : _a2.replace(/^Bearer\s+/i, "")) || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY : "") || "";
11437
+ if (!effectiveLicenseKey) {
11438
+ this.purgeCache(effectiveLicenseKey);
11384
11439
  throw new LicenseValidationError("Missing RETRIVORA_LICENSE_KEY in request.");
11385
11440
  }
11386
- const cached = this.cache.get(licenseKey);
11441
+ const cached = this.cache.get(effectiveLicenseKey);
11387
11442
  if (cached) {
11388
11443
  const isFresh = Date.now() - cached.timestamp < _LicenseValidator.SUCCESS_CACHE_TTL_MS;
11389
11444
  if (isFresh && cached.response.valid && cached.response.licenseStatus === "ACTIVE" && !cached.response.forceUpgrade) {
11390
11445
  return cached.response;
11391
11446
  } else {
11392
- this.cache.delete(licenseKey);
11447
+ this.cache.delete(effectiveLicenseKey);
11393
11448
  }
11394
11449
  }
11395
11450
  const base = retrivoraApiBase || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_RETRIVORA_API_BASE || process.env.RETRIVORA_API_BASE : "") || "/api/retrivora";
11396
11451
  const validateUrl = `${base.replace(/\/$/, "")}/v1/license/validate`;
11397
- let res;
11452
+ let res = null;
11398
11453
  try {
11399
11454
  res = await fetch(validateUrl, {
11400
11455
  method: "POST",
11401
11456
  cache: "no-store",
11402
11457
  headers: __spreadValues({
11403
11458
  "Content-Type": "application/json",
11404
- "x-license-key": licenseKey,
11459
+ "x-license-key": effectiveLicenseKey,
11405
11460
  "x-sdk-version": sdkVersion
11406
11461
  }, customHeaders || {}),
11407
11462
  body: JSON.stringify({
@@ -11413,8 +11468,40 @@ var _LicenseValidator = class _LicenseValidator {
11413
11468
  })
11414
11469
  });
11415
11470
  } catch (err) {
11416
- this.purgeCache(licenseKey);
11417
- throw new LicenseValidationError(`License validation request failed: ${(err == null ? void 0 : err.message) || "Network error"}`);
11471
+ try {
11472
+ const payload = LicenseVerifier.verify(licenseKey, projectId || "my-rag-app");
11473
+ const fallbackResp = {
11474
+ valid: true,
11475
+ licenseStatus: "ACTIVE",
11476
+ minimumSupportedVersion: "2.1.0",
11477
+ latestVersion: SDK_VERSION,
11478
+ forceUpgrade: false,
11479
+ expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1e3).toISOString() : null,
11480
+ message: "Local license verification active."
11481
+ };
11482
+ this.cache.set(licenseKey, { response: fallbackResp, timestamp: Date.now() });
11483
+ return fallbackResp;
11484
+ } catch (e) {
11485
+ this.purgeCache(licenseKey);
11486
+ throw new LicenseValidationError(`License validation request failed: ${(err == null ? void 0 : err.message) || "Network error"}`);
11487
+ }
11488
+ }
11489
+ if (res && !res.ok) {
11490
+ try {
11491
+ const payload = LicenseVerifier.verify(licenseKey, projectId || "my-rag-app");
11492
+ const fallbackResp = {
11493
+ valid: true,
11494
+ licenseStatus: "ACTIVE",
11495
+ minimumSupportedVersion: "2.1.0",
11496
+ latestVersion: SDK_VERSION,
11497
+ forceUpgrade: false,
11498
+ expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1e3).toISOString() : null,
11499
+ message: "Local license verification active."
11500
+ };
11501
+ this.cache.set(licenseKey, { response: fallbackResp, timestamp: Date.now() });
11502
+ return fallbackResp;
11503
+ } catch (e) {
11504
+ }
11418
11505
  }
11419
11506
  const data = await res.json().catch(() => ({
11420
11507
  valid: false,
package/dist/server.mjs CHANGED
@@ -4723,7 +4723,7 @@ var ConfigValidator = class {
4723
4723
  // package.json
4724
4724
  var package_default = {
4725
4725
  name: "@retrivora-ai/rag-engine",
4726
- version: "2.2.7",
4726
+ version: "2.2.8",
4727
4727
  description: "Retrivora AI is a plug-and-play AI engine for RAG chat experiences \u2014 generic vector DB + LLM provider, embeddable or standalone.",
4728
4728
  author: "Abhinav Alkuchi",
4729
4729
  license: "UNLICENSED",
@@ -10912,6 +10912,46 @@ function createHealthHandler(configOrPlugin, options) {
10912
10912
  }
10913
10913
  };
10914
10914
  }
10915
+ function createLicenseHandler(configOrPlugin, options) {
10916
+ const plugin = getOrCreatePlugin(configOrPlugin);
10917
+ const onAuthorize = options == null ? void 0 : options.onAuthorize;
10918
+ return async function POST(req) {
10919
+ var _a2;
10920
+ if (req) {
10921
+ const authResult = await checkAuth(req, onAuthorize);
10922
+ if (authResult) return authResult;
10923
+ }
10924
+ try {
10925
+ const body = await req.json().catch(() => ({}));
10926
+ const config = plugin.getConfig();
10927
+ const licenseKey = req.headers.get("x-license-key") || (body == null ? void 0 : body.licenseKey) || ((_a2 = req.headers.get("authorization")) == null ? void 0 : _a2.replace(/^Bearer\s+/i, "")) || config.licenseKey || process.env.RETRIVORA_LICENSE_KEY || process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || "";
10928
+ const projectId = (body == null ? void 0 : body.projectId) || config.projectId || "my-rag-app";
10929
+ const payload = LicenseVerifier.verify(licenseKey, projectId);
10930
+ return NextResponse.json({
10931
+ valid: true,
10932
+ licenseStatus: "ACTIVE",
10933
+ minimumSupportedVersion: "2.1.0",
10934
+ latestVersion: SDK_VERSION,
10935
+ forceUpgrade: false,
10936
+ expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1e3).toISOString() : null,
10937
+ message: "License key is active and valid."
10938
+ });
10939
+ } catch (err) {
10940
+ return NextResponse.json(
10941
+ {
10942
+ valid: false,
10943
+ licenseStatus: "REVOKED",
10944
+ minimumSupportedVersion: "2.1.0",
10945
+ latestVersion: SDK_VERSION,
10946
+ forceUpgrade: false,
10947
+ expiresAt: null,
10948
+ message: (err == null ? void 0 : err.message) || "License validation failed."
10949
+ },
10950
+ { status: 400 }
10951
+ );
10952
+ }
10953
+ };
10954
+ }
10915
10955
  function createUploadHandler(configOrPlugin, options) {
10916
10956
  const plugin = getOrCreatePlugin(configOrPlugin);
10917
10957
  const onAuthorize = options == null ? void 0 : options.onAuthorize;
@@ -11163,6 +11203,7 @@ function createRagHandler(configOrPlugin, options) {
11163
11203
  const historyHandler = createHistoryHandler(plugin, { onAuthorize });
11164
11204
  const feedbackHandler = createFeedbackHandler(plugin, { onAuthorize });
11165
11205
  const sessionsHandler = createSessionsHandler(plugin, { onAuthorize });
11206
+ const licenseHandler = createLicenseHandler(plugin, { onAuthorize });
11166
11207
  async function routePostRequest(req, segment) {
11167
11208
  switch (segment) {
11168
11209
  case "chat":
@@ -11180,6 +11221,9 @@ function createRagHandler(configOrPlugin, options) {
11180
11221
  return historyHandler(req);
11181
11222
  case "feedback":
11182
11223
  return feedbackHandler(req);
11224
+ case "license/validate":
11225
+ case "v1/license/validate":
11226
+ return licenseHandler(req);
11183
11227
  default:
11184
11228
  return NextResponse.json({ error: `Not Found: POST segment "${segment}" not supported.` }, { status: 404 });
11185
11229
  }
@@ -11209,6 +11253,7 @@ function createRagHandler(configOrPlugin, options) {
11209
11253
  try {
11210
11254
  const url = new URL(req.url);
11211
11255
  const pathname = url.pathname;
11256
+ if (pathname.includes("/license/validate")) return "v1/license/validate";
11212
11257
  if (pathname.endsWith("/upload")) return "upload";
11213
11258
  if (pathname.endsWith("/chat") || pathname.endsWith("/chat-sync")) return "chat";
11214
11259
  if (pathname.endsWith("/health")) return "health";
@@ -11270,6 +11315,7 @@ var _LicenseValidator = class _LicenseValidator {
11270
11315
  * Validate license status and SDK version against Retrivora License Service
11271
11316
  */
11272
11317
  async validate(request) {
11318
+ var _a2;
11273
11319
  const {
11274
11320
  licenseKey,
11275
11321
  projectId,
@@ -11279,29 +11325,38 @@ var _LicenseValidator = class _LicenseValidator {
11279
11325
  retrivoraApiBase,
11280
11326
  headers: customHeaders
11281
11327
  } = request;
11282
- if (!licenseKey) {
11283
- this.purgeCache(licenseKey);
11328
+ const getHeader = (name) => {
11329
+ if (!customHeaders) return void 0;
11330
+ const target = name.toLowerCase();
11331
+ for (const [k, v] of Object.entries(customHeaders)) {
11332
+ if (k.toLowerCase() === target) return v;
11333
+ }
11334
+ return void 0;
11335
+ };
11336
+ const effectiveLicenseKey = licenseKey || getHeader("x-license-key") || ((_a2 = getHeader("authorization")) == null ? void 0 : _a2.replace(/^Bearer\s+/i, "")) || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY : "") || "";
11337
+ if (!effectiveLicenseKey) {
11338
+ this.purgeCache(effectiveLicenseKey);
11284
11339
  throw new LicenseValidationError("Missing RETRIVORA_LICENSE_KEY in request.");
11285
11340
  }
11286
- const cached = this.cache.get(licenseKey);
11341
+ const cached = this.cache.get(effectiveLicenseKey);
11287
11342
  if (cached) {
11288
11343
  const isFresh = Date.now() - cached.timestamp < _LicenseValidator.SUCCESS_CACHE_TTL_MS;
11289
11344
  if (isFresh && cached.response.valid && cached.response.licenseStatus === "ACTIVE" && !cached.response.forceUpgrade) {
11290
11345
  return cached.response;
11291
11346
  } else {
11292
- this.cache.delete(licenseKey);
11347
+ this.cache.delete(effectiveLicenseKey);
11293
11348
  }
11294
11349
  }
11295
11350
  const base = retrivoraApiBase || (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_RETRIVORA_API_BASE || process.env.RETRIVORA_API_BASE : "") || "/api/retrivora";
11296
11351
  const validateUrl = `${base.replace(/\/$/, "")}/v1/license/validate`;
11297
- let res;
11352
+ let res = null;
11298
11353
  try {
11299
11354
  res = await fetch(validateUrl, {
11300
11355
  method: "POST",
11301
11356
  cache: "no-store",
11302
11357
  headers: __spreadValues({
11303
11358
  "Content-Type": "application/json",
11304
- "x-license-key": licenseKey,
11359
+ "x-license-key": effectiveLicenseKey,
11305
11360
  "x-sdk-version": sdkVersion
11306
11361
  }, customHeaders || {}),
11307
11362
  body: JSON.stringify({
@@ -11313,8 +11368,40 @@ var _LicenseValidator = class _LicenseValidator {
11313
11368
  })
11314
11369
  });
11315
11370
  } catch (err) {
11316
- this.purgeCache(licenseKey);
11317
- throw new LicenseValidationError(`License validation request failed: ${(err == null ? void 0 : err.message) || "Network error"}`);
11371
+ try {
11372
+ const payload = LicenseVerifier.verify(licenseKey, projectId || "my-rag-app");
11373
+ const fallbackResp = {
11374
+ valid: true,
11375
+ licenseStatus: "ACTIVE",
11376
+ minimumSupportedVersion: "2.1.0",
11377
+ latestVersion: SDK_VERSION,
11378
+ forceUpgrade: false,
11379
+ expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1e3).toISOString() : null,
11380
+ message: "Local license verification active."
11381
+ };
11382
+ this.cache.set(licenseKey, { response: fallbackResp, timestamp: Date.now() });
11383
+ return fallbackResp;
11384
+ } catch (e) {
11385
+ this.purgeCache(licenseKey);
11386
+ throw new LicenseValidationError(`License validation request failed: ${(err == null ? void 0 : err.message) || "Network error"}`);
11387
+ }
11388
+ }
11389
+ if (res && !res.ok) {
11390
+ try {
11391
+ const payload = LicenseVerifier.verify(licenseKey, projectId || "my-rag-app");
11392
+ const fallbackResp = {
11393
+ valid: true,
11394
+ licenseStatus: "ACTIVE",
11395
+ minimumSupportedVersion: "2.1.0",
11396
+ latestVersion: SDK_VERSION,
11397
+ forceUpgrade: false,
11398
+ expiresAt: payload.expiresAt ? new Date(payload.expiresAt * 1e3).toISOString() : null,
11399
+ message: "Local license verification active."
11400
+ };
11401
+ this.cache.set(licenseKey, { response: fallbackResp, timestamp: Date.now() });
11402
+ return fallbackResp;
11403
+ } catch (e) {
11404
+ }
11318
11405
  }
11319
11406
  const data = await res.json().catch(() => ({
11320
11407
  valid: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "2.2.7",
3
+ "version": "2.2.8",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "UNLICENSED",
@@ -46,7 +46,7 @@ const GEMINI_STYLES = `
46
46
  }
47
47
  `;
48
48
 
49
- export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, retrivoraApiBase, headers }: ChatWidgetProps) {
49
+ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, retrivoraApiBase, licenseKey: licenseKeyProp, headers }: ChatWidgetProps) {
50
50
  const { ui, projectId } = useConfig();
51
51
  const [isOpen, setIsOpen] = useState(false);
52
52
  const [hasUnread, setHasUnread] = useState(false);
@@ -72,9 +72,26 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, ret
72
72
  const handleOpen = async () => {
73
73
  setIsValidating(true);
74
74
  try {
75
- const licenseKey = headers?.['x-license-key'] || headers?.['authorization']?.replace(/^Bearer\s+/i, '') || '';
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;
80
+ }
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
+
76
93
  await LicenseValidator.getInstance().validate({
77
- licenseKey,
94
+ licenseKey: resolvedLicenseKey,
78
95
  projectId,
79
96
  retrivoraApiBase,
80
97
  headers,
@@ -167,6 +184,7 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, ret
167
184
  onAddToCart={onAddToCart}
168
185
  apiUrl={apiUrl}
169
186
  retrivoraApiBase={retrivoraApiBase}
187
+ licenseKey={licenseKeyProp}
170
188
  headers={headers}
171
189
  />
172
190
  {/* Pointer (Tail) */}
@@ -68,6 +68,7 @@ export function ChatWindow({
68
68
  onAddToCart,
69
69
  apiUrl,
70
70
  retrivoraApiBase,
71
+ licenseKey: licenseKeyProp,
71
72
  headers
72
73
  }: ChatWindowProps) {
73
74
  const { ui, projectId } = useConfig();
@@ -81,6 +82,7 @@ export function ChatWindow({
81
82
  namespace: projectId,
82
83
  apiUrl,
83
84
  retrivoraApiBase,
85
+ licenseKey: licenseKeyProp,
84
86
  headers,
85
87
  });
86
88
 
@@ -98,9 +100,26 @@ export function ChatWindow({
98
100
  let isMounted = true;
99
101
  async function validateSessionLicense() {
100
102
  try {
101
- const licenseKey = headers?.['x-license-key'] || headers?.['authorization']?.replace(/^Bearer\s+/i, '') || '';
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
+
102
121
  await LicenseValidator.getInstance().validate({
103
- licenseKey,
122
+ licenseKey: resolvedLicenseKey,
104
123
  projectId,
105
124
  retrivoraApiBase,
106
125
  headers,
@@ -1,5 +1,6 @@
1
1
  import { SDK_VERSION } from '../version';
2
2
  import { SDKVersionUnsupportedError, LicenseValidationError } from '../exceptions';
3
+ import { LicenseVerifier } from './LicenseVerifier';
3
4
 
4
5
  export interface LicenseValidationRequest {
5
6
  licenseKey: string;
@@ -65,19 +66,37 @@ export class LicenseValidator {
65
66
  headers: customHeaders,
66
67
  } = request;
67
68
 
68
- if (!licenseKey) {
69
- this.purgeCache(licenseKey);
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);
70
89
  throw new LicenseValidationError('Missing RETRIVORA_LICENSE_KEY in request.');
71
90
  }
72
91
 
73
92
  // 1. Check successful 5-minute cache
74
- const cached = this.cache.get(licenseKey);
93
+ const cached = this.cache.get(effectiveLicenseKey);
75
94
  if (cached) {
76
95
  const isFresh = Date.now() - cached.timestamp < LicenseValidator.SUCCESS_CACHE_TTL_MS;
77
96
  if (isFresh && cached.response.valid && cached.response.licenseStatus === 'ACTIVE' && !cached.response.forceUpgrade) {
78
97
  return cached.response;
79
98
  } else {
80
- this.cache.delete(licenseKey);
99
+ this.cache.delete(effectiveLicenseKey);
81
100
  }
82
101
  }
83
102
 
@@ -85,14 +104,14 @@ export class LicenseValidator {
85
104
  const base = retrivoraApiBase || (typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_RETRIVORA_API_BASE || process.env.RETRIVORA_API_BASE : '') || '/api/retrivora';
86
105
  const validateUrl = `${base.replace(/\/$/, '')}/v1/license/validate`;
87
106
 
88
- let res: Response;
107
+ let res: Response | null = null;
89
108
  try {
90
109
  res = await fetch(validateUrl, {
91
110
  method: 'POST',
92
111
  cache: 'no-store',
93
112
  headers: {
94
113
  'Content-Type': 'application/json',
95
- 'x-license-key': licenseKey,
114
+ 'x-license-key': effectiveLicenseKey,
96
115
  'x-sdk-version': sdkVersion,
97
116
  ...(customHeaders || {}),
98
117
  },
@@ -105,12 +124,45 @@ export class LicenseValidator {
105
124
  }),
106
125
  });
107
126
  } catch (err: any) {
108
- // Fallback: If network error occurs, do not cache and throw error
109
- this.purgeCache(licenseKey);
110
- throw new LicenseValidationError(`License validation request failed: ${err?.message || 'Network error'}`);
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 */ }
111
163
  }
112
164
 
113
- const data: LicenseValidationResponse = await res.json().catch(() => ({
165
+ const data: LicenseValidationResponse = await res!.json().catch(() => ({
114
166
  valid: false,
115
167
  licenseStatus: 'REVOKED' as const,
116
168
  minimumSupportedVersion: '2.1.0',
@@ -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.
@@ -596,6 +596,64 @@ export function createHealthHandler(
596
596
  };
597
597
  }
598
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
+
599
657
  /**
600
658
  * createUploadHandler — factory for the file upload ingestion endpoint.
601
659
  */
@@ -932,6 +990,7 @@ export function createRagHandler(
932
990
  const historyHandler = createHistoryHandler(plugin, { onAuthorize });
933
991
  const feedbackHandler = createFeedbackHandler(plugin, { onAuthorize });
934
992
  const sessionsHandler = createSessionsHandler(plugin, { onAuthorize });
993
+ const licenseHandler = createLicenseHandler(plugin, { onAuthorize });
935
994
 
936
995
  async function routePostRequest(req: any, segment: string) {
937
996
  switch (segment) {
@@ -950,6 +1009,9 @@ export function createRagHandler(
950
1009
  return historyHandler(req);
951
1010
  case 'feedback':
952
1011
  return feedbackHandler(req);
1012
+ case 'license/validate':
1013
+ case 'v1/license/validate':
1014
+ return licenseHandler(req);
953
1015
  default:
954
1016
  return NextResponse.json({ error: `Not Found: POST segment "${segment}" not supported.` }, { status: 404 });
955
1017
  }
@@ -983,6 +1045,7 @@ export function createRagHandler(
983
1045
  try {
984
1046
  const url = new URL(req.url);
985
1047
  const pathname = url.pathname;
1048
+ if (pathname.includes('/license/validate')) return 'v1/license/validate';
986
1049
  if (pathname.endsWith('/upload')) return 'upload';
987
1050
  if (pathname.endsWith('/chat') || pathname.endsWith('/chat-sync')) return 'chat';
988
1051
  if (pathname.endsWith('/health')) return 'health';