lisonge / vite-plugin-monkey

A vite plugin server and build your.user.js for userscript engine like Tampermonkey, Violentmonkey, Greasemonkey, ScriptCat
MIT License
1.33k stars 70 forks source link

DEV模式下获取不到@require的脚本里的全局变量 #12

Closed fuxin052 closed 2 years ago

fuxin052 commented 2 years ago

1.x和2.00都有这个情况, 导致开发模式不顺利

fuxin052 commented 2 years ago

我采用@require方式引而不是用包方式引入, 是因为这个代码被编译过后效果好像发生了变化, 其中的原因我也没搞明白, 所以用@require让它不被编译

lisonge commented 2 years ago

~不好意思刚刚在打游戏没注意邮件通知~

@require 如果是 iife ,会在那个作用域 出现一个变量,如果是 umd ,会在那个作用域的 window 添加一个 属性 因为 dev 模式的 你的代码 运行的 window 是 被注入网页的 window ,不是 monkey 插件的 window

lisonge commented 2 years ago

如果你引入的包是 umd 格式的,假设你的 cdn 导出的叫 Vue,可以通过以下方式获取

import{monkeyWindow}from'$'
const Vue  = monkeyWindow.Vue

如果是 iife 格式,你需要在刚刚的cdn后面加入额外 @require 一个cdn,它的内容是这样

try{
  this.Vue = this.Vue ?? Vue
}catch{}
try{
  window.Vue = window.Vue ?? Vue
}catch{}

或者使用 //@grant none 或者 //@unwrap这样就只有一个 window ,限制是你不能使用 GM_api

lisonge commented 2 years ago

我采用@require方式引而不是用包方式引入, 是因为这个代码被编译过后效果好像发生了变化, 其中的原因我也没搞明白, 所以用@require让它不被编译

@fuxin052 你要 @require 的 cdn url 是什么呢?这个包是什么?

fuxin052 commented 2 years ago

我做的是115提取下载链接 参照这个chrome扩展的代码

包是https://github.com/acgotaku/115/blob/master/src/vendor/jsencrypt.js 加解密代码是https://github.com/acgotaku/115/blob/master/src/js/lib/secret.js

我尝试过

  1. 使用npm的jsencrypt@3,当时没注意版本, 加密的的数据服务器不能解密, 使用npm的jsencrypt@2.3.0, 结果忘记了, 反正不行 我又试了下, 是执行报错, 里面有一行 报错信息如下 https://github.com/travist/jsencrypt/blob/v2.3.0/bin/jsencrypt.js#L2021

    Uncaught ReferenceError: KJUR is not defined
    at jsencrypt.js:2021:42
    at node_modules/jsencrypt/bin/jsencrypt.js (jsencrypt.js:4342:1)
    at __require (chunk-RSJERJUL.js?v=71cb38fe:3:50)
    at dep:jsencrypt:1:16
  2. jsencrypt.js文件下载到项目目录 通过import引入, 此时jsencrypt.js引入时会报错SyntaxError: The requested module '/src/libs/jsencrypt.js?t=1660359267496' does not provide an export named 'default' (at secret.ts:2:8), 按下面修改后可引入 但是 加密成功, 但是服务器返回的加密数据我不能解密

    // 注释下面
    // (function (global, factory) {
    //   if (typeof exports === 'object' && typeof module !== 'undefined') {
    //     module.exports = factory;
    //   } else if (typeof define === 'function' && define.amd) {
    //     define(factory);
    //   } else {
    //     global.JSEncrypt = factory;
    //   }
    // }(this, JSEncryptExports.JSEncrypt));
    // 改成这个
    export default JSEncryptExports.JSEncrypt;
  3. jsencrypt.js用@require引入, secret.js改造下也用@require引入, 其中的md5也是用cdn, 结果是dev模式方法找不到, build模式加密解密都能成功 使用monkeyWindow拿到jsencrypt后成功

1和2产生的原因完全搞不清楚, 所以我想如果能解决3的问题, 应该也解决了

fuxin052 commented 2 years ago

