中安拓也のブログ

プログラミングについて書くブログ。 Twitterやってます @l08084

Webviewのキャッシュを削除するCordovaプラグインを作成しました

はじめに

情報漏洩の対策として、Webviewに格納されるキャッシュを削除するCordovaプラグインを作成しました。Androidアプリのみに対応しています。

github.com

以前の記事において、cordova-plugin-ionic-webviewを改修することで、Webviewのキャッシュを削除する方法を紹介していますが、今回のプラグインでも同様の方法を採用してキャッシュを削除しています。

関連記事

以前書いたWebviewのキャッシュ削除についての記事になります。

【Cordova】【Android】 Webviewのキャッシュを消す - 中安拓也のブログ

環境

今回作成したCordovaプラグイン、cordova-plugin-cache-deleteは、下記バージョンのIonic(Angular, Cordova)で作成したAndroidアプリ上で動作確認しています。

ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.6
   @angular-devkit/build-angular : 0.1102.11
   @angular-devkit/schematics    : 11.2.11
   @angular/cli                  : 11.2.11
   @ionic/angular-toolkit        : 3.1.1

Cordova:

   Cordova CLI       : 10.0.0
   Cordova Platforms : android 9.1.0
   Cordova Plugins   : cordova-plugin-ionic-keyboard 2.2.0, cordova-plugin-ionic-webview 4.2.1, (and 5 other plugins)

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   ios-deploy : 1.9.2
   ios-sim    : 6.1.2
   NodeJS     : v12.13.1 (/usr/local/bin/node)
   npm        : 6.14.12
   OS         : macOS Catalina
   Xcode      : Xcode 12.4 Build version 12D4e

Cordovaプラグイン作成

今回のCordovaプラグイン開発は以下のフォルダ構成で行います。

.
├── LICENSE.txt
├── README.md
├── package.json
├── plugin.xml
├── src
│   └── android
│       └── CacheDelete.java
└── www
    └── CacheDelete.js

LICENSE.txtREADME.mdは、Cordovaプラグインの機能と直接関係があるファイルではないため、説明を割愛します。

package.jsonの作成

今回作成したプラグイン、cordova-plugin-cache-deletepackage.jsonになります。

package.json

