code:
---
title: Whitelist
description: Whitelist external content accessible by your app.
---
<!--
# license: Licensed to the Apache Software Foundation (ASF) under one
#         or more contributor license agreements.  See the NOTICE file
#         distributed with this work for additional information
#         regarding copyright ownership.  The ASF licenses this file
#         to you under the Apache License, Version 2.0 (the
#         "License"); you may not use this file except in compliance
#         with the License.  You may obtain a copy of the License at
#
#           http://www.apache.org/licenses/LICENSE-2.0
#
#         Unless required by applicable law or agreed to in writing,
#         software distributed under the License is distributed on an
#         "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#         KIND, either express or implied.  See the License for the
#         specific language governing permissions and limitations
#         under the License.
-->

# cordova-plugin-whitelist

This plugin implements a whitelist policy for navigating the application webview on Cordova 4.0

## Installation

You can install whitelist plugin with Cordova CLI, from npm:

```
$ cordova plugin add cordova-plugin-whitelist
$ cordova prepare
```

## Supported Cordova Platforms

* Android 4.0.0 or above

## Navigation Whitelist

Controls which URLs the WebView itself can be navigated to. Applies to
top-level navigations only.

By default navigations are only allowed to `file://` URLs. To allow others URLs, you must add `<allow-navigation>` tags to your `config.xml`:

    <!-- Allow links to example.com -->
    <allow-navigation href="http://example.com/*" />

    <!-- Wildcards are allowed for the protocol, as a prefix
         to the host, or as a suffix to the path -->
    <allow-navigation href="*://*.example.com/*" />

    <!-- A wildcard can be used to whitelist the entire network,
         over HTTP and HTTPS.
         *NOT RECOMMENDED* -->
    <allow-navigation href="*" />

    <!-- The above is equivalent to these three declarations -->
    <allow-navigation href="http://*/*" />
    <allow-navigation href="https://*/*" />
    <allow-navigation href="data:*" />

Quirks: on Android it also applies to iframes for non-http(s) schemes.

## Intent Whitelist

Controls which URLs the app is allowed to ask the system to open.

In `config.xml`, add `<allow-intent>` tags, like this:

    <!-- Allow links to web pages to open in a browser -->
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />

    <!-- Allow links to example.com to open in a browser -->
    <allow-intent href="http://example.com/*" />

    <!-- Wildcards are allowed for the protocol, as a prefix
         to the host, or as a suffix to the path -->
    <allow-intent href="*://*.example.com/*" />

    <!-- Allow SMS links to open messaging app -->
    <allow-intent href="sms:*" />

    <!-- Allow tel: links to open the dialer -->
    <allow-intent href="tel:*" />

    <!-- Allow geo: links to open maps -->
    <allow-intent href="geo:*" />

    <!-- Allow all unrecognized URLs to open installed apps
         *NOT RECOMMENDED* -->
    <allow-intent href="*" />

Without any `<allow-intent>` tags, no requests to external URLs are allowed. However, the default Cordova application includes a quite liberal set of `allow-intent` entries by default. It is advised to narrow this down based on each app's needs.

On Android, this equates to sending an intent of type BROWSEABLE.

This whitelist does not apply to plugins, only hyperlinks and calls to `window.open()`.

Note: `allow-navigation` takes precedence over `allow-intent`. Allowing navigation to all URLs with `<allow-navigation href="*" />` for example has the side effect of "capturing" all intents, so the webview navigates to them instead of triggering e.g. external apps.

## Network Request Whitelist

Controls which network requests (images, XHRs, etc) are allowed to be made (via cordova native hooks).

Note: We suggest you use a Content Security Policy (see below), which is more secure.  This whitelist is mostly historical for webviews which do not support CSP.

In `config.xml`, add `<access>` tags, like this:

    <!-- Allow images, xhrs, etc. to google.com -->
    <access origin="http://google.com" />
    <access origin="https://google.com" />

    <!-- Access to the subdomain maps.google.com -->
    <access origin="http://maps.google.com" />

    <!-- Access to all the subdomains on google.com -->
    <access origin="http://*.google.com" />

    <!-- Enable requests to content: URLs -->
    <access origin="content:///*" />

    <!-- Don't block any requests -->
    <access origin="*" />

Without any `<access>` tags, only requests to `file://` URLs are allowed. However, the default Cordova application includes `<access origin="*">` by default.

Note: Whitelist cannot block network redirects from a whitelisted remote website (i.e. http or https) to a non-whitelisted website. Use CSP rules to mitigate redirects to non-whitelisted websites for webviews that support CSP.

Quirk: Android also allows requests to https://ssl.gstatic.com/accessibility/javascript/android/ by default, since this is required for TalkBack to function properly.

### Content Security Policy

Controls which network requests (images, XHRs, etc) are allowed to be made (via webview directly).

