@shijiu/jsview 2.2.201 → 2.2.373

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,7 @@ import {
12
12
  parseArguments,
13
13
  Logger,
14
14
  } from './jsview-common.mjs';
15
+ import { assert } from 'node:console';
15
16
 
16
17
  // npm run build 时检查jsview-dom是否处于Debug模式。
17
18
  async function checkDomDebugDisabled(options) {
@@ -24,15 +25,52 @@ async function checkDomDebugDisabled(options) {
24
25
  }
25
26
  }
26
27
 
27
- // main.js处理AppData信息
28
- async function prepareMainAppData(options, fileMd5)
28
+ function getEntryFilePath(options)
29
+ {
30
+ let jsEntryFilePath;
31
+ const jsFileNames = fs.readdirSync(options.distJsDir);
32
+ for(const fileName of jsFileNames) {
33
+ if (fileName.startsWith('main.jsv') && fileName.endsWith('.js')) {
34
+ jsEntryFilePath = path.resolve(options.distJsDir, fileName);
35
+ break;
36
+ }
37
+ };
38
+
39
+ return jsEntryFilePath;
40
+ }
41
+
42
+ function getEncryptBase64(appPrivKey, appPubKey, plainText)
43
+ {
44
+ // 编码md5值
45
+ const encryptBase64 = crypto.privateEncrypt({
46
+ key: appPrivKey,
47
+ padding: crypto.constants.RSA_PKCS1_PADDING
48
+ },
49
+ Buffer.from(plainText)
50
+ ).toString('base64');
51
+
52
+ // 校验publicKey是否是配对的
53
+ const decryptText = crypto.publicDecrypt({
54
+ key: appPubKey,
55
+ padding: crypto.constants.RSA_PKCS1_PADDING
56
+ },
57
+ Buffer.from(encryptBase64, 'base64')
58
+ ).toString();
59
+ if (decryptText !== plainText) {
60
+ Logger.ErrorAndExit('public key dismath to private key!');
61
+ }
62
+
63
+ return encryptBase64;
64
+ }
65
+
66
+ function updateOptions(options)
29
67
  {
30
68
  // 加载私钥文件
31
69
  if (!fs.existsSync(options.appPrivKeyFile)) {
32
70
  Logger.ErrorAndExit('Failed to open private key file from ' + options.appPrivKeyFile);
33
71
  }
34
- const privateKeyText = fs.readFileSync(options.appPrivKeyFile);
35
- if (privateKeyText.indexOf('-----BEGIN PRIVATE KEY-----') < 0) {
72
+ const privKey = fs.readFileSync(options.appPrivKeyFile);
73
+ if (privKey.indexOf('-----BEGIN PRIVATE KEY-----') < 0) {
36
74
  Logger.ErrorAndExit('Get private from src/appConfig/app_sign_private_key.crt failed.');
37
75
  }
38
76
 
@@ -40,40 +78,62 @@ async function prepareMainAppData(options, fileMd5)
40
78
  if (!fs.existsSync(options.appPubKeyFile)) {
41
79
  Logger.ErrorAndExit('Failed to open public key file from ' + options.appPubKeyFile);
42
80
  }
43
- const publicKeyText = fs.readFileSync(options.appPubKeyFile);
44
- if (publicKeyText.indexOf('-----BEGIN PUBLIC KEY-----') < 0) {
81
+ const pubKey = fs.readFileSync(options.appPubKeyFile);
82
+ if (pubKey.indexOf('-----BEGIN PUBLIC KEY-----') < 0) {
45
83
  Logger.ErrorAndExit('Get private from src/appConfig/app_sign_public_key.pem failed.');
46
84
  }
47
- // 公钥格式转化pem -> der,因为java中的解码处理只识别der格式
48
- let publicKeyDerText = publicKeyText.toString();
49
- if (publicKeyDerText.indexOf('\r') >= 0) {
50
- publicKeyDerText = publicKeyDerText.replace(/\r/g, '');
51
- }
52
- if (publicKeyDerText.indexOf('\n') >= 0) {
53
- publicKeyDerText = publicKeyDerText.replace(/\n/g, '');
54
- }
55
- publicKeyDerText = publicKeyDerText.replace('-----BEGIN PUBLIC KEY-----', '');
56
- publicKeyDerText = publicKeyDerText.replace('-----END PUBLIC KEY-----', '');
57
-
58
- // 编码md5值
59
- const encryptCodeBase64 = crypto.privateEncrypt({
60
- key: privateKeyText,
61
- padding: crypto.constants.RSA_PKCS1_PADDING
62
- },
63
- Buffer.from(fileMd5)
64
- ).toString('base64');
65
85
 
66
86
  // 校验publicKey是否是配对的
67
- const decryptCode = crypto.publicDecrypt({
68
- key: publicKeyText,
69
- padding: crypto.constants.RSA_PKCS1_PADDING
70
- },
71
- Buffer.from(encryptCodeBase64, 'base64')
72
- ).toString();
73
- if (decryptCode !== fileMd5) {
74
- Logger.ErrorAndExit('public key dismath to private key!');
87
+ const encryptBase64 = getEncryptBase64(privKey, pubKey, 'crypto-test');
88
+ assert(encryptBase64)
89
+
90
+ const appEntryFilePath = getEntryFilePath(options);
91
+ const appEntryContent = fs.readFileSync(appEntryFilePath, 'utf8');
92
+ const appEntryMd5 = crypto.createHash('md5').update(appEntryContent).digest('hex');
93
+
94
+ options.appPrivKey = privKey;
95
+ options.appPubKey = pubKey;
96
+ options.appEntryMd5 = appEntryMd5;
97
+ }
98
+
99
+ function getOtherSignVerifyKeys(options)
100
+ {
101
+ const signVerifyKeys = {};
102
+ const signVerifyKeyFiles = fs.readdirSync(options.appConfigSVKeysDir);
103
+ for (const keyName of signVerifyKeyFiles) {
104
+ const filePath = path.resolve(options.appConfigSVKeysDir, keyName);
105
+ if (keyName == '.gitkeep') {
106
+ continue;
107
+ } else if (!keyName.endsWith('.pem')) {
108
+ Logger.Warn(`Ignore to append invalid signature verification key: ${filePath}`);
109
+ continue;
110
+ }
111
+
112
+ const keyValue = fs.readFileSync(filePath, 'utf8');
113
+ signVerifyKeys[keyName] = keyValue;
75
114
  }
76
115
 
116
+ return signVerifyKeys;
117
+ }
118
+
119
+ function publicPemToDer(pubKeyPem)
120
+ {
121
+ let pubKeyDer = pubKeyPem.toString();
122
+ if (pubKeyDer.indexOf('\r') >= 0) {
123
+ pubKeyDer = pubKeyDer.replace(/\r/g, '');
124
+ }
125
+ if (pubKeyDer.indexOf('\n') >= 0) {
126
+ pubKeyDer = pubKeyDer.replace(/\n/g, '');
127
+ }
128
+ pubKeyDer = pubKeyDer.replace('-----BEGIN PUBLIC KEY-----', '');
129
+ pubKeyDer = pubKeyDer.replace('-----END PUBLIC KEY-----', '');
130
+
131
+ return pubKeyDer;
132
+ }
133
+
134
+ // main.js处理AppData信息
135
+ async function prepareMainAppData(options)
136
+ {
77
137
  // 获取AppData信息
78
138
  const appConfigFilePath = path.resolve(options.appConfigDir, 'app.config.mjs');
79
139
  if (!fs.existsSync(appConfigFilePath)) {
@@ -82,29 +142,47 @@ async function prepareMainAppData(options, fileMd5)
82
142
  const appConfigFileUrl = url.pathToFileURL(appConfigFilePath);
83
143
  const { default: appConfig } = await import(appConfigFileUrl);
84
144
 
85
- // 获取 chunk.jsv的map
86
- const chunkHash = {};
145
+ // 获取PublickKey数组
146
+ const publicKeys = [publicPemToDer(options.appPubKey)];
147
+ // TODO: 验签PubKey的安全性尚需考虑
148
+ // const signVerifyKeys = getOtherSignVerifyKeys(options);
149
+ // for(const [keyName, keyValue] of Object.entries(signVerifyKeys)) {
150
+ // Logger.Info(` -> Append ${keyName} to jsvapp.`)
151
+ // publicKeys.push(publicPemToDer(keyValue));
152
+ // }
153
+
154
+ // 获取密文
155
+ const encryptBase64 = getEncryptBase64(options.appPrivKey, options.appPubKey, options.appEntryMd5);
156
+
157
+ // 获取 预加载的文件名
158
+ const filterKeys = [
159
+ 'domNativePath',
160
+ 'NativePlatformDomBridge',
161
+ 'ForgeExtension',
162
+ 'CodeRevision',
163
+ '__vue_app__',
164
+ 'mount("#app")',
165
+ ];
166
+ const preloadChunks = [];
87
167
  const jsFileNames = fs.readdirSync(options.distJsDir);
88
168
  for(const fileName of jsFileNames) {
89
169
  if (!fileName.startsWith('chunk.jsv') || !fileName.endsWith('.js')) {
90
170
  continue;
91
171
  }
92
172
 
93
- const hash = fileName.replace(/.*chunk\.jsv.([a-fA-F0-9].*).js/, '$1');
94
-
95
173
  const filePath = path.resolve(options.distJsDir, fileName);
96
174
  const sourceContent = fs.readFileSync(filePath, 'utf8');
97
- const md5 = sourceContent.replace(/\n/g, '').replace(/.*\/\*jsvmd5:([a-fA-F0-9]{8})[a-fA-F0-9]*\*\/.*/, '$1');
175
+ if(filterKeys.some(it => sourceContent.includes(it)) == false) {
176
+ continue;
177
+ }
98
178
 
99
- chunkHash[hash] = md5;
179
+ preloadChunks.push(fileName);
100
180
  }
101
181
 
102
182
  // 组装AppData
103
- appConfig.PublicKeys = [publicKeyDerText]; // 使用签名数组,支持后续追加签名
104
- appConfig.EncryptCodes = [encryptCodeBase64]; // 同样使用数组,支持后续追加
105
- appConfig.ChunkHash = chunkHash;
106
-
107
-
183
+ appConfig.PublicKeys = publicKeys; // 使用签名数组,支持后续追加签名
184
+ appConfig.EncryptCodes = [encryptBase64]; // 同样使用数组,支持后续追加
185
+ appConfig.PreloadChunks = preloadChunks;
108
186
 
109
187
  return JSON.stringify(appConfig);
110
188
  }
@@ -146,20 +224,31 @@ async function signApp(options)
146
224
 
147
225
  var sourceContent = fs.readFileSync(filePath, 'utf8');
148
226
 
149
- sourceContent += '\n'; // 把\n归入算进md5计算中
227
+ const jsvMd5 = crypto.createHash('md5').update(sourceContent).digest('hex');
228
+
229
+ // 获取签名并验签
230
+ const cryptoSigner = crypto.createSign('sha256').update(jsvMd5).end();
231
+ const cryptoVerifier = crypto.createVerify('sha256').update(jsvMd5).end();
232
+ const jsvSign = cryptoSigner.sign(options.appPrivKey, 'hex');
233
+ const verified = cryptoVerifier.verify(options.appPubKey, jsvSign, 'hex');
234
+ if(!verified) {
235
+ Logger.ErrorAndExit('Failed to create signature verification.');
236
+ }
150
237
 
151
- const jsvAppMd5 = crypto.createHash('md5').update(sourceContent).digest('hex');
152
238
  let appDataInfo = '';
153
239
  if (fileName.indexOf('main.jsv.') >= 0) {
154
240
  // 对main文件加入应用头信息
155
- appDataInfo = await prepareMainAppData(options, jsvAppMd5);
241
+ appDataInfo = await prepareMainAppData(options, jsvMd5);
156
242
 
157
243
  // 格式化jsvapp信息 /*jsvapp:内容长度:{内容}*/
158
244
  // 使用TextEncoder解决中文长度问题
159
245
  const infoLen = new TextEncoder().encode(appDataInfo).length;
160
246
  appDataInfo = '/*jsvapp:' + infoLen + ':' + appDataInfo + ':' + infoLen + ':jsvapp*/';
161
247
  }
162
- sourceContent = sourceContent + appDataInfo + '/*jsvmd5:' + jsvAppMd5 + '*/';
248
+ sourceContent = sourceContent + appDataInfo;
249
+
250
+ sourceContent = sourceContent + '/*jsvsign:' + jsvSign + '*/';
251
+
163
252
  fs.writeFileSync(filePath, sourceContent, 'utf8');
164
253
  }
165
254
  }
@@ -182,6 +271,9 @@ function redirectSourceMappingURL(options)
182
271
  '# sourceMappingURL=', '# sourceMappingURL=http://localhost:57245/map/');
183
272
  sourceContent = sourceContent.substring(0, mapUrlOffset) + mapUrlStr;
184
273
 
274
+ //添加一个回车,后续把\n归入算进md5计算中
275
+ sourceContent += '\n';
276
+
185
277
  fs.writeFileSync(filePath, sourceContent, 'utf8');
186
278
  };