{
  "name": "cordova-plugin-cache-delete",
  "version": "1.0.0",
  "author": {
    "name": "Takuya Nakayasu",
    "email": "l08084.1989@gmail.com",
    "url": "https://github.com/l08084"
  },
  "homepage": "https://github.com/l08084/cordova-plugin-cache-delete",
  "cordova": {
    "id": "cordova-plugin-cache-delete",
    "platforms": [
      "android"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/l08084/cordova-plugin-cache-delete.git"
  },
  "description": "This is a WebView cache plugin for Cordova supporting Android. It allows to delete the cordova webview cache.",
  "license": "Apache-2.0",
  "keywords": [
    "cordova",
    "webview",
    "cache",
    "delete",
    "Code Cache",
    "android",
    "HTTP Cache",
    "cordova-android"
  ]
}

npm package.json 日本語版 取扱説明書 に従って項目を設定していきます。後述するplugin.xmlと記述内容の不一致が発生しないように注意する必要があります。

plugin.xmlの作成

続いて、plugin.xmlファイルを作成します。

<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
        xmlns:android="http://schemas.android.com/apk/res/android"
        id="cordova-plugin-cache-delete" version="1.0.0">

  <name>Cordova Cache Delete</name>
  <description>This is a WebView cache plugin for Cordova supporting Android. It allows to delete the cordova webview cache.</description>
  <license>Apache 2.0</license>
  <keywords>cordova, webview, cache, delete, Code Cache, android, HTTP Cache, cordova-android</keywords>
  <repo>https://github.com/l08084/cordova-plugin-cache-delete.git</repo>

  <js-module src="www/CacheDelete.js" name="CacheDelete">
    <clobbers target="CacheDelete" />
  </js-module>

  <platform name="android">
    <config-file target="res/xml/config.xml" parent="/*">
      <feature name="CacheDelete">
        <param name="android-package" value="jp.l08084.plugin.CacheDelete"/>
      </feature>
    </config-file>
    <source-file src="src/android/CacheDelete.java" target-dir="src/jp/l08084/plugin/" />
  </platform>

</plugin>

plugin.xmlファイルの設定内容について説明していきます。

xmlnsxmlns:androidxmlns:属性であり、XMLドキュメント内で名前空間の機能を提供して、要素名や属性名の競合を防ぐ機能があります。 別のCordovaプラグインを作成する場合でもxmlnsxmlns:androidはURIを含めてそのままコピーアンドペーストで問題ないと思います。

<js-module>はネイティブコードとのIFとして使用されるJavaScriptファイルの設定です。jsファイル内でmodule.exportsしたオブジェクトは、clobberstargetに設定したオブジェクトで参照できるようになります。

<platform name="android">はAndroidに関する設定です。android-packagevalueはパッケージ名、target-dirの値はビルド後にソースコードが配置されるディレクトリになるため、それぞれ対応した値を設定する必要があります。

plugin.xmlファイルに関して言うと、私は<param name="android-package" value="jp.l08084.plugin.CacheDelete"/>に設定されているパッケージ名と<source-file src="src/android/CacheDelete.java" target-dir="src/jp/l08084/plugin/" />に設定しているtarget-dirの値(ソースコードが配置されるディレクトリ)が一致していないせいでCordovaプラグインが起動しないバグに苦しみました。皆様もお気をつけ下さい......

JavaScriptファイルの作成

モバイルアプリからネイティブコード(Android)を呼び出す時のIFになってくれるJavaScriptファイルを作成します。

www/CacheDelete.js

var exec = require("cordova/exec");

module.exports = {
  deleteCache: function (success, error) {
    exec(success, error, "CacheDelete", "deleteCache", []);
  },
};

Webviewのキャッシュを削除するメソッド、deleteCacheを定義しています。

plugin.xmlファイルで<clobbers target="CacheDelete" />と設定しているため、アプリ側から、上記のdeleteCacheメソッドを呼び出す時は、下記のように呼び出す必要があります。

CacheDelete.deleteCache();

メソッド(deleteCache)内で呼び出している、exec(success, error, "CacheDelete", "deleteCache", [])メソッドは、ネイティブコード(Angular, iOS)などの処理を呼び出すメソッドになります。execメソッドの内容としては下記の通りになります。

  • 第一引数は処理の成功時に呼び出したいコールバック関数。アプリ側から渡したものをそのままネイティブコード側に渡しているため、上記のJSファイルには内容は記載されていません
  • 第二引数は処理の失敗時に呼び出したいコールバック関数。。アプリ側から渡したものをそのままネイティブコード側に渡しているため、上記のJSファイルには内容は記載されていません
  • 第三引数はネイティブコード側で呼び出すサービス名です。今回の例では、CacheDeleteクラスを呼び出したいため、"CacheDelete"を設定しています
  • 第四引数は、ネイティブコード側に渡されるアクション名です。ネイティブコード側では、渡されたアクションとマッピングするこで対応するメソッドを呼び出します。今回は"deleteCache"という文字列を渡しています
  • 第五引数は、ネイティブコード側に渡したい引数になります。今回は渡したい引数がないため、空の配列([])を渡しています

ネイティブコード(Android)の作成

下記のネイティブコード(Android)で、キャッシュファイルをFile.delete()メソッドで全て削除する処理を実装しています。

src/android/CacheDelete.java

package jp.l08084.plugin;

import java.io.File;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;

import android.util.Log;

public class CacheDelete extends CordovaPlugin {
    private static final String TAG = "CacheDelete";
    private static final String DELETE_CACHE_MESSAGE = "Cordova CacheDelete.deleteCache() called.";
    private static final String ERROR_MESSAGE = "Failed to delete the cache, error";
    
    @Override
    public boolean execute(String action, JSONArray args, final CallbackContext callbackContext)
            throws JSONException {
        Log.v(TAG, DELETE_CACHE_MESSAGE);
        if("deleteCache".equals(action)) {
            deleteCache(callbackContext);
            return true;
        }
        return false;
    }

    private void deleteCache(final CallbackContext callbackContext) {
        File cacheDir = cordova.getActivity().getApplicationContext().getCacheDir();
        clearCacheFolder(cacheDir, callbackContext);
    }

    private void clearCacheFolder (File dir, final CallbackContext callbackContext) {
        try {
            if (dir != null && dir.isDirectory()) {
                for (File child : dir.listFiles()) {
                    if (child.isDirectory()) {
                        clearCacheFolder(child, callbackContext);
                    }
                    child.delete();
                }
            }
            callbackContext.success();
        } catch (Exception ex) {
            Log.e(TAG, ERROR_MESSAGE, ex);
            callbackContext.error(ERROR_MESSAGE);
        }
    }
}

上記のネイティブコードだと、execute(String action, JSONArray args, final CallbackContext callbackContext)メソッドがキーポイントになります。executeメソッドはJSファイルのexecからコールされています。executeメソッドとexecメソッドは下記の通り対応しています。

  • executeメソッドのaction引数にはexecメソッドの第四引数で渡した文字列がセットされます。今回は"deleteCache"がセットされます
  • executeメソッドのargs引数にはexecメソッドの第四引数で渡した配列がJSONArrayとしてセットされます。今回は使用していません
  • executeメソッドのcallbackContext引数にはexecメソッドの第一、二引数の成功/失敗時のコールバック関数がCallbackContextとしてセットされます

キャッシュファイルの削除処理が正常完了したら、callbackContext.success();を呼び出して成功時のコールバック処理を実行し、キャッシュファイルの削除処理が失敗した場合はcallbackContext.error(ERROR_MESSAGE);で失敗時のコールバック関数を呼び出しています。

プラグインのテスト

上記で作成したcordova-plugin-cache-deleteをIonicプロジェクトに取り込んで、正常に動作するかテストしていきます。

まず、下記のコマンドでIonic(Cordova)のプロジェクトcordovaPluginWebviewCacheTestを作成します。

$ ionic start cordovaPluginWebviewCacheTest tabs --cordova

続いて、下記のコマンドでIonicプロジェクトにcordova-plugin-ionic-webviewをインストールします。

$ cordova plugin add https://github.com/l08084/cordova-plugin-cache-delete.git

下記のエラーCordovaError: Current working directory is not a Cordova-based project.が発生してインストールに失敗しました。

(node:18236) UnhandledPromiseRejectionWarning: CordovaError: Current working directory is not a Cordova-based project.
    at Object.cdProjectRoot (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/src/cordova/util.js:170:15)
    at /usr/local/lib/node_modules/cordova/node_modules/cordova-lib/src/cordova/plugin/index.js:36:40
    at _fulfilled (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/q/q.js:787:54)
    at /usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/q/q.js:816:30
    at Promise.promise.promiseDispatch (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/q/q.js:749:13)
    at /usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/q/q.js:810:14
    at flush (/usr/local/lib/node_modules/cordova/node_modules/cordova-lib/node_modules/q/q.js:108:17)
    at processTicksAndRejections (internal/process/task_queues.js:75:11)
(node:18236) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:18236) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

