@applicaster/zapp-react-native-bridge
Version:
Applicaster Zapp React Native modules
178 lines (144 loc) • 4.45 kB
text/typescript
import { NativeModules } from "react-native";
import * as R from "ramda";
import md5 from "md5";
import { reducePromises } from "@applicaster/zapp-react-native-utils/arrayUtils";
import { bridgeLogger } from "../logger";
const { OfflineAssetsBridge } = NativeModules as {
OfflineAssetsBridge: OfflineAssetsBridgeI;
};
const defaultOptions = { abortOnFail: false };
export function isAssetCacheEnabled(): boolean {
return typeof OfflineAssetsBridge !== "undefined";
}
const reduceArrayResponse = R.when(
Array.isArray,
R.reduce((results, fileResult) => {
return R.mergeLeft(
results,
R.zipObj(R.keys(fileResult), R.values(fileResult))
);
}, {})
);
export async function storeFiles(
files: FilesList,
options: StoreFileOptions = defaultOptions
): Promise<StoredFilesResult> {
const result = await OfflineAssetsBridge?.storeFiles?.(files, options);
return reduceArrayResponse(result);
}
export function deleteFolders(folders: string[]): Promise<boolean> {
return reducePromises<string>(
(current: string, _) => OfflineAssetsBridge?.delete?.(current),
Promise.resolve(),
folders
);
}
export function getNativeRootPath(): Promise<string> {
return OfflineAssetsBridge?.getFilesDirectory?.();
}
function isImageAsset(value) {
if (typeof value !== "string") {
return false;
}
return /\.(jpg|png|jpeg)$/.test(value.split("?")[0]);
}
function getNativeFileName(nativeRootPath, url, path) {
const fileNameWithoutQuery = url.split("?")[0];
const extension = R.compose(R.last, R.split("."))(fileNameWithoutQuery);
return `${nativeRootPath}/${path}/${md5(url)}.${extension}`;
}
function propOrIndex(value) {
const number = Number(value);
return Number.isNaN(number) ? value : number;
}
export const fileIsSaved = R.curry((savedFilesResult, { file }) => {
if (typeof savedFilesResult === "undefined") return true;
return (
savedFilesResult?.[file] ||
savedFilesResult?.[file?.replace("file://", "")] ||
savedFilesResult?.[`file://${file}`]
);
});
export const remapAssetPath = (
assets: FilesList,
source: Record<string, any>,
localFiles: Record<string, string>,
tag: string = "unknown"
): Record<string, any> => {
const startTime = performance.now();
const result = assets.reduce(
(obj, { path, url }) => {
const localFile = localFiles[url];
if (!localFile) {
bridgeLogger.warning({
message: `remapAssetPath: Could not replace asset with local file for url: ${url}`,
data: { path, obj },
});
return obj;
}
let target = obj;
// Traverse the path to the appropriate nested object
for (let i = 0; i < path.length - 1; i++) {
target = target[path[i]] = target[path[i]];
}
if (!target) {
bridgeLogger.error({
message: `remapAssetPath: Could not find path ${path.join(".")}`,
data: obj,
});
} else {
// Set the final value
target[path[path.length - 1]] = localFile;
}
return obj;
},
JSON.parse(JSON.stringify(source))
);
bridgeLogger.log({
message: `remapAssetPath: remapping took ${
performance.now() - startTime
} ms, tag: ${tag}`,
});
return result;
};
export function collectAssets(object, nativeRootPath, source, path = []) {
if (!object) {
const message = `collectAssets: object is null or undefined for source: ${source}`;
bridgeLogger.error({ message, data: { object, path } });
throw new Error(message);
}
return Object.entries(object).reduce((assets, [prop, value]) => {
const propPath = [...path, propOrIndex(prop)];
if (R.is(Object, value) && !R.isEmpty(value)) {
assets.push(...collectAssets(value, nativeRootPath, source, propPath));
} else if (isImageAsset(value)) {
assets.push({
url: value,
path: propPath,
file: getNativeFileName(nativeRootPath, value, source),
source,
});
}
return assets;
}, []);
}
export const getFolders = R.compose(
R.uniq,
// @ts-ignore
R.map(R.compose(R.join("/"), R.init, R.split("/"), R.prop("file")))
);
if (process.env.NODE_ENV === "test") {
module.exports = {
isImageAsset,
getNativeFileName,
propOrIndex,
remapAssetPath,
collectAssets,
getFolders,
deleteFolders,
getNativeRootPath,
storeFiles,
reduceArrayResponse,
fileIsSaved,
};
}