npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-native-archives

v0.2.7

Published

react native pack manager

Downloads

65

Readme

react-native-archives

说明

项目源码部分来源或参考 react-native-pushyreact-native-fs

安装

yarn add react-native-archives

Android

<manifest>
...
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
<application>

iOS

<key>NSPhotoLibraryUsageDescription</key>
<string>请允许APP访问您的相册</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>请允许APP保存图片到相册</string>

使用

import {
    fs, 
    utils, 
    dirs, 
    status, 
    external, 
    fetchPlus, 
    HttpService
} from "react-native-archives"

dirs

手机内部存储相关文件夹,专属于 app 的私有目录,无需权限

dirs:{
    // 安装包位置
    // 二者可以使用 zip 解码读取
    MainBundle:"",  
      // Android: /data/app/com.vendor.product-.../base.apk
      // iOS: /prefix/Bundle/Application/E57.../project.app

    // 个人文件保存目录,可以创建子文件夹
    // 保存用户的私有文件,云同步时一般都会同步该文件夹
    Document:"",    
      // Android: /data/user/0/com.vendor.product/files
      // iOS: /prefix/Data/Application/F18.../Documents

    // app 配置文件保存目录,可以创建子文件夹
    // iOS 默认有 "Caches"/"Preferences" 两个文件夹
    // (Preferences 可存放一些用户的偏好设置)
    // 云同步会同步除 "Caches" 文件夹之外的所有文件
    // Android 云同步规则未知
    Library:"",     
      // Android: /data/user/0/com.vendor.product/files
      // iOS: /prefix/Data/Application/F18.../Library

    // 缓存文件保存目录
    // 用于存放不重要,删除了也没影响,但希望尽量不要删除的文件
    Caches: "",     
      // Android: /data/user/0/com.vendor.product/cache
      // iOS: /prefix/Data/Application/F18.../Library/Caches

    // 临时文件保存目录
    // 存放随时可删除而不影响运行的临时文件
    Temporary:"",   
      // Android: /data/user/0/com.vendor.product/cache
      // iOS: /prefix/Data/Application/F18.../tmp
}

external

Android only (iOS 仅能访问 app 所属沙盒目录),外部存储目录,有可能在 SD 卡中,若手机没有 SD 卡,一般也可用,系统虚拟出来的外部存储目录。

若需要保存较大文件,建议存在这个系列的目录下,而不是 dirs 目录下

external:{
    // app 在外部存储上的缓存、文件目录,也会随着 app 的卸载而删除
    AppCaches: "",
      // Android: /storage/emulated/0/Android/data/com.vendor.product/cache

    AppDocument:"",
      // Android: /storage/emulated/0/Android/data/com.vendor.product/files
 

    // 以下是所有 app 的公用目录,存储的文件不会随着 app 卸载而删除, 需要额外申请权限

    Root:"",    // 外部存储根目录
      // Android: /storage/emulated/0
    Music:"",   // 音乐文件夹
      // Android: /storage/emulated/0/Music
    Picture:"",   // 图片
      // Android: /storage/emulated/0/Pictures
    DCIM:"",   // 相片
      // Android: /storage/emulated/0/DCIM
    Movie:"",   // 影音
      // Android: /storage/emulated/0/Movies
    Download:"",   // 下载
      // Android: /storage/emulated/0/Download
    Podcasts:"",   // 播客,订阅
      // Android: /storage/emulated/0/Podcasts
    Ringtones:"",   // 来电铃声
      // Android: /storage/emulated/0/Ringtones
    Alarms:"",      // 闹钟
      // Android: /storage/emulated/0/Alarms
    Notifications:"",   // 通知铃声
      // Android: /storage/emulated/0/Notifications
}

iOS 目录

status

为热更提供的相关变量

status: {
    downloadRootDir: "",  //热更包保存路径
      // Android: /data/user/0/com.vendor.product/files/_epush
      // iOS: 
    packageVersion: "",   //当前包主版本
    currentVersion: "",   //当前热更版本
    isFirstTime: "",      //是否为该热更版本首次运行(需手动标记为成功)
    isRolledBack:"",      //是否为热更失败,回退到 currentVersion 版本, 仅提示一次
}

utils

这其实内部使用的一个方法集合,一般用不到,不过内部提供的一个方法,可能用的到,所以也导出了。

// 将 Blob 类型转为 字符串 或 base64 字符串
const render = blobRender(Blob:blob)

render.text();
render.base64();

fs

// 由文件路径获取其 mime type
fs.getMime(String:filePath).then((String:mimeType) => {})
fs.getMime([String:filePath]).then((Array:[mimeType]) => {})

// 获取文件的 hash 值 (MD5|SHA-1|SHA-256|SHA-512)
fs.getHash(String:filePath, String:algorithm).then((String:hash) => {})

// 获取文件的共享 uri, android 为  content://
// 可以让其他程序读取该文件
fs.getShareUri(String:filePath).then((String:uri) => {})