刚刚试了下monkeyWindow可以, 感谢 就是有一点 调用的时候类型报错 只能用(monkeyWindow as any).JSEncrypt()来处理

lisonge commented 2 years ago

类型错误也可以通过以下方式解决

首先 pnpm add jsencrypt@version 以便在代码里使用类型,version 是你 @require 加载的版本,最新包就用 latest

我看你评论里说使用的 https://unpkg.com/browse/jsencrypt@2.3.0/ 这个版本很旧是没有类型提示的,只有 3.x.x 才有,如果想使用正确的类型提示,确保你手动 @require 的是 3.x.x 或者自己手动声明

然后可通过以下方式使用

方式1,使用 Reflect.get 或者 //@ts-ignore

// main.ts
import{monkeyWindow}from'$'
import type JSEncryptT from 'jsencrypt';

const  JSEncrypt  = Reflect.get(monkeyWindow, 'JSEncrypt') as typeof JSEncryptT
const obj = new JSEncrypt()
// main.ts
import{monkeyWindow}from'$'
import type JSEncryptT from 'jsencrypt';
//@ts-ignore
const  JSEncrypt  = monkeyWindow.JSEncrypt as typeof JSEncryptT
const obj = new JSEncrypt()

方式2,扩展类型声明,修改 vite-env.d.ts

// file->vite-env.d.ts
/// <reference types="vite/client" />

/**
 * alias of vite-plugin-monkey/dist/client
 */
declare module '$' {
  export * from 'vite-plugin-monkey/dist/client';
  // ------new-start-----
  import type { MonkeyWindow } from 'vite-plugin-monkey/dist/client';
  import type JSEncryptT from 'jsencrypt';
  export const monkeyWindow: MonkeyWindow & {
    JSEncrypt: typeof JSEncryptT;
  };
 //---------new-----end-------
}

然后正常使用

// main.ts
import{monkeyWindow}from'$'
const  JSEncrypt  = monkeyWindow.JSEncrypt
const obj = new JSEncrypt()

推荐使用 方式2,一次声明,到处使用 另外方式1需要注意必须使用 import type 否则 vite build 的时候可能把 jsencrypt 打包进去

lisonge commented 2 years ago

对于 https://github.com/lisonge/vite-plugin-monkey/issues/12#issuecomment-1213622455 的两个错误 错误1:Uncaught ReferenceError: KJUR is not defined ,你的代码 dev 模式下是 esm ,esm 是 严格模式,不允许使用未声明的变量 而 115/**/jsencrypt.js 不报这个错是因为 它用 var 声明了 KJUR 解决方式也很简单

['KJUR'].forEach((s) => {
  Reflect.set(window, s, void 0);
});
// 必须在导入 `jsencrypt` 之前运行
import JSEncrypt from 'jsencrypt';

错误2我不太明白意思,能给出具体的加密数据展示吗?

fuxin052 commented 2 years ago

我创了一个仓库来展示这个错误: https://github.com/fuxin052/monkey-test secretRequire方法是使用@require的解密库 secretLib是将解密库放到本地通过import引入

测试结果如下

// 1. 只执行require的解密方法
var r = secretRequire.decode(str, key);
console.log('r: ', r);
// var l = secretLib.decode(str, key);
// console.log('l: ', l);
// r:  {"2406025156334976380":{"file_name":"323.txt","file_size":"3","pick_code":"argtzklaodttphfjt","url":{"url":"https:\/\/cdnfhnfile.115.com\/5fab54fa697fc76ecd15c6532b6d52d7a189c905\/323.txt?t=1660404201&u=364042722&s=3250586&d=vip-666703667-argtzklaodttphfjt-1&c=0&f=1&k=610d9fa42bfa26b67aa79a3a5bcdad6f&us=32505856&uc=10&v=1","client":3,"desc":null,"isp":null,"oss_id":"fhnfile\/5fab54fa697fc76ecd15c6532b6d52d7a189c905","ooid":"\/c101\/0\/3B20hkVMCVqrHgBXvXcNNnjdHH8kgD_v4ZuaPt-A"}}}