187
279
  }
@@ -192,41 +284,35 @@ function makeMainJsvMjs(options, framework)
192
284
  return;
193
285
  }
194
286
 
195
- let jsEntryFilePath;
196
- const jsFileNames = fs.readdirSync(options.distJsDir);
197
- for(const fileName of jsFileNames) {
198
- if (fileName.startsWith('main.jsv') && fileName.endsWith('.js')) {
199
- jsEntryFilePath = path.resolve(options.distJsDir, fileName);
200
- break;
201
- }
202
- };
203
-
204
- const sourceContent = fs.readFileSync(jsEntryFilePath, 'utf8');
205
- const md5 = sourceContent.replace(/\n/g, '').replace(/.*\/\*jsvmd5:([a-fA-F0-9]{8})[a-fA-F0-9]*\*\/.*/, '$1');
206
- const newEntryFileName = `main.jsv.${md5}.js`;
287
+ // 更新entry hash
288
+ const jsEntryFilePath = getEntryFilePath(options);
289
+ const newEntryFileName = `main.jsv.${options.appEntryMd5.slice(0, 8)}.js`;
207
290
  const newEntryFilePath = path.resolve(path.dirname(jsEntryFilePath), newEntryFileName);
208
291
  Logger.Info(' -> ' + path.relative(options.projectDir, newEntryFilePath));
