auto-webmcp 0.2.4 → 0.2.6
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/analyzer.d.ts +5 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/auto-webmcp.cjs.js +253 -12
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +253 -12
- package/dist/auto-webmcp.esm.js.map +2 -2
- package/dist/auto-webmcp.iife.js +2 -2
- package/dist/auto-webmcp.iife.js.map +3 -3
- package/dist/discovery.d.ts.map +1 -1
- package/dist/interceptor.d.ts +5 -0
- package/dist/interceptor.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/analyzer.d.ts
CHANGED
|
@@ -14,4 +14,9 @@ export interface ToolMetadata {
|
|
|
14
14
|
export declare function resetFormIndex(): void;
|
|
15
15
|
/** Derive ToolMetadata from a <form> element */
|
|
16
16
|
export declare function analyzeForm(form: HTMLFormElement, override?: FormOverride): ToolMetadata;
|
|
17
|
+
/**
|
|
18
|
+
* Derive ToolMetadata from a group of form controls that are NOT inside a <form>.
|
|
19
|
+
* Used by discovery.ts's orphan-input scanner for pages like newsletter landing pages.
|
|
20
|
+
*/
|
|
21
|
+
export declare function analyzeOrphanInputGroup(container: Element, inputs: Array<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>, submitBtn: HTMLButtonElement | HTMLInputElement | null): ToolMetadata;
|
|
17
22
|
//# sourceMappingURL=analyzer.d.ts.map
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAA8H,MAAM,aAAa,CAAC;AACrK,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAMxF"}
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAA8H,MAAM,aAAa,CAAC;AACrK,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAMxF;AAkaD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,KAAK,CAAC,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC,EACzE,SAAS,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,GACrD,YAAY,CAKd"}
|
package/dist/auto-webmcp.cjs.js
CHANGED
|
@@ -454,6 +454,12 @@ function resolveNativeControlFallbackKey(control) {
|
|
|
454
454
|
const label = control.getAttribute("aria-label");
|
|
455
455
|
if (label)
|
|
456
456
|
return sanitizeName(label);
|
|
457
|
+
if ((control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) && control.placeholder?.trim()) {
|
|
458
|
+
return sanitizeName(control.placeholder.trim());
|
|
459
|
+
}
|
|
460
|
+
if (control instanceof HTMLInputElement && control.type !== "text") {
|
|
461
|
+
return control.type;
|
|
462
|
+
}
|
|
457
463
|
return null;
|
|
458
464
|
}
|
|
459
465
|
function collectAriaControls(form) {
|
|
@@ -538,6 +544,9 @@ function inferFieldTitle(control) {
|
|
|
538
544
|
return humanizeName(control.name);
|
|
539
545
|
if (control.id)
|
|
540
546
|
return humanizeName(control.id);
|
|
547
|
+
if ((control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) && control.placeholder?.trim()) {
|
|
548
|
+
return control.placeholder.trim();
|
|
549
|
+
}
|
|
541
550
|
return "";
|
|
542
551
|
}
|
|
543
552
|
function inferFieldDescription(control) {
|
|
@@ -588,6 +597,88 @@ function labelTextWithoutNested(label) {
|
|
|
588
597
|
function humanizeName(raw) {
|
|
589
598
|
return raw.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
590
599
|
}
|
|
600
|
+
function analyzeOrphanInputGroup(container, inputs, submitBtn) {
|
|
601
|
+
const name = inferOrphanToolName(container, submitBtn);
|
|
602
|
+
const description = inferOrphanToolDescription(container);
|
|
603
|
+
const { schema: inputSchema, fieldElements } = buildSchemaFromInputs(inputs);
|
|
604
|
+
return { name, description, inputSchema, fieldElements };
|
|
605
|
+
}
|
|
606
|
+
function inferOrphanToolName(container, submitBtn) {
|
|
607
|
+
if (submitBtn) {
|
|
608
|
+
const text = submitBtn instanceof HTMLInputElement ? submitBtn.value.trim() : submitBtn.textContent?.trim() ?? "";
|
|
609
|
+
if (text && text.length > 0 && text.length < 80)
|
|
610
|
+
return sanitizeName(text);
|
|
611
|
+
}
|
|
612
|
+
const heading = getNearestHeadingTextFrom(container);
|
|
613
|
+
if (heading)
|
|
614
|
+
return sanitizeName(heading);
|
|
615
|
+
const title = document.title?.trim();
|
|
616
|
+
if (title)
|
|
617
|
+
return sanitizeName(title);
|
|
618
|
+
return `form_${++formIndex}`;
|
|
619
|
+
}
|
|
620
|
+
function inferOrphanToolDescription(container) {
|
|
621
|
+
const heading = getNearestHeadingTextFrom(container);
|
|
622
|
+
const pageTitle = document.title?.trim();
|
|
623
|
+
if (heading && pageTitle && heading !== pageTitle)
|
|
624
|
+
return `${heading} on ${pageTitle}`;
|
|
625
|
+
if (heading)
|
|
626
|
+
return heading;
|
|
627
|
+
if (pageTitle)
|
|
628
|
+
return pageTitle;
|
|
629
|
+
return "Submit form";
|
|
630
|
+
}
|
|
631
|
+
function getNearestHeadingTextFrom(el) {
|
|
632
|
+
const inner = el.querySelector("h1, h2, h3");
|
|
633
|
+
if (inner?.textContent?.trim())
|
|
634
|
+
return inner.textContent.trim();
|
|
635
|
+
let node = el;
|
|
636
|
+
while (node) {
|
|
637
|
+
let sibling = node.previousElementSibling;
|
|
638
|
+
while (sibling) {
|
|
639
|
+
if (/^H[1-3]$/i.test(sibling.tagName)) {
|
|
640
|
+
const text = sibling.textContent?.trim() ?? "";
|
|
641
|
+
if (text)
|
|
642
|
+
return text;
|
|
643
|
+
}
|
|
644
|
+
sibling = sibling.previousElementSibling;
|
|
645
|
+
}
|
|
646
|
+
node = node.parentElement;
|
|
647
|
+
if (!node || node === document.body)
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
return "";
|
|
651
|
+
}
|
|
652
|
+
function buildSchemaFromInputs(inputs) {
|
|
653
|
+
const properties = {};
|
|
654
|
+
const required = [];
|
|
655
|
+
const fieldElements = /* @__PURE__ */ new Map();
|
|
656
|
+
const processedRadioGroups = /* @__PURE__ */ new Set();
|
|
657
|
+
for (const control of inputs) {
|
|
658
|
+
const name = control.name;
|
|
659
|
+
const fieldKey = name || resolveNativeControlFallbackKey(control);
|
|
660
|
+
if (!fieldKey)
|
|
661
|
+
continue;
|
|
662
|
+
if (control instanceof HTMLInputElement && control.type === "radio") {
|
|
663
|
+
if (processedRadioGroups.has(fieldKey))
|
|
664
|
+
continue;
|
|
665
|
+
processedRadioGroups.add(fieldKey);
|
|
666
|
+
}
|
|
667
|
+
const schemaProp = inputTypeToSchema(control);
|
|
668
|
+
if (!schemaProp)
|
|
669
|
+
continue;
|
|
670
|
+
schemaProp.title = inferFieldTitle(control);
|
|
671
|
+
const desc = inferFieldDescription(control);
|
|
672
|
+
if (desc)
|
|
673
|
+
schemaProp.description = desc;
|
|
674
|
+
properties[fieldKey] = schemaProp;
|
|
675
|
+
if (!name)
|
|
676
|
+
fieldElements.set(fieldKey, control);
|
|
677
|
+
if (control.required)
|
|
678
|
+
required.push(fieldKey);
|
|
679
|
+
}
|
|
680
|
+
return { schema: { type: "object", properties, required }, fieldElements };
|
|
681
|
+
}
|
|
591
682
|
|
|
592
683
|
// src/discovery.ts
|
|
593
684
|
init_registry();
|
|
@@ -610,7 +701,22 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
610
701
|
return new Promise((resolve, reject) => {
|
|
611
702
|
pendingExecutions.set(form, { resolve, reject });
|
|
612
703
|
if (config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0) {
|
|
613
|
-
|
|
704
|
+
setTimeout(() => {
|
|
705
|
+
fillFormFields(form, params);
|
|
706
|
+
let submitForm = form;
|
|
707
|
+
if (!form.isConnected) {
|
|
708
|
+
const liveBtn = document.querySelector(
|
|
709
|
+
'button[type="submit"]:not([disabled]), input[type="submit"]:not([disabled])'
|
|
710
|
+
);
|
|
711
|
+
const found = liveBtn?.closest("form");
|
|
712
|
+
if (found) {
|
|
713
|
+
submitForm = found;
|
|
714
|
+
pendingExecutions.set(submitForm, { resolve, reject });
|
|
715
|
+
attachSubmitInterceptor(submitForm, toolName);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
submitForm.requestSubmit();
|
|
719
|
+
}, 300);
|
|
614
720
|
}
|
|
615
721
|
});
|
|
616
722
|
};
|
|
@@ -701,16 +807,25 @@ function fillFormFields(form, params) {
|
|
|
701
807
|
continue;
|
|
702
808
|
}
|
|
703
809
|
const ariaEl = fieldEls?.get(key);
|
|
704
|
-
if (ariaEl
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
810
|
+
if (ariaEl) {
|
|
811
|
+
let effectiveEl = ariaEl;
|
|
812
|
+
if (!ariaEl.isConnected) {
|
|
813
|
+
const elId = ariaEl.id;
|
|
814
|
+
if (elId) {
|
|
815
|
+
const fresh = document.getElementById(elId) ?? findInShadowRoots(document, `#${CSS.escape(elId)}`);
|
|
816
|
+
if (fresh)
|
|
817
|
+
effectiveEl = fresh;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (effectiveEl instanceof HTMLInputElement) {
|
|
821
|
+
fillInput(effectiveEl, form, key, value);
|
|
822
|
+
} else if (effectiveEl instanceof HTMLTextAreaElement) {
|
|
823
|
+
setReactValue(effectiveEl, String(value ?? ""));
|
|
824
|
+
} else if (effectiveEl instanceof HTMLSelectElement) {
|
|
825
|
+
effectiveEl.value = String(value ?? "");
|
|
826
|
+
effectiveEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
712
827
|
} else {
|
|
713
|
-
fillAriaField(
|
|
828
|
+
fillAriaField(effectiveEl, value);
|
|
714
829
|
}
|
|
715
830
|
}
|
|
716
831
|
}
|
|
@@ -779,8 +894,7 @@ function serializeFormData(form, params, fieldEls) {
|
|
|
779
894
|
for (const key of Object.keys(params)) {
|
|
780
895
|
if (key in result)
|
|
781
896
|
continue;
|
|
782
|
-
const
|
|
783
|
-
const el = findNativeField(form, key) ?? (storedEl?.isConnected ? storedEl : null) ?? null;
|
|
897
|
+
const el = findNativeField(form, key) ?? fieldEls?.get(key) ?? null;
|
|
784
898
|
if (!el)
|
|
785
899
|
continue;
|
|
786
900
|
if (el instanceof HTMLInputElement && el.type === "checkbox") {
|
|
@@ -799,6 +913,31 @@ function serializeFormData(form, params, fieldEls) {
|
|
|
799
913
|
}
|
|
800
914
|
return result;
|
|
801
915
|
}
|
|
916
|
+
function fillElement(el, value) {
|
|
917
|
+
if (el instanceof HTMLInputElement) {
|
|
918
|
+
const type = el.type.toLowerCase();
|
|
919
|
+
if (type === "checkbox") {
|
|
920
|
+
setReactChecked(el, Boolean(value));
|
|
921
|
+
} else if (type === "radio") {
|
|
922
|
+
if (el.value === String(value)) {
|
|
923
|
+
if (_checkedSetter)
|
|
924
|
+
_checkedSetter.call(el, true);
|
|
925
|
+
else
|
|
926
|
+
el.checked = true;
|
|
927
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
928
|
+
}
|
|
929
|
+
} else {
|
|
930
|
+
setReactValue(el, String(value ?? ""));
|
|
931
|
+
}
|
|
932
|
+
} else if (el instanceof HTMLTextAreaElement) {
|
|
933
|
+
setReactValue(el, String(value ?? ""));
|
|
934
|
+
} else if (el instanceof HTMLSelectElement) {
|
|
935
|
+
el.value = String(value ?? "");
|
|
936
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
937
|
+
} else {
|
|
938
|
+
fillAriaField(el, value);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
802
941
|
|
|
803
942
|
// src/enhancer.ts
|
|
804
943
|
async function enrichMetadata(metadata, enhancer) {
|
|
@@ -911,6 +1050,7 @@ async function registerForm(form, config) {
|
|
|
911
1050
|
const execute = buildExecuteHandler(form, config, metadata.name, metadata);
|
|
912
1051
|
await registerFormTool(form, metadata, execute);
|
|
913
1052
|
registeredForms.add(form);
|
|
1053
|
+
registeredFormCount++;
|
|
914
1054
|
if (config.debug) {
|
|
915
1055
|
console.debug(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
|
|
916
1056
|
}
|
|
@@ -930,6 +1070,7 @@ async function unregisterForm(form, config) {
|
|
|
930
1070
|
}
|
|
931
1071
|
var observer = null;
|
|
932
1072
|
var registeredForms = /* @__PURE__ */ new WeakSet();
|
|
1073
|
+
var registeredFormCount = 0;
|
|
933
1074
|
var reAnalysisTimers = /* @__PURE__ */ new Map();
|
|
934
1075
|
var RE_ANALYSIS_DEBOUNCE_MS = 300;
|
|
935
1076
|
function isInterestingNode(node) {
|
|
@@ -1011,6 +1152,102 @@ async function scanForms(config) {
|
|
|
1011
1152
|
const forms = Array.from(document.querySelectorAll("form"));
|
|
1012
1153
|
await Promise.allSettled(forms.map((form) => registerForm(form, config)));
|
|
1013
1154
|
}
|
|
1155
|
+
var ORPHAN_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
|
|
1156
|
+
"password",
|
|
1157
|
+
"hidden",
|
|
1158
|
+
"file",
|
|
1159
|
+
"submit",
|
|
1160
|
+
"reset",
|
|
1161
|
+
"button",
|
|
1162
|
+
"image"
|
|
1163
|
+
]);
|
|
1164
|
+
async function scanOrphanInputs(config) {
|
|
1165
|
+
if (!isWebMCPSupported())
|
|
1166
|
+
return;
|
|
1167
|
+
const SUBMIT_BTN_SELECTOR = '[type="submit"]:not([disabled]), button:not([type]):not([disabled])';
|
|
1168
|
+
const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search/i;
|
|
1169
|
+
const orphanInputs = Array.from(
|
|
1170
|
+
document.querySelectorAll(
|
|
1171
|
+
"input:not(form input), textarea:not(form textarea), select:not(form select)"
|
|
1172
|
+
)
|
|
1173
|
+
).filter((el) => {
|
|
1174
|
+
if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase())) {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
const rect = el.getBoundingClientRect();
|
|
1178
|
+
return rect.width > 0 && rect.height > 0;
|
|
1179
|
+
});
|
|
1180
|
+
if (orphanInputs.length === 0)
|
|
1181
|
+
return;
|
|
1182
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1183
|
+
for (const input of orphanInputs) {
|
|
1184
|
+
let container = input.parentElement;
|
|
1185
|
+
let foundContainer = input.parentElement ?? document.body;
|
|
1186
|
+
while (container && container !== document.body) {
|
|
1187
|
+
const hasSubmitBtn = container.querySelector(SUBMIT_BTN_SELECTOR) !== null || Array.from(container.querySelectorAll("button")).some(
|
|
1188
|
+
(b) => SUBMIT_TEXT_RE.test(b.textContent ?? "")
|
|
1189
|
+
);
|
|
1190
|
+
if (hasSubmitBtn) {
|
|
1191
|
+
foundContainer = container;
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
container = container.parentElement;
|
|
1195
|
+
}
|
|
1196
|
+
if (!groups.has(foundContainer))
|
|
1197
|
+
groups.set(foundContainer, []);
|
|
1198
|
+
groups.get(foundContainer).push(input);
|
|
1199
|
+
}
|
|
1200
|
+
for (const [container, inputs] of groups) {
|
|
1201
|
+
const allCandidates = Array.from(
|
|
1202
|
+
container.querySelectorAll(SUBMIT_BTN_SELECTOR)
|
|
1203
|
+
).filter((b) => {
|
|
1204
|
+
const r = b.getBoundingClientRect();
|
|
1205
|
+
return r.width > 0 && r.height > 0;
|
|
1206
|
+
});
|
|
1207
|
+
let submitBtn = allCandidates[allCandidates.length - 1] ?? null;
|
|
1208
|
+
if (!submitBtn) {
|
|
1209
|
+
const pageBtns = Array.from(document.querySelectorAll("button")).filter(
|
|
1210
|
+
(b) => {
|
|
1211
|
+
const r = b.getBoundingClientRect();
|
|
1212
|
+
return r.width > 0 && r.height > 0 && SUBMIT_TEXT_RE.test(b.textContent ?? "");
|
|
1213
|
+
}
|
|
1214
|
+
);
|
|
1215
|
+
submitBtn = pageBtns[pageBtns.length - 1] ?? null;
|
|
1216
|
+
}
|
|
1217
|
+
const metadata = analyzeOrphanInputGroup(container, inputs, submitBtn);
|
|
1218
|
+
const inputPairs = [];
|
|
1219
|
+
const schemaProps = metadata.inputSchema.properties;
|
|
1220
|
+
for (const el of inputs) {
|
|
1221
|
+
const key = el.name || el.dataset["webmcpName"] || el.id || el.getAttribute("aria-label") || null;
|
|
1222
|
+
const safeKey = key ? key.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) : null;
|
|
1223
|
+
if (safeKey && schemaProps[safeKey]) {
|
|
1224
|
+
inputPairs.push({ key: safeKey, el });
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const toolName = metadata.name;
|
|
1228
|
+
const execute = async (params) => {
|
|
1229
|
+
for (const { key, el } of inputPairs) {
|
|
1230
|
+
if (params[key] !== void 0) {
|
|
1231
|
+
fillElement(el, params[key]);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
|
|
1235
|
+
return { content: [{ type: "text", text: "Fields filled. Ready to submit." }] };
|
|
1236
|
+
};
|
|
1237
|
+
try {
|
|
1238
|
+
await navigator.modelContext.registerTool({
|
|
1239
|
+
name: metadata.name,
|
|
1240
|
+
description: metadata.description,
|
|
1241
|
+
inputSchema: metadata.inputSchema,
|
|
1242
|
+
execute
|
|
1243
|
+
});
|
|
1244
|
+
if (config.debug) {
|
|
1245
|
+
console.debug(`[auto-webmcp] Orphan tool registered: ${metadata.name}`, metadata);
|
|
1246
|
+
}
|
|
1247
|
+
} catch {
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1014
1251
|
function warnToolQuality(name, description) {
|
|
1015
1252
|
if (/^form_\d+$|^submit$|^form$/.test(name)) {
|
|
1016
1253
|
console.warn(`[auto-webmcp] Tool "${name}" has a generic name. Consider adding a toolname or data-webmcp-name attribute.`);
|
|
@@ -1028,9 +1265,13 @@ async function startDiscovery(config) {
|
|
|
1028
1265
|
(resolve) => document.addEventListener("DOMContentLoaded", () => resolve(), { once: true })
|
|
1029
1266
|
);
|
|
1030
1267
|
}
|
|
1268
|
+
registeredFormCount = 0;
|
|
1031
1269
|
startObserver(config);
|
|
1032
1270
|
listenForRouteChanges(config);
|
|
1033
1271
|
await scanForms(config);
|
|
1272
|
+
if (registeredFormCount === 0) {
|
|
1273
|
+
await scanOrphanInputs(config);
|
|
1274
|
+
}
|
|
1034
1275
|
}
|
|
1035
1276
|
function stopDiscovery() {
|
|
1036
1277
|
observer?.disconnect();
|