// 2. 只执行使用import引入的解密方法
// var r = secretRequire.decode(str, key);
// console.log('r: ', r);
var l = secretLib.decode(str, key);
console.log('l: ', l);
// l:  G›«ï؂G—{
// 乱码代表解密失败

// 3. 以上两种同时执行
var r = secretRequire.decode(str, key);
console.log('r: ', r);
var l = secretLib.decode(str, key);
console.log('l: ', l);
// r:  {"2406025156334976380":{"file_name":"323.txt","file_size":"3","pick_code":"argtzklaodttphfjt","url":{"url":"https:\/\/cdnfhnfile.115.com\/5fab54fa697fc76ecd15c6532b6d52d7a189c905\/323.txt?t=1660404201&u=364042722&s=3250586&d=vip-666703667-argtzklaodttphfjt-1&c=0&f=1&k=610d9fa42bfa26b67aa79a3a5bcdad6f&us=32505856&uc=10&v=1","client":3,"desc":null,"isp":null,"oss_id":"fhnfile\/5fab54fa697fc76ecd15c6532b6d52d7a189c905","ooid":"\/c101\/0\/3B20hkVMCVqrHgBXvXcNNnjdHH8kgD_v4ZuaPt-A"}}}
// l:  {"2406025156334976380":{"file_name":"323.txt","file_size":"3","pick_code":"argtzklaodttphfjt","url":{"url":"https:\/\/cdnfhnfile.115.com\/5fab54fa697fc76ecd15c6532b6d52d7a189c905\/323.txt?t=1660404201&u=364042722&s=3250586&d=vip-666703667-argtzklaodttphfjt-1&c=0&f=1&k=610d9fa42bfa26b67aa79a3a5bcdad6f&us=32505856&uc=10&v=1","client":3,"desc":null,"isp":null,"oss_id":"fhnfile\/5fab54fa697fc76ecd15c6532b6d52d7a189c905","ooid":"\/c101\/0\/3B20hkVMCVqrHgBXvXcNNnjdHH8kgD_v4ZuaPt-A"}}}

结果有点出乎意料, 第3个居然都解密成功了, 让人摸不着头脑

lisonge commented 2 years ago

对于第二种情况

// 2. 只执行使用import引入的解密方法
// var r = secretRequire.decode(str, key);
// console.log('r: ', r);
var l = secretLib.decode(str, key);
console.log('l: ', l);
// l:  G�«ïØ�G�{
// 乱码代表解密失败

首先由于 https://github.com/fuxin052/monkey-test/blob/eb980f23f64486402fca1c520046f019cafc8616/src/libs/jsencrypt.js#L4221-L4229 image

也就是报错直接返回 false

然后由于 https://github.com/fuxin052/monkey-test/blob/eb980f23f64486402fca1c520046f019cafc8616/src/libs/jsencrypt.js#L1744 image

代码运行到这里根据 esm 严格模式 会直接报错 Uncaught ReferenceError: v is not defined 然后上面的函数就直接返回 false

解码函数接收到的就是 falsefalsefalsefalsefalsefalse..., 最终造成乱码


对于第三种同时执行的情况

// 3. 以上两种同时执行
var r = secretRequire.decode(str, key);
console.log('r: ', r);
var l = secretLib.decode(str, key);
console.log('l: ', l);

由于 @require 的代码运行在 非严格模式 下,并且你的代码顺序是先执行 secretRequire ,当执行到 https://github.com/fuxin052/monkey-test/blob/eb980f23f64486402fca1c520046f019cafc8616/src/libs/jsencrypt.js#L1744 image 非严格模式下,相当于 window.v = xxx 不会报错,直接给 window 添加一个 v 属性,最终也就输出了正确的结果

接下来执行 secretLib.decode,当执行到它的相同位置的代码时,由于此时 window.v 是有值的,所以虽然代码在严格模式下, 但是并不会报 v is not defined 的错,代码正常执行,也输出了正确的结果

lisonge commented 2 years ago

可以用 eslint 检查潜在的错误

eslint.org/play/**/jsencrypt.js

image

fuxin052 commented 2 years ago

非常感谢解答了超出范围的问题