209
292
  fs.renameSync(jsEntryFilePath, newEntryFilePath);
210
293
 
294
+ // 更新index.html hash
211
295
  var indexContent = fs.readFileSync(options.distJsIndexFile, 'utf8');
212
296
  indexContent = indexContent.replace(path.basename(jsEntryFilePath), newEntryFileName);
213
297
  Logger.Info(' -> ' + path.relative(options.projectDir, options.distJsIndexFile));
214
298
  fs.writeFileSync(options.distJsIndexFile, indexContent, 'utf8');
215
299
 
300
+ // 创建JsView 加载用的 mjs entry
216
301
  const moduleEntryFilePath = newEntryFilePath.replace(/\.js$/, '.mjs');
217
302
  fs.copyFileSync(newEntryFilePath, moduleEntryFilePath);
303
+
304
+ // 创建JsView Debug用的 mjs entry
305
+ const debugEntryFilePath = path.resolve(options.distJsDir, 'main.jsv.mjs');
306
+ Logger.Info(' -> ' + path.relative(options.projectDir, debugEntryFilePath));
307
+ fs.copyFileSync(newEntryFilePath, debugEntryFilePath);
218
308
  }
219
309
 
220
310
  function makeDebugMap(options, framework)
221
311
  {
222
312
  fs.mkdirSync(options.distDebugMapDir, { recursive: true });
223
313
 
224
- let jsEntryFilePath;
225
314
  const jsFileNames = fs.readdirSync(options.distJsDir);
226
315
  for(const fileName of jsFileNames) {
227
- if (fileName.startsWith('main.jsv') && fileName.endsWith('.mjs')) {
228
- jsEntryFilePath = path.resolve(options.distJsDir, fileName);
229
- }
230
316
  if (!fileName.endsWith('.map')) {
231
317
  continue;
232
318
  }
@@ -237,16 +323,6 @@ function makeDebugMap(options, framework)
237
323
  fs.renameSync(from, to);
238
324
  };
239
325
 
240
- if (jsEntryFilePath) {
241
- let extName = path.extname(jsEntryFilePath);
242
- if (framework == 'vue') {
243
- extName = '.mjs'
244
- }
245
- const to = path.resolve(options.distJsDir, 'main.jsv' + extName);
246
- Logger.Info(' -> ' + path.relative(options.projectDir, to));
247
- fs.copyFileSync(jsEntryFilePath, to);
248
- }
249
-
250
326
  const jsmapServeName = 'jsview-jsmap-serve.mjs';
251
327
  const jsmapServePath = path.resolve(options.jsviewToolsDir, jsmapServeName);
252
328
  if (!fs.existsSync(jsmapServePath)) {
@@ -335,6 +411,9 @@ async function main(argv)
335
411
  redirectSourceMappingURL(options)
336
412
  Logger.Info('Redirected JsView source map...');
337
413
 
414
+ // EntryMd5 要放在sourcemap更新之后
415
+ updateOptions(options);
416
+
338
417
  Logger.Info('Checking JsView app config...');
339
418
  await checkAppConfig(options);
340
419
  Logger.Info('Checked JsView app config...');
@@ -55,8 +55,10 @@ async function runVueDevtoolsStandalone(options)
55
55
 
56
56
  const exportElectronMirror = exportPrefix + ' ELECTRON_MIRROR=http://cdn.release.qcast.cn/RN_devtools/electron-env/'
57
57
  let cmdline = exportElectronMirror + ' && npm install --package-lock-only --save-exact --save-dev electron@21.4.4 @vue/devtools@6.5.1'
58
+ cmdline = cmdline.replace(' &&', '&&'); // 解决window系统设置后多了一个空格的问题
58
59
  execCommand(cmdline);
59
60
  cmdline = exportElectronMirror + ' && npm ci'
61
+ cmdline = cmdline.replace(' &&', '&&'); // 解决window系统设置后多了一个空格的问题
60
62
  execCommand(cmdline);
61
63
 
62
64
  console.log()