UNPKG

@rechunk/core

Version:

Core functionality for ReChunk dynamic code loading and chunk management in React Native

263 lines (256 loc) 8.89 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { default: () => index_default, importChunk: () => importChunk, on: () => on }); module.exports = __toCommonJS(index_exports); // src/polyfill.ts var import_base_64 = require("base-64"); if (!global.btoa) { global.btoa = import_base_64.encode; } if (!global.atob) { global.atob = import_base_64.decode; } // src/ChunkManager.ts var import_react_native = require("@metro-requirex/react-native"); var import_api_client = require("@rechunk/api-client"); var import_tiny_emitter = require("tiny-emitter"); var import_tiny_invariant = __toESM(require("tiny-invariant")); var import_tiny_warning = __toESM(require("tiny-warning")); // src/jws.ts var import_jsrsasign = require("jsrsasign"); var createHashGenerator = (algorithm) => { return (data) => { const md = new import_jsrsasign.KJUR.crypto.MessageDigest({ alg: algorithm }); md.updateString(data); return md.digest(); }; }; var createJWSVerifier = (algorithm, publicKey) => { const verify = (jwsString) => { return import_jsrsasign.KJUR.jws.JWS.verify(jwsString, publicKey, [algorithm]); }; const decode2 = (jwsString) => { return import_jsrsasign.KJUR.jws.JWS.parse(jwsString); }; return { verify, decode: decode2 }; }; var createIntegrityChecker = (hashAlgorithm, jwsAlgorithm, publicKey) => { const generateHash = createHashGenerator(hashAlgorithm); const { verify: verifyJWS, decode: decodeJWS } = createJWSVerifier( jwsAlgorithm, publicKey ); const verify = (data, jwsString) => { if (!verifyJWS(jwsString)) { return false; } const decoded = decodeJWS(jwsString); const expectedHash = decoded.payloadPP; const actualHash = generateHash(data); return expectedHash === actualHash; }; return { generateHash, verify }; }; // src/ChunkManager.ts var ChunkManager = class _ChunkManager extends import_tiny_emitter.TinyEmitter { /** * Represents a static instance of the ChunkManager class. * This static instance allows access to the ChunkManager functionality without the need to create new instances. * @type {ChunkManager} * @protected */ static instance; /** * Cache to store imported chunks. * This cache improves performance by storing previously imported chunks for future use. * @type {Record<string, React.ComponentType<any>>} * @protected */ cache = {}; /** * An instance of the ReChunkApi class that is used to make requests to the API. * * @type {ReChunkApi} * @protected */ request = new import_api_client.ChunksApi( new import_api_client.Configuration({ basePath: process.env.__RECHUNK_HOST__ }) ); /** * Resolver function used to resolve chunk imports. * This function is responsible for dynamically loading and resolving imported chunks. * @type {ResolverFunction} * @protected */ resolver = async (chunkId) => { try { if (!chunkId || typeof chunkId !== "string") { throw new Error("[ReChunk]: Invalid chunkId provided"); } const response = await this.request.getChunkById( { projectId: process.env.__RECHUNK_PROJECT__, chunkId }, { headers: { Authorization: `Basic ${btoa( `${process.env.__RECHUNK_PROJECT__}:${process.env.__RECHUNK_READ_KEY__}` )}` } } ); if (!response.data || !response.token) { throw new Error(`[ReChunk]: Failed to fetch chunk with ID ${chunkId}`); } return response; } catch (error) { throw new Error(`[ReChunk]: Failed to fetch chunk: ${error.message}`); } }; /** * Flag indicating whether verification is enabled. * @type {boolean} * @protected */ verify = true; /** * The public key used to verify function. * @type {string} * @protected */ publicKey = process.env.__RECHUNK_PUBLIC_KEY__; /** * Get the shared instance of ChunkManager. * @returns {ChunkManager} The shared instance of ChunkManager. */ static get shared() { if (!_ChunkManager.instance) { _ChunkManager.instance = new _ChunkManager(); } return _ChunkManager.instance; } /** * Creates an instance of ChunkManager. * @param {Object} nativeChunkManager - Native chunk manager module. * @throws {Error} Throws error if instance is already created or if native chunk manager module is not found. */ constructor() { super(); (0, import_tiny_invariant.default)( !_ChunkManager.instance, "[ReChunk]: ChunkManager was already instantiated. Use ChunkManager.shared instead." ); } /** * Converts chunk string to a JavaScript component. * @param {string} chunkId - The chunk identifier. * @param {string} chunk - The chunk string. * @returns {*} The JavaScript component generated from the chunk. */ chunkToComponent(chunkId, chunk) { const exports2 = {}; const module2 = { exports: exports2 }; const Component = (0, import_react_native.evalx)(chunk); this.cache[chunkId] = Component; this.emit(chunkId); return Component; } /** * Adds configuration settings to the ChunkManager instance. * This method sets the public key, resolver function, verification flag, and global object for the ChunkManager instance. * @param {Configuration} config - The configuration object for ChunkManager. * @param {boolean} [config.verify] - Flag indicating whether verification is enabled. * @param {CustomRequire} [config.global] - Object representing protected global variables and functions. * @param {ResolverFunction} [config.resolver] - The resolver function used to resolve chunk imports. * @param {ResolverFunction} [config.publicKey] - The publicKey for ChunkManager configuration. */ addConfiguration({ resolver, verify, publicKey }) { if (resolver) { this.resolver = resolver; } if (verify !== void 0) { (0, import_tiny_warning.default)(verify, "[ReChunk]: verification is off; chunks are insecure."); this.verify = verify; } if (publicKey) { this.publicKey = publicKey; } } /** * Imports a chunk asynchronously and returns the corresponding JavaScript component. * @param {string} chunkId - The ID of the chunk to import. * @returns {Promise<*>} A promise resolving to the JavaScript component imported from the chunk. */ async importChunk(chunkId) { if (this.cache[chunkId]) { return { default: this.cache[chunkId] }; } const chunk = await this.resolver(chunkId); const integrityChecker = createIntegrityChecker( "sha256", "RS256", this.publicKey ); if (this.verify) { const verified = integrityChecker.verify(chunk.data, chunk.token); if (!verified) { throw new Error("cannot verify hash"); } return { default: this.chunkToComponent(chunkId, chunk.data) }; } return { default: this.chunkToComponent(chunkId, chunk.data) }; } }; // src/index.ts async function importChunk(chunkId) { return ChunkManager.shared.importChunk(chunkId); } function addConfiguration(config) { ChunkManager.shared.addConfiguration(config); } function on(event, callback, ctx) { return ChunkManager.shared.on(event, callback, ctx); } var index_default = { addConfiguration }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { importChunk, on });