// 路径是否为文件夹 (true: 是, false:是文件夹, null:不存在)
fs.isDir(String:filePath).then((Boolean:yes) => {})

// 创建文件夹, 创建失败会抛出异常
fs.mkDir(String:filePath, Boolean:recursive).then((NULL) => {})

// 读取文件夹下 文件列表
fs.readDir(String:filePath).then((Array:list) => {})

// 删除文件夹
fs.rmDir(String:filePath, Boolean:recursive).then((NULL) => {})

// 获取系统 Content (一般为相册) 的 uri,  获取到的结果可用在 readDir
// 即读取系统相册内容列表 
// mediaType: Files | Images$Media | Audio$Media | Video$Media
// name: internal | external
fs.getContentUri(String:mediaType, String:name).then((String:uri) => {})

// 读取文件内容
fs.readFile(
    String:filePath,  // 文件路径
    String:encoding,  // text | blob | base64 | buffer
    Int:offset,       // 读取的起点位置, 若为负数, 则从文件末尾算起, 不指定则从开头开始
    Int:length        // 读取长度, 不指定, 则读取到结尾
).then(any =>{})

// 写文件
fs.writeFile(
   String:filePath,  // 文件路径
   content, // 写入内容, 可以是 Blob 或 string, 
            // 若写入base64, 设置为 [base64Str], 保存时会自动 decode
   flag     //  不指定(覆盖写入) 
            //  true(追加写入) 
            //  Number(在指定的位置写入, 为负数则从文件尾部算起)
).then(NULL => {})

// 复制文件, 失败会抛出异常
fs.copyFile(String:sourcePath, String:destPath, Boolean:overwrite).then(NULL)

// 移动文件, 失败会抛出异常
fs.moveFile(String:sourcePath, String:destPath, Boolean:overwrite).then(NULL)

// 删除文件, 失败会抛出异常
fs.unlink(String:filePath).then(NULL)

// 使用系统默认应用打开文件, mimeType 默认根据文件后缀自动
// 若为后缀不规范, 可手动强制指定
fs.openFile(String:filePath, String:mimeType).then(NULL)

/*
使用系统自带的 downloadManager 下载文件 (android only)
options: {
    *url: 'http://',
    mime:'',  缺省情况会更加文件后缀自动判断, 若为 url 文件后缀与mime不匹配, 需手工设置
    dest: '', 默认下载到 external 私有目录(无需权限), 
              也可以指定为 external 公共目录, 需要有 WRITE_EXTERNAL_STORAGE 权限
    title:'',
    description:'',
    scannable:Bool, 是否可被扫描
    roaming:Bool, 漫游状态是否下载
    quiet: Bool, 是否在推送栏显示
    network:int,  MOBILE:1, WIFI:2, ALL:3
    headers:{}  自定义 header 头
    onProgress: Function({total, loaded, percent}), 监听下载进度
    onError: Function(error),  下载失败回调
    onDownload: Function({file, url, mime, size, mtime}),  下载完成的回调
    onAutoOpen: Function(null|error), 尝试自动打开文件,并监听打开是否成功
}
*/
fs.download(options).then(taskId)

/*
使用其他 http 方法(如 fetch) 下载完文件, 
可使用该函数添加一个下载完毕的推送 (android only)
options: {
    *file: '',
    mime:'',
    title: '',
    description:'',
    quiet:Bool  若true,用户可在下载文件管理中看到,不显示到推送栏
}
*/
fs.addDownload(options).then(NULL)

// 加载一个字体文件
fs.loadFont(fontFamily, file).then(NULL)

// 解压 zip 文件, md5 可缺省, 若设置了, 会在解压前校验 zip 文件的 md5 hash
// 校验失败会抛出异常
fs.unzip(String:filePath, String:dir, String:md5).then(NULL)

// 将 path 使用 bsdiff 算法 合并到 source, 保存为 dest
fs.bsPatch(String:source, String:patch, String:dest).then(NULL)

// 重载应用 (release 模式重载 js bundle / debug 模式会重启 app)
fs.reload();

// 重启 app
fs.restart();

fs

以下为专门应对热更的接口


/** 
 * 解压 热更全量包
 * filePath: 已下载好的全量包本地地址 
 * md5: 可选, 全量包的 md5 值, 若提供则会在解压前进行验证
 *      若不提供则自动获取
 * 
 * 成功后可
 * switchVersion(md5 [, reload])
 * 
*/
fs.unzipBundle(String:filePath, String:md5).then(NULL)


/** 
 * 解压相对于安装包的 增量 patch 包
 * file: 已下载好的增量 patch 包本地地址
 * md5Version: 必须提供, 该 md5 值为 patch 合并到安装包后的 md5 值
 *             即本次的热更版本号
 * patchMd5: 可选,patch 文件的 md5 值
 * 
 * 
 * 成功后操作同上
*/
fs.unzipPatch(String:file, String:md5Version, String:patchMd5).then(NULL)


/** 
 * 解压相对于 originVersion 的 增量 patch 包
 * file: 已下载好的增量 patch 包本地地址
 * md5Version: 必须提供, 该 md5 值为 patch 合并到 originVersion 后的 md5 值
 *             即本次的热更版本号
 * originVersion: 必须提供, 原热更版本包的 md5 值
 *                该值通常为 status.currentVersion
 * patchMd5: 可选,patch 文件的 md5 值
 * 
 * 
 * 成功后操作同上
*/
fs.unzipDiff(
    String:file, 
    String:md5Version, 
    String:originVersion, 
    String:patchMd5
).then(NULL)

/**
 * 切换到指定的热更版本
 * md5Version: 要切换到的热更版本
 * reload: 是否立即重启(默认为false)
*/
fs.switchVersion(String:md5Version, Boolean:reload).then(NULL)

/**
 * 通过 status.isFirstTime 判断是否为热更版本首次启动, 
 * 通过该方法生效当前热更版本
 * 1. 若在启动后不调用该方法, 下次启动会回退
 * 2. 若启动后发生异常, 无法执行该方法, 下次启动回退
*/
fs.markSuccess();

fetchPlus

使用方法与 fetch 一致,但增加来一些参数

fetchPlus(options)
fetchPlus(Request|url, options)

// options 支持 fetch 原有参数, 
options:{
  url,
  method,
  credentials,
  headers,
  mode,
  body,
  signal
}


// 新增以下参数
options:{
    timeout:int,      // 超时时间 (毫秒)
    resText:Boolean,  // 默认与原 fetch 保持一致, 为 false
    saveTo:String,    // 将请求获得结果保存为文件, 指定文件路径
    keepBlob: Boolean, // 默认为 false

    onHeader: Function, // 得到 header 响应的回调
    onUpload: Function, // post 请求, 上传进度回调
    onDownload: Function, // response body 下载进度 回调
}

这里说一下 resTextkeepBlob

RN 请求默认会将请求结果缓存在原生中,JS 层得到一个 Blob, 利用 Blob 读取原生缓存,可读取为 stirng / base64 / buffer 等,即实现 JS 中 Response 对象的 text() / json() / blob() 等方法。

这样做的好处是通用性较强,但也带来一定副作用,一般使用中,很少在使用完手动关闭 Blob 对象,造成这个缓存可能在 app 生命周期内缓存在内容中, RN 获取会回收这部分内存,但目前尚不明确其回收机制。

所以,若明确知道请求后获得的 Response 为 String 类型,可设置 resText:true, 这样可避免原生层缓存 fetch 结果。

keepBlob 是针对 saveTo 的设置,在指定了 saveTo 的情况下,请求被强制为 resText:false,保存文件后,默认会关闭 Blob 对象,此时就无法在 fetch().then() 中读取文件 Blob 数据来,因为一般保存文件,是不需要再读取文件内容,若有特殊需要,可以设置 keepBlob:true,这样就不会关闭 Blob 对象了

HttpService

在 fetchPlus 基础上拓展的一个 JS 类,不多做说明,具体建议看源码。

class Service extends HttpService {
  // handle 当前 Service 的错误进行上报
  onError(err){
  }
  
  // 可针对当前 Service 所有 response 集中进行通用处理
  // 比如默认情况下 fetch 404 也被认为是成功, 这里可以抛个错来中断
  // 且抛错在 onError 中也能捕获
  onResponse(res){
    return res;
  }

  // 设计一个通用 header 的 api, 发送一些公用信息, 比如设备信息之类的
  // 然后重写 request 方法, 带上通用 header
  commonHeader = {};
  setCommonHeader(header){
    this.commonHeader = header;
  }
  request(input, init){
    return super.request(input, init).header(this.commonHeader)
  }

  // 快捷方法扩充
  asChrome(request){
    request.userAgent('chrome/71')
  }
  withToken(request, token){
    request.header('X-Reuest-Token', token)
  }

  
  // Service API
  login(name, pass){
    return this.request('/login').param({
      name, pass
    }, false).send()
  }
  updateAvatar(file){
    return this.request('/updateAvatar')
        .withToken('dddd')
        .param('avatar', file)
        .send()
  }
}
export default new Service('https://host.com');


//在其他地方 就可以这么用了
//------------------------------------------------------------

import React from 'react';
import service from './Service';

class Page extends React.Component {

  // promise 异步方式
  _foo(){
    service.request('/foo').query('a', 'a').send().then()
    service.request('/foo').asChrome().send().then()
    service.request('/foo').withToken('token').send().then()
    service.login(name, pass).then()
  }

  // await async 伪同步方式
  async _bar() {
    const rs = await service.login(name, pass);
    const rsJson = await rs.json();
  }
}

命令行

需额外安装 yarn add [-g] easypush

命令行 easypush 查看可用命令,若未全局安装, npx easypush 替代即可

可使用命令行完成 热更 全量包/补丁包 的生成,并上传到服务器,服务端需要实现的接口参见 api.js,自行实现即可

(搜索 POST: 可查看所有需要实现的 api)