<

{{title}}

{{file.summary.functions.pct}}%

Functions
{{file.summary.functions.covered}}/{{file.summary.functions.total}}

{{file.summary.branches.pct}}%

Branches
{{file.summary.branches.covered}}/{{file.summary.branches.total}}

{{file.summary.lines.pct}}%

Lines
{{file.summary.lines.covered}}/{{file.summary.lines.total}}
          
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
                
import http from '@ohos.net.http'; 
import { WarmupManifest } from './WarmupManifest'; 
 
/** 
 * manifest 就绪回调。`versionChanged` 表示本次拉取的 version 与旧缓存不同。 
 */ 
export type ManifestReadyCallback = (manifest: WarmupManifest, versionChanged: boolean) => void; 
 
/** 
 * manifest 拉取 / 缓存 / 新鲜度管理(进程内单例语义,由 Prewarmer 持有)。 
 * 
 * 策略(design.md Decision 5): 
 * - 内存缓存整个 session;TTL 读自响应头 `Cache-Control: max-age`,缺省 300s。 
 * - `ensureFresh`:缺失则拉取并排队回调;已有但过期则后台重拉(stale-while-revalidate)。 
 * - `isFetching` 保护:每 TTL 窗口最多一次重拉。 
 * - 拉取失败静默 no-op。 
 */ 
export class ManifestStore { 
  /** pending 回调队列上限。弱网下 ensureFresh 可能多次入队,设上限防御异常累积。 */ 
  private static readonly MAX_PENDING: number = 16; 
 
  private manifest: WarmupManifest | null = null; 
  private lastFetchAtMs: number = 0; 
  private ttlMs: number = 300000; 
  private isFetching: boolean = false; 
  private manifestUrl: string = ''; 
  private pending: ManifestReadyCallback[] = []; 
 
  /** 设置 manifest URL(environment 映射或显式覆盖的结果)。 */ 
  public setManifestUrl(url: string): void { 
    this.manifestUrl = url; 
  } 
 
  /** 当前缓存的 manifest(可能为 null 或略旧)。 */ 
  public getManifest(): WarmupManifest | null { 
    return this.manifest; 
  } 
 
  /** 缓存是否过期。 */ 
  public isStale(): boolean { 
    return (Date.now() - this.lastFetchAtMs) > this.ttlMs; 
  } 
 
  /** 
   * 确保 manifest 可用且新鲜。 
   * - 无缓存:触发拉取,`onReady` 排队等 manifest 到达后回调(pending replay)。 
   * - 有缓存但过期:后台重拉(不阻塞),并在成功后回调 `onReady`(用于 version 变化通知)。 
   * - 有缓存且新鲜:立即以当前缓存回调 `onReady`。 
   */ 
  public ensureFresh(onReady?: ManifestReadyCallback): void { 
    if (this.manifest !== null) { 
      if (onReady !== undefined) { 
        onReady(this.manifest, false); 
      } 
      if (this.isStale()) { 
        this.fetch(); 
      } 
      return; 
    } 
    // 无缓存:排队 + 拉取 
    if (onReady !== undefined) { 
      // 上限保护:超限直接丢弃新回调,避免弱网下 pending 无界增长。 
      if (this.pending.length < ManifestStore.MAX_PENDING) { 
        this.pending.push(onReady); 
      } 
    } 
    this.fetch(); 
  } 
 
  /** 后台拉取 manifest(带 isFetching 保护,失败静默)。 */ 
  private fetch(): void { 
    if (this.isFetching || this.manifestUrl.length === 0) { 
      return; 
    } 
    this.isFetching = true; 
    const request = http.createHttp(); 
    request.request( 
      this.manifestUrl, 
      { 
        method: http.RequestMethod.GET, 
        header: { 'Accept': 'application/json' }, 
        connectTimeout: 8000, 
        readTimeout: 8000 
      }, 
      (err: Object | undefined, resp: http.HttpResponse): void => { 
        this.isFetching = false; 
        let success: boolean = false; 
        try { 
          if (err !== undefined && err !== null) { 
            return; 
          } 
          if (resp.responseCode !== 200) { 
            return; 
          } 
          this.ttlMs = ManifestStore.parseMaxAge(resp.header) ?? 300000; 
          const body: string = typeof resp.result === 'string' 
            ? resp.result as string 
            : ''; 
          if (body.length === 0) { 
            return; 
          } 
          const parsed: WarmupManifest = JSON.parse(body) as WarmupManifest; 
          if (parsed === null || parsed.hosts === undefined) { 
            return; 
          } 
          const oldVersion: string = this.manifest !== null ? this.manifest.version : ''; 
          const versionChanged: boolean = oldVersion.length > 0 && oldVersion !== parsed.version; 
          this.manifest = parsed; 
          this.lastFetchAtMs = Date.now(); 
          success = true; 
          // pending replay 
          const callbacks: ManifestReadyCallback[] = this.pending; 
          this.pending = []; 
          callbacks.forEach((cb: ManifestReadyCallback): void => { 
            try { 
              cb(parsed, false); 
            } catch (_e) { 
              // ignore 
            } 
          }); 
          // version 变化通知(用于去重重置) 
          if (versionChanged) { 
            this.onVersionChanged(parsed); 
          } 
        } catch (_e) { 
          // 解析失败静默 
        } finally { 
          try { 
            request.destroy(); 
          } catch (_e2) { 
            // ignore 
          } 
          // 拉取失败:清空 pending(符合"失败静默不回调"契约),避免回调无界累积。 
          // 下次 ensureFresh 会重新入队重试;去重状态未更新故不会误判已预热。 
          if (!success) { 
            this.pending = []; 
          } 
        } 
      } 
    ); 
  } 
 
  /** version 变化钩子,由 Prewarmer 注入用于清理去重状态。 */ 
  public onVersionChanged: (manifest: WarmupManifest) => void = (): void => { 
    // 默认空实现 
  }; 
 
  /** 解析 `Cache-Control: max-age=NNN` → 毫秒;无则返回 null。 */ 
  private static parseMaxAge(header: Object): number | null { 
    try { 
      const h: Record<string, ESObject> = header as Record<string, ESObject>; 
      let raw: ESObject = h['cache-control']; 
      if (raw === undefined) { 
        raw = h['Cache-Control']; 
      } 
      if (raw === undefined || typeof raw !== 'string') { 
        return null; 
      } 
      const cc: string = (raw as string).toLowerCase(); 
      const idx: number = cc.indexOf('max-age='); 
      if (idx < 0) { 
        return null; 
      } 
      const rest: string = cc.substring(idx + 8); 
      const match: string = rest.split(',')[0].trim(); 
      const seconds: number = parseInt(match, 10); 
      if (isNaN(seconds) || seconds <= 0) { 
        return null; 
      } 
      return seconds * 1000; 
    } catch (_e) { 
      return null; 
    } 
  } 
} 

              
Code coverage generated by bjc at {{date}}