anything-llm/server/endpoints/browserExtension.js

225 lines
6.8 KiB
JavaScript
Raw Normal View History

AnythingLLM Chrome Extension (#2066) * initial commit for chrome extension * wip browser extension backend * wip frontend browser extension settings * fix typo for browserExtension route * implement verification codes + frontend panel for browser extension keys * reorganize + state management for all connection states * implement embed to workspace * add send page to anythingllm extension option + refactor * refactor connection string auth + update context menus + organize background.js into models * popup extension from main app and save if successful * fix hebrew translation misspelling * fetch custom logo inside chrome extension * delete api keys on disconnect of extension * use correct apiUrl constant in frontend + remove unneeded comments * remove upload-link endpoint and send inner text html to raw text collector endpoint * update readme * fix readme link * fix readme typo * update readme * handle deletion of browser keys with key id and DELETE endpoint * move event string to constant * remove tablename and writable fields from BrowserExtensionApiKey backend model * add border-none to all buttons and inputs for desktop compatibility * patch prisma injections * update delete endpoints to delete keys by id * remove unused prop * add button to attempt browser extension connection + remove max active keys * wip multi user mode support * multi user mode support * clean up backend + show created by in frotend browser extension page * show multi user warning message on key creation + hide context menus when no workspaces * show browser extension options to managers * small backend changes and refactors * extension cleanup * rename submodule * extension updates & docs * dev docker build --------- Co-authored-by: shatfield4 <seanhatfield5@gmail.com>
2024-08-27 23:58:47 +02:00
const { Workspace } = require("../models/workspace");
const { BrowserExtensionApiKey } = require("../models/browserExtensionApiKey");
const { Document } = require("../models/documents");
const {
validBrowserExtensionApiKey,
} = require("../utils/middleware/validBrowserExtensionApiKey");
const { CollectorApi } = require("../utils/collectorApi");
const { reqBody, multiUserMode, userFromSession } = require("../utils/http");
const { validatedRequest } = require("../utils/middleware/validatedRequest");
const {
flexUserRoleValid,
ROLES,
} = require("../utils/middleware/multiUserProtected");
const { Telemetry } = require("../models/telemetry");
function browserExtensionEndpoints(app) {
if (!app) return;
app.get(
"/browser-extension/check",
[validBrowserExtensionApiKey],
async (request, response) => {
try {
const user = await userFromSession(request, response);
const workspaces = multiUserMode(response)
? await Workspace.whereWithUser(user)
: await Workspace.where();
const apiKeyId = response.locals.apiKey.id;
response.status(200).json({
connected: true,
workspaces,
apiKeyId,
});
} catch (error) {
console.error(error);
response
.status(500)
.json({ connected: false, error: "Failed to fetch workspaces" });
}
}
);
app.delete(
"/browser-extension/disconnect",
[validBrowserExtensionApiKey],
async (_request, response) => {
try {
const apiKeyId = response.locals.apiKey.id;
const { success, error } =
await BrowserExtensionApiKey.delete(apiKeyId);
if (!success) throw new Error(error);
response.status(200).json({ success: true });
} catch (error) {
console.error(error);
response
.status(500)
.json({ error: "Failed to disconnect and revoke API key" });
}
}
);
app.get(
"/browser-extension/workspaces",
[validBrowserExtensionApiKey],
async (request, response) => {
try {
const user = await userFromSession(request, response);
const workspaces = multiUserMode(response)
? await Workspace.whereWithUser(user)
: await Workspace.where();
response.status(200).json({ workspaces });
} catch (error) {
console.error(error);
response.status(500).json({ error: "Failed to fetch workspaces" });
}
}
);
app.post(
"/browser-extension/embed-content",
[validBrowserExtensionApiKey],
async (request, response) => {
try {
const { workspaceId, textContent, metadata } = reqBody(request);
const user = await userFromSession(request, response);
const workspace = multiUserMode(response)
? await Workspace.getWithUser(user, { id: parseInt(workspaceId) })
: await Workspace.get({ id: parseInt(workspaceId) });
if (!workspace) {
response.status(404).json({ error: "Workspace not found" });
return;
}
const Collector = new CollectorApi();
const { success, reason, documents } = await Collector.processRawText(
textContent,
metadata
);
if (!success) {
response.status(500).json({ success: false, error: reason });
return;
}
const { failedToEmbed = [], errors = [] } = await Document.addDocuments(
workspace,
[documents[0].location],
user?.id
);
if (failedToEmbed.length > 0) {
response.status(500).json({ success: false, error: errors[0] });
return;
}
await Telemetry.sendTelemetry("browser_extension_embed_content");
response.status(200).json({ success: true });
} catch (error) {
console.error(error);
response.status(500).json({ error: "Failed to embed content" });
}
}
);
app.post(
"/browser-extension/upload-content",
[validBrowserExtensionApiKey],
async (request, response) => {
try {
const { textContent, metadata } = reqBody(request);
const Collector = new CollectorApi();
const { success, reason } = await Collector.processRawText(
textContent,
metadata
);
if (!success) {
response.status(500).json({ success: false, error: reason });
return;
}
await Telemetry.sendTelemetry("browser_extension_upload_content");
response.status(200).json({ success: true });
} catch (error) {
console.error(error);
response.status(500).json({ error: "Failed to embed content" });
}
}
);
// Internal endpoints for managing API keys
app.get(
"/browser-extension/api-keys",
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
async (request, response) => {
try {
const user = await userFromSession(request, response);
const apiKeys = multiUserMode(response)
? await BrowserExtensionApiKey.whereWithUser(user)
: await BrowserExtensionApiKey.where();
response.status(200).json({ success: true, apiKeys });
} catch (error) {
console.error(error);
response
.status(500)
.json({ success: false, error: "Failed to fetch API keys" });
}
}
);
app.post(
"/browser-extension/api-keys/new",
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
async (request, response) => {
try {
const user = await userFromSession(request, response);
const { apiKey, error } = await BrowserExtensionApiKey.create(
user?.id || null
);
if (error) throw new Error(error);
response.status(200).json({
apiKey: apiKey.key,
});
} catch (error) {
console.error(error);
response.status(500).json({ error: "Failed to create API key" });
}
}
);
app.delete(
"/browser-extension/api-keys/:id",
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
async (request, response) => {
try {
const { id } = request.params;
const user = await userFromSession(request, response);
if (multiUserMode(response) && user.role !== ROLES.admin) {
const apiKey = await BrowserExtensionApiKey.get({
id: parseInt(id),
user_id: user?.id,
});
if (!apiKey) {
return response.status(403).json({ error: "Unauthorized" });
}
}
const { success, error } = await BrowserExtensionApiKey.delete(id);
if (!success) throw new Error(error);
response.status(200).json({ success: true });
} catch (error) {
console.error(error);
response.status(500).json({ error: "Failed to revoke API key" });
}
}
);
}
module.exports = { browserExtensionEndpoints };