react-native-codepush-sdk
Version:
A React Native CodePush SDK for over-the-air updates
140 lines (118 loc) • 4.13 kB
text/typescript
import RNFS from 'react-native-fs';
import { Platform } from 'react-native';
export class BundleManager {
private static readonly ORIGINAL_BUNDLE_PATH = Platform.select({
ios: `${RNFS.MainBundlePath}/main.jsbundle`,
android: 'assets://index.android.bundle',
});
private static readonly CUSTOM_BUNDLE_PATH = `${RNFS.DocumentDirectoryPath}/CustomCodePush/current.bundle`;
/**
* Get the path to the current bundle that should be loaded
*/
public static getCurrentBundlePath(): string {
// In a real implementation, this would check if a custom bundle exists
// and return its path, otherwise return the original bundle path
return BundleManager.CUSTOM_BUNDLE_PATH;
}
/**
* Check if a custom bundle exists
*/
public static async hasCustomBundle(): Promise<boolean> {
try {
return await RNFS.exists(BundleManager.CUSTOM_BUNDLE_PATH);
} catch (error) {
return false;
}
}
/**
* Copy a bundle from source to the current bundle location
*/
public static async installBundle(sourcePath: string): Promise<void> {
try {
const bundlePath = `${sourcePath}/index.bundle`;
if (!(await RNFS.exists(bundlePath))) {
throw new Error('Bundle file not found in update package');
}
// Ensure directory exists
const bundleDir = BundleManager.CUSTOM_BUNDLE_PATH.substring(
0,
BundleManager.CUSTOM_BUNDLE_PATH.lastIndexOf('/')
);
await RNFS.mkdir(bundleDir);
// Copy bundle
await RNFS.copyFile(bundlePath, BundleManager.CUSTOM_BUNDLE_PATH);
// Copy assets if they exist
const assetsSourcePath = `${sourcePath}/assets`;
const assetsDestPath = `${bundleDir}/assets`;
if (await RNFS.exists(assetsSourcePath)) {
// Custom folder copy implementation needed here. For now, this is a placeholder.
// TODO: Implement folder copy logic or use a third-party utility.
}
} catch (error) {
console.error('Failed to install bundle:', error);
throw error;
}
}
/**
* Remove the current custom bundle and revert to original
*/
public static async removeCustomBundle(): Promise<void> {
try {
if (await RNFS.exists(BundleManager.CUSTOM_BUNDLE_PATH)) {
await RNFS.unlink(BundleManager.CUSTOM_BUNDLE_PATH);
}
// Also remove assets directory
const bundleDir = BundleManager.CUSTOM_BUNDLE_PATH.substring(
0,
BundleManager.CUSTOM_BUNDLE_PATH.lastIndexOf('/')
);
const assetsPath = `${bundleDir}/assets`;
if (await RNFS.exists(assetsPath)) {
await RNFS.unlink(assetsPath);
}
} catch (error) {
console.error('Failed to remove custom bundle:', error);
throw error;
}
}
/**
* Validate that a bundle is properly formatted
*/
public static async validateBundle(bundlePath: string): Promise<boolean> {
try {
// Check if bundle file exists
if (!(await RNFS.exists(bundlePath))) {
return false;
}
// Check file size (should be > 0)
const stats = await RNFS.stat(bundlePath);
if (stats.size === 0) {
return false;
}
// Additional validation could include:
// - Checking bundle format
// - Verifying bundle signature
// - Testing bundle loading
return true;
} catch (error) {
console.error('Bundle validation failed:', error);
return false;
}
}
/**
* Get bundle metadata
*/
public static async getBundleMetadata(bundlePath: string): Promise<any> {
try {
const metadataPath = `${bundlePath}/metadata.json`;
if (await RNFS.exists(metadataPath)) {
const metadataContent = await RNFS.readFile(metadataPath, 'utf8');
return JSON.parse(metadataContent);
}
return null;
} catch (error) {
console.error('Failed to read bundle metadata:', error);
return null;
}
}
}