commit 2a0f58f0d156b500145a4f04f6a6b32d4ab3a11f Author: wujiawei <12345678> Date: Wed May 29 10:39:56 2024 +0800 [fix] change diff --git a/CloudClientInternalNetworkPenetration.README.md b/CloudClientInternalNetworkPenetration.README.md new file mode 100644 index 00000000..c4652dfd --- /dev/null +++ b/CloudClientInternalNetworkPenetration.README.md @@ -0,0 +1,92 @@ +### 内网穿透使用 + +#### 模块说明 + +| 模块 | 所属层级 | 描述 | 端口 | +|------------------------------------------------------------------------------------|------|------------------------|----------------------------------------------------------------| +| [wu-smart-agent-network-heartbeat-common](wu-smart-agent-network-heartbeat-common) | 基础模块 | 基于Netty数据解码、编码、通道处理器声明 | 无 | +| [wu-smart-agent-network-heartbeat-server](wu-smart-agent-network-heartbeat-server) | 启动模块 | 内网穿透服务端 | http端口:6001、tcp端口:7001 (默认tcp端口=http端口+1000 如:6001+1000=7001) | +| [wu-smart-agent-network-heartbeat-client](wu-smart-agent-network-heartbeat-client) | 启动模块 | 内网穿透客户端 | 6004 | + +#### 功能 + + 1.将局域网IP映射到公网IP + 2. 支持tcp、http映射 + +#### 使用 + +```text +客户端配置信息 +``` + +```yaml +spring: + middleground: + netty: + inet-host: 127.0.0.1 # 服务端地址 + inet-port: 7001 #服务端端口 + client-id: local # 客户端ID +``` + +```text +服务端配置客户端映射地址 +数据库表【internal_network_penetration_mapping】 添加数据 +``` + +| 客户端ID | 客户端真实地址 | 客户端真实端口 | 创建时间 | id | 是否删除 | 更新时间 | 访客端口 | 描述 | +|--------------|----------------|---------|------|----|------|------|-------|------------------------------------------------------------| +| local | 127.0.0.1 | 18080 | null | 1 | 0 | null | 19080 | 访客通过 --> 19080 --> 访问 --> 客户端 local本地的 18080 | +| local | 127.0.0.1 | 28080 | null | 2 | 0 | null | 29080 | 访客通过 --> 29080 --> 访问 --> 客户端 local本地的 28080 | +| local | 127.0.0.1 | 3306 | null | 3 | 0 | null | 4306 | 访客通过 --> 4306 --> 访问 --> 客户端 local本地的 3306 | +| local | 192.168.17.185 | 80 | null | 4 | 0 | null | 30080 | 访客通过 --> 30080 --> 访问 --> 客户端 local局域网内192.168.17.185的 80 | +| middleground | web-nginx | 80 | null | 5 | 0 | null | 31570 | 访客通过 --> 31570 --> 访问 --> 客户端 local局域网内web-nginx的 80 | + + + +#### 部署 + +##### 云端部署 + +```text +云端部署:内网穿透服务端 +如果云端需要部署云上暂存+内网穿透功能:需要部署 内网穿透服务端、暂存服务、内网穿透客户端、云上离线网关 +``` + +| 模块 | 说明 | 部署内网穿透必须 | 部署内网穿透+云上暂存必须 | +|------------------------------------------------------------------|------------|----------|---------------| +| [wu-smart-agent-network-heartbeat-server](wu-smart-agent-network-heartbeat-server) | 内网穿透+心跳服务端 | ☑️ | ☑️ | +| [wu-smart-agent-network-heartbeat-client](wu-smart-agent-network-heartbeat-client) | 内网穿透+心跳客户端 | ✖️ | ☑️ | +| [wu-lazy-cloud-staging-provider](wu-lazy-cloud-staging-provider) | 暂存服务 | ✖️ | ☑️ | +| [wu-lazy-cloud-central-gateway](wu-lazy-cloud-central-gateway) | 云上暂存网关 | ✖️ | ☑️ | +| [wu-lazy-cloud-central-gateway](wu-lazy-cloud-central-gateway) | 云下暂存网关 | ✖️ | ✖️ | + +##### 云网关部署 + +```text +云网关部署内网穿透客户端 +``` + +| 模块 | 说明 | 部署内网穿透必须 | 部署内网穿透+云上暂存必须 | +|------------------------------------------------------------------|------------|----------|---------------| +| [wu-smart-agent-network-heartbeat-server](wu-smart-agent-network-heartbeat-server) | 内网穿透+心跳服务端 | ✖️ | ✖️ | +| [wu-smart-agent-network-heartbeat-client](wu-smart-agent-network-heartbeat-client) | 内网穿透+心跳客户端 | ✖️ | ☑️ | +| [wu-lazy-cloud-staging-provider](wu-lazy-cloud-staging-provider) | 暂存服务 | ✖️ | ✖️ | +| [wu-lazy-cloud-central-gateway](wu-lazy-cloud-central-gateway) | 云上暂存网关 | ✖️ | ☑️ | +| [wu-lazy-cloud-central-gateway](wu-lazy-cloud-central-gateway) | 云下暂存网关 | ✖️ | ✖️ | + +##### 独立租户部署 + +```text +拥有内网穿透能力:需要部署内网穿透客户端 +内网穿透+离线暂存能力: 需要部署内网穿透客户端、离线网关、离线暂存服务 +``` + +| 模块 | 说明 | 部署内网穿透必须 | 部署内网穿透+云上暂存必须 | +|------------------------------------------------------------------|------------|----------|---------------| +| [wu-smart-agent-network-heartbeat-server](wu-smart-agent-network-heartbeat-server) | 内网穿透+心跳服务端 | ✖️ | ✖️ | +| [wu-smart-agent-network-heartbeat-client](wu-smart-agent-network-heartbeat-client) | 内网穿透+心跳客户端 | ☑️ | ☑️ | +| [wu-lazy-cloud-staging-provider](wu-lazy-cloud-staging-provider) | 暂存服务 | ✖️ | ☑️ | +| [wu-lazy-cloud-central-gateway](wu-lazy-cloud-central-gateway) | 云上暂存网关 | ✖️ | ✖️ | +| [wu-lazy-cloud-central-gateway](wu-lazy-cloud-central-gateway) | 云下暂存网关 | ✖️ | ☑️ | + + diff --git a/CloudClientInternalNetworkPenetration1.0-.png b/CloudClientInternalNetworkPenetration1.0-.png new file mode 100644 index 00000000..dd2d74dd Binary files /dev/null and b/CloudClientInternalNetworkPenetration1.0-.png differ diff --git a/CloudClientInternalNetworkPenetration1.0.puml b/CloudClientInternalNetworkPenetration1.0.puml new file mode 100644 index 00000000..c5391862 --- /dev/null +++ b/CloudClientInternalNetworkPenetration1.0.puml @@ -0,0 +1,77 @@ + + +@startuml + +title 内网穿透 + +actor 访客 + +package "客户端"{ + + + + node "Netty客户端" { + component [客户端当前通道]{ + [客户端心跳通道] + component [客户端代理通信通道]{ + [客户端通信通道读数据] + [客户端通信通道返回数据] + } + } + + +' [客户端当前通道] <...right... [客户端真实代理通道]: 返回真实服务请求结果 +' [客户端当前通道] ...right..> [客户端真实代理通道]: 转发二进制请求到真实服务通道 + } + node “客户端真实服务”{ + component [客户端需要代理的真实服务A]{ + [客户端真实通道读数据] + [客户端真实通道返回数据] + } + } + +' [客户端真实代理通道] ...right...> [客户端真实服务]: 发送真实二进制请求到真实服务 + +} +package "服务端"{ + node "Netty服务端" { + + component [Netty服务端通道] { + component [服务端心跳通道]{ + + } + component [服务端代理通信通道]{ + [服务端通信通道读数据] + [服务端通信通道返回数据] + } + } + + component [Netty服务端绑定访客端口] { + component [服务端访客真实通道]{ + [服务端访客真实通道读数据] + [服务端访客真实通道返回数据] + } + } + + } + +} + + + + [服务端心跳通道] <----> [客户端心跳通道]:长连接channel + + + [访客] ..> [服务端访客真实通道读数据]: 访客访问数据 + [服务端访客真实通道读数据] ...> [服务端通信通道读数据]: 服务端访客数据转发到通信通道 + [服务端通信通道读数据] ..down..> [客户端通信通道读数据]: 服务端通信将数据转发到客户端通信通道 + [客户端通信通道读数据] ..down..> [客户端真实通道读数据]: 客户端通信通道将数据转发道客户端端真实代理通道 + [客户端真实通道读数据] ..left..> [客户端真实通道返回数据]: 处理数据。。。 + [客户端真实通道返回数据] ..up..> [客户端通信通道返回数据]: 客户端真实服务返回数据 + [客户端通信通道返回数据] ..up..> [服务端通信通道返回数据]: 将客户端返回的数据发送给访客真实通道 + [服务端通信通道返回数据] ..up..> [服务端访客真实通道返回数据]: 返回数据 + + + + +@enduml \ No newline at end of file diff --git a/Cluster.puml b/Cluster.puml new file mode 100644 index 00000000..a7bec99d --- /dev/null +++ b/Cluster.puml @@ -0,0 +1,55 @@ +@startuml +'https://plantuml.com/component-diagram +title 网络代理集群模式设计集群初始化 node1 初始化后自动初始化node2 + +package "Network Cluster" { + + database "Network DB" { + + } + + node "node1"{ + + node "Network Server Node1" { + + } + node "Network Client Node1" { + + } + + "Network Server Node1" ----> "Network Server Node1" : 5. rescan node config and register + + "Network Server Node1" --left--> "Network DB" :node1 config story get and use + +' "Network DB" --down-- "Network Client Node1" :node1 config get and use + + "Network Client Node1" --up--> "Network Server Node1" :node1 register + + ' "Network Server Node2" --> "Network Client Node1" :scan node config and register + } + node "node2"{ + + node "Network Server Node2" { + + } + node "Network Client Node2" { + + } + + "Network Server Node2" ----> "Network Server Node2" : rescan node config and register + + "Network Server Node2" --right--> "Network DB" :1. node2 config story get and use + +' "Network DB" --down--> "Network Client Node2" :node2 config get and use + + "Network Client Node2" --up--> "Network Server Node2" :2. node2 register.. + "Network Server Node2" --down--> "Network Client Node2" :3. node2 register success + + "Network Client Node2" --left-> "Network Server Node1" :4. send msg to all register server ,tail it scan node config and register +' "Network Client Node2" ---> "Network Server Node2" :send msg to all register server ,tail it scan node config and register + } + +} + + +@enduml \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2fcc70b9 --- /dev/null +++ b/README.md @@ -0,0 +1,320 @@ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ * pathPatterns 格式 /acw-client-ui/** + * locations 格式 classpath:/acw-local-client/v1/ + *
+ * + * @return true、false + */ + @Override + public boolean support() { + return true; + } + + /** + * @return UI 描述 + */ + @Override + public String desc() { + return "Netty 本地客户端 UI"; + } + + /** + * @return UI 访问的path + * 例如:/acw-client-ui/** + */ + @Override + public String pathPatterns() { + return UI_URL; + } + + /** + * 返回页面首页地址 + * + * @return String + * 例如 /acw-client-ui/index.html + */ + @Override + public String index() { + return UI_URL_INDEX; + } + + /** + * @return 文件资源 + * 例如:classpath:/acw-local-client/v1/ + */ + @Override + public String locations() { + return CLASSPATH; + } +} diff --git a/wu-smart-agent-network-heartbeat-client/src/main/resources/META-INF/spring.factories b/wu-smart-agent-network-heartbeat-client/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..03a9ba6d --- /dev/null +++ b/wu-smart-agent-network-heartbeat-client/src/main/resources/META-INF/spring.factories @@ -0,0 +1,6 @@ +# Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.framework.smart.agent.network.heartbeat.client.EnableHeartbeatClientAutoConfiguration,\ +org.framework.smart.agent.network.heartbeat.client.config.NettyClientProperties,\ +org.framework.smart.agent.network.heartbeat.client.config.InitConfig,\ +org.framework.smart.agent.network.heartbeat.client.config.HeartbeatClientConfiguration diff --git a/wu-smart-agent-network-heartbeat-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/wu-smart-agent-network-heartbeat-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..88f206b2 --- /dev/null +++ b/wu-smart-agent-network-heartbeat-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,4 @@ +org.framework.smart.agent.network.heartbeat.client.EnableHeartbeatClientAutoConfiguration +org.framework.smart.agent.network.heartbeat.client.config.NettyClientProperties +org.framework.smart.agent.network.heartbeat.client.config.InitConfig +org.framework.smart.agent.network.heartbeat.client.config.HeartbeatClientConfiguration diff --git a/wu-smart-agent-network-heartbeat-client/src/main/resources/netty-client-local-ui/v1/assets/index-BTaJShFE.js b/wu-smart-agent-network-heartbeat-client/src/main/resources/netty-client-local-ui/v1/assets/index-BTaJShFE.js new file mode 100644 index 00000000..2acdebc4 --- /dev/null +++ b/wu-smart-agent-network-heartbeat-client/src/main/resources/netty-client-local-ui/v1/assets/index-BTaJShFE.js @@ -0,0 +1,65 @@ +var __=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var cie=__((Un,Yn)=>{(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))o(a);new MutationObserver(a=>{for(const r of a)if(r.type==="childList")for(const l of r.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&o(l)}).observe(document,{childList:!0,subtree:!0});function n(a){const r={};return a.integrity&&(r.integrity=a.integrity),a.referrerPolicy&&(r.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?r.credentials="include":a.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(a){if(a.ep)return;a.ep=!0;const r=n(a);fetch(a.href,r)}})();/** +* @vue/shared v3.4.21 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function g0(e,t){const n=new Set(e.split(","));return t?o=>n.has(o.toLowerCase()):o=>n.has(o)}const Yt={},tl=[],Et=()=>{},b_=()=>!1,ec=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),y0=e=>e.startsWith("onUpdate:"),ln=Object.assign,_0=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},w_=Object.prototype.hasOwnProperty,yt=(e,t)=>w_.call(e,t),Ie=Array.isArray,nl=e=>Us(e)==="[object Map]",tc=e=>Us(e)==="[object Set]",wr=e=>Us(e)==="[object Date]",Xe=e=>typeof e=="function",Ze=e=>typeof e=="string",Ba=e=>typeof e=="symbol",ct=e=>e!==null&&typeof e=="object",ys=e=>(ct(e)||Xe(e))&&Xe(e.then)&&Xe(e.catch),hm=Object.prototype.toString,Us=e=>hm.call(e),Fi=e=>Us(e).slice(8,-1),Cu=e=>Us(e)==="[object Object]",b0=e=>Ze(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,as=g0(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),nc=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},C_=/-(\w)/g,lo=nc(e=>e.replace(C_,(t,n)=>n?n.toUpperCase():"")),S_=/\B([A-Z])/g,qa=nc(e=>e.replace(S_,"-$1").toLowerCase()),Ys=nc(e=>e.charAt(0).toUpperCase()+e.slice(1)),Ki=nc(e=>e?`on${Ys(e)}`:""),za=(e,t)=>!Object.is(e,t),Wi=(e,t)=>{for(let n=0;n