新增调试信息

This commit is contained in:
2025-10-27 22:15:25 +08:00
parent ae62457d8c
commit 04642cb2f0
5479 changed files with 683397 additions and 3450 deletions
+12
View File
@@ -0,0 +1,12 @@
import * as a from "./rpc/extension_grpc_web_pb.js";
const client = new ExtensionHostServiceClient('http://localhost:8080');
const request = new GetHelloRequest();
export const getHello = (name) => {
request.setName(name)
client.getHello(request, {}, (err, response) => {
console.log(request.getName());
console.log(response.toObject());
});
}
getHello("D")
+82
View File
@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hiddify Extensions</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" integrity="sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
pre {
background-color: black !important; overflow: auto;
color: white!important; }
</style>
</head>
<body>
<div class="container mt-5">
<div id="connection-page" class="card p-4">
<div id="connection-before-connect" class="card-body">
<h2 class="card-title mb-4">Connection Settings</h2>
<div class="mb-3">
<label for="config-content" class="form-label">Config String</label>
<textarea id="config-content" class="form-control" placeholder="Enter config string here..." rows="3"></textarea>
</div>
<div class="mb-3">
<label for="hiddify-settings" class="form-label">Hiddify Settings</label>
<textarea id="hiddify-settings" class="form-control" placeholder="Enter Hiddify settings here..." rows="3"></textarea>
</div>
<div class="d-flex justify-content-between">
<button id="connect-button" class="btn btn-success">Connect</button>
</div>
</div>
<div id="connection-connecting" class="card-body d-none">
<h2 id="connection-status" class="card-title mb-4">Connecting...</h2>
<button id="disconnect-button" class="btn btn-danger">Disconnect</button>
</div>
</div>
<div id="extension-list-container" class="card p-4">
<h1 class="mb-4">
Extension List
</h1>
<div id="extension-list" class="list-group">
</div>
</div>
<div id="extension-page-container" class="card p-4">
<div id="extension-page"></div>
</div>
</div>
<div class="modal fade" id="extension-dialog" style="display: none;" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Extension List</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="extension-page-containerdialog"></div>
</div>
<div class="modal-footer" id="modal-footer">
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/ansi_up@5.0.0/ansi_up.js"></script>
<script src="https://cdn.jsdelivr.net/npm/protobufjs@7.X.X/dist/protobuf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js" integrity="sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="rpc.js?1"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
+460
View File
@@ -0,0 +1,460 @@
// source: base.proto
/**
* @fileoverview
* @enhanceable
* @suppress {missingRequire} reports error on implicit type usages.
* @suppress {messageConventions} JS Compiler reports an error if a variable or
* field starts with 'MSG_' and isn't a translatable message.
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
/* eslint-disable */
// @ts-nocheck
var jspb = require('google-protobuf');
var goog = jspb;
var global =
(typeof globalThis !== 'undefined' && globalThis) ||
(typeof window !== 'undefined' && window) ||
(typeof global !== 'undefined' && global) ||
(typeof self !== 'undefined' && self) ||
(function () { return this; }).call(null) ||
Function('return this')();
goog.exportSymbol('proto.hiddifyrpc.Empty', null, global);
goog.exportSymbol('proto.hiddifyrpc.HelloRequest', null, global);
goog.exportSymbol('proto.hiddifyrpc.HelloResponse', null, global);
goog.exportSymbol('proto.hiddifyrpc.ResponseCode', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.hiddifyrpc.HelloRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.hiddifyrpc.HelloRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.hiddifyrpc.HelloRequest.displayName = 'proto.hiddifyrpc.HelloRequest';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.hiddifyrpc.HelloResponse = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.hiddifyrpc.HelloResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.hiddifyrpc.HelloResponse.displayName = 'proto.hiddifyrpc.HelloResponse';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.hiddifyrpc.Empty = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.hiddifyrpc.Empty, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.hiddifyrpc.Empty.displayName = 'proto.hiddifyrpc.Empty';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.hiddifyrpc.HelloRequest.prototype.toObject = function(opt_includeInstance) {
return proto.hiddifyrpc.HelloRequest.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.hiddifyrpc.HelloRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hiddifyrpc.HelloRequest.toObject = function(includeInstance, msg) {
var f, obj = {
name: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.hiddifyrpc.HelloRequest}
*/
proto.hiddifyrpc.HelloRequest.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.hiddifyrpc.HelloRequest;
return proto.hiddifyrpc.HelloRequest.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.hiddifyrpc.HelloRequest} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.hiddifyrpc.HelloRequest}
*/
proto.hiddifyrpc.HelloRequest.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setName(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.hiddifyrpc.HelloRequest.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.hiddifyrpc.HelloRequest.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.hiddifyrpc.HelloRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hiddifyrpc.HelloRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getName();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string name = 1;
* @return {string}
*/
proto.hiddifyrpc.HelloRequest.prototype.getName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.hiddifyrpc.HelloRequest} returns this
*/
proto.hiddifyrpc.HelloRequest.prototype.setName = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.hiddifyrpc.HelloResponse.prototype.toObject = function(opt_includeInstance) {
return proto.hiddifyrpc.HelloResponse.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.hiddifyrpc.HelloResponse} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hiddifyrpc.HelloResponse.toObject = function(includeInstance, msg) {
var f, obj = {
message: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.hiddifyrpc.HelloResponse}
*/
proto.hiddifyrpc.HelloResponse.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.hiddifyrpc.HelloResponse;
return proto.hiddifyrpc.HelloResponse.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.hiddifyrpc.HelloResponse} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.hiddifyrpc.HelloResponse}
*/
proto.hiddifyrpc.HelloResponse.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setMessage(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.hiddifyrpc.HelloResponse.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.hiddifyrpc.HelloResponse.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.hiddifyrpc.HelloResponse} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hiddifyrpc.HelloResponse.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getMessage();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string message = 1;
* @return {string}
*/
proto.hiddifyrpc.HelloResponse.prototype.getMessage = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.hiddifyrpc.HelloResponse} returns this
*/
proto.hiddifyrpc.HelloResponse.prototype.setMessage = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.hiddifyrpc.Empty.prototype.toObject = function(opt_includeInstance) {
return proto.hiddifyrpc.Empty.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.hiddifyrpc.Empty} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hiddifyrpc.Empty.toObject = function(includeInstance, msg) {
var f, obj = {
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.hiddifyrpc.Empty}
*/
proto.hiddifyrpc.Empty.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.hiddifyrpc.Empty;
return proto.hiddifyrpc.Empty.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.hiddifyrpc.Empty} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.hiddifyrpc.Empty}
*/
proto.hiddifyrpc.Empty.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.hiddifyrpc.Empty.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.hiddifyrpc.Empty.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.hiddifyrpc.Empty} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hiddifyrpc.Empty.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
};
/**
* @enum {number}
*/
proto.hiddifyrpc.ResponseCode = {
OK: 0,
FAILED: 1
};
goog.object.extend(exports, proto.hiddifyrpc);
+8
View File
@@ -0,0 +1,8 @@
const hiddify = require("./hiddify_grpc_web_pb.js");
const extension = require("./extension_grpc_web_pb.js");
const grpcServerAddress = '/';
const extensionClient = new extension.ExtensionHostServicePromiseClient(grpcServerAddress, null, null);
const hiddifyClient = new hiddify.CorePromiseClient(grpcServerAddress, null, null);
module.exports = { extensionClient ,hiddifyClient};
@@ -0,0 +1,109 @@
const { hiddifyClient } = require('./client.js');
const hiddify = require("./hiddify_grpc_web_pb.js");
function openConnectionPage() {
$("#extension-list-container").show();
$("#extension-page-container").hide();
$("#connection-page").show();
connect();
$("#connect-button").click(async () => {
const hsetting_request = new hiddify.ChangeHiddifySettingsRequest();
hsetting_request.setHiddifySettingsJson($("#hiddify-settings").val());
try{
const hres=await hiddifyClient.changeHiddifySettings(hsetting_request, {});
}catch(err){
$("#hiddify-settings").val("")
console.log(err)
}
const parse_request = new hiddify.ParseRequest();
parse_request.setContent($("#config-content").val());
try{
const pres=await hiddifyClient.parse(parse_request, {});
if (pres.getResponseCode() !== hiddify.ResponseCode.OK){
alert(pres.getMessage());
return
}
$("#config-content").val(pres.getContent());
}catch(err){
console.log(err)
alert(JSON.stringify(err))
return
}
const request = new hiddify.StartRequest();
request.setConfigContent($("#config-content").val());
request.setEnableRawConfig(false);
try{
const res=await hiddifyClient.start(request, {});
console.log(res.getCoreState(),res.getMessage())
handleCoreStatus(res.getCoreState());
}catch(err){
console.log(err)
alert(JSON.stringify(err))
return
}
})
$("#disconnect-button").click(async () => {
const request = new hiddify.Empty();
try{
const res=await hiddifyClient.stop(request, {});
console.log(res.getCoreState(),res.getMessage())
handleCoreStatus(res.getCoreState());
}catch(err){
console.log(err)
alert(JSON.stringify(err))
return
}
})
}
function connect(){
const request = new hiddify.Empty();
const stream = hiddifyClient.coreInfoListener(request, {});
stream.on('data', (response) => {
console.log('Receving ',response);
handleCoreStatus(response);
});
stream.on('error', (err) => {
console.error('Error opening extension page:', err);
// openExtensionPage(extensionId);
});
stream.on('end', () => {
console.log('Stream ended');
setTimeout(connect, 1000);
});
}
function handleCoreStatus(status){
if (status == hiddify.CoreState.STOPPED){
$("#connection-before-connect").show();
$("#connection-connecting").hide();
}else{
$("#connection-before-connect").hide();
$("#connection-connecting").show();
if (status == hiddify.CoreState.STARTING){
$("#connection-status").text("Starting");
$("#connection-status").css("color", "yellow");
}else if (status == hiddify.CoreState.STOPPING){
$("#connection-status").text("Stopping");
$("#connection-status").css("color", "red");
}else if (status == hiddify.CoreState.STARTED){
$("#connection-status").text("Connected");
$("#connection-status").css("color", "green");
}
}
}
module.exports = { openConnectionPage };
+8
View File
@@ -0,0 +1,8 @@
const { listExtensions } = require('./extensionList.js');
const { openConnectionPage } = require('./connectionPage.js');
window.onload = () => {
listExtensions();
openConnectionPage();
};
@@ -0,0 +1,90 @@
const { extensionClient } = require('./client.js');
const extension = require("./extension_grpc_web_pb.js");
async function listExtensions() {
$("#extension-list-container").show();
$("#extension-page-container").hide();
$("#connection-page").show();
try {
const extensionListContainer = document.getElementById('extension-list');
extensionListContainer.innerHTML = ''; // Clear previous entries
const response = await extensionClient.listExtensions(new extension.Empty(), {});
const extensionList = response.getExtensionsList();
extensionList.forEach(ext => {
const listItem = createExtensionListItem(ext);
extensionListContainer.appendChild(listItem);
});
} catch (err) {
console.error('Error listing extensions:', err);
}
}
function createExtensionListItem(ext) {
const listItem = document.createElement('li');
listItem.className = 'list-group-item d-flex justify-content-between align-items-center';
listItem.setAttribute('data-extension-id', ext.getId());
const contentDiv = document.createElement('div');
const titleElement = document.createElement('span');
titleElement.innerHTML = `<strong>${ext.getTitle()}</strong>`;
contentDiv.appendChild(titleElement);
const descriptionElement = document.createElement('p');
descriptionElement.className = 'mb-0';
descriptionElement.textContent = ext.getDescription();
contentDiv.appendChild(descriptionElement);
contentDiv.style.width="100%";
listItem.appendChild(contentDiv);
const switchDiv = createSwitchElement(ext);
listItem.appendChild(switchDiv);
const {openExtensionPage} = require('./extensionPage.js');
contentDiv.addEventListener('click', () =>{
if (!ext.getEnable() ){
alert("Extension is not enabled")
return
}
openExtensionPage(ext.getId())
});
return listItem;
}
function createSwitchElement(ext) {
const switchDiv = document.createElement('div');
switchDiv.className = 'form-check form-switch';
const switchButton = document.createElement('input');
switchButton.type = 'checkbox';
switchButton.className = 'form-check-input';
switchButton.checked = ext.getEnable();
switchButton.addEventListener('change', (e) => {
toggleExtension(ext.getId(), switchButton.checked)
});
switchDiv.appendChild(switchButton);
return switchDiv;
}
async function toggleExtension(extensionId, enable) {
const request = new extension.EditExtensionRequest();
request.setExtensionId(extensionId);
request.setEnable(enable);
try {
await extensionClient.editExtension(request, {});
console.log(`Extension ${extensionId} updated to ${enable ? 'enabled' : 'disabled'}`);
} catch (err) {
console.error('Error updating extension status:', err);
}
listExtensions();
}
module.exports = { listExtensions };
@@ -0,0 +1,87 @@
const { extensionClient } = require('./client.js');
const extension = require("./extension_grpc_web_pb.js");
const { renderForm } = require('./formRenderer.js');
const { listExtensions } = require('./extensionList.js');
var currentExtensionId = undefined;
function openExtensionPage(extensionId) {
currentExtensionId = extensionId;
$("#extension-list-container").hide();
$("#extension-page-container").show();
$("#connection-page").hide();
connect()
}
function connect() {
const request = new extension.ExtensionRequest();
request.setExtensionId(currentExtensionId);
const stream = extensionClient.connect(request, {});
stream.on('data', (response) => {
console.log('Receiving ', response);
if (response.getExtensionId() === currentExtensionId) {
ui = JSON.parse(response.getJsonUi())
if (response.getType() == proto.hiddifyrpc.ExtensionResponseType.SHOW_DIALOG) {
renderForm(ui, "dialog", handleSubmitButtonClick, undefined);
} else {
renderForm(ui, "", handleSubmitButtonClick, handleStopButtonClick);
}
}
});
stream.on('error', (err) => {
console.error('Error opening extension page:', err);
// openExtensionPage(extensionId);
});
stream.on('end', () => {
console.log('Stream ended');
setTimeout(connect, 1000);
});
}
async function handleSubmitButtonClick(event, button) {
event.preventDefault();
bootstrap.Modal.getOrCreateInstance("#extension-dialog").hide();
const request = new extension.SendExtensionDataRequest();
request.setButton(button);
if (event.type != 'hidden.bs.modal') {
const formData = new FormData(event.target.closest('form'));
const datamap = request.getDataMap()
formData.forEach((value, key) => {
datamap.set(key, value);
});
}
request.setExtensionId(currentExtensionId);
try {
await extensionClient.submitForm(request, {});
console.log('Form submitted successfully.');
} catch (err) {
console.error('Error submitting form:', err);
}
}
async function handleStopButtonClick(event) {
event.preventDefault();
const request = new extension.ExtensionRequest();
request.setExtensionId(currentExtensionId);
bootstrap.Modal.getOrCreateInstance("#extension-dialog").hide();
try {
await extensionClient.close(request, {});
console.log('Extension stopped successfully.');
currentExtensionId = undefined;
listExtensions(); // Return to the extension list
} catch (err) {
console.error('Error stopping extension:', err);
}
}
module.exports = { openExtensionPage };
@@ -0,0 +1,441 @@
/**
* @fileoverview gRPC-Web generated client stub for hiddifyrpc
* @enhanceable
* @public
*/
// Code generated by protoc-gen-grpc-web. DO NOT EDIT.
// versions:
// protoc-gen-grpc-web v1.5.0
// protoc v5.28.0
// source: extension.proto
/* eslint-disable */
// @ts-nocheck
const grpc = {};
grpc.web = require('grpc-web');
var base_pb = require('./base_pb.js')
const proto = {};
proto.hiddifyrpc = require('./extension_pb.js');
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?grpc.web.ClientOptions} options
* @constructor
* @struct
* @final
*/
proto.hiddifyrpc.ExtensionHostServiceClient =
function(hostname, credentials, options) {
if (!options) options = {};
options.format = 'text';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname.replace(/\/+$/, '');
};
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?grpc.web.ClientOptions} options
* @constructor
* @struct
* @final
*/
proto.hiddifyrpc.ExtensionHostServicePromiseClient =
function(hostname, credentials, options) {
if (!options) options = {};
options.format = 'text';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname.replace(/\/+$/, '');
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.hiddifyrpc.Empty,
* !proto.hiddifyrpc.ExtensionList>}
*/
const methodDescriptor_ExtensionHostService_ListExtensions = new grpc.web.MethodDescriptor(
'/hiddifyrpc.ExtensionHostService/ListExtensions',
grpc.web.MethodType.UNARY,
base_pb.Empty,
proto.hiddifyrpc.ExtensionList,
/**
* @param {!proto.hiddifyrpc.Empty} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hiddifyrpc.ExtensionList.deserializeBinary
);
/**
* @param {!proto.hiddifyrpc.Empty} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionList)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionList>|undefined}
* The XHR Node Readable Stream
*/
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.listExtensions =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/ListExtensions',
request,
metadata || {},
methodDescriptor_ExtensionHostService_ListExtensions,
callback);
};
/**
* @param {!proto.hiddifyrpc.Empty} request The
* request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!Promise<!proto.hiddifyrpc.ExtensionList>}
* Promise that resolves to the response
*/
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.listExtensions =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/ListExtensions',
request,
metadata || {},
methodDescriptor_ExtensionHostService_ListExtensions);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.hiddifyrpc.ExtensionRequest,
* !proto.hiddifyrpc.ExtensionResponse>}
*/
const methodDescriptor_ExtensionHostService_Connect = new grpc.web.MethodDescriptor(
'/hiddifyrpc.ExtensionHostService/Connect',
grpc.web.MethodType.SERVER_STREAMING,
proto.hiddifyrpc.ExtensionRequest,
proto.hiddifyrpc.ExtensionResponse,
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hiddifyrpc.ExtensionResponse.deserializeBinary
);
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request The request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionResponse>}
* The XHR Node Readable Stream
*/
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.connect =
function(request, metadata) {
return this.client_.serverStreaming(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/Connect',
request,
metadata || {},
methodDescriptor_ExtensionHostService_Connect);
};
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request The request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionResponse>}
* The XHR Node Readable Stream
*/
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.connect =
function(request, metadata) {
return this.client_.serverStreaming(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/Connect',
request,
metadata || {},
methodDescriptor_ExtensionHostService_Connect);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.hiddifyrpc.EditExtensionRequest,
* !proto.hiddifyrpc.ExtensionActionResult>}
*/
const methodDescriptor_ExtensionHostService_EditExtension = new grpc.web.MethodDescriptor(
'/hiddifyrpc.ExtensionHostService/EditExtension',
grpc.web.MethodType.UNARY,
proto.hiddifyrpc.EditExtensionRequest,
proto.hiddifyrpc.ExtensionActionResult,
/**
* @param {!proto.hiddifyrpc.EditExtensionRequest} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
);
/**
* @param {!proto.hiddifyrpc.EditExtensionRequest} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
* The XHR Node Readable Stream
*/
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.editExtension =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/EditExtension',
request,
metadata || {},
methodDescriptor_ExtensionHostService_EditExtension,
callback);
};
/**
* @param {!proto.hiddifyrpc.EditExtensionRequest} request The
* request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
* Promise that resolves to the response
*/
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.editExtension =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/EditExtension',
request,
metadata || {},
methodDescriptor_ExtensionHostService_EditExtension);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.hiddifyrpc.SendExtensionDataRequest,
* !proto.hiddifyrpc.ExtensionActionResult>}
*/
const methodDescriptor_ExtensionHostService_SubmitForm = new grpc.web.MethodDescriptor(
'/hiddifyrpc.ExtensionHostService/SubmitForm',
grpc.web.MethodType.UNARY,
proto.hiddifyrpc.SendExtensionDataRequest,
proto.hiddifyrpc.ExtensionActionResult,
/**
* @param {!proto.hiddifyrpc.SendExtensionDataRequest} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
);
/**
* @param {!proto.hiddifyrpc.SendExtensionDataRequest} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
* The XHR Node Readable Stream
*/
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.submitForm =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/SubmitForm',
request,
metadata || {},
methodDescriptor_ExtensionHostService_SubmitForm,
callback);
};
/**
* @param {!proto.hiddifyrpc.SendExtensionDataRequest} request The
* request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
* Promise that resolves to the response
*/
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.submitForm =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/SubmitForm',
request,
metadata || {},
methodDescriptor_ExtensionHostService_SubmitForm);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.hiddifyrpc.ExtensionRequest,
* !proto.hiddifyrpc.ExtensionActionResult>}
*/
const methodDescriptor_ExtensionHostService_Close = new grpc.web.MethodDescriptor(
'/hiddifyrpc.ExtensionHostService/Close',
grpc.web.MethodType.UNARY,
proto.hiddifyrpc.ExtensionRequest,
proto.hiddifyrpc.ExtensionActionResult,
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
);
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
* The XHR Node Readable Stream
*/
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.close =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/Close',
request,
metadata || {},
methodDescriptor_ExtensionHostService_Close,
callback);
};
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
* request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
* Promise that resolves to the response
*/
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.close =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/Close',
request,
metadata || {},
methodDescriptor_ExtensionHostService_Close);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.hiddifyrpc.ExtensionRequest,
* !proto.hiddifyrpc.ExtensionActionResult>}
*/
const methodDescriptor_ExtensionHostService_GetUI = new grpc.web.MethodDescriptor(
'/hiddifyrpc.ExtensionHostService/GetUI',
grpc.web.MethodType.UNARY,
proto.hiddifyrpc.ExtensionRequest,
proto.hiddifyrpc.ExtensionActionResult,
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hiddifyrpc.ExtensionActionResult.deserializeBinary
);
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.RpcError, ?proto.hiddifyrpc.ExtensionActionResult)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.hiddifyrpc.ExtensionActionResult>|undefined}
* The XHR Node Readable Stream
*/
proto.hiddifyrpc.ExtensionHostServiceClient.prototype.getUI =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/GetUI',
request,
metadata || {},
methodDescriptor_ExtensionHostService_GetUI,
callback);
};
/**
* @param {!proto.hiddifyrpc.ExtensionRequest} request The
* request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!Promise<!proto.hiddifyrpc.ExtensionActionResult>}
* Promise that resolves to the response
*/
proto.hiddifyrpc.ExtensionHostServicePromiseClient.prototype.getUI =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/hiddifyrpc.ExtensionHostService/GetUI',
request,
metadata || {},
methodDescriptor_ExtensionHostService_GetUI);
};
module.exports = proto.hiddifyrpc;
File diff suppressed because it is too large Load Diff
+239
View File
@@ -0,0 +1,239 @@
const ansi_up = new AnsiUp({
escape_html: false,
});
function renderForm(json, dialog, submitAction, stopAction) {
const container = document.getElementById(`extension-page-container${dialog}`);
const formId = `dynamicForm${json.id}${dialog}`;
const existingForm = document.getElementById(formId);
if (existingForm) {
existingForm.remove();
}
const form = document.createElement('form');
container.appendChild(form);
form.id = formId;
if (dialog === "dialog") {
document.getElementById("modalLabel").textContent = json.title;
} else {
const titleElement = createTitleElement(json);
const stopBtn = document.createElement('button');
stopBtn.type = 'button';
stopBtn.className = 'btn btn-danger';
stopBtn.textContent = 'Close';
stopBtn.addEventListener('click', stopAction);
form.appendChild(stopBtn);
form.appendChild(titleElement);
}
addElementsToForm(form, json,submitAction);
if (dialog === "dialog") {
document.getElementById("modal-footer").innerHTML = '';
// if ($(form.lastChild).find("button").length > 0) {
// document.getElementById("modal-footer").appendChild(form.lastChild);
// }
const extensionDialog = document.getElementById("extension-dialog");
const dialog = bootstrap.Modal.getOrCreateInstance(extensionDialog);
dialog.show();
extensionDialog.addEventListener("hidden.bs.modal", (e)=>submitAction(e,"CloseDialog"));
}
}
function addElementsToForm(form, json,submitAction) {
const description = document.createElement('p');
description.textContent = json.description;
form.appendChild(description);
if (json.fields) {
json.fields.forEach(field => {
div=document.createElement("div")
div.classList.add("row")
form.appendChild(div)
for (let i = 0; i < field.length; i++) {
const formGroup = createFormGroup(field[i], submitAction);
formGroup.classList.add("col")
div.appendChild(formGroup);
}
});
}
return form;
}
function createTitleElement(json) {
const title = document.createElement('h1');
title.textContent = json.title;
return title;
}
function createFormGroup(field, submitAction) {
const formGroup = document.createElement('div');
formGroup.classList.add('mb-3');
if (field.type == "Button") {
const button = document.createElement('button');
button.textContent = field.label;
button.name=field.key
button.classList.add('btn');
if (field.key == "Submit") {
button.classList.add('btn-primary');
} else if (field.key == "Cancel") {
button.classList.add('btn-secondary');
}else{
button.classList.add('btn', 'btn-outline-secondary');
}
button.addEventListener('click', (e) => submitAction(e,field.key));
formGroup.appendChild(button);
} else {
if (field.label && !field.labelHidden) {
const label = document.createElement('label');
label.textContent = field.label;
label.setAttribute('for', field.key);
formGroup.appendChild(label);
}
const input = createInputElement(field);
formGroup.appendChild(input);
}
return formGroup;
}
function createInputElement(field) {
let input;
switch (field.type) {
case "Console":
input = document.createElement('pre');
input.innerHTML = ansi_up.ansi_to_html(field.value || field.placeholder || '');
input.style.maxHeight = field.lines * 20 + 'px';
break;
case "TextArea":
input = document.createElement('textarea');
input.rows = field.lines || 3;
input.textContent = field.value || '';
break;
case "Checkbox":
case "RadioButton":
input = createCheckboxOrRadioGroup(field);
break;
case "Switch":
input = createSwitchElement(field);
break;
case "Select":
input = document.createElement('select');
field.items.forEach(item => {
const option = document.createElement('option');
option.value = item.value;
option.text = item.label;
input.appendChild(option);
});
break;
default:
input = document.createElement('input');
input.type = field.type.toLowerCase();
input.value = field.value;
break;
}
input.id = field.key;
input.name = field.key;
if (field.readOnly) input.readOnly = true;
if (field.type == "Checkbox" || field.type == "RadioButton" || field.type == "Switch") {
} else {
if (field.required) input.required = true;
input.classList.add('form-control');
if (field.placeholder) input.placeholder = field.placeholder;
}
return input;
}
function createCheckboxOrRadioGroup(field) {
const wrapper = document.createDocumentFragment();
field.items.forEach(item => {
const inputWrapper = document.createElement('div');
inputWrapper.classList.add('form-check');
const input = document.createElement('input');
input.type = field.type === "Checkbox" ? 'checkbox' : 'radio';
input.classList.add('form-check-input');
input.id = `${field.key}_${item.value}`;
input.name = field.key; // Grouping by name for radio buttons
input.value = item.value;
input.checked = field.value === item.value;
const itemLabel = document.createElement('label');
itemLabel.classList.add('form-check-label');
itemLabel.setAttribute('for', input.id);
itemLabel.textContent = item.label;
inputWrapper.appendChild(input);
inputWrapper.appendChild(itemLabel);
wrapper.appendChild(inputWrapper);
});
return wrapper;
}
function createSwitchElement(field) {
const switchWrapper = document.createElement('div');
switchWrapper.classList.add('form-check', 'form-switch');
const input = document.createElement('input');
input.type = 'checkbox';
input.classList.add('form-check-input');
input.setAttribute('role', 'switch');
input.id = field.key;
input.checked = field.value === "true";
const label = document.createElement('label');
label.classList.add('form-check-label');
label.setAttribute('for', field.key);
label.textContent = field.label;
switchWrapper.appendChild(input);
switchWrapper.appendChild(label);
return switchWrapper;
}
function createButtonGroup(json, submitAction, cancelAction) {
const buttonGroup = document.createElement('div');
buttonGroup.classList.add('btn-group');
json.buttons.forEach(buttonText => {
const btn = document.createElement('button');
btn.classList.add('btn', "btn-default");
buttonGroup.appendChild(btn);
btn.textContent = buttonText
if (buttonText == "Cancel") {
btn.classList.add('btn-secondary');
btn.addEventListener('click', cancelAction);
} else {
if (buttonText == "Submit" || buttonText == "Ok")
btn.classList.add('btn-primary');
btn.addEventListener('click', submitAction);
}
})
return buttonGroup;
}
module.exports = { renderForm };
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff