All files / utils RichUrl.ts

100% Statements 37/37
100% Branches 18/18
100% Functions 17/17
100% Lines 37/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186                                                23x                         61x                           31x                   29x 5x       24x 24x   1x                                       36x 36x 4x   12x 12x     4x 32x 8x   22x 22x     8x 24x 14x   10x                       3x                                 9x   9x 9x           1x           8x 8x 8x 8x 8x 8x         8x 8x                               1x 7x        
import { isPlainObject } from 'lodash-uni'
 
export interface ParsedRichUrl<TDesc> {
  url: string
  desc?: TDesc
}
 
export interface ParsedFileRichUrl {
  url: string
  file: File
}
 
/**
 * 富链接,同普通链接相比,富链接可包含一些描述信息。
 *
 * 结构描述:
 * ```text
 * rich://{"url":"***","desc":"***"}
 * ```
 */
export class RichUrl {
  /**
   * 标识符。
   */
  private static readonly identity = 'rich://'
 
  /**
   * 检查是否是富链接。
   *
   * @param value 要检查的值
   * @returns 返回检查结果
   * @example
   * ```typescript
   * RichUrl.check('http://www.google.com') // => false
   * ```
   */
  static check(value: any): value is string {
    return (
      typeof value === 'string' &&
      value.substr(0, RichUrl.identity.length) === RichUrl.identity
    )
  }
 
  /**
   * 创建富链接。
   *
   * @param url 普通链接
   * @param desc 描述信息
   * @returns 返回创建的富链接
   */
  static build(url: string, desc?: any): string {
    return `${RichUrl.identity}${JSON.stringify({ url, desc })}`
  }
 
  /**
   * 解析富链接。非富链接的会直接将其值作为 url 返回。
   *
   * @param richUrl 富链接
   * @returns 返回解析结果
   */
  static parse<TDesc>(richUrl: string): ParsedRichUrl<TDesc> {
    if (!RichUrl.check(richUrl)) {
      return {
        url: richUrl,
      }
    }
    try {
      return JSON.parse(richUrl.substr(RichUrl.identity.length))
    } catch {
      return {
        url: richUrl,
      }
    }
  }
 
  /**
   * 转换数据中的富链接。
   *
   * @param data 数据
   * @param callback 回调
   * @returns 返回转换后的数据
   */
  static transform<TData, TDesc>(
    data: TData,
    callback: (
      parsedRichUrl: ParsedRichUrl<TDesc>,
      data: TData,
    ) => Promise<string>,
  ): Promise<TData> {
    return new Promise((resolve, reject) => {
      if (Array.isArray(data)) {
        Promise.all(
          (data as any[]).map((value, index) => {
            return RichUrl.transform(value, callback).then(res => {
              ;(data as any[])[index] = res
            })
          }),
        ).then(() => resolve(data), reject)
      } else if (isPlainObject(data)) {
        Promise.all(
          Object.keys(data as any).map(key => {
            return RichUrl.transform((data as any)[key], callback).then(res => {
              ;(data as any)[key] = res
            })
          }),
        ).then(() => resolve(data), reject)
      } else if (RichUrl.check(data)) {
        callback(RichUrl.parse(data), data).then(resolve as any, reject)
      } else {
        resolve(data)
      }
    })
  }
 
  /**
   * 将文件转换为文件富链接。
   *
   * @param file 要转换的文件
   * @returns 返回转换后的文件富链接
   */
  static fromFile(file: File): string {
    return RichUrl.build(URL.createObjectURL(file), {
      name: file.name,
      size: file.size,
      type: file.type,
      lastModified: file.lastModified,
    } as Partial<File>)
  }
 
  /**
   * 将文件富链接转换为文件和普通链接。
   *
   * @param richUrl 要转换的文件富链接
   * @returns 返回转换后的文件和普通链接
   */
  static toFile(
    richUrl: string | ParsedRichUrl<File>,
  ): Promise<ParsedFileRichUrl> {
    return new Promise((resolve, reject) => {
      const { url, desc } =
        typeof richUrl === 'string' ? RichUrl.parse<File>(richUrl) : richUrl
      if (
        !url ||
        !desc ||
        url.substr(0, 5) !== 'blob:' ||
        typeof desc !== 'object'
      ) {
        reject(
          new Error(
            `richUrl 不是一个合法的文件富链接: ${JSON.stringify(richUrl)}`,
          ),
        )
      } else {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'blob'
        xhr.onload = () => {
          const file = new File([xhr.response], desc.name, desc)
          resolve({
            url: url,
            file: file,
          })
        }
        xhr.onerror = reject
        xhr.send()
      }
    })
  }
 
  /**
   * 转换数据中的文件富链接。
   *
   * @param data 数据
   * @param callback 回调
   * @returns 返回转换后的数据
   */
  static transformFile<TData>(
    data: TData,
    callback: (parsedFileRichUrl: ParsedFileRichUrl) => Promise<string>,
  ): Promise<TData> {
    return RichUrl.transform<TData, File>(data, parsedRichUrl => {
      return RichUrl.toFile(parsedRichUrl).then(callback)
    })
  }
}