<

{{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 common from '@ohos.app.ability.common'; 
import { TencentQianWebViewConfig } from '../model/TencentQianWebViewConfig'; 
import { OnSignResult, OnSdkEvent } from '../model/SignCallback'; 
import { 
  SchemeInterceptedEvent, 
  SchemeNotMatchedEvent, 
  DownloadFailedEvent 
} from '../model/SdkEvent'; 
import { SignResultParser } from './SignResultParser'; 
import { ExternalSchemeHandler } from './ExternalSchemeHandler'; 
 
/** 主框架疑似下载链接的回调,容器侧据此走 DownloadHandler。 */ 
export type OnDirectDownload = (url: string) => void; 
 
/** 
 * Scheme 拦截层(SDK 内部使用)。 
 * 
 * 对 Web 即将加载的 URL 做归类处理: 
 * - `qianapp://` → 解析为 SignResult,触发 `onSignResult` 回调 
 * - 白名单外部 scheme(weixin / alipays 等)→ startAbility 跳出本 App 
 * - 主框架 http(s) 链接后缀疑似下载 → 走 DownloadHandler 
 * - `blob:` → 暂不支持,通过 `DownloadFailed` 事件提示客户 
 * - 其余 http(s) / about / data / javascript → 放行 Web 组件加载 
 * - 未匹配的 scheme → 丢弃并通过 `SchemeNotMatched` 事件上报 
 */ 
export class SchemeInterceptor { 
  /** 直连下载的文件后缀白名单(对齐 Android 下载行为)。 */ 
  private static readonly DOWNLOAD_SUFFIXES: string[] = [ 
    '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.csv', '.ppt', '.pptx', 
    '.txt', '.zip', '.rar', '.7z', '.tar', '.gz', 
    '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', 
    '.mp3', '.mp4', '.m4a', '.wav', '.avi', '.mov', 
    '.apk', '.hap' 
  ]; 
 
  private context: common.UIAbilityContext; 
  private config: TencentQianWebViewConfig; 
  private onSignResult: OnSignResult; 
  private onSdkEvent?: OnSdkEvent; 
  private onDirectDownload?: OnDirectDownload; 
 
  constructor( 
    context: common.UIAbilityContext, 
    config: TencentQianWebViewConfig, 
    onSignResult: OnSignResult, 
    onSdkEvent?: OnSdkEvent, 
    onDirectDownload?: OnDirectDownload 
  ) { 
    this.context = context; 
    this.config = config; 
    this.onSignResult = onSignResult; 
    this.onSdkEvent = onSdkEvent; 
    this.onDirectDownload = onDirectDownload; 
  } 
 
  /** 
   * 拦截判定。 
   * 
   * @param url 即将加载的 URL 
   * @returns true = SDK 已处理,Web 组件应取消导航;false = 放行 
   */ 
  public intercept(url: string): boolean { 
    if (url === null || url === undefined || url.length === 0) { 
      return false; 
    } 
 
    if (SignResultParser.isQianAppScheme(url)) { 
      return this.handleQianApp(url); 
    } 
 
    const scheme: string = ExternalSchemeHandler.schemeOf(url); 
 
    if (scheme === 'blob') { 
      this.emitBlobUnsupported(url); 
      return true; 
    } 
 
    if (scheme === 'http' || scheme === 'https') { 
      return this.handleHttpUrl(url); 
    } 
 
    if (scheme === 'about' || scheme === 'data' || scheme === 'javascript') { 
      return false; 
    } 
 
    if (ExternalSchemeHandler.isWhitelisted(url, this.config.externalSchemes)) { 
      this.handleExternalScheme(scheme, url); 
      return true; 
    } 
 
    this.emitSchemeNotMatched(url); 
    return true; 
  } 
 
  /** 解析 `qianapp://` 并触发 `onSignResult` 回调。 */ 
  private handleQianApp(url: string): boolean { 
    this.emitEvent(new SchemeInterceptedEvent('qianapp', url)); 
    try { 
      this.onSignResult(SignResultParser.parse(url)); 
    } catch (_e) { 
      // callback 异常不影响 SDK 
    } 
    return true; 
  } 
 
  /** http(s) 链接:后缀疑似下载交给 DownloadHandler,否则放行 Web 组件。 */ 
  private handleHttpUrl(url: string): boolean { 
    if (!this.isDownloadUrl(url) || this.onDirectDownload === undefined) { 
      return false; 
    } 
    this.emitEvent(new SchemeInterceptedEvent('download', url)); 
    try { 
      this.onDirectDownload(url); 
    } catch (_e) { 
      // ignore 
    } 
    return true; 
  } 
 
  /** 白名单外部 scheme:`startAbility` 跳出本 App;失败上报 SchemeNotMatched。 */ 
  private handleExternalScheme(scheme: string, url: string): void { 
    this.emitEvent(new SchemeInterceptedEvent(scheme, url)); 
    ExternalSchemeHandler.open(this.context, url).then((ok: boolean) => { 
      if (!ok) { 
        this.emitSchemeNotMatched(url); 
      } 
    }); 
  } 
 
  private emitBlobUnsupported(url: string): void { 
    this.emitEvent(new SchemeInterceptedEvent('blob', url)); 
    this.emitEvent(new DownloadFailedEvent(url, 'blob 下载暂不支持,请通过 JSBridge 自行实现')); 
  } 
 
  private emitSchemeNotMatched(url: string): void { 
    this.emitEvent(new SchemeNotMatchedEvent(url)); 
  } 
 
  private emitEvent(event: SchemeInterceptedEvent | SchemeNotMatchedEvent | DownloadFailedEvent): void { 
    if (this.onSdkEvent !== undefined) { 
      this.onSdkEvent(event); 
    } 
  } 
 
  /** 根据 URL 路径后缀判断是否疑似下载链接。 */ 
  private isDownloadUrl(url: string): boolean { 
    try { 
      const path: string = url.split('?')[0].split('#')[0].toLowerCase(); 
      for (const suffix of SchemeInterceptor.DOWNLOAD_SUFFIXES) { 
        if (path.endsWith(suffix)) { 
          return true; 
        } 
      } 
    } catch (_e) { 
      // ignore 
    } 
    return false; 
  } 
} 

              
Code coverage generated by bjc at {{date}}