react-native-update
Version:
react-native hot update
188 lines (157 loc) • 6.58 kB
JavaScript
const fs = require('fs');
/**
* 从 Hermes 字节码文件中读取 metadata
* 支持两种方式:
* 1. 从文件末尾的自定义 Meta 块读取(新方式,推荐)
* 2. 从 Hermes -meta 参数注入的数据读取(旧方式,备用)
*/
function readBundleMetadata(filePath) {
const buffer = fs.readFileSync(filePath);
// 优先尝试从文件末尾的自定义 Meta 块读取
const metaFromFooter = readMetadataFromHBCFooter(buffer);
if (metaFromFooter) {
return metaFromFooter;
}
// 如果末尾没有 Meta 块,尝试从 Hermes -meta 参数注入的数据读取(备用方案)
console.log('No metadata footer found, trying to read from Hermes -meta parameter...');
return readMetadataFromHermesMeta(buffer);
}
/**
* 从 HBC 文件末尾的自定义 Meta 块读取元数据
* 格式: [原始HBC][MAGIC_START][LENGTH][JSON_DATA][MAGIC_END]
*/
function readMetadataFromHBCFooter(buffer) {
const MAGIC = Buffer.from('RNUPDATE', 'utf8'); // 8 bytes
const MAGIC_SIZE = 8;
const LENGTH_SIZE = 4;
console.log(`\n[DEBUG] Reading metadata from HBC footer...`);
console.log(`[DEBUG] File size: ${buffer.length} bytes`);
console.log(`[DEBUG] Last 100 bytes (hex): ${buffer.slice(-100).toString('hex')}`);
console.log(`[DEBUG] Last 50 bytes (utf8): ${buffer.slice(-50).toString('utf8', 0, 50).replace(/[^\x20-\x7E]/g, '.')}`);
// 检查文件是否足够大以包含 meta 块
// 最小大小: MAGIC_START(8) + LENGTH(4) + JSON(至少2字节"{}") + MAGIC_END(8) = 22 bytes
if (buffer.length < 22) {
console.log(`[DEBUG] File too small: ${buffer.length} < 22`);
return null;
}
// 从文件末尾向前查找最后一个 MAGIC_END
const lastMagicIndex = buffer.lastIndexOf(MAGIC);
console.log(`[DEBUG] MAGIC string: "${MAGIC.toString('utf8')}"`);
console.log(`[DEBUG] Last MAGIC index: ${lastMagicIndex}`);
if (lastMagicIndex === -1) {
console.log(`[DEBUG] MAGIC not found in file`);
return null;
}
// MAGIC_END 应该在文件末尾
console.log(`[DEBUG] Expected MAGIC_END at: ${buffer.length - MAGIC_SIZE}`);
console.log(`[DEBUG] Found MAGIC_END at: ${lastMagicIndex}`);
if (lastMagicIndex + MAGIC_SIZE !== buffer.length) {
console.warn(`⚠️ Found MAGIC but not at file end (expected ${buffer.length - MAGIC_SIZE}, found ${lastMagicIndex})`);
return null;
}
// 计算 MAGIC_START 的位置
// 从 MAGIC_END 向前:MAGIC_END(8) + JSON(?) + LENGTH(4) + MAGIC_START(8)
const magicEndStart = lastMagicIndex;
// 读取长度字段(在 MAGIC_END 之前的 4 字节)
const lengthStart = magicEndStart - LENGTH_SIZE;
if (lengthStart < MAGIC_SIZE) {
console.log(`[DEBUG] lengthStart too small: ${lengthStart} < ${MAGIC_SIZE}`);
return null; // 文件太小
}
const jsonLength = buffer.readUInt32LE(lengthStart);
console.log(`[DEBUG] JSON length from buffer: ${jsonLength}`);
// 验证长度是否合理(JSON 数据应该小于 10KB)
if (jsonLength > 10240 || jsonLength < 2) {
console.warn(`⚠️ Invalid JSON length: ${jsonLength}`);
return null;
}
// 计算 JSON 数据的起始位置
const jsonStart = lengthStart - jsonLength;
console.log(`[DEBUG] JSON start position: ${jsonStart}`);
if (jsonStart < MAGIC_SIZE) {
console.log(`[DEBUG] jsonStart too small: ${jsonStart} < ${MAGIC_SIZE}`);
return null;
}
// 验证 MAGIC_START
const magicStartPos = jsonStart - MAGIC_SIZE;
console.log(`[DEBUG] MAGIC_START position: ${magicStartPos}`);
const magicStart = buffer.slice(magicStartPos, jsonStart);
console.log(`[DEBUG] MAGIC_START bytes: ${magicStart.toString('hex')}`);
console.log(`[DEBUG] Expected MAGIC bytes: ${MAGIC.toString('hex')}`);
if (!magicStart.equals(MAGIC)) {
console.warn('⚠️ MAGIC_START mismatch');
console.warn(` Expected: ${MAGIC.toString('hex')}`);
console.warn(` Got: ${magicStart.toString('hex')}`);
return null;
}
// 读取 JSON 数据
try {
const jsonBuffer = buffer.slice(jsonStart, lengthStart);
const jsonString = jsonBuffer.toString('utf8');
console.log(`[DEBUG] JSON string: ${jsonString}`);
const metadata = JSON.parse(jsonString);
console.log(`✅ Found metadata in HBC footer: contentHash=${metadata.contentHash?.slice(0, 16)}...`);
console.log(` Metadata: ${JSON.stringify(metadata, null, 2)}`);
return metadata;
} catch (error) {
console.error('❌ Failed to parse metadata JSON:', error);
return null;
}
}
/**
* 从 Hermes -meta 参数注入的数据读取(备用方案)
*/
function readMetadataFromHermesMeta(buffer) {
const metadata = {};
const searchPattern = Buffer.from('contentHash=', 'utf8');
// 在整个文件中查找模式
let index = buffer.indexOf(searchPattern);
if (index !== -1) {
// 找到了 "contentHash=",读取后面的 hash 值
const hashStart = index + searchPattern.length;
// Hash 应该是 64 个十六进制字符 (SHA256)
let hashEnd = hashStart;
while (hashEnd < buffer.length && hashEnd < hashStart + 64) {
const byte = buffer[hashEnd];
// 检查是否是有效的十六进制字符 (0-9, a-f, A-F)
if ((byte >= 48 && byte <= 57) || // 0-9
(byte >= 97 && byte <= 102) || // a-f
(byte >= 65 && byte <= 70)) { // A-F
hashEnd++;
} else {
break;
}
}
if (hashEnd > hashStart) {
metadata.contentHash = buffer.toString('utf8', hashStart, hashEnd);
console.log(`✅ Found contentHash in Hermes -meta: ${metadata.contentHash.slice(0, 16)}...`);
}
} else {
console.warn('⚠️ contentHash not found in Hermes -meta parameter');
}
return metadata;
}
/**
* 从普通 JS bundle 的注释中读取 metadata
*/
function readMetadataFromJSBundle(buffer) {
const content = buffer.toString('utf8');
const metadata = {};
// 查找 //# BUNDLE_METADATA {...} 注释
const metaMatch = content.match(/\/\/# BUNDLE_METADATA\s+(\{[^}]+\})/);
if (metaMatch) {
try {
const metaObj = JSON.parse(metaMatch[1]);
metadata.contentHash = metaObj.contentHash;
console.log(`✅ Found contentHash in JS bundle comment: ${metadata.contentHash.slice(0, 16)}...`);
} catch (e) {
console.error('Failed to parse BUNDLE_METADATA comment:', e);
}
} else {
console.warn('⚠️ BUNDLE_METADATA comment not found in JS bundle');
}
return metadata;
}
// 测试读取
const metadata = readBundleMetadata('./index.bundlejs');
console.log('Final metadata:', metadata);