@rechunk/core
Version:
Core functionality for ReChunk dynamic code loading and chunk management in React Native
263 lines (256 loc) • 8.89 kB
JavaScript
;
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
});