上記のエラーを修正するため、下記の記事を参考にルートに空のwwwフォルダを作成します。

【Cordova】「Current working directory is not a Cordova based project」エラーの対処法 – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発

f:id:l08084:20210504193002p:plain
空のwwwディレクトリを作成する

再度cordova plugin addコマンドを実行すると、今度はインストールに成功します。

続いて、Ionicプロジェクトからcordova-plugin-ionic-webviewを呼び出すソースコードを書きます。

src/app/tab1/tab1.page.ts

import { Component } from '@angular/core';
import { Platform } from '@ionic/angular';

declare var CacheDelete: any;

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss'],
})
export class Tab1Page {
  constructor(private platform: Platform) {}

  public ngOnInit(): void {
    this.platform.ready().then(() => {
      if (this.platform.is('android')) {
        CacheDelete.deleteCache(this.successCallback, this.errorCallback);
      }
    });
  }

  private successCallback(): void {
    console.log('success');
  }

  private errorCallback(): void {
    console.log('error');
  }
}

TypeScript(Ionicのプロジェクト)からJSのライブラリ(Cordovaプラグイン)を呼び出すという形になるため、declare var CacheDelete: any;アンビエント宣言をすることで、CacheDeleteオブジェクトを利用可能な状態にしています。

Webviewのキャッシュを削除する処理を呼び出している箇所はCacheDelete.deleteCache(this.successCallback, this.errorCallback);になります。