On Android and iOS, the network request whitelist (see above) is not able to filter all types of requests (e.g. `<video>` & WebSockets are not blocked). So, in addition to the whitelist, you should use a [Content Security Policy](http://content-security-policy.com/) `<meta>` tag on all of your pages.

On Android, support for CSP within the system webview starts with KitKat (but is available on all versions using Crosswalk WebView).

Here are some example CSP declarations for your `.html` pages:

    <!-- Good default declaration:
        * gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication
        * https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly
        * Disables use of eval() and inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:
            * Enable inline JS: add 'unsafe-inline' to default-src
            * Enable eval(): add 'unsafe-eval' to default-src
    -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com; style-src 'self' 'unsafe-inline'; media-src *">

    <!-- Allow everything but only from the same origin and foo.com -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' foo.com">

    <!-- This policy allows everything (eg CSS, AJAX, object, frame, media, etc) except that
        * CSS only from the same origin and inline styles,
        * scripts only from the same origin and inline styles, and eval()
    -->
    <meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'">

    <!-- Allows XHRs only over HTTPS on the same domain. -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' https:">

    <!-- Allow iframe to https://cordova.apache.org/ -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; frame-src 'self' https://cordova.apache.org">


code:
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
-->
# Release Notes

### 1.3.4 (Jun 19, 2019)

-   chore: manually fix lines breaks to LF ([`d804ef2`](https://github.com/apache/cordova-plugin-whitelist/commit/d804ef2))
-   build: add `.gitattributes` to force LF (instead of possible CRLF on Windows) ([`ed0206b`](https://github.com/apache/cordova-plugin-whitelist/commit/ed0206b))
-   build: add `.npmignore` to remove unneeded files from npm package ([`bf8fea5`](https://github.com/apache/cordova-plugin-whitelist/commit/bf8fea5))
-   test,ci(travis): Tests (extracted from cordova-mobile-spec) ([#38](https://github.com/apache/cordova-plugin-whitelist/issues/38)) ([`e4f17b0`](https://github.com/apache/cordova-plugin-whitelist/commit/e4f17b0))
-   docs: Added information on allow-navigation preceding allow-intent ([#41](https://github.com/apache/cordova-plugin-whitelist/issues/41)) ([`7725fed`](https://github.com/apache/cordova-plugin-whitelist/commit/7725fed))
-   chore(github): Add or update GitHub pull request and issue template ([`86b3ee1`](https://github.com/apache/cordova-plugin-whitelist/commit/86b3ee1))
-   fix: Remove information about cordova-android ([#27](https://github.com/apache/cordova-plugin-whitelist/issues/27)) ([`34ed9d0`](https://github.com/apache/cordova-plugin-whitelist/commit/34ed9d0))
-   docs: remove JIRA link ([`fa78675`](https://github.com/apache/cordova-plugin-whitelist/commit/fa78675))
-   docs: Clarify unconfigured Intent Whitelist behaviour ([#26](https://github.com/apache/cordova-plugin-whitelist/issues/26)) ([`8d3f86b`](https://github.com/apache/cordova-plugin-whitelist/commit/8d3f86b))

### 1.3.3 (Nov 06, 2017)
* [CB-12847](https://issues.apache.org/jira/browse/CB-12847) added `bugs` entry to `package.json`.

### 1.3.2 (Feb 28, 2017)
* [CB-12236](https://issues.apache.org/jira/browse/CB-12236) Fixed `RELEASENOTES` for `cordova-plugin-whitelist`

### 1.3.1 (Dec 07, 2016)
* [CB-12224](https://issues.apache.org/jira/browse/CB-12224) Updated version and RELEASENOTES.md for release 1.3.1
* [CB-11917](https://issues.apache.org/jira/browse/CB-11917) - Remove pull request template checklist item: "iCLA has been submitted…"
* Edit package.json license to match SPDX id
* [CB-11832](https://issues.apache.org/jira/browse/CB-11832) Incremented plugin version.

### 1.3.0 (Sep 08, 2016)
* [CB-11795](https://issues.apache.org/jira/browse/CB-11795) Add 'protective' entry to cordovaDependencies
* Updated installation section
* Plugin uses `Android Log class` and not `Cordova LOG class`
* Add pull request template.
* [CB-10866](https://issues.apache.org/jira/browse/CB-10866) Adding engine info to `package.json`
* [CB-10996](https://issues.apache.org/jira/browse/CB-10996) Adding front matter to README.md

### 1.2.2 (Apr 15, 2016)
* add note about redirects
* [CB-10624](https://issues.apache.org/jira/browse/CB-10624) remove error message from `whitelist.js`, which leaves it empty

### 1.2.1 (Jan 15, 2016)
* [CB-10194](https://issues.apache.org/jira/browse/CB-10194) info tag prints for ios when not applicable

### 1.2.0 (Nov 18, 2015)
* removed **iOS** engine check from `plugin.xml`
* [CB-10035](https://issues.apache.org/jira/browse/CB-10035) Updated `RELEASENOTES` to be newest to oldest
* [CB-9972](https://issues.apache.org/jira/browse/CB-9972) - Remove **iOS** whitelist
* Updated the text, it should read 4.0.x and greater, since this plugin will be required for `cordova-android 5.0`
* Fixing contribute link.
* Updated `plugin.xml <info>` tag to remove warning about not needing this plugin if you are using the **iOS 9 SDK**
* [CB-9738](https://issues.apache.org/jira/browse/CB-9738) - Disable whitelist use when runtime environment is **iOS 9**
* [CB-9740](https://issues.apache.org/jira/browse/CB-9740) - Add `<info>` tag describing whitelist plugin not needed on `cordova-ios` and cordova-android 3.x`
* [CB-9568](https://issues.apache.org/jira/browse/CB-9568) - Update whitelist plugin to allow all network access by default
* [CB-9337](https://issues.apache.org/jira/browse/CB-9337) - enable use of `<access>` tags for native code network requests

### 1.1.0 (Jun 17, 2015)
* [CB-9128](https://issues.apache.org/jira/browse/CB-9128) cordova-plugin-whitelist documentation translation: cordova-plugin-whitelist
* fix npm md issue
* Usage of CDVURLRequestFilter protocol.
* [CB-9089](https://issues.apache.org/jira/browse/CB-9089) - iOS whitelist plugin does not compile
* [CB-9090](https://issues.apache.org/jira/browse/CB-9090) - Enable whitelist plugin for cordova-ios 4.0.0
* Fixed error in Content-Security-Policy example

### 1.0.0 (Mar 25, 2015)
* [CB-8739](https://issues.apache.org/jira/browse/CB-8739) added missing license headers
* Add @Override to CustomConfigXmlParser methods
* Change ID to cordova-plugin-whitelist rather than reverse-DNS-style
* Tweak CSP examples in README
* [CB-8660](https://issues.apache.org/jira/browse/CB-8660) remove extra commas from package.json

repo_name: Area/degrees

license: mit


cordova-plugin-whitelist

description: Cordova Whitelist Plugin

type: git

keywords: cordova whitelist ecosystem:cordova cordova-android

test: echo "no tests implemented for this plugin"

author: Apache Software Foundation


code:
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an
  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.
-->

<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
           id="cordova-plugin-whitelist"
      version="1.3.4">
    <name>Whitelist</name>
    <description>Cordova Network Whitelist Plugin</description>
    <license>Apache 2.0</license>
    <keywords>cordova,whitelist,policy</keywords>

    <engines>
      <engine name="cordova-android" version=">=4.0.0" />
    </engines>

    <platform name="android">
        <config-file target="res/xml/config.xml" parent="/*">
            <feature name="Whitelist" >
                <param name="android-package" value="org.apache.cordova.whitelist.WhitelistPlugin"/>
                <param name="onload" value="true" />
            </feature>
        </config-file>

        <source-file src="src/android/WhitelistPlugin.java" target-dir="src/org/apache/cordova/whitelist" />
    </platform>

</plugin>


code:
/*
       Licensed to the Apache Software Foundation (ASF) under one
       or more contributor license agreements.  See the NOTICE file
       distributed with this work for additional information
       regarding copyright ownership.  The ASF licenses this file
       to you under the Apache License, Version 2.0 (the
       "License"); you may not use this file except in compliance
       with the License.  You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing,
       software distributed under the License is distributed on an
       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       KIND, either express or implied.  See the License for the
       specific language governing permissions and limitations
       under the License.
*/

package org.apache.cordova.whitelist;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.ConfigXmlParser;
import org.apache.cordova.LOG;
import org.apache.cordova.Whitelist;
import org.xmlpull.v1.XmlPullParser;

import android.content.Context;

public class WhitelistPlugin extends CordovaPlugin {
    private static final String LOG_TAG = "WhitelistPlugin";
    private Whitelist allowedNavigations;
    private Whitelist allowedIntents;
    private Whitelist allowedRequests;

    // Used when instantiated via reflection by PluginManager
    public WhitelistPlugin() {
    }
    // These can be used by embedders to allow Java-configuration of whitelists.
    public WhitelistPlugin(Context context) {
        this(new Whitelist(), new Whitelist(), null);
        new CustomConfigXmlParser().parse(context);
    }
    public WhitelistPlugin(XmlPullParser xmlParser) {
        this(new Whitelist(), new Whitelist(), null);
        new CustomConfigXmlParser().parse(xmlParser);
    }
    public WhitelistPlugin(Whitelist allowedNavigations, Whitelist allowedIntents, Whitelist allowedRequests) {
        if (allowedRequests == null) {
            allowedRequests = new Whitelist();
            allowedRequests.addWhiteListEntry("file:///*", false);
            allowedRequests.addWhiteListEntry("data:*", false);
        }
        this.allowedNavigations = allowedNavigations;
        this.allowedIntents = allowedIntents;
        this.allowedRequests = allowedRequests;
    }
    @Override
    public void pluginInitialize() {
        if (allowedNavigations == null) {
            allowedNavigations = new Whitelist();
            allowedIntents = new Whitelist();
            allowedRequests = new Whitelist();
            new CustomConfigXmlParser().parse(webView.getContext());
        }
    }

    private class CustomConfigXmlParser extends ConfigXmlParser {
        @Override
        public void handleStartTag(XmlPullParser xml) {
            String strNode = xml.getName();
            if (strNode.equals("content")) {
                String startPage = xml.getAttributeValue(null, "src");
                allowedNavigations.addWhiteListEntry(startPage, false);
            } else if (strNode.equals("allow-navigation")) {
                String origin = xml.getAttributeValue(null, "href");
                if ("*".equals(origin)) {
                    allowedNavigations.addWhiteListEntry("http://*/*", false);
                    allowedNavigations.addWhiteListEntry("https://*/*", false);
                    allowedNavigations.addWhiteListEntry("data:*", false);
                } else {
                    allowedNavigations.addWhiteListEntry(origin, false);
                }
            } else if (strNode.equals("allow-intent")) {
                String origin = xml.getAttributeValue(null, "href");
                allowedIntents.addWhiteListEntry(origin, false);
            } else if (strNode.equals("access")) {
                String origin = xml.getAttributeValue(null, "origin");
                String subdomains = xml.getAttributeValue(null, "subdomains");
                boolean external = (xml.getAttributeValue(null, "launch-external") != null);
                if (origin != null) {
                    if (external) {
                        LOG.w(LOG_TAG, "Found <access launch-external> within config.xml. Please use <allow-intent> instead.");
                        allowedIntents.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
                    } else {
                        if ("*".equals(origin)) {
                            allowedRequests.addWhiteListEntry("http://*/*", false);
                            allowedRequests.addWhiteListEntry("https://*/*", false);
                        } else {
                            allowedRequests.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
                        }
                    }
                }
            }
        }
        @Override
        public void handleEndTag(XmlPullParser xml) {
        }
    }

    @Override
    public Boolean shouldAllowNavigation(String url) {
        if (allowedNavigations.isUrlWhiteListed(url)) {
            return true;
        }
        return null; // Default policy
    }

    @Override
    public Boolean shouldAllowRequest(String url) {
        if (Boolean.TRUE == shouldAllowNavigation(url)) {
            return true;
        }
        if (allowedRequests.isUrlWhiteListed(url)) {
            return true;
        }
        return null; // Default policy
    }

    @Override
    public Boolean shouldOpenExternalUrl(String url) {
        if (allowedIntents.isUrlWhiteListed(url)) {
            return true;
        }
        return null; // Default policy
    }

    public Whitelist getAllowedNavigations() {
        return allowedNavigations;
    }

    public void setAllowedNavigations(Whitelist allowedNavigations) {
        this.allowedNavigations = allowedNavigations;
    }

    public Whitelist getAllowedIntents() {
        return allowedIntents;
    }

    public void setAllowedIntents(Whitelist allowedIntents) {
        this.allowedIntents = allowedIntents;
    }

    public Whitelist getAllowedRequests() {
        return allowedRequests;
    }

    public void setAllowedRequests(Whitelist allowedRequests) {
        this.allowedRequests = allowedRequests;
    }
}


code:
---
 license: Licensed to the Apache Software Foundation (ASF) under one
         or more contributor license agreements.  See the NOTICE file
         distributed with this work for additional information
         regarding copyright ownership.  The ASF licenses this file
         to you under the Apache License, Version 2.0 (the
         "License"); you may not use this file except in compliance
         with the License.  You may obtain a copy of the License at

           http://www.apache.org/licenses/LICENSE-2.0

         Unless required by applicable law or agreed to in writing,
         software distributed under the License is distributed on an
         "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
         KIND, either express or implied.  See the License for the
         specific language governing permissions and limitations
         under the License.
---

# cordova-plugin-whitelist-tests

This are an [optional JavaScript interface](#whitelist) to the core Cordova Whitelist functionality and the test plugin that uses it to test cordova-plugin-whitelist.

## Whitelist

> The `whitelist` object provides an interface for testing whether arbitrary
> URLs are allowed by the currently active configuration, or would be allowed
> by a given set of whitelist patterns.

### Methods

- cordova.whitelist.match
- cordova.whitelist.test

#### cordova.whitelist.match

Indicates whether a given URL would be allowed by a set of Whitelist URL
patterns.

    cordova.whitelist.match(url, patterns, callback);

`callback` will be invoked with a boolean argument indicating whether the
url matches the set of patterns.

#### cordova.whitelist.test

Indicates whether a given URL would be allowed by the current application
configuration.

    cordova.whitelist.test(url, callback);

`callback` will be invoked with a boolean argument indicating whether the
url is currently whitelisted.


code:
<?xml version="1.0" encoding="UTF-8"?>

<!--
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an
  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.
-->

<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
           id="cordova-plugin-whitelist-tests"
      version="1.3.4">
    <name>Cordova Plugin Whitelist Tests</name>
    <license>Apache 2.0</license>

    <js-module src="tests.js" name="tests">
    </js-module>

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

    <!-- android -->
    <platform name="android">
        <config-file target="res/xml/config.xml" parent="/*">
            <feature name="WhitelistAPI" >
                <param name="android-package" value="org.apache.cordova.test.WhitelistAPI"/>
            </feature>
        </config-file>

        <source-file src="src/android/WhitelistAPI.java" target-dir="src/org/apache/cordova/test" />

        <!-- remove <access origin="*" /> -->
        <hook type="after_prepare" src="scripts/remove-access.js"/>

        <!-- add whitelist configuration the tests test for -->
        <config-file target="config.xml" parent="/*">
            <allow-navigation href="http://*.apache.org" />
            <allow-navigation href="https://*.apache.org" />
        </config-file>

    </platform>

</plugin>


code:
var fs = require('fs');
var path = require('path');
var rootdir = "";
var file = path.join(rootdir, "platforms/android/app/src/main/res/xml/config.xml");

fs.readFile( file, "utf8", function( err, data )
{
    if (err)
        return console.log( err );

    var result = data;
    result = result.replace( "<access origin=\"*\" />", "" );

    fs.writeFile( file, result, "utf8", function( err )
    {
        if (err)
            return console.log( err );
    } );
} );


code:
/*
       Licensed to the Apache Software Foundation (ASF) under one
       or more contributor license agreements.  See the NOTICE file
       distributed with this work for additional information
       regarding copyright ownership.  The ASF licenses this file
       to you under the Apache License, Version 2.0 (the
       "License"); you may not use this file except in compliance
       with the License.  You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing,
       software distributed under the License is distributed on an
       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       KIND, either express or implied.  See the License for the
       specific language governing permissions and limitations
       under the License.
*/
package org.apache.cordova.test;

import org.apache.cordova.Whitelist;
import org.apache.cordova.Config;

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

import org.apache.cordova.PluginManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class WhitelistAPI extends CordovaPlugin {
    /**
     * Executes the request and returns PluginResult.
     *
     * @param action            The action to execute.
     * @param args              JSONArry of arguments for the plugin.
     * @param callbackContext   The callback id used when calling back into JavaScript.
     * @return                  True if the action was valid, false if not.
     */
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (action.equals("URLMatchesPatterns")) {
            String url = args.getString(0);
            JSONArray patterns = args.getJSONArray(1);
            Whitelist whitelist = new Whitelist();
            for (int i=0; i < patterns.length(); i++) {
                String pattern = patterns.getString(i);
                whitelist.addWhiteListEntry(pattern, false);
            }
            boolean isAllowed = whitelist.isUrlWhiteListed(url);
            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, isAllowed));
            return true;
        } else if (action.equals("URLIsAllowed")) {
            String url = args.getString(0);
            /* This code exists for compatibility between 3.x and 4.x versions of Cordova.
             * Previously the CordovaWebView class had a method, getWhitelist, which would
             * return a Whitelist object. Since the fixed whitelist is removed in Cordova 4.x,
             * the correct call now is to shouldAllowRequest from the plugin manager.
             */
            Boolean isAllowed = null;
            try {
                Method isUrlWhiteListed = Config.class.getDeclaredMethod("isUrlWhitelisted", String.class);
                isAllowed = (Boolean)isUrlWhiteListed.invoke(url);
            } catch (NoSuchMethodException e) {
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
            if (isAllowed == null) {
                try {
                    Method gpm = webView.getClass().getMethod("getPluginManager");
                    PluginManager pm = (PluginManager)gpm.invoke(webView);
                    Method isAllowedMethod = pm.getClass().getMethod("shouldAllowRequest", String.class);
                    isAllowed = (Boolean)isAllowedMethod.invoke(pm, url);
                    if (isAllowed == null) {
                        isAllowed = false;
                    }
                } catch (NoSuchMethodException e) {
                } catch (IllegalAccessException e) {
                } catch (InvocationTargetException e) {
                }
            }

            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, isAllowed));
            return true;
        }
        return false;
    }
}


code:
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

exports.defineAutoTests = function () {

    var isAndroid = (cordova.platformId === "android");

    describe('Whitelist API (cordova.whitelist)', function () {

        it("should exist", function () {
            expect(cordova.whitelist).toBeDefined();
        });

        describe("Match function (cordova.whitelist.match) that checks URLs against patterns", function () {
            function expectMatchWithResult(result) {
                return (function (url, patterns, description) {
                    description = description || ((result ? "should accept " : "should reject ") + url + " for " + JSON.stringify(patterns));
                    this.result = result;

                    describe("Match function", function () {
                        if (!isAndroid) {
                            pending("Whitelist Plugin only exists for Android");
                        }

                        // Timeout is 7.5 seconds to allow physical devices enough
                        // time to query the response. This is important for some
                        // Android devices.
                        var originalTimeout,
                            cb;

                        beforeEach(function (done) {
                            originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
                            jasmine.DEFAULT_TIMEOUT_INTERVAL = 7500;

                            cb = jasmine.createSpy('spy').and.callFake(function () {
                                done();
                            });
                            cordova.whitelist.match(url, patterns, cb);
                        });

                        afterEach(function () {
                            jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
                        });

                        it(description, function () {
                            expect(cb).toHaveBeenCalledWith(result);
                        });
                    });
                });
            }

            var itShouldMatch = expectMatchWithResult(true);
            var itShouldNotMatch = expectMatchWithResult(false);

            it("should exist", function () {
                expect(cordova.whitelist.match).toBeDefined();
                expect(typeof cordova.whitelist.match).toBe("function");
            });

            itShouldMatch('http://www.apache.org/',     ['*'],  "should accept any domain for *");
            itShouldNotMatch('http://www.apache.org/',  [],     "should not accept any domain for []");

            itShouldMatch('http://apache.org/',                                             ['http://*.apache.org']);
            itShouldMatch('http://www.apache.org/',                                         ['http://*.apache.org']);
            itShouldMatch('http://www.apache.org/some/path',                                ['http://*.apache.org']);
            itShouldMatch('http://some.domain.under.apache.org/',                           ['http://*.apache.org']);
            itShouldMatch('http://user:pass@apache.org/',                                   ['http://*.apache.org']);
            itShouldMatch('http://user:pass@www.apache.org/',                               ['http://*.apache.org']);
            itShouldMatch('http://www.apache.org/?some=params',                             ['http://*.apache.org']);
            itShouldNotMatch('http://apache.com/',                                          ['http://*.apache.org']);
            itShouldNotMatch('http://www.evil.com/?url=www.apache.org',                     ['http://*.apache.org']);
            itShouldNotMatch('http://www.evil.com/?url=http://www.apache.org',              ['http://*.apache.org']);
            itShouldNotMatch('http://www.evil.com/?url=http%3A%2F%2Fwww%2Eapache%2Eorg',    ['http://*.apache.org']);
            itShouldNotMatch('https://apache.org/',                                         ['http://*.apache.org']);
            itShouldNotMatch('http://www.apache.org:pass@evil.com/',                        ['http://*.apache.org']);
            itShouldNotMatch('http://www.apache.org.evil.com/',                             ['http://*.apache.org']);

            itShouldMatch('http://www.apache.org/',     ['http://*.apache.org', 'https://*.apache.org']);
            itShouldMatch('https://www.apache.org/',    ['http://*.apache.org', 'https://*.apache.org']);
            itShouldNotMatch('ftp://www.apache.org/',   ['http://*.apache.org', 'https://*.apache.org']);
            itShouldNotMatch('http://www.apache.com/',  ['http://*.apache.org', 'https://*.apache.org']);

            itShouldMatch('http://www.apache.org/',         ['http://www.apache.org']);
            itShouldNotMatch('http://build.apache.org/',    ['http://www.apache.org']);
            itShouldNotMatch('http://apache.org/',          ['http://www.apache.org']);

            itShouldMatch('http://www.apache.org/',             ['http://*/*']);
            itShouldMatch('http://www.apache.org/foo/bar.html', ['http://*/*']);

            itShouldMatch('http://www.apache.org/foo',          ['http://*/foo*']);
            itShouldMatch('http://www.apache.org/foo/bar.html', ['http://*/foo*']);
            itShouldNotMatch('http://www.apache.org/',          ['http://*/foo*']);

            itShouldMatch('file:///foo', ['file:///*']);

            itShouldMatch('file:///foo',                ['file:///foo*']);
            itShouldMatch('file:///foo/bar.html',       ['file:///foo*']);
            itShouldNotMatch('file:///foo.html',        []);
            itShouldNotMatch('http://www.apache.org/etc/foo',   ['http://www.apache.org/foo*']);
            itShouldNotMatch('http://www.apache.org/foo',       ['file:///foo*']);

            itShouldMatch('http://www.apache.org/',     ['*://www.apache.org/*']);
            itShouldMatch('https://www.apache.org/',    ['*://www.apache.org/*']);
            itShouldMatch('ftp://www.apache.org/',      ['*://www.apache.org/*']);
            itShouldMatch('file://www.apache.org/',     ['*://www.apache.org/*']);
            if (cordova.platformId == 'android')
                itShouldMatch('content://www.apache.org/', ['*://www.apache.org/*']);
            itShouldMatch('foo://www.apache.org/',      ['*://www.apache.org/*']);
            itShouldNotMatch('http://www.apache.com/',  ['*://www.apache.org/*']);

            itShouldMatch('http://www.apache.org/',     ['*.apache.org']);
            itShouldMatch('https://www.apache.org/',    ['*.apache.org']);
            itShouldNotMatch('ftp://www.apache.org/',   ['*.apache.org']);

            itShouldMatch('http://www.apache.org:81/',                          ['http://www.apache.org:81/*']);
            itShouldMatch('http://user:pass@www.apache.org:81/foo/bar.html',    ['http://www.apache.org:81/*']);
            itShouldNotMatch('http://www.apache.org:80/',                       ['http://www.apache.org:81/*']);
            itShouldNotMatch('http://www.apache.org/',                          ['http://www.apache.org:81/*']);
            itShouldNotMatch('http://www.apache.org:foo/',                      ['http://www.apache.org:81/*']);
            itShouldNotMatch('http://www.apache.org:81@www.apache.org/',        ['http://www.apache.org:81/*']);
            itShouldNotMatch('http://www.apache.org:81@www.evil.com/',          ['http://www.apache.org:81/*']);

            itShouldMatch('http://www.APAche.org/', ['*.apache.org']);
            itShouldMatch('http://WWw.apache.org/', ['*.apache.org']);
            itShouldMatch('http://www.apache.org/', ['*.APACHE.ORG']);
            itShouldMatch('HTTP://www.apache.org/', ['*.apache.org']);
            itShouldMatch('HTTP://www.apache.org/', ['http://*.apache.org']);
            itShouldMatch('http://www.apache.org/', ['HTTP://*.apache.org']);

            itShouldMatch('http://www.apache.org/foo/',         ['*://*.apache.org/foo/*']);
            itShouldMatch('http://www.apache.org/foo/bar',      ['*://*.apache.org/foo/*']);
            itShouldNotMatch('http://www.apache.org/bar/foo/',  ['*://*.apache.org/foo/*']);
            itShouldNotMatch('http://www.apache.org/Foo/',      ['*://*.apache.org/foo/*']);
            itShouldNotMatch('http://www.apache.org/Foo/bar',   ['*://*.apache.org/foo/*']);
        });

        describe("Test function (cordova.whitelist.test) that checks against config.xml", function () {
            function expectTestWithResult(result) {
                return (function (url, description) {
                    description = description || ((result ? "should accept " : "should reject ") + url);

                    describe("Test function", function () {
                        if (!isAndroid) {
                            pending("Whitelist Plugin only exists for Android");
                        }

                        var cb,
                            originalTimeout;

                        beforeEach(function (done) {
                            originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
                            jasmine.DEFAULT_TIMEOUT_INTERVAL = 7500;
                            cb = jasmine.createSpy('spy').and.callFake(function (){
                                done();
                            });
                            cordova.whitelist.test(url, cb);
                        });

                        afterEach(function () {
                            jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
                        });

                        it(description, function () {
                            expect(cb).toHaveBeenCalledWith(result);
                        });
                    });
                });
            }

            var itShouldAccept = expectTestWithResult(true);
            var itShouldReject = expectTestWithResult(false);

            it("should exist", function () {
                expect(cordova.whitelist.test).toBeDefined();
                expect(typeof cordova.whitelist.test).toBe("function");
            });

            itShouldAccept('http://apache.org');
            itShouldAccept('http://apache.org/');
            itShouldAccept('http://www.apache.org/');
            itShouldAccept('http://www.apache.org/some/path');
            itShouldAccept('http://some.domain.under.apache.org/');
            itShouldAccept('http://user:pass@apache.org/');
            itShouldAccept('http://user:pass@www.apache.org/');
            itShouldAccept('https://www.apache.org/');

            itShouldReject('ftp://www.apache.org/');
            itShouldReject('http://www.apache.com/');
            itShouldReject('http://www.apache.org:pass@evil.com/');
            itShouldReject('http://www.apache.org.evil.com/');

            itShouldAccept('file:///foo');
            if (cordova.platformId == 'android')
                itShouldReject('content:///foo');
        });
    });
}


code:
/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

var argscheck = require('cordova/argscheck'),
    exec = require('cordova/exec');

module.exports = {
    match: function(url, patterns, callback) {
        //argscheck.checkArgs('fF', 'Whitelist.match', arguments);
        exec(callback, callback, "WhitelistAPI", "URLMatchesPatterns", [url, patterns]);
    },
    test: function(url, callback) {
        //argscheck.checkArgs('fF', 'Whitelist.test', arguments);
        exec(callback, callback, "WhitelistAPI", "URLIsAllowed", [url]);
    }
}


code:
const fs = require('fs-extra')
const path = require('path');
const ROOT = path.resolve('/src');

if (fs.existsSync(path.resolve(ROOT, 'LICENSE'))) {
    copyFilesToRoot();
} else {
    console.log(`No root directory found. Skipping copy.`)
}
function copyFilesToRoot() {
    const OUTPUT_ROOT = path.resolve("platforms/android/app/build/outputs/apk/");
    const DIST_ROOT = path.resolve(ROOT, 'dist')
    const version = fs.readFileSync(path.resolve(ROOT, 'version'));
    const paths = {
        android: {
            src: {
                debug: path.resolve(OUTPUT_ROOT, "debug/app-debug.apk"),
                release: path.resolve(OUTPUT_ROOT, "release/app-release.apk")
            },
            dest: {
                debug: path.resolve(DIST_ROOT, `degrees-of-lewdity-${version}-debug.apk`),
                release: path.resolve(DIST_ROOT, `degrees-of-lewdity-${version}.apk`)
            }
        }
    };

    fs.ensureDirSync(DIST_ROOT);
    let numberCopied = 0;
    Object.entries(paths.android.src).forEach(([buildType, srcPath]) => {
        if (fs.existsSync(srcPath)) {
            fs.copyFileSync(srcPath, paths.android.dest[buildType]);
            console.log(`Copied apk ${srcPath} -> ${paths.android.dest[buildType]}`);
            numberCopied++;
        }
    });
    if (numberCopied === 0) {
        console.warn(`No built apks found.`)
    }
}


code:
const fs = require('fs-extra')
const path = require('path');

const ROOT = path.resolve('/src')
if (fs.existsSync(path.resolve(ROOT, 'img'))) {
    copyFilesFromRoot();
} else {
    console.warn(`No root directory found. Skipping copy. Continuing with build as a dry run.`)
}

function copyFilesFromRoot() {
    const MAIN = 'Degrees of Lewdity.html'
    const ANDROID_PATH = path.resolve('./platforms/android/app/src/main/assets/www')
    const paths = {
        src: {
            main: path.resolve(ROOT, MAIN),
            img: path.resolve(ROOT, 'img')
        },
        dest: {
            android: {
                main: path.resolve(ANDROID_PATH, MAIN),
                img: path.resolve(ANDROID_PATH, 'img')
            }
        }
    }
    if (!fs.existsSync(paths.src.main)) {
        throw new Error(`Missing ${paths.src.main}. Be sure to compile dol into html first.`);
    }

    fs.ensureDirSync(paths.dest.android.img);
    fs.copySync(paths.src.main, paths.dest.android.main);
    fs.copySync(paths.src.img, paths.dest.android.img);
}


code:
/**
 * Simple script to ask user for password. Normally this is handled automatically by
 * gradle but it doesn't work in all systems, so have this commandline shim instead.
 */
const cordova = require('cordova');
const read = require('read');
const fs = require('fs-extra');
const path = require('path');

const KEYSTORE_PATH = path.resolve('/src/keys/dol.keystore');
if (!fs.existsSync(KEYSTORE_PATH)) {
    throw new Error(`Missing keystore @${KEYSTORE_PATH}. See the docs for information on how to obtain or build the key first.`)
}
read({ prompt: 'Please enter the password: ', silent: true, replace: '*' }, (err, password) => {
    if (err) { throw err; }
    const args = [process.argv[0], require.resolve('cordova'), 'build',
        '--release',
        '--',
        `--storePassword=${password}`,
        `--password=${password}`,
        `--keystore=${KEYSTORE_PATH}`,
        `--alias=dol`
    ];
    cordova.cli(args);
})


code:
const fs = require('fs-extra');
const path = require('path');
const process = require('process');
const CORDOVA = path.resolve('/cordovasrc');
const SRC = path.resolve('/src');

let version = process.argv[2]
if (!version) {
    const versionFile = path.resolve(SRC, 'version');
    if (!fs.existsSync(versionFile)) {
        console.log(`A version number is required. Unable to open ${versionFile} as a default version number. Aborting version update.`);
        process.exit(1)
    }
    version = fs.readFileSync(versionFile).toString();
} else {
    console.log(`Updating to version number "${version}"`)
}

update(path.resolve(CORDOVA, 'package.json'), (data) => {
    const packageJson = JSON.parse(data);
    packageJson.version = version;
    return JSON.stringify(packageJson, undefined, 2);
}, (err, stage) => {
    console.log(`Failed to update package.json version during ${STAGE[stage]}`)
    throw err;
});
const CONFIG_VERSION_REGEX = /<widget([^>]*)version\s*=\s*"([^"]*)"/g;
const configXmlPath = path.resolve(CORDOVA, 'config.xml')
update(configXmlPath, (data) => {
    CONFIG_VERSION_REGEX.lastIndex = 0;
    const updatedData = data.replace(CONFIG_VERSION_REGEX, `<widget$1version="${version}"`);
    CONFIG_VERSION_REGEX.lastIndex = 0;
    const updatedVersionMatch = CONFIG_VERSION_REGEX.exec(updatedData);
    if (!updatedVersionMatch) {
        throw new Error(`Failed to update version. Widget version attribute is not found. Check contents of ${configXmlPath}.`)
    }
    if (updatedVersionMatch[2].trim() != version.trim()) {
        throw new Error(`Failed to update version. Expected ${version}, but got ${updatedVersionMatch[2]}.`)
    }
    return updatedData;
}, (err, stage) => {
    console.log(`Failed to update config.xml version during ${STAGE[stage]}`)
    throw err;
});

const STAGE = [
    'file read',
    'version update',
    'file write'
]
function update(filename, doUpdate, onError) {
    console.log(`Updating ${filename}`);
    fs.readFile(filename, (err, data) => {
        if (err) {
            onError(err, 0);
        } else {
            let updatedData;
            try {
                updatedData = doUpdate(data.toString())
            } catch (e) {
                onError(e, 1);
            }
            if (!updatedData) {
                onError(new Error('Update failed: returned data is empty'), 1);
            }
            return fs.writeFile(filename, updatedData, (err) => {
                if (err) {
                    onError(err, 2);
                }
            });
        }
    })
}


code:
node_modules/
renderer.js


code:
# TypeScript sources of Canvas renderer

Because JS sucks and TS sucks less.

## Build

```sh
npm install
npm run build
# or
yarn
yarn build
```
Then copy `renderer.js` and `tinycolor.js` to `game/03-JavaScript/00-libs`


code:
declare interface AnyDict {
	[index: string]: any;
}
declare interface Dict<T> {
	[index: string]: T;
}
declare interface BlendGradientSpec {
	gradient: 'linear'|'radial';
	/**
	 * * For linear gradient: [x0, y0, x1, y1].
	 * * For radial gradient: [x0, y0, r0, x1, y1, r1].
	 */
	values: number[];
	/**
	 * Color stops.
	 * Pairs of `[offset, color]` or `[color]`.
	 * Default offsets are for evenly spaced gradient
	 */
	colors: ([number,string]|string)[];
}
declare interface BlendPatternSpec {
	/**
	 * Pattern identifier or a specification, sent to pattern provider to get an actual image
	 */
	pattern: string|object;
}
declare type BlendSpec = string|BlendGradientSpec|BlendPatternSpec;

declare interface CompositeLayerParams {
	/**
	 * Render the layer. Default true, only exact `false` value disables rendering
	 */
	show?: boolean;
	/**
	 * Z-index, bigger is above, default 0
	 */
	z?: number;
	/**
	 * Blend color/gradient/pattern. For color, CSS color string.
	 */
	blend?: BlendSpec;
	/**
	 * Blend mode.
	 */
	blendMode?: string;
	/**
	 * Desaturate the image before processing.
	 */
	desaturate?: boolean;
	/**
	 * Adjust brightness before processing. -1..+1, 0 is don't change
	 */
	brightness?: number;
	/**
	 * Adjust contrast before processing. >=0, 1 is don't change
	 */
	contrast?: number;
	/**
	 * Mask, a stencil image to cut out and display only select parts of this layer.
	 */
	masksrc?: string;
	/**
	 * Alpha, 0-1. Default 1
	 */
	alpha?: number;
	/**
	 * Canvas CSS filter. Default "none". Not recommended to use (experimental feature, low support in browsers)
	 */
	prefilter?: string;
	/**
	 * For CSS-animated canvases:
	 * Override animation frames generation, array of animation frame index to subsprite index.
	 * Ex. defaults for 4-sprite 4-frame are [0, 1, 2, 3], for 2-sprite 4-frame are [0, 0, 1, 1].
	 *
	 * For animated canvases: one-element array of current subsprite index
	 */
	frames?: number[];
	/**
	 * Subsprite size
	 */
	width?: number;
	height?: number;
	/**
	 * Subsprite position on target canvas
	 */
	dx?: number;
	dy?: number;
	/**
	 * Animation name
	 */
	animation?: string;
}
declare interface CompositeLayerSpec extends CompositeLayerParams {
	name?: string;
	/**
	 * Image URL
	 */
	src: string;
}

declare interface KeyframeSpec {
	/**
	 * Index of frame (=subsprite) to display, base 0
	 */
	frame: number;
	/**
	 * Duration of this keyframe, milliseconds (= delay before next keyframe)
	 */
	duration: number;

	// Animating layer properties
	blend?: BlendSpec;
	show?: boolean;
	brightness?: number;
	contrast?: number;
	alpha?: number;
	dx?: number;
	dy?: number;
}

declare type AnimationSpec = KeyframeAnimationSpec | SimpleAnimationSpec;
declare interface KeyframeAnimationSpec {
	keyframes: KeyframeSpec[];
}
declare interface SimpleAnimationSpec {
	frames: number;
	/**
	 * Duration of *every* keyframe
	 */
	duration: number;
}

declare interface CompositeLayer extends CompositeLayerSpec {
	/**
	 * `src` of cached `image` (if `src` changes, `image` will be reloaded)
	 */
	imageSrc?: string;
	/**
	 * Cached image to render
	 */
	image?: CanvasImageSource;
	/**
	 * Loaded/cached mask image
	 */
	mask?: CanvasImageSource;
	/**
	 * Value of `masksrc` corresponding to current `mask` (if masksrc changes mask will be reloaded)
	 */
	cachedMaskSrc?: string;
	/**
	 * Encoded processing options used to display cachedImage
	 */
	cachedProcessing?: string;
	/**
	 * Last displayed composed image
	 */
	cachedImage?: CanvasImageSource;
}


code:
/// <reference path="model.d.ts" />
/// <reference types="tinycolor2" />
declare namespace Renderer {
    export interface LayerImageLoader {
        loadImage(src: string, layer: CompositeLayer, successCallback: (src: string, layer: CompositeLayer, image: HTMLImageElement) => any, errorCallback: (src: string, layer: CompositeLayer, error: any) => any): any;
    }
    export const DefaultImageLoader: LayerImageLoader;
    export let ImageLoader: LayerImageLoader;
    export interface RendererListener {
        error?: (error: Error, context: any) => any;
        composeLayers?: (layers: CompositeLayer[]) => any;
        loaded?: (layer: string, src: string) => any;
        loadError?: (layer: string, src: string) => any;
        loadingDone?: (time: number, count: number) => any;
        beforeRender?: (layers: CompositeLayer[]) => any;
        layerCacheMiss?: (layer: CompositeLayer) => any;
        layerCacheHit?: (layer: CompositeLayer) => any;
        processingStep?: (layer: string, processing: string, canvas: HTMLCanvasElement, dt: number) => any;
        composition?: (layer: string, result: HTMLCanvasElement) => any;
        renderingDone?: (time: number) => any;
        keyframe?: (animation: string, keyframeIndex: number, keyframe: KeyframeSpec) => any;
        keyframeRender?: (spec: string, cacheHit: boolean, cacheRenderTime: number) => any;
        animationStop?: () => any;
    }
    /**
     * Last arguments to composeLayers
     */
    export let lastCall: any[] | undefined;
    /**
     * Last arguments to animateLayers
     */
    export let lastAnimateCall: any[] | undefined;
    /**
     * Last result of animateLayers
     */
    export let lastAnimation: AnimatingCanvas | undefined;
    /**
     * Use "pixels" of this size when generating images.
     */
    export let pixelSize: number;
    export function emptyLayerFilter(): CompositeLayerParams;
    /**
     * 0 -> "#000000", 0.5 -> "#808080", 1.0 -> "#FFFFFF"
     */
    export function gray(value: number): string;
    export function createCanvas(w: number, h: number, fill?: string): CanvasRenderingContext2D;
    export function ensureCanvas(image: CanvasImageSource): HTMLCanvasElement;
    /**
     * Free to use CanvasRenderingContext2D (to create image data, gradients, patterns)
     */
    export const globalC2D: CanvasRenderingContext2D;
    /**
     * Creates a cutout of color in shape of sourceImage
     */
    export function cutout(sourceImage: CanvasImageSource, color: string, canvas?: CanvasRenderingContext2D): CanvasRenderingContext2D;
    /**
     * Cuts out from base a shape in form of stencil.
     * Modifies and returns base.
     */
    export function cutoutFrom(base: CanvasRenderingContext2D, stencil: CanvasImageSource): CanvasRenderingContext2D;
    /**
     * Paints sourceImage over cutout of it filled with color.
     */
    export function composeOverCutout(sourceImage: CanvasImageSource, color: string, blendMode?: string, canvas?: CanvasRenderingContext2D): CanvasRenderingContext2D;
    /**
     * Repeatedly fill all sub-frames of canvas with same style.
     * (Makes sense with gradient and pattern fills, to keep consistents across all sub-frames)
     */
    export function fillFrames(fillStyle: string | CanvasGradient | CanvasPattern, canvas: CanvasRenderingContext2D, frameCount: number, frameWidth: number): void;
    export let Patterns: Dict<CanvasPattern>;
    /**
     * CanvasPattern generator/provider.
     * Default implementation looks up in the Renderer.Patterns object, can be replaced to accept complex object
     * and generate custom pattern.
     */
    export let PatternProvider: (spec: string | object) => (CanvasPattern | null);
    export function createGradient(spec: BlendGradientSpec): CanvasGradient;
    /**
     * Paints sourceImage over same-sized canvas filled with pattern or gradient
     */
    export function composeOverSpecialRect(sourceImage: CanvasImageSource, fillStyle: CanvasGradient | CanvasPattern, blendMode: string, frameCount: number, targetCanvas?: CanvasRenderingContext2D): CanvasRenderingContext2D;
    /**
     * Paints sourceImage over same-sized canvas filled with color
     */
    export function composeOverRect(sourceImage: CanvasImageSource, color: string, blendMode: string, targetCanvas?: CanvasRenderingContext2D): CanvasRenderingContext2D;
    /**
     * Paints over sourceImage a cutout of it filled with color.
     */
    export function composeUnderCutout(sourceImage: CanvasImageSource, color: string, blendMode?: string, canvas?: CanvasRenderingContext2D): CanvasRenderingContext2D;
    /**
     * Paints over sourceImage a same-sized canvas filled with color
     */
    export function composeUnderRect(sourceImage: CanvasImageSource, color: string, blendMode?: string, targetCanvas?: CanvasRenderingContext2D): CanvasRenderingContext2D;
    export let ImageCaches: {
        [index: string]: HTMLImageElement;
    };
    export let ImageErrors: {
        [index: string]: boolean;
    };
    /**
     * Switch between compose(Over|Under)(Rect|Cutout)
     */
    export function compose(composeOver: boolean, doCutout: boolean, sourceImage: CanvasImageSource, color: string, blendMode: string, targetCanvas?: CanvasRenderingContext2D): CanvasRenderingContext2D;
    /**
     * Fills properties in `target` from `source`.
     * If `overwrite` is false, only missing properties are copied.
     * In both cases, brightness is added, contrast is multiplied.
     * Returns target
     */
    export function mergeLayerData(target: CompositeLayerSpec, source: CompositeLayerParams, overwrite?: boolean): CompositeLayerSpec;
    export function encodeProcessing(spec: CompositeLayerSpec): string;
    export function composeLayersAgain(): void;
    export function desaturateImage(image: CanvasImageSource, resultCanvas?: CanvasRenderingContext2D, doCutout?: boolean): HTMLCanvasElement;
    export function filterImage(image: CanvasImageSource, filter: string, resultCanvas?: CanvasRenderingContext2D): HTMLCanvasElement;
    export function adjustBrightness(image: CanvasImageSource, brightness: number, resultCanvas?: CanvasRenderingContext2D, doCutout?: boolean): HTMLCanvasElement;
    export function adjustLevels(image: CanvasImageSource,
    /**
     * scale factor, 1 - no change, >1 - higher contrast, <1 - lower contrast.
     */
    factor: number,
    /**
     * shift, 0 - no change, >0 - brighter, <0 - darker
     */
    shift: number, resultCanvas?: CanvasRenderingContext2D): HTMLCanvasElement;
    export function adjustContrast(image: CanvasImageSource, factor: number, resultCanvas?: CanvasRenderingContext2D): HTMLCanvasElement;
    export function adjustBrightnessAndContrast(image: CanvasImageSource, brightness: number, contrast: number, resultCanvas?: CanvasRenderingContext2D): HTMLCanvasElement;
    export interface RenderPipelineContext {
        layer: CompositeLayer;
        /**
         * Updates along the pipeline
         */
        image: CanvasImageSource;
        listener: RendererListener;
        rects: LayerRects;
        needsCutout: boolean;
        [index: string]: any;
    }
    /**
     * Abstraction of layer processing steps.
     * All steps are stored in RenderingPipeline array, and can be changed externally
     */
    export interface RenderingStep {
        name: string;
        /**
         * Return true if this step has to be performed
         */
        condition(layer: CompositeLayer, context: RenderPipelineContext): boolean;
        /**
         * Rendering function, returns resulting image.
         */
        render(image: CanvasImageSource, layer: CompositeLayer, context: RenderPipelineContext): HTMLCanvasElement;
    }
    /**
     * Rendering steps used. Order matters!
     */
    export const RenderingPipeline: RenderingStep[];
    export function processLayer(layer: CompositeLayer, rects: LayerRects, listener: RendererListener): CanvasImageSource;
    interface LayerRects {
        width: number;
        height: number;
        frameWidth: number;
        frameCount: number;
        subspriteWidth: number;
        subspriteHeight: number;
        subspriteFrameCount: number;
        dx: number;
        dy: number;
    }
    export function composeProcessedLayer(layer: CompositeLayer, targetCanvas: CanvasRenderingContext2D, rects: LayerRects): void;
    export function composeLayers(targetCanvas: CanvasRenderingContext2D, layerSpecs: CompositeLayerSpec[], frameCount: number, listener: RendererListener): void;
    export interface AnimationInfo {
        spec: KeyframeAnimationSpec;
        /**
         * True if any layer properties other than `frame`, are animated
         */
        complex: boolean;
        /**
         * Affected layers
         */
        layers: CompositeLayerSpec[];
        name: string;
        /**
         * id from setTimeout
         */
        timeoutId: number;
        /**
         * Current keyframe index
         */
        keyframeIndex: number;
        /**
         * Current keyframe
         */
        keyframe: KeyframeSpec;
        /**
         * Time of current keyframe start, milliseconds
         */
        time: number;
    }
    export interface AnimatingCanvas {
        playing: boolean;
        time: number;
        target: CanvasRenderingContext2D;
        keyframeCaches: Dict<CanvasRenderingContext2D>;
        animations: AnimationInfo[];
        /**
         * True during rendering a frame
         */
        busy: boolean;
        redraw(): void;
        invalidateCaches(): void;
        start(): void;
        stop(): void;
    }
    export function invalidateLayerCaches(layers: CompositeLayer[]): void;
    export function animateLayersAgain(): any;
    export let Animations: Dict<AnimationSpec>;
    /**
     * Animation spec provider; default implementation is look up in Renderer.Animations by layer's `animation` property.
     *
     * Can be overridden to auto-generate animations, for example.
     */
    export let AnimationProvider: (layer: CompositeLayerSpec) => (AnimationSpec | undefined);
    /**
     * Animatable properties of KeyframeSpec and CompositeLayer
     */
    export const AnimatableProps: string[];
    export function animateLayers(targetCanvas: CanvasRenderingContext2D, layerSpecs: CompositeLayerSpec[], listener: RendererListener, autoStop?: boolean): AnimatingCanvas;
    /**
     * Linear interpolation.
     *
     * f(0) = min,
     * f(1) = max.
     */
    export function lint(value: number, min: number, max: number, allowOverflow?: boolean): number;
    export function lintArray(value: number, mins: number[], maxes: number[], allowOverflow?: boolean): number[];
    export function lintStaged(value: number, points: number[]): number;
    export function lintRgb(value: number, min: tinycolor.ColorInput, max: tinycolor.ColorInput): tinycolor.Instance;
    export function lintRgbStaged(value: number, points: tinycolor.ColorInput[]): tinycolor.Instance;
    export {};
}
interface Window {
    lint(value: number, min: number, max: number, allowOverflow?: boolean): number;
    lintArray(value: number, mins: number[], maxes: number[], allowOverflow?: boolean): number[];
    lintStaged(value: number, points: number[]): number;
    lintRgb(value: number, min: tinycolor.ColorInput, max: tinycolor.ColorInput): tinycolor.Instance;
    lintRgbStaged(value: number, points: tinycolor.ColorInput[]): tinycolor.Instance;
}