diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 3e15c4540..dc8dfbe89 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -1,4 +1,4 @@
-import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced } from "../script.js";
+import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders } from "../script.js";
import { isSubsetOf, debounce } from "./utils.js";
export {
getContext,
@@ -376,28 +376,41 @@ function addExtensionScript(name, manifest) {
return Promise.resolve();
}
-function showExtensionsDetails() {
- let html = '
Modules provided by your Extensions API:
';
- html += modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
- html += 'Available extensions:
';
- Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order).forEach(extension => {
+
+
+function showExtensionsDetails() {
+ let htmlDefault = 'Default Extensions:
';
+ let htmlExternal = 'External Extensions:
';
+
+ Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order).forEach(extension => async () =>{
const name = extension[0];
const manifest = extension[1];
const isActive = activeExtensions.has(name);
const isDisabled = extension_settings.disabledExtensions.includes(name);
+ const isExternal = name.startsWith('third-party');
const titleClass = isActive ? "extension_enabled" : isDisabled ? "extension_disabled" : "extension_missing";
- const iconString = isActive ? "\u2705" : isDisabled ? "\u274C" : "";
+ const checkboxClass = isDisabled ? "checkbox_disabled" : "";
- let toggleElement = `Unavailable`;
- if (isActive) {
- toggleElement = `Disable`;
+ const displayName = manifest.display_name;
+ // if external, get the version from a requst to get_extension_verion
+ let displayVersion = manifest.version ? ` v${manifest.version}` : "";
+ if(isExternal) {
+ let data = await getExtensionVersion(name.replace('third-party', ''));
+ let branch = data.currentBranchName;
+ let commitHash = data.currentCommitHash;
+ displayVersion = ` v${branch}-${commitHash}`;
}
- else if (isDisabled) {
- toggleElement = `Enable`;
+
+
+ let toggleElement = ``;
+ if (isActive || isDisabled) {
+ toggleElement = ``;
}
- html += `
${iconString} ${DOMPurify.sanitize(manifest.display_name)} ${toggleElement}
`;
+
+ let updateButton = isExternal ? `` : '';
+ let extensionHtml = `
${DOMPurify.sanitize(displayName)}${displayVersion} ${toggleElement}${updateButton}
`;
if (isActive) {
if (Array.isArray(manifest.optional)) {
@@ -405,7 +418,7 @@ function showExtensionsDetails() {
modules.forEach(x => optional.delete(x));
if (optional.size > 0) {
const optionalString = DOMPurify.sanitize([...optional].join(', '));
- html += `Optional modules: ${optionalString}
`;
+ extensionHtml += `Optional modules: ${optionalString}
`;
}
}
}
@@ -413,13 +426,69 @@ function showExtensionsDetails() {
const requirements = new Set(manifest.requires);
modules.forEach(x => requirements.delete(x));
const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
- html += `Missing modules: ${requirementsString}
`
+ extensionHtml += `Missing modules: ${requirementsString}
`
+ }
+
+ // Append the HTML to the correct section
+ if (isExternal) {
+ htmlExternal += extensionHtml;
+ } else {
+ htmlDefault += extensionHtml;
}
});
+ let html = 'Modules provided by your Extensions API:
';
+ html += modules.length ? `${DOMPurify.sanitize(modules.join(', '))}
` : 'Not connected to the API!
';
+ html += htmlDefault + htmlExternal;
+
callPopup(`${html}
`, 'text');
}
+
+
+async function onUpdateClick() {
+ const extensionName = $(this).data('name');
+ try {
+ const response = await fetch('/update_extension', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ extensionName })
+ });
+
+ console.log('Response', response);
+ const data = await response.json();
+ console.log('Data', data);
+ if (data.isUpToDate) {
+ console.log('Extension is up to date');
+ toastr.success('Extension is already up to date');
+ } else {
+ console.log('Extension updated');
+ toastr.success(`Extension updated to ${data.shortCommitHash}`);
+ }
+ $(this).text(data.shortCommitHash);
+ console.log(data);
+ } catch (error) {
+ console.error('Error:', error);
+ }
+};
+
+async function getExtensionVersion(extensionName) {
+ try {
+ const response = await fetch('/get_extension_version', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ extensionName })
+ });
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error:', error);
+ }
+}
+
+
+
async function loadExtensionSettings(settings) {
if (settings.extension_settings) {
Object.assign(extension_settings, settings.extension_settings);
@@ -463,4 +532,5 @@ $(document).ready(async function () {
$("#extensions_details").on('click', showExtensionsDetails);
$(document).on('click', '.toggle_disable', onDisableExtensionClick);
$(document).on('click', '.toggle_enable', onEnableExtensionClick);
+ $(document).on('click', '.btn_update', onUpdateClick);
});
diff --git a/server.js b/server.js
index 6f7f3a9e8..f857bf9d1 100644
--- a/server.js
+++ b/server.js
@@ -2731,7 +2731,7 @@ app.get('/discover_extensions', jsonParser, function (_, response) {
if (!fs.existsSync(path.join(directories.extensions, 'third-party'))) {
return response.send(extensions);
}
-
+
const thirdPartyExtensions = fs
.readdirSync(path.join(directories.extensions, 'third-party'))
.filter(f => fs.statSync(path.join(directories.extensions, 'third-party', f)).isDirectory());
@@ -4426,3 +4426,83 @@ app.post('/get_extension', jsonParser, async (request, response) => {
return response.status(500).send(`Server Error: ${error.message}`);
}
});
+
+/**
+ * HTTP POST handler function to pull the latest updates from a given git repository
+ * based on the extension name and return the latest commit hash.
+ *
+ * @param {Object} request - HTTP Request object, expects a JSON body with an 'extensionName' property.
+ * @param {Object} response - HTTP Response object used to respond to the HTTP request.
+ *
+ * @returns {void}
+ */
+app.post('/update_extension', jsonParser, async (request, response) => {
+ if (!request.body.extensionName) {
+ return response.status(400).send('Bad Request: extensionName is required in the request body.');
+ }
+
+ try {
+ const extensionName = request.body.extensionName;
+ const extensionPath = path.join(directories.extensions, 'third-party', extensionName);
+
+ if (!fs.existsSync(extensionPath)) {
+ return response.status(404).send(`Directory does not exist at ${extensionPath}`);
+ }
+
+ const currentBranch = await git.cwd(extensionPath).branch();
+ const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ const isUpToDate = await git.cwd(extensionPath).log({
+ from: currentCommitHash,
+ to: `origin/${currentBranch.current}`,
+ });
+
+ if (isUpToDate.total === 0) {
+ await git.cwd(extensionPath).pull('origin', currentBranch.current);
+ console.log(`Extension has been updated at ${extensionPath}`);
+ } else {
+ console.log(`Extension is up to date at ${extensionPath}`);
+ }
+
+ const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ const shortCommitHash = fullCommitHash.slice(0, 7);
+
+ return response.send({ shortCommitHash, extensionPath, isUpToDate: isUpToDate.total === 0 });
+
+ } catch (error) {
+ console.log('Updating custom content failed', error);
+ return response.status(500).send(`Server Error: ${error.message}`);
+ }
+});
+
+/**
+ * Function to get current git commit hash and branch name for a given extension.
+ */
+app.post('/get_extension_version', jsonParser, async (request, response) => {
+ if (!request.body.extensionName) {
+ return response.status(400).send('Bad Request: extensionName is required in the request body.');
+ }
+
+ try {
+ const extensionName = request.body.extensionName;
+ const extensionPath = path.join(directories.extensions, 'third-party', extensionName);
+
+ if (!fs.existsSync(extensionPath)) {
+ return response.status(404).send(`Directory does not exist at ${extensionPath}`);
+ }
+
+ const currentBranch = await git.cwd(extensionPath).branch();
+ // get only the working branch
+ const currentBranchName = currentBranch.current;
+ const currentCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
+ console.log(currentBranch, currentCommitHash);
+ return response.send({ currentBranchName, currentCommitHash });
+
+ } catch (error) {
+ console.log('Getting extension version failed', error);
+ return response.status(500).send(`Server Error: ${error.message}`);
+ }
+ }
+);
+
+
+