続いて、下記のコマンドでIonicプロジェクトからAndroidアプリを作成して、動作確認をしていきます。

$ ionic cordova build android

上記のビルドコマンドで作成されたAndroidプロジェクトを見ていきます。

plugin.xmlの設定通り、src/main/res/xml/config.xmlCacheDeleteの設定が反映されていることがわかります。

f:id:l08084:20210507215551p:plain
config.xmlにplugin.xmlの設定が反映されている

plugin.xmlの<source-file src="src/android/CacheDelete.java" target-dir="src/jp/l08084/plugin/" />で設定した通りの箇所にソースファイルが配置されていることも確認できます。

f:id:l08084:20210507232713p:plain
plugin.xmlの設定通りにJava(Android)が配置されている

エミュレーターでIonicのAndroidプロジェクトを立ち上げます。

f:id:l08084:20210508003502p:plain
エミュレーター上でAndroidのサンプルアプリを立ち上げる

上記AndroidのサンプルアプリからCacheDelete.deleteCache()を呼び出すと、下記の通り、Webviewのキャッシュファイルが全て削除されることが確認できました。

f:id:l08084:20210508003305p:plain
キャッシュファイルが全て削除される

参考サイト

Ionic Native Pluginを自作する - Qiita

Native APIs - Ionic Framework 日本語ドキュメンテーション

GitHub - tiltshiftfocus/cordova-plugin-cache: This is a phonegap / cordova 3.3.0 plugin (iOS / Android) which allows to clear the cordova webview cache.

GitHub - Sharinglabs/cordova-plugin-cache: This is a phonegap / cordova 3.3.0 plugin (iOS / Android) which allows to clear the cordova webview cache.

GitHub - moderna/cordova-plugin-cache: This is a phonegap / cordova 3.3.0 plugin (iOS / Android) which allows to clear the cordova webview cache.

GitHub - rehiy/cordova-plugin-cache-clear: WebView cache plugin for Cordova

GitHub - ionic-team/ionic-native: Native features for mobile apps built with Cordova/PhoneGap and open web technologies. Complete with TypeScript support. The successor to ngCordova. Pairs exquisitely with a nice bottle of Ionic Framework.

npm package.json 日本語版 取扱説明書

npm

GitHub - timbru31/cordova-plugin-delete-launch-screen-cache: 🧹🌅 Apache Cordova plugin to programmatically delete the iOS launch screen cache on iOS 13+

【ionic】Ionic native pluginの作成|ブログ|West Wind Corporation

Capacitor - Webアプリをクロスプラットフォームに展開

GitHub - mesmotronic/cordova-plugin-fullscreen: Plugin for Cordova (PhoneGap) to enable Android's various full screen modes

Cordova plugin.xmlの内容について - Qiita

エンジニアが最低限理解しておくべきOSSライセンスの基礎知識 | フューチャー技術ブログ

OpenSharingGuideline/ApacheLicense2.0ライセンス付与のしかた:採用の理由.md at master · YCAMInterlab/OpenSharingGuideline · GitHub

ApacheライセンスのソースコードをGitHubにあげるまで - Qiita

Cordova Pluginの基本事項 - Qiita

Android Plugins - Apache Cordova

【Cordova】「Current working directory is not a Cordova based project」エラーの対処法 – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発

TypeScript|アンビエント宣言(declare)と型定義ファイル(.d.ts) - わくわくBank

アンビエント宣言(declare) - TypeScript Deep Dive 日本語版

GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.

ionic3のNative pluginを作る #ionic #cordova | 最初の4つの音の先へ

IPA ISEC セキュア・プログラミング講座:Webアプリケーション編 第5章 暴露対策:プロキシキャッシュ対策

重要情報の漏えいにつながるスマホアプリのキャッシュ問題と対策 | セキュリティ対策のラック

XML ドキュメントでの名前空間の管理 | Microsoft Docs

Cordova PluginのJavaScript部分の実装 - Qiita