Rename Client to App and move into separate module

Move crypto providers into separate modules
Start implementing new Client class for communication with the server
Update project structure to monorepo structure with multiple packages
This commit is contained in:
Martin Kleinschrodt 2018-06-20 21:46:15 +02:00
parent 8380bf381a
commit 3b6c804d5b
709 changed files with 141840 additions and 28005 deletions

15
.gitignore vendored
View File

@ -1,19 +1,16 @@
bower_components
node_modules
packages/*/node_modules
deploy
dist
# files/directories generated by cordova
cordova/platforms
cordova/plugins
cordova/www
packages/cordova/platforms
pacakges/cordova/plugins
pacakges/cordova/www
// Core files compiled from typescript
app/src/core/*.js
app/src/core/*.js.map
core/*.js
core/*.js.map
packages/*/lib
tests.js
app/build
packages/app/build
build

View File

@ -1,278 +0,0 @@
import { sjcl } from "../../vendor/sjcl";
import { isCordova, hasNode } from "./platform";
declare var pbkdf2:
| undefined
| ((pass: string, salt: string, opts: { iterations: number; keySize: number }) => Promise<string>);
const nodeCrypto = hasNode() && window.require("crypto");
export class CryptoError {
constructor(
public code:
| "invalid_container_data"
| "unsupported_container_version"
| "invalid_key_params"
| "decryption_failed"
| "encryption_failed"
) {}
}
// Available cipher algorithms
export type Cipher = "aes";
// Available cipher modes
export type Mode = "ccm" | "ocb2";
// Available key sizes
export type KeySize = 128 | 192 | 256;
// Available authentication tag sizes
export type AtSize = 64 | 96 | 128;
// Minimum number of pbkdf2 iterations
const PBKDF2_ITER_MIN = 1e4;
// Default number of pbkdf2 iterations
const PBKDF2_ITER_DEFAULT = 5e4;
// Maximum number of pbkdf2 iterations
const PBKDF2_ITER_MAX = 1e7;
// Shorthands for codec functions
const bitsToBase64 = sjcl.codec.base64.fromBits;
const base64ToBits = sjcl.codec.base64.toBits;
const bitsToUtf8 = sjcl.codec.utf8String.fromBits;
const utf8ToBits = sjcl.codec.utf8String.toBits;
/**
* Returns a base64 encoded random string
*/
function randBase64(): string {
return bitsToBase64(sjcl.random.randomWords(4, 0));
}
export interface Pbkdf2Params {
keySize: KeySize;
salt: string;
iter: number;
}
export interface KeyParams extends Pbkdf2Params {
password: string;
}
export interface CipherParams {
cipher: Cipher;
mode: Mode;
iv: string;
adata: string;
ts: AtSize;
}
function validateKeyParams(params: any) {
return (
[128, 192, 256].includes(params.keySize) &&
typeof params.iter == "number" && // valid PBKDF2 iteration count
params.iter <= PBKDF2_ITER_MAX && // sane pbkdf2 iteration count
typeof params.salt == "string"
); //valid salt
}
function genKey(params: KeyParams): Promise<string> {
if (!params.password || typeof params.password !== "string" || !validateKeyParams(params)) {
throw new CryptoError("invalid_key_params");
}
if (isCordova() && typeof pbkdf2 === "function") {
return pbkdf2(params.password, params.salt, {
iterations: params.iter,
keySize: params.keySize
});
} else if (nodeCrypto) {
return new Promise((resolve, reject) => {
nodeCrypto.pbkdf2(
params.password,
new Buffer(params.salt, "base64"),
params.iter,
params.keySize / 8,
"sha256",
(e: Error, key: string) => {
if (e) {
reject(e);
} else {
resolve(new Buffer(key).toString("base64"));
}
}
);
});
} else {
let k = sjcl.misc.pbkdf2(utf8ToBits(params.password), base64ToBits(params.salt), params.iter, params.keySize);
return Promise.resolve(bitsToBase64(k));
}
}
function decrypt(key: string, ct: string, params: CipherParams): string {
try {
const cipher = new sjcl.cipher[params.cipher](base64ToBits(key));
const pt = sjcl.mode[params.mode].decrypt(
cipher,
base64ToBits(ct),
base64ToBits(params.iv),
base64ToBits(params.adata),
params.ts
);
return bitsToUtf8(pt);
} catch (e) {
throw new CryptoError("decryption_failed");
}
}
function encrypt(key: string, pt: string, params: CipherParams): string {
try {
const cipher = new sjcl.cipher[params.cipher](base64ToBits(key));
const mode = sjcl.mode[params.mode];
var ct = mode.encrypt(cipher, utf8ToBits(pt), base64ToBits(params.iv), base64ToBits(params.adata), params.ts);
return bitsToBase64(ct);
} catch (e) {
throw new CryptoError("encryption_failed");
}
}
export interface RawContainerV0 extends Pbkdf2Params, CipherParams {
version: undefined;
ct: string;
}
export interface RawContainerV1 extends Pbkdf2Params, CipherParams {
version: 1;
ct: string;
}
export type RawContainer = RawContainerV0 | RawContainerV1;
export class Container implements KeyParams, CipherParams {
password: string;
salt: string;
iv: string;
adata: string;
ct: string;
private keyCache: Map<string, string>;
constructor(
public cipher: Cipher = "aes",
public mode: Mode = "ccm",
public keySize: KeySize = 256,
public iter = PBKDF2_ITER_DEFAULT,
public ts: AtSize = 64
) {
this.salt = randBase64();
this.keyCache = new Map<string, string>();
}
private async genKey(): Promise<string> {
const p = JSON.stringify([this.password, this.salt, this.iter, this.keySize]);
let key = this.keyCache.get(p);
if (!key) {
key = await genKey(this);
this.keyCache.set(p, key);
}
return key;
}
async set(data: string): Promise<void> {
// Make sure minium number of iterations is used
this.iter = Math.max(this.iter, PBKDF2_ITER_MIN);
const key = await this.genKey();
this.iv = randBase64();
this.adata = randBase64();
this.ct = encrypt(key, data, this);
}
async get(): Promise<string> {
const key = await this.genKey();
return decrypt(key, this.ct, this);
}
raw(): RawContainer {
return {
version: 1,
cipher: this.cipher,
mode: this.mode,
keySize: this.keySize,
iter: this.iter,
ts: this.ts,
salt: this.salt,
iv: this.iv,
adata: this.adata,
ct: this.ct
};
}
toJSON(): string {
return JSON.stringify(this.raw());
}
clear(): void {
this.password = "";
this.ct = "";
this.keyCache.clear();
}
static fromRaw(raw: RawContainer): Container {
if (!Container.validateRaw(raw)) {
throw new CryptoError("invalid_container_data");
}
switch (raw.version) {
case undefined:
// Legacy versions of Padlock had a bug where the base64-encoded
// `adata` value was not converted to a BitArray before being
// passed to `sjcl.mode.ccm.encrypt/decrypt` and the raw string was
// passed instead. This went unnoticed as the functions in question
// quietly accepted the string and simply treated it as an array.
// So in order to successfully decrypt legacy containers we have to
// perfom this conversion first.
raw.adata = bitsToBase64(raw.adata);
break;
case 1:
break;
default:
throw new CryptoError("unsupported_container_version");
}
let cont = new Container(raw.cipher, raw.mode, raw.keySize, raw.iter, raw.ts);
Object.assign(cont, { salt: raw.salt, iv: raw.iv, adata: raw.adata, ct: raw.ct });
return cont;
}
static fromJSON(json: string): Container {
let raw: RawContainer;
try {
raw = JSON.parse(json);
raw.cipher = raw.cipher.toLowerCase() as Cipher;
raw.mode = raw.mode.toLowerCase() as Mode;
} catch (e) {
throw new CryptoError("invalid_container_data");
}
return Container.fromRaw(raw);
}
static validateRaw(obj: RawContainer) {
return (
typeof obj == "object" &&
(obj.version === undefined || typeof obj.version === "number") && // has a valid version
validateKeyParams(obj) &&
["aes"].includes(obj.cipher) && // valid cipher
["ccm", "ocb2"].includes(obj.mode) && // exiting mode
typeof obj.iv == "string" && // valid initialisation vector
typeof obj.ct == "string" && // valid cipher text
typeof obj.adata == "string" && // valid authorisation data
[64, 96, 128].includes(obj.ts)
); // valid authorisation tag length
}
}

View File

@ -1,276 +0,0 @@
import { uuid } from "./util";
import { getAppVersion } from "./platform";
import { Source } from "./source";
function compareProperty(p: string): (a: Object, b: Object) => number {
return (a, b) => {
const x = typeof a[p] === "string" ? a[p].toLowerCase() : a[p];
const y = typeof b[p] === "string" ? b[p].toLowerCase() : b[p];
return x > y ? 1 : x < y ? -1 : 0;
};
}
const compareName = compareProperty("name");
export interface Field {
name: string;
value: string;
masked?: boolean;
}
function normalizeTag(tag: string): string {
return tag.replace(",", "");
}
export class Record {
private _tags: Set<string>;
name: string;
fields: Array<Field>;
uuid: string;
updated: Date;
removed: boolean;
lastUsed?: Date;
constructor(
name = "",
fields?: Array<Field>,
tags?: string[],
id?: string,
updated?: Date,
removed = false,
lastUsed?: Date
) {
this.name = name;
this.fields = fields || new Array<Field>();
if (!Array.isArray(tags)) {
tags = [];
}
this._tags = new Set<string>(tags.map(normalizeTag));
this.uuid = id || uuid();
this.updated = updated || new Date();
this.removed = removed;
this.lastUsed = lastUsed;
}
static fromRaw(obj: any): Record {
const fields = obj.fields && <Array<Field>>obj.fields;
const updated = obj.updated && (obj.updated instanceof Date ? obj.updated : new Date(obj.updated));
const lastUsed = obj.lastUsed && (obj.lastUsed instanceof Date ? obj.lastUsed : new Date(obj.lastUsed));
const tags = obj.tags || (obj.category && [obj.category]);
return new Record(obj.name, fields, tags, obj.uuid, updated, obj.removed, lastUsed);
}
static compare(a: Record, b: Record): number {
return compareName(a, b);
}
get tags() {
return [...this._tags];
}
addTag(tag: string) {
this._tags.add(normalizeTag(tag));
}
removeTag(tag: string) {
this._tags.delete(tag);
}
hasTag(tag: string) {
return this._tags.has(tag);
}
remove(): void {
this.name = "";
this.fields = [];
this._tags = new Set<string>();
this.removed = true;
this.updated = new Date();
}
raw(): Object {
return {
name: this.name,
fields: this.fields,
tags: this.tags,
uuid: this.uuid,
updated: this.updated,
removed: this.removed,
lastUsed: this.lastUsed
};
}
}
export class Collection {
private _records: Map<string, Record>;
constructor(records?: Record[]) {
this._records = new Map<string, Record>();
if (records) {
this.add(records);
}
}
get records(): Array<Record> {
return Array.from(this._records.values());
}
get tags(): string[] {
const tags = new Set<string>();
for (const r of this.records) {
for (const t of r.tags) {
tags.add(t);
}
}
return [...tags];
}
async fetch(source: Source): Promise<void> {
let data = await source.get();
if (data) {
let records = JSON.parse(data).map(Record.fromRaw);
this.add(records);
}
}
save(source: Source): Promise<void> {
return source.set(this.toJSON());
}
add(rec: Record | Array<Record>) {
let records = Array.isArray(rec) ? rec : [rec];
for (let r of records) {
let existing = this._records.get(r.uuid);
if (!existing || r.updated > existing.updated) {
this._records.set(r.uuid, r);
}
}
}
toJSON(): string {
return JSON.stringify(this.records.map(rec => rec.raw()));
}
clear(): void {
this._records.clear();
}
}
export interface Device {
description: string;
tokenId: string;
}
export interface Account {
email: string;
devices: Device[];
}
export class Settings {
static defaults = {
autoLock: true,
// Auto lock delay in minutes
autoLockDelay: 5,
stripePubKey: "",
syncHostUrl: "https://cloud.padlock.io",
syncCustomHost: false,
syncEmail: "",
syncToken: "",
syncDevice: "",
syncConnected: false,
syncAuto: false,
syncSubStatus: "",
syncTrialEnd: 0,
syncDeviceCount: 0,
account: undefined,
defaultFields: ["username", "password"],
obfuscateFields: false,
syncRequireSubscription: false,
syncId: "",
version: ""
};
loaded: boolean;
// Auto lock settings
autoLock: boolean;
// Auto lock delay in minutes
autoLockDelay: number;
peekValues: boolean;
// Stripe settings
stripePubKey: string;
// Synchronization settings
syncHostUrl: string;
syncCustomHost: boolean;
syncEmail: string;
syncToken: string;
syncConnected: boolean;
syncAuto: boolean;
syncSubStatus: string;
syncTrialEnd: number;
syncId: string;
syncDeviceCount: number;
account?: Account;
// Record-related settings
recordDefaultFields: Array<string>;
recordObfuscateFields: boolean;
// Miscellaneous settings
showedBackupReminder: number;
version: string;
constructor() {
// Set defaults
this.clear();
// Flag used to indicate if the settings have been loaded from persistent storage initially
this.loaded = false;
}
loadJSON(json: string) {
let data: any;
try {
data = JSON.parse(json);
} catch (e) {
data = {};
}
// Copy over setting values
Object.assign(this, data);
}
//* Returns a raw JS object containing the current settings
raw(): Object {
let obj = {};
// Extract settings from `Settings` Object based on property names in `properties` member
for (let prop in Settings.defaults) {
obj[prop] = this[prop];
}
return obj;
}
toJSON(): string {
return JSON.stringify(this.raw());
}
async fetch(source: Source): Promise<void> {
let json = await source.get();
this.loadJSON(json);
this.version = await getAppVersion();
// Update loaded flag to indicate that data has been loaded from persistent storage at least once
this.loaded = true;
}
async save(source: Source): Promise<void> {
await source.set(this.toJSON());
this.loaded = true;
}
clear(): void {
Object.assign(this, Settings.defaults);
this.loaded = false;
}
}

View File

@ -1,355 +0,0 @@
import { request, Method, AjaxError, Client } from "./ajax";
import { FileManager, HTML5FileManager, CordovaFileManager, NodeFileManager } from "./file";
import { Settings } from "./data";
import { Container } from "./crypto";
import { isCordova, isElectron, getDeviceInfo, isChromeApp } from "./platform";
declare var chrome: any;
const chromeLocalStorage = typeof chrome !== "undefined" && chrome.storage && chrome.storage.local;
export interface Source {
get(): Promise<string>;
set(data: string): Promise<void>;
clear(): Promise<void>;
}
export class MemorySource implements Source {
constructor(private data = "") {}
async get(): Promise<string> {
return this.data;
}
async set(data: string): Promise<void> {
this.data = data;
}
async clear(): Promise<void> {
this.data = "";
}
}
export class HTML5LocalStorageSource implements Source {
constructor(public key: string) {}
async get(): Promise<string> {
return localStorage.getItem(this.key) || "";
}
async set(data: string): Promise<void> {
localStorage.setItem(this.key, data);
}
async clear(): Promise<void> {
localStorage.removeItem(this.key);
}
}
export class ChromeLocalStorageSource implements Source {
constructor(public key: string) {}
get(): Promise<string> {
return new Promise(resolve => {
chromeLocalStorage.get(this.key, (obj: any) => {
let data = obj[this.key];
if (typeof data === "object") {
data = JSON.stringify(data);
}
resolve(data || "");
});
});
}
set(data: string): Promise<void> {
const obj = {
[this.key]: data
};
return new Promise<void>(resolve => chromeLocalStorage.set(obj, resolve));
}
clear(): Promise<void> {
return new Promise<void>(resolve => chromeLocalStorage.remove(this.key, resolve));
}
}
export const LocalStorageSource = isChromeApp() ? ChromeLocalStorageSource : HTML5LocalStorageSource;
export class AjaxSource implements Source {
constructor(private _url: string) {}
get url() {
return this._url;
}
request(method: Method, url: string, data?: string, headers?: Map<string, string>): Promise<XMLHttpRequest> {
return request(method, url, data, headers);
}
get(): Promise<string> {
return this.request("GET", this.url).then(req => req.responseText);
}
set(data: string): Promise<void> {
return this.request("POST", this.url, data).then(() => {});
}
clear(): Promise<void> {
return this.set("").then(() => {});
}
}
export interface CloudAuthToken {
email: string;
token: string;
id: string;
// Activation url returned by server. Only used for testing
actUrl?: string;
}
export class CloudSource extends AjaxSource implements Client {
urlForPath(path: string): string {
// Remove trailing slashes
const host = this.settings.syncCustomHost
? this.settings.syncHostUrl.replace(/\/+$/, "")
: "https://cloud.padlock.io";
return `${host}/${path}/`;
}
constructor(public settings: Settings) {
super("");
}
get url() {
return this.urlForPath("store");
}
async request(method: Method, url: string, data?: string, headers?: Map<string, string>): Promise<XMLHttpRequest> {
headers = headers || new Map<string, string>();
headers.set("Accept", "application/vnd.padlock;version=1");
if (this.settings.syncEmail && this.settings.syncToken) {
headers.set("Authorization", "AuthToken " + this.settings.syncEmail + ":" + this.settings.syncToken);
}
const { uuid, platform, osVersion, appVersion, manufacturer, model, hostName } = await getDeviceInfo();
headers.set("X-Device-App-Version", appVersion || "");
headers.set("X-Device-Platform", platform || "");
headers.set("X-Device-UUID", uuid || "");
headers.set("X-Device-Manufacturer", manufacturer || "");
headers.set("X-Device-OS-Version", osVersion || "");
headers.set("X-Device-Model", model || "");
headers.set("X-Device-Hostname", hostName || "");
const req = await super.request(method, url, data, headers);
const subStatus = req.getResponseHeader("X-Sub-Status");
if (subStatus !== null) {
this.settings.syncSubStatus = subStatus;
}
const stripePubKey = req.getResponseHeader("X-Stripe-Pub-Key");
if (stripePubKey !== null) {
this.settings.stripePubKey = stripePubKey;
}
const trialEnd = req.getResponseHeader("X-Sub-Trial-End");
if (trialEnd !== null) {
try {
this.settings.syncTrialEnd = parseInt(trialEnd, 10);
} catch (e) {
//
}
}
return req;
}
async authenticate(
email: string,
create = false,
authType = "api",
redirect = "",
actType = ""
): Promise<CloudAuthToken> {
const params = new URLSearchParams();
params.set("email", email);
params.set("type", authType);
params.set("redirect", redirect);
params.set("actType", actType);
const req = await this.request(
create ? "POST" : "PUT",
this.urlForPath("auth"),
params.toString(),
new Map<string, string>().set("Content-Type", "application/x-www-form-urlencoded")
);
let authToken: CloudAuthToken;
try {
authToken = <CloudAuthToken>JSON.parse(req.responseText);
} catch (e) {
throw new AjaxError(req);
}
return authToken;
}
async requestAuthToken(email: string, create = false, redirect = "", actType?: string): Promise<CloudAuthToken> {
const authToken = await this.authenticate(email, create, "api", redirect, actType);
this.settings.syncEmail = authToken.email;
this.settings.syncToken = authToken.token;
return authToken;
}
async getLoginUrl(redirect: string) {
if (!this.settings.syncConnected) {
throw { code: "invalid_auth_token", message: "Need to be authenticated to get a login link." };
}
const authToken = await this.authenticate(this.settings.syncEmail, false, "web", redirect);
return authToken.actUrl;
}
async testCredentials(): Promise<boolean> {
try {
await this.get();
return true;
} catch (e) {
const err = <AjaxError>e;
if (err.code === "invalid_auth_token") {
return false;
} else {
throw err;
}
}
}
activateToken(code: string): Promise<Boolean> {
const params = new URLSearchParams();
params.set("code", code);
params.set("email", this.settings.syncEmail);
return this.request(
"POST",
this.urlForPath("activate"),
params.toString(),
new Map<string, string>().set("Content-Type", "application/x-www-form-urlencoded")
)
.then(() => true)
.catch(e => {
if (e.code === "bad_request") {
return false;
} else {
throw e;
}
});
}
logout(): Promise<XMLHttpRequest> {
return this.request("GET", this.urlForPath("logout"));
}
async getAccountInfo(): Promise<Account> {
const res = await this.request("GET", this.urlForPath("account"));
const account = JSON.parse(res.responseText);
this.settings.account = account;
return account;
}
revokeAuthToken(tokenId: string): Promise<XMLHttpRequest> {
const params = new URLSearchParams();
params.set("id", tokenId);
return this.request(
"POST",
this.urlForPath("revoke"),
params.toString(),
new Map<string, string>().set("Content-Type", "application/x-www-form-urlencoded")
);
}
subscribe(stripeToken = "", coupon = "", source = ""): Promise<XMLHttpRequest> {
const params = new URLSearchParams();
params.set("stripeToken", stripeToken);
params.set("coupon", coupon);
params.set("source", source);
return this.request(
"POST",
this.urlForPath("subscribe"),
params.toString(),
new Map<string, string>().set("Content-Type", "application/x-www-form-urlencoded")
);
}
cancelSubscription(): Promise<XMLHttpRequest> {
return this.request("POST", this.urlForPath("unsubscribe"));
}
getPlans(): Promise<any[]> {
return this.request("GET", this.urlForPath("plans")).then(res => <any[]>JSON.parse(res.responseText));
}
}
export class EncryptedSource implements Source {
private container?: Container;
public password: string;
constructor(public source: Source) {}
async get(): Promise<string> {
let data = await this.source.get();
if (data == "") {
return "";
}
let cont = (this.container = Container.fromJSON(data));
cont.password = this.password;
return await cont.get();
}
async set(data: string): Promise<void> {
// Reuse container if possible
let cont = (this.container = this.container || new Container());
cont.password = this.password;
await cont.set(data);
return this.source.set(cont.toJSON());
}
async hasData(): Promise<boolean> {
const data = await this.source.get();
return data !== "";
}
clear(): Promise<void> {
this.password = "";
if (this.container) {
this.container.clear();
}
delete this.container;
return this.source.clear();
}
}
export class FileSource implements Source {
private fileManager: FileManager;
constructor(private filePath: string) {
this.fileManager = isElectron()
? new NodeFileManager()
: isCordova()
? new CordovaFileManager()
: new HTML5FileManager();
}
get(): Promise<string> {
return this.fileManager.read(this.filePath);
}
set(data: string): Promise<void> {
return this.fileManager.write(this.filePath, data);
}
clear(): Promise<void> {
return this.fileManager.write(this.filePath, "");
}
}

View File

@ -1,26 +0,0 @@
import { FileSource } from "./source";
const statsSource = new FileSource("stats.json");
export type Stats = { [prop: string]: number | string };
let stats: Stats;
const statsLoaded = statsSource.get().then(data => {
try {
stats = JSON.parse(data);
} catch (e) {
stats = {};
}
});
const saveStats = () => statsSource.set(JSON.stringify(stats));
export async function get(): Promise<Stats> {
await statsLoaded;
return stats;
}
export async function set(data: Stats): Promise<void> {
await statsLoaded;
Object.assign(stats, data);
return saveStats();
}

View File

@ -1,580 +0,0 @@
import {
Base64String,
bytesToBase64,
base64ToBytes,
stringToBytes,
stringToBase64,
base64ToString,
marshal,
unmarshal
} from "./encoding";
import { sjcl } from "../app/vendor/sjcl";
import { Storable, Storage } from "./storage";
// Minimum number of pbkdf2 iterations
const PBKDF2_ITER_MIN = 1e4;
// Default number of pbkdf2 iterations
const PBKDF2_ITER_DEFAULT = 5e4;
// Maximum number of pbkdf2 iterations
const PBKDF2_ITER_MAX = 1e7;
export type CipherText = Base64String;
export type PlainText = Base64String;
// Available Symmetric Key Sizes
export type KeySize = 128 | 192 | 256;
// Available authentication tag sizes
export type TagSize = 64 | 96 | 128;
export type SymmetricKey = Base64String;
export type PublicKey = Base64String;
export type PrivateKey = Base64String;
export type Key = SymmetricKey | PublicKey | PrivateKey;
export type CipherType = "symmetric" | "asymmetric";
export interface BaseCipherParams {
cipherType: CipherType;
algorithm: string;
}
export interface SymmetricCipherParams extends BaseCipherParams {
cipherType: "symmetric";
algorithm: "AES-GCM" | "AES-CCM";
tagSize: TagSize;
keySize: KeySize;
iv?: Base64String;
additionalData?: Base64String;
}
export interface AsymmetricCipherParams extends BaseCipherParams {
cipherType: "asymmetric";
algorithm: "RSA-OAEP";
}
export type CipherParams = SymmetricCipherParams | AsymmetricCipherParams;
export interface KeyDerivationparams {
algorithm: "PBKDF2";
hash: "SHA-256" | "SHA-512";
keySize: KeySize;
iterations: number;
salt?: string;
}
export interface WrapKeyParams {
algorithm: "RSA-OAEP";
}
export interface CryptoProvider {
isAvailable(): boolean;
randomBytes(n: number): Base64String;
randomKey(n: KeySize): Promise<SymmetricKey>;
deriveKey(password: string, params: KeyDerivationparams): Promise<SymmetricKey>;
encrypt(key: Key, data: PlainText, params: CipherParams): Promise<CipherText>;
decrypt(key: Key, data: Base64String, params: CipherParams): Promise<PlainText>;
generateKeyPair(): Promise<{ privateKey: PrivateKey; publicKey: PublicKey }>;
}
export class CryptoError {
constructor(
public code:
| "invalid_container_data"
| "unsupported_container_version"
| "invalid_cipher_params"
| "invalid_key_params"
| "decryption_failed"
| "encryption_failed"
| "not_supported"
) {}
}
export function validateCipherParams(params: CipherParams) {
switch (params.cipherType) {
case "symmetric":
return (
["AES-GCM", "AES-CCM"].includes(params.algorithm) &&
// TODO: validate base 64
!!params.iv &&
typeof params.iv === "string" &&
!!params.additionalData &&
typeof params.additionalData === "string" &&
params.tagSize &&
[64, 96, 128].includes(params.tagSize)
);
break;
case "asymmetric":
return params.algorithm === "RSA-OAEP";
break;
default:
return false;
}
}
export function defaultKeyDerivationParams(): KeyDerivationparams {
return {
algorithm: "PBKDF2",
hash: "SHA-256",
keySize: 256,
iterations: PBKDF2_ITER_DEFAULT
};
}
export function validateDeriveKeyParams(params: KeyDerivationparams): boolean {
return (
params.algorithm === "PBKDF2" &&
!!params.salt &&
typeof params.salt === "string" &&
!!params.iterations &&
params.iterations >= PBKDF2_ITER_MIN &&
params.iterations <= PBKDF2_ITER_MAX &&
[192, 256, 512].includes(params.keySize) &&
["SHA-256", "SHA-512"].includes(params.hash)
);
}
// Shorthands for codec functions
const bitsToBase64: (bits: any) => Base64String = sjcl.codec.base64url.fromBits;
const base64ToBits = (base64: Base64String): any => {
try {
return sjcl.codec.base64url.toBits(base64);
} catch {
return sjcl.codec.base64.toBits(base64);
}
};
const utf8ToBits = sjcl.codec.utf8String.toBits;
export var SJCLProvider: CryptoProvider = {
isAvailable() {
return true;
},
randomBytes(bytes: number): Base64String {
if (bytes % 4) {
throw "Number of bytes must be dividable by 4";
}
return bitsToBase64(sjcl.random.randomWords(bytes / 4, 0));
},
async deriveKey(password: string, params: KeyDerivationparams): Promise<SymmetricKey> {
if (!validateDeriveKeyParams(params)) {
throw new CryptoError("invalid_key_params");
}
const k = sjcl.misc.pbkdf2(utf8ToBits(password), base64ToBits(params.salt!), params.iterations, params.keySize);
return bitsToBase64(k);
},
async randomKey(n = 256) {
return sjcl.randomBytes(n / 8);
},
async decrypt(key: Key, ct: CipherText, params: CipherParams): Promise<PlainText> {
if (params.cipherType !== "symmetric" || params.algorithm !== "AES-CCM") {
throw new CryptoError("invalid_cipher_params");
}
// Only AES and CCM are supported
const algorithm = "aes";
const mode = "ccm";
try {
const cipher = new sjcl.cipher[algorithm](base64ToBits(key));
const pt = sjcl.mode[mode].decrypt(
cipher,
base64ToBits(ct),
base64ToBits(params.iv!),
base64ToBits(params.additionalData!),
params.tagSize
);
return bitsToBase64(pt);
} catch (e) {
throw new CryptoError("decryption_failed");
}
},
async encrypt(key: Key, pt: PlainText, params: CipherParams): Promise<CipherText> {
if (params.cipherType !== "symmetric" || params.algorithm !== "AES-CCM") {
throw new CryptoError("invalid_cipher_params");
}
// Only AES and CCM are supported
const algorithm = "aes";
const mode = "ccm";
try {
const cipher = new sjcl.cipher[algorithm](base64ToBits(key));
var ct = sjcl.mode[mode].encrypt(
cipher,
base64ToBits(pt),
base64ToBits(params.iv!),
base64ToBits(params.additionalData!),
params.tagSize
);
return bitsToBase64(ct);
} catch (e) {
throw new CryptoError("encryption_failed");
}
},
async generateKeyPair(): Promise<{ privateKey: PrivateKey; publicKey: PublicKey }> {
throw new CryptoError("not_supported");
}
};
const webCrypto = window.crypto && window.crypto.subtle;
async function webCryptoGetArgs(key: Key, params: CipherParams, action = "encrypt") {
if (!validateCipherParams(params)) {
throw new CryptoError("invalid_cipher_params");
}
const keyFormat = params.cipherType === "symmetric" ? "raw" : action === "encrypt" ? "spki" : "pkcs8";
const k = await webCrypto.importKey(
keyFormat,
base64ToBytes(key),
{ name: params.algorithm, hash: "SHA-1" },
false,
[action]
);
const p = { name: params.algorithm };
if (params.cipherType === "symmetric") {
Object.assign(p, {
iv: base64ToBytes(params.iv!),
additionalData: base64ToBytes(params.additionalData!),
tagLength: params.tagSize
});
}
return { p, k };
}
export var WebCryptoProvider: CryptoProvider = {
isAvailable() {
return !!webCrypto;
},
randomBytes(n: number): Base64String {
const bytes = window.crypto.getRandomValues(new Uint8Array(n));
return bytesToBase64(bytes as Uint8Array);
},
async randomKey(size = 256) {
return WebCryptoProvider.randomBytes(size / 8);
},
async deriveKey(password: string, params: KeyDerivationparams): Promise<SymmetricKey> {
if (!validateDeriveKeyParams(params)) {
throw new CryptoError("invalid_key_params");
}
const baseKey = await webCrypto.importKey("raw", stringToBytes(password!), params.algorithm, false, [
"deriveKey"
]);
const key = await webCrypto.deriveKey(
{
name: params.algorithm,
salt: base64ToBytes(params.salt!),
iterations: params.iterations,
hash: params.hash
},
baseKey,
{ name: "AES-GCM", length: params.keySize },
true,
["encrypt", "decrypt"]
);
const raw = await webCrypto.exportKey("raw", key);
return bytesToBase64(new Uint8Array(raw));
},
async encrypt(key: Key, data: PlainText, params: CipherParams): Promise<CipherText> {
if (params.algorithm === "AES-CCM") {
return SJCLProvider.encrypt(key, data, params);
}
const { p, k } = await webCryptoGetArgs(key, params, "encrypt");
const buf = await webCrypto.encrypt(p, k, base64ToBytes(data));
return bytesToBase64(new Uint8Array(buf));
},
async decrypt(key: Key, data: CipherText, params: CipherParams): Promise<string> {
if (params.algorithm === "AES-CCM") {
return SJCLProvider.decrypt(key, data, params);
}
const { p, k } = await webCryptoGetArgs(key, params, "decrypt");
const buf = await webCrypto.decrypt(p, k, base64ToBytes(data));
return bytesToBase64(new Uint8Array(buf));
},
async generateKeyPair(): Promise<{ privateKey: PrivateKey; publicKey: PublicKey }> {
const keyPair = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: "SHA-1"
},
true,
["encrypt", "decrypt"]
);
const privateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
const publicKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);
return {
privateKey: bytesToBase64(new Uint8Array(privateKey)),
publicKey: bytesToBase64(new Uint8Array(publicKey))
};
}
};
export const provider = WebCryptoProvider;
export type EncryptionScheme = "simple" | "PBES2" | "shared";
interface BaseRawContainer {
version: 2;
scheme: EncryptionScheme;
id: string;
ep: SymmetricCipherParams;
ct: CipherText;
}
interface SimpleRawContainer extends BaseRawContainer {
scheme: "simple";
}
interface PasswordBasedRawContainer extends BaseRawContainer {
scheme: "PBES2";
kp: KeyDerivationparams;
}
interface SharedRawContainer extends BaseRawContainer {
scheme: "shared";
wp: AsymmetricCipherParams;
ek: {
[id: string]: CipherText;
};
}
type RawContainer = SimpleRawContainer | PasswordBasedRawContainer | SharedRawContainer;
export function defaultEncryptionParams(): SymmetricCipherParams {
return {
cipherType: "symmetric",
algorithm: "AES-GCM",
tagSize: 64,
keySize: 256
};
}
export function defaultWrappingParams(): AsymmetricCipherParams {
return {
cipherType: "asymmetric",
algorithm: "RSA-OAEP"
};
}
export interface Participant {
id: string;
publicKey: PublicKey;
privateKey?: PrivateKey;
encryptedKey?: CipherText;
}
export class Container implements Storage, Storable {
id: string = "";
cipherText?: CipherText;
key?: SymmetricKey;
password?: string;
user?: Participant;
private encryptedKeys: { [id: string]: CipherText } = {};
constructor(
public scheme: EncryptionScheme = "simple",
public encryptionParams: SymmetricCipherParams = defaultEncryptionParams(),
public keyDerivationParams: KeyDerivationparams = defaultKeyDerivationParams(),
public wrappingParams: AsymmetricCipherParams = defaultWrappingParams()
) {}
async getKey(): Promise<SymmetricKey> {
switch (this.scheme) {
case "simple":
if (!this.key) {
this.key = await provider.randomKey(this.encryptionParams.keySize);
}
return this.key;
case "PBES2":
if (!this.keyDerivationParams.salt) {
this.keyDerivationParams.salt = provider.randomBytes(16);
}
if (!this.password) {
throw "no password provided";
}
return await provider.deriveKey(this.password, this.keyDerivationParams);
case "shared":
if (!this.user || !this.user.privateKey || !this.encryptedKeys) {
throw "Cannot derive key";
}
if (Object.keys(this.encryptedKeys).length) {
const encryptedKey = this.encryptedKeys[this.user.id];
return provider.decrypt(this.user.privateKey, encryptedKey, this.wrappingParams);
} else {
return await provider.randomKey(this.encryptionParams.keySize);
}
}
}
async set(data: Storable) {
this.encryptionParams.iv = provider.randomBytes(16);
// TODO: useful additional authenticated data?
this.encryptionParams.additionalData = provider.randomBytes(16);
const key = await this.getKey();
const pt = stringToBase64(marshal(await data.serialize()));
this.cipherText = await provider.encrypt(key, pt, this.encryptionParams);
}
async get(data: Storable) {
if (!this.cipherText) {
throw "Nothing to get";
}
const key = await this.getKey();
const pt = base64ToString(await provider.decrypt(key, this.cipherText, this.encryptionParams));
await data.deserialize(unmarshal(pt));
}
async serialize(): Promise<RawContainer> {
const raw = {
version: 2,
scheme: this.scheme,
id: this.id,
ep: this.encryptionParams,
ct: this.cipherText
} as RawContainer;
if (this.scheme === "PBES2") {
(raw as PasswordBasedRawContainer).kp = this.keyDerivationParams;
}
if (this.scheme === "shared") {
(raw as SharedRawContainer).wp = this.wrappingParams;
(raw as SharedRawContainer).ek = this.encryptedKeys;
}
return raw;
}
async deserialize(raw: RawContainer) {
this.id = raw.id;
this.scheme = raw.scheme;
this.cipherText = raw.ct;
this.encryptionParams = raw.ep;
if (raw.scheme === "PBES2") {
this.keyDerivationParams = raw.kp;
}
if (raw.scheme === "shared") {
this.wrappingParams = raw.wp;
this.encryptedKeys = raw.ek;
}
return this;
}
async addParticipant(p: Participant) {
if (this.scheme !== "shared") {
throw "Cannot add participant in this scheme";
}
const key = await this.getKey();
this.encryptedKeys[p.id] = await provider.encrypt(p.publicKey, key, this.wrappingParams);
}
async clear() {
delete this.password;
delete this.user;
delete this.key;
delete this.cipherText;
this.id = "";
this.encryptedKeys = {};
}
}
// export class SimpleContainer extends Container {
// scheme: "simple";
// public key: SymmetricKey;
//
// async getKey(): Promise<SymmetricKey> {}
// }
//
// export class PasswordBasedContainer extends Container {
// password: string;
//
// constructor(
// encryptionParams: SymmetricCipherParams = defaultEncryptionParams(),
// public keyDerivationParams: KeyDerivationparams = defaultKeyDerivationParams()
// ) {
// super(encryptionParams);
// }
//
// async getKey() {}
//
// async serialize(): Promise<PasswordBasedRawContainer> {
// const raw = (await super.serialize()) as PasswordBasedRawContainer;
// raw.kp = this.keyDerivationParams;
// return raw;
// }
//
// async deserialize(raw: PasswordBasedRawContainer) {
// this.keyDerivationParams = raw.kp!;
// return super.deserialize(raw);
// }
// }
//
// export class SharedContainer extends Container {
// user?: Participant;
// private encryptedKeys: { [id: string]: CipherText } = {};
//
// constructor(
// encryptionParams: SymmetricCipherParams,
// public wrappingParams: AsymmetricCipherParams = defaultWrappingParams()
// ) {
// super(encryptionParams);
// }
//
// async getKey() {
// if (!this.user || !this.user.privateKey || !this.encryptedKeys) {
// throw "Cannot derive key";
// }
// const encryptedKey = this.encryptedKeys[this.user.id];
// return provider.decrypt(this.user.privateKey, encryptedKey, this.wrappingParams);
// }
//
// async addParticipant(p: Participant) {
// const key = await this.getKey();
// this.encryptedKeys[p.id] = await provider.encrypt(p.publicKey, key, this.wrappingParams);
// }
//
// async serialize(): Promise<SharedRawContainer> {
// const raw = (await super.serialize()) as SharedRawContainer;
// raw.wp = this.wrappingParams;
// raw.ek = this.encryptedKeys;
// return raw;
// }
//
// async deserialize(raw: SharedRawContainer) {
// this.wrappingParams = raw.wp || defaultWrappingParams();
// this.encryptedKeys = raw.ek!;
// }
// }

View File

@ -1,125 +0,0 @@
type Base64String = string;
// Valid Key Types ( https://tools.ietf.org/html/rfc7518#section-6.1)
type KeyType = "RSA" | "oct";
// Valid Key Use Value (https://tools.ietf.org/html/rfc7517#section-4.2)
type PublicKeyUse = "sig" | "enc";
// Valid Key Operations (https://tools.ietf.org/html/rfc7517#section-4.3)
type KeyOperation = "sign" | "verify" | "encrypt" | "decrypt" | "wrapKey" | "unwrapKey" | "deriveKey" | "deriveBits";
// Key Management Algorithms (https://tools.ietf.org/html/rfc7518#section-4.1)
type WrappingAlgorithm = "RSA-OAEP" | "dir";
// Encryption Algorithms
type EncryptionAlgorithm = "A256GCM";
export type Algorithm = WrappingAlgorithm | EncryptionAlgorithm;
// JSON Web Key (https://tools.ietf.org/html/rfc7517)
export interface JWKBase {
kty: KeyType;
alg: Algorithm;
use?: PublicKeyUse;
key_ops?: KeyOperation[];
kid?: string;
}
// RSA private or public key (https://tools.ietf.org/html/rfc7518#section-6.3)
export interface RSAKey extends JWKBase {
kty: "RSA";
}
export type AsymmetricKey = RSAKey;
export type PublicKey = RSAPublicKey;
export type PrivateKey = RSAPrivateKey;
// RSA public key (https://tools.ietf.org/html/rfc7518#section-6.3.1)
export interface RSAPrivateKey extends RSAKey {
// module
n: Base64String;
// exponent
e: Base64String;
}
// RSA private key (https://tools.ietf.org/html/rfc7518#section-6.3.2)
export interface RSAPublicKey extends RSAKey {
// private exponent
d: Base64String;
// first prime factor
p: Base64String;
// second prime factor
q: Base64String;
// first factor CRT Exponent
dp: Base64String;
// second factor CRT Exponent
dq: Base64String;
// first CRT coefficient
qi: Base64String;
// other primes info
oth?: {
// prime factor
r: Base64String;
// factor CRT component
d: Base64String;
// factor CRT coefficient
t: Base64String;
}[];
}
// Symmetric Key (https://tools.ietf.org/html/rfc7518#section-6.4)
export interface SymmetricKey extends JWKBase {
kty: "oct";
// key value
k: Base64String;
}
export type JWK = SymmetricKey | PublicKey | PrivateKey;
// JOSE Header (https://tools.ietf.org/html/rfc7515#section-4, https://tools.ietf.org/html/rfc7516#section-4)
export interface Header {
// algorithm used for wrapping/signing
alg?: Algorithm;
// content encryption algorithm
enc?: Algorithm;
// compression algorithm
zip?: string;
// JWK set url
jku?: string;
// public key used for for wrapping/signing
jwk?: PublicKey;
// key ID
kid?: string;
// certificate url
x5u?: string;
// certificate chain
x5c?: string;
// certificate thumbprint (SHA-1)
x5t?: string;
// certificate thumbprint (SHA-256)
"x5t#S256"?: string;
// media type
typ?: string;
// content type
cty?: string;
// additional critical header fields
crit?: string[];
}
// JWE recipient (https://tools.ietf.org/html/rfc7516#section-7.2.1)
export interface Recipient {
header: Header;
encrypted_key: Base64String;
}
// JSON Web Encryption (https://tools.ietf.org/html/rfc7516#section-7.2.1)
export interface JWE {
protected?: Base64String;
unprotected?: Header;
iv: Base64String;
ciphertext: Base64String;
tag?: Base64String;
aad: Base64String;
recipients?: Recipient[];
}

View File

@ -1,8 +0,0 @@
// RFC4122-compliant uuid generator
export function uuid(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}

25653
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
{
"name": "padlock",
"private": true,
"version": "3.0.0",
"description": "A minimalist password manager",
"author": "Martin Kleinschrodt <martin@maklesoft.com>",
@ -10,62 +11,5 @@
"url": "git+https://github.com/maklesoft/padlock.git"
},
"main": "main.js",
"devDependencies": {
"@types/chai": "^3.4.34",
"@types/core-js": "^0.9.35",
"@types/mocha": "^2.2.33",
"@types/papaparse": "^4.1.34",
"@types/semver": "^5.3.33",
"@types/zxcvbn": "^4.4.0",
"archiver": "^1.2.0",
"bower": "^1.8.0",
"browserify": "^13.1.1",
"chai": "^4.0.2",
"core-js": "^2.4.1",
"crisper": "^2.1.1",
"electron": "^1.8.4",
"electron-builder": "^20.10.0",
"eslint": "^4.0.0",
"estraverse": "^4.2.0",
"gulp": "^3.9.1",
"gulp-insert-lines": "0.0.4",
"js-yaml": "^3.8.4",
"karma": "^1.7.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^2.1.1",
"karma-mocha": "^1.3.0",
"merge-stream": "^1.0.1",
"mocha": "^3.4.2",
"polymer-analyzer": "^2.2.0",
"polymer-build": "^3.0.1",
"polymer-bundler": "^2.3.1",
"polymer-cli": "^1.7.2",
"prettier": "^1.13.4",
"rimraf": "^2.5.4",
"st": "^1.2.0",
"tsify": "^2.0.3",
"typescript": "^2.9.1",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.7.0"
},
"dependencies": {
"electron-store": "^1.1.0",
"electron-updater": "^2.21.0",
"fs-extra": "^5.0.0",
"uuid": "^3.1.0",
"yargs": "^4.8.1"
},
"scripts": {
"compile": "tsc -p ./tsconfig.json",
"browser": "cd app && polymer serve --open",
"lint": "eslint app/**/*.js && prettier --list-different 'app/src/**/*.{js,ts}'",
"test": "karma start --single-run --browsers ChromeHeadless karma.conf.js",
"app": "npm run build:electron && electron . --dir app/build/electron",
"build:electron": "cd app && polymer build --name electron --js-compile --bundle --js-transform-modules-to-amd && cd ..",
"build:pwa": "cd app && polymer build --name pwa --add-service-worker",
"build:mac": "gulp build --mac --silent",
"build:win": "gulp build --win --silent",
"build:linux": "gulp build --linux --silent",
"postinstall": "npm run compile && mkdir -p cordova/www"
}
"workspaces": ["packages/*"]
}

View File

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 782 B

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -0,0 +1,9 @@
{
"name": "@padlock/chrome",
"version": "1.0.0",
"description": "Padlock for ChromeOS",
"main": "index.js",
"author": "Martin Kleinschrodt <martin@maklesoft.com>",
"license": "GPLv3",
"private": true
}

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.maklesoft.padlock" version="2.7.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget id="com.maklesoft.padlock" version="3.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Padlock</name>
<description>
</description>
@ -23,6 +23,7 @@
<preference name="BackupWebStorage" value="cloud" />
<preference name="cordova-custom-config-autorestore" value="true" />
<preference name="xwalkVersion" value="xwalk_core_library:23.53.589.4" />
<preference name="xwalkMultipleApk" value="false" />
<hook src="scripts/build-www.js" type="before_prepare" />
<platform name="android">
<icon density="ldpi" src="res/android/icon/app-icon-ldpi.png" />
@ -106,15 +107,8 @@
<plugin name="cordova-plugin-customurlscheme" spec="~4.2.0">
<variable name="URL_SCHEME" value="padlock" />
</plugin>
<plugin name="cordova-plugin-inappbrowser" spec="~1.4.0" />
<plugin name="cordova-plugin-privacyscreen" spec="~0.3.1" />
<plugin name="cordova-plugin-whitelist" spec="~1.2.2" />
<plugin name="ionic-plugin-keyboard" spec="~2.2.1" />
<plugin name="cordova-plugin-file" spec="^4.3.3" />
<plugin name="cordova-plugin-splashscreen" spec="^4.0.3" />
<plugin name="cordova-plugin-migrate-localstorage" spec="0.0.2" />
<plugin name="cordova-custom-config" spec="^4.0.2" />
<plugin name="cordova-plugin-pbkdf2" spec="^0.2.0" />
<plugin name="cordova-plugin-device" spec="^1.1.6" />
<plugin name="cordova-plugin-compat" spec="^1.2.0" />
<plugin name="cordova-plugin-crosswalk-webview" spec="^2.4.0">
@ -122,12 +116,19 @@
<variable name="XWALK_LITEVERSION" value="xwalk_core_library_canary:17+" />
<variable name="XWALK_COMMANDLINE" value="--disable-pull-to-refresh-effect" />
<variable name="XWALK_MODE" value="embedded" />
<variable name="XWALK_MULTIPLEAPK" value="true" />
<variable name="XWALK_MULTIPLEAPK" value="false" />
</plugin>
<plugin name="cordova-clipboard" spec="^1.1.1" />
<plugin name="cordova-plugin-statusbar" spec="^2.4.1" />
<plugin name="cordova-plugin-wkwebview-inputfocusfix" spec="^1.0.2" />
<plugin name="cordova-plugin-wkwebview-engine" spec="^1.1.4" />
<engine name="android" spec="^6.2.3" />
<plugin name="cordova-plugin-file" spec="^6.0.1" />
<plugin name="cordova-plugin-statusbar" spec="^2.4.2" />
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
<plugin name="cordova-plugin-privacyscreen" spec="^0.4.0" />
<plugin name="cordova-plugin-wkwebview-inputfocusfix" spec="^1.0.2" />
<plugin name="cordova-clipboard" spec="^1.2.1" />
<plugin name="cordova-custom-config" spec="^5.0.2" />
<plugin name="cordova-android-support-gradle-release" spec="^1.4.3">
<variable name="ANDROID_SUPPORT_VERSION" value="27.+" />
</plugin>
<plugin name="cordova-plugin-inappbrowser" spec="^3.0.0" />
<engine name="ios" spec="^4.5.4" />
</widget>

View File

@ -14,32 +14,40 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
"integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg="
},
"big-integer": {
"version": "1.6.30",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.30.tgz",
"integrity": "sha512-LGDF7k/8yjS+GTbfFRGiSdcPnIwcjM6kQ0lmbja3tKJzVMmqHmUFnTuUOm/Lt2KVQ3mAZVupf9KNcsew0QV8Kw=="
},
"bplist-creator": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.4.tgz",
"integrity": "sha1-SsBJZ4LhJ6hcHSAmpPXrIqev+ZE=",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz",
"integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=",
"requires": {
"stream-buffers": "0.2.6"
"stream-buffers": "~2.2.0"
}
},
"bplist-parser": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.0.6.tgz",
"integrity": "sha1-ONo0cYF9+dRKs4kuJ3B7u9daEbk="
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz",
"integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=",
"requires": {
"big-integer": "^1.6.7"
}
},
"brace-expansion": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM="
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz",
"integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw=="
},
"concat-map": {
"version": "0.0.1",
@ -47,20 +55,25 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"cordova-android": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-6.2.3.tgz",
"integrity": "sha1-JJ8hts5fHxyEenq4OxaQnb7Vqig=",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-7.0.0.tgz",
"integrity": "sha1-yVvt/PvDhjsYDE0p7/7E95Nh0Z0=",
"requires": {
"cordova-common": "2.0.2",
"android-versions": "^1.2.1",
"cordova-common": "^2.2.0",
"elementtree": "0.1.6",
"nopt": "3.0.6",
"properties-parser": "0.2.3",
"q": "1.5.0",
"shelljs": "0.5.3"
"nopt": "^3.0.1",
"properties-parser": "^0.2.3",
"q": "^1.4.1",
"shelljs": "^0.5.3"
},
"dependencies": {
"abbrev": {
"version": "1.1.0",
"version": "1.1.1",
"bundled": true
},
"android-versions": {
"version": "1.2.1",
"bundled": true
},
"ansi": {
@ -68,7 +81,7 @@
"bundled": true
},
"balanced-match": {
"version": "0.4.2",
"version": "1.0.0",
"bundled": true
},
"base64-js": {
@ -76,21 +89,21 @@
"bundled": true
},
"big-integer": {
"version": "1.6.22",
"version": "1.6.26",
"bundled": true
},
"bplist-parser": {
"version": "0.1.1",
"bundled": true,
"requires": {
"big-integer": "1.6.22"
"big-integer": "^1.6.7"
}
},
"brace-expansion": {
"version": "1.1.7",
"version": "1.1.8",
"bundled": true,
"requires": {
"balanced-match": "0.4.2",
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
@ -99,22 +112,22 @@
"bundled": true
},
"cordova-common": {
"version": "2.0.2",
"version": "2.2.0",
"bundled": true,
"requires": {
"ansi": "0.3.1",
"bplist-parser": "0.1.1",
"cordova-registry-mapper": "1.1.15",
"ansi": "^0.3.1",
"bplist-parser": "^0.1.0",
"cordova-registry-mapper": "^1.1.8",
"elementtree": "0.1.6",
"glob": "5.0.15",
"minimatch": "3.0.3",
"osenv": "0.1.4",
"plist": "1.2.0",
"q": "1.5.0",
"semver": "5.3.0",
"shelljs": "0.5.3",
"underscore": "1.8.3",
"unorm": "1.4.1"
"glob": "^5.0.13",
"minimatch": "^3.0.0",
"osenv": "^0.1.3",
"plist": "^1.2.0",
"q": "^1.4.1",
"semver": "^5.0.1",
"shelljs": "^0.5.3",
"underscore": "^1.8.3",
"unorm": "^1.3.3"
}
},
"cordova-registry-mapper": {
@ -132,19 +145,19 @@
"version": "5.0.15",
"bundled": true,
"requires": {
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.3",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
@ -156,24 +169,24 @@
"bundled": true
},
"minimatch": {
"version": "3.0.3",
"version": "3.0.4",
"bundled": true,
"requires": {
"brace-expansion": "1.1.7"
"brace-expansion": "^1.1.7"
}
},
"nopt": {
"version": "3.0.6",
"bundled": true,
"requires": {
"abbrev": "1.1.0"
"abbrev": "1"
}
},
"once": {
"version": "1.4.0",
"bundled": true,
"requires": {
"wrappy": "1.0.2"
"wrappy": "1"
}
},
"os-homedir": {
@ -188,8 +201,8 @@
"version": "0.1.4",
"bundled": true,
"requires": {
"os-homedir": "1.0.2",
"os-tmpdir": "1.0.2"
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
}
},
"path-is-absolute": {
@ -203,7 +216,7 @@
"base64-js": "0.0.8",
"util-deprecate": "1.0.2",
"xmlbuilder": "4.0.0",
"xmldom": "0.1.27"
"xmldom": "0.1.x"
}
},
"properties-parser": {
@ -211,7 +224,7 @@
"bundled": true
},
"q": {
"version": "1.5.0",
"version": "1.5.1",
"bundled": true
},
"sax": {
@ -219,7 +232,7 @@
"bundled": true
},
"semver": {
"version": "5.3.0",
"version": "5.4.1",
"bundled": true
},
"shelljs": {
@ -246,7 +259,7 @@
"version": "4.0.0",
"bundled": true,
"requires": {
"lodash": "3.10.1"
"lodash": "^3.5.0"
}
},
"xmldom": {
@ -255,23 +268,31 @@
}
}
},
"cordova-android-support-gradle-release": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/cordova-android-support-gradle-release/-/cordova-android-support-gradle-release-1.4.3.tgz",
"integrity": "sha512-b72Ne+nfrda8kidwTRu6LToWvkT/18eXIfLrmVC1vrdO9nJpFNo+sCdKYrBg+u33U3c1QP9wAejB0zLD9pNx7Q==",
"requires": {
"xml2js": "~0.4.19"
}
},
"cordova-clipboard": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cordova-clipboard/-/cordova-clipboard-1.1.1.tgz",
"integrity": "sha1-h2eowTiuhAyY0gouChhx7W9OHQo="
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cordova-clipboard/-/cordova-clipboard-1.2.1.tgz",
"integrity": "sha512-WTGxyQJYsgmll8wDEo0u4XevZDUH1ZH1VPoOwwNkQ8YOtCNQS8gRIIVtZ70Kan+Vo+CiUMV0oJXdNAdARb8JwQ=="
},
"cordova-custom-config": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/cordova-custom-config/-/cordova-custom-config-4.0.2.tgz",
"integrity": "sha1-duiKQK0hN1QGny5kmB6RZ6WX3+4=",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/cordova-custom-config/-/cordova-custom-config-5.0.2.tgz",
"integrity": "sha512-BOlDpmll+CIL+pSFfYrp0ederNCDKvB3X8tDY2EacEvcar/kB2MKs9M5Htv4VTrkfdIbjJtoePD2vaH3apY1Tg==",
"requires": {
"colors": "1.1.2",
"elementtree": "0.1.7",
"lodash": "4.17.4",
"plist": "1.2.0",
"shelljs": "0.7.8",
"tostr": "0.1.0",
"xcode": "0.8.9"
"colors": "^1.1.2",
"elementtree": "^0.1.6",
"lodash": "^4.3.0",
"plist": "github:xiangpingmeng/plist.js#5ccd600b0b7fd3ae204edc9e69c1c30406d07747",
"shelljs": "^0.7.0",
"tostr": "^0.1.0",
"xcode": "^1.0.0"
}
},
"cordova-ios": {
@ -280,13 +301,13 @@
"integrity": "sha1-yAZIBYlyloVw3BXalzFP+S0H3+c=",
"requires": {
"cordova-common": "2.1.0",
"ios-sim": "6.1.2",
"nopt": "3.0.6",
"plist": "1.2.0",
"q": "1.5.1",
"shelljs": "0.5.3",
"xcode": "0.9.3",
"xml-escape": "1.1.0"
"ios-sim": "^6.1.2",
"nopt": "^3.0.6",
"plist": "^1.2.0",
"q": "^1.4.1",
"shelljs": "^0.5.3",
"xcode": "^0.9.0",
"xml-escape": "^1.1.0"
},
"dependencies": {
"abbrev": {
@ -313,21 +334,21 @@
"version": "0.0.7",
"bundled": true,
"requires": {
"stream-buffers": "2.2.0"
"stream-buffers": "~2.2.0"
}
},
"bplist-parser": {
"version": "0.1.1",
"bundled": true,
"requires": {
"big-integer": "1.6.25"
"big-integer": "^1.6.7"
}
},
"brace-expansion": {
"version": "1.1.8",
"bundled": true,
"requires": {
"balanced-match": "1.0.0",
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
@ -339,19 +360,19 @@
"version": "2.1.0",
"bundled": true,
"requires": {
"ansi": "0.3.1",
"bplist-parser": "0.1.1",
"cordova-registry-mapper": "1.1.15",
"ansi": "^0.3.1",
"bplist-parser": "^0.1.0",
"cordova-registry-mapper": "^1.1.8",
"elementtree": "0.1.6",
"glob": "5.0.15",
"minimatch": "3.0.4",
"osenv": "0.1.4",
"plist": "1.2.0",
"q": "1.5.1",
"semver": "5.4.1",
"shelljs": "0.5.3",
"underscore": "1.8.3",
"unorm": "1.4.1"
"glob": "^5.0.13",
"minimatch": "^3.0.0",
"osenv": "^0.1.3",
"plist": "^1.2.0",
"q": "^1.4.1",
"semver": "^5.0.1",
"shelljs": "^0.5.3",
"underscore": "^1.8.3",
"unorm": "^1.3.3"
}
},
"cordova-registry-mapper": {
@ -369,19 +390,19 @@
"version": "5.0.15",
"bundled": true,
"requires": {
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
@ -392,10 +413,10 @@
"version": "6.1.2",
"bundled": true,
"requires": {
"bplist-parser": "0.0.6",
"bplist-parser": "^0.0.6",
"nopt": "1.0.9",
"plist": "1.2.0",
"simctl": "1.1.1"
"plist": "^1.2.0",
"simctl": "^1.1.1"
},
"dependencies": {
"bplist-parser": {
@ -406,7 +427,7 @@
"version": "1.0.9",
"bundled": true,
"requires": {
"abbrev": "1.1.1"
"abbrev": "1"
}
}
}
@ -419,21 +440,21 @@
"version": "3.0.4",
"bundled": true,
"requires": {
"brace-expansion": "1.1.8"
"brace-expansion": "^1.1.7"
}
},
"nopt": {
"version": "3.0.6",
"bundled": true,
"requires": {
"abbrev": "1.1.1"
"abbrev": "1"
}
},
"once": {
"version": "1.4.0",
"bundled": true,
"requires": {
"wrappy": "1.0.2"
"wrappy": "1"
}
},
"os-homedir": {
@ -448,8 +469,8 @@
"version": "0.1.4",
"bundled": true,
"requires": {
"os-homedir": "1.0.2",
"os-tmpdir": "1.0.2"
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
}
},
"path-is-absolute": {
@ -467,7 +488,7 @@
"base64-js": "0.0.8",
"util-deprecate": "1.0.2",
"xmlbuilder": "4.0.0",
"xmldom": "0.1.27"
"xmldom": "0.1.x"
}
},
"q": {
@ -490,8 +511,8 @@
"version": "1.1.1",
"bundled": true,
"requires": {
"shelljs": "0.2.6",
"tail": "0.4.0"
"shelljs": "^0.2.6",
"tail": "^0.4.0"
},
"dependencies": {
"shelljs": {
@ -519,7 +540,7 @@
"requires": {
"base64-js": "1.1.2",
"xmlbuilder": "8.2.2",
"xmldom": "0.1.27"
"xmldom": "0.1.x"
}
},
"xmlbuilder": {
@ -560,8 +581,8 @@
"version": "0.9.3",
"bundled": true,
"requires": {
"pegjs": "0.10.0",
"simple-plist": "0.2.1",
"pegjs": "^0.10.0",
"simple-plist": "^0.2.1",
"uuid": "3.0.1"
}
},
@ -573,7 +594,7 @@
"version": "4.0.0",
"bundled": true,
"requires": {
"lodash": "3.10.1"
"lodash": "^3.5.0"
}
},
"xmldom": {
@ -613,39 +634,29 @@
"integrity": "sha1-LSF2TK18m4AVI+TgmjDgJLJJM0s="
},
"cordova-plugin-file": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-4.3.3.tgz",
"integrity": "sha1-AS6Xqhr7kfhJFuY0G1SDZtI96bk="
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-6.0.1.tgz",
"integrity": "sha1-SWBrjBWlaI1HKPkuSnMloGHeB/U="
},
"cordova-plugin-inappbrowser": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-inappbrowser/-/cordova-plugin-inappbrowser-1.4.0.tgz",
"integrity": "sha1-sDH2jjwiT/FXdKGHEelig3El3Oc="
},
"cordova-plugin-migrate-localstorage": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-migrate-localstorage/-/cordova-plugin-migrate-localstorage-0.0.2.tgz",
"integrity": "sha512-WYCuLzEhtTR6y3+IWhyRB88P4OUn1VMx8nCX/3rp0ZIZQGTlFESelWMtIkdPEQgWj8TKA6Gu7S1iYBS3aelzbQ=="
},
"cordova-plugin-pbkdf2": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-pbkdf2/-/cordova-plugin-pbkdf2-0.2.0.tgz",
"integrity": "sha512-QBd0BsInzxh4/vKojtEV2nqOlSO2Jy/AUTCI/uxBA2ZEndfkY856U5gheGybckv8CDR4XQc/edAEIfwUxZHaEg=="
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-inappbrowser/-/cordova-plugin-inappbrowser-3.0.0.tgz",
"integrity": "sha1-1K4A02Z2IQdRBXrSWK5K1KkWGto="
},
"cordova-plugin-privacyscreen": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cordova-plugin-privacyscreen/-/cordova-plugin-privacyscreen-0.3.1.tgz",
"integrity": "sha1-9N5ZrV/V1ihNJ72XitjaD3OmKd8="
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-privacyscreen/-/cordova-plugin-privacyscreen-0.4.0.tgz",
"integrity": "sha1-cSgnaJJb3Dax2r+OSFeiEwwZFHg="
},
"cordova-plugin-splashscreen": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-4.0.3.tgz",
"integrity": "sha1-dzzRNjwfO6y5kBZj6eN5PdPgoZ0="
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-5.0.2.tgz",
"integrity": "sha1-dH509W4gHNWFvGLRS8oZ9oZ/8e0="
},
"cordova-plugin-statusbar": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-2.4.1.tgz",
"integrity": "sha1-IiYop4qlTTTUySeZACDQo7rOVUU="
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-2.4.2.tgz",
"integrity": "sha1-/B+9wNjXAzp+jh8ff/FnrJvU+vY="
},
"cordova-plugin-whitelist": {
"version": "1.2.2",
@ -680,12 +691,12 @@
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
@ -693,8 +704,8 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
@ -703,9 +714,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"interpret": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz",
"integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
"integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ="
},
"ionic-plugin-keyboard": {
"version": "2.2.1",
@ -713,29 +724,24 @@
"integrity": "sha1-8qnhabvptVIkADR8n9bTRn7j+hI="
},
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.8"
"brace-expansion": "^1.1.7"
}
},
"node-uuid": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz",
"integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
"wrappy": "1"
}
},
"path-is-absolute": {
@ -749,19 +755,18 @@
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
},
"pegjs": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.9.0.tgz",
"integrity": "sha1-9q76LjzlYWkgjlIXnf5B+JFBo2k="
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
"integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0="
},
"plist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz",
"integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=",
"version": "github:xiangpingmeng/plist.js#5ccd600b0b7fd3ae204edc9e69c1c30406d07747",
"from": "github:xiangpingmeng/plist.js",
"requires": {
"base64-js": "0.0.8",
"util-deprecate": "1.0.2",
"xmlbuilder": "4.0.0",
"xmldom": "0.1.27"
"xmldom": "0.1.x"
}
},
"rechoir": {
@ -769,15 +774,15 @@
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
"requires": {
"resolve": "1.4.0"
"resolve": "^1.1.6"
}
},
"resolve": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
"integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
"requires": {
"path-parse": "1.0.5"
"path-parse": "^1.0.5"
}
},
"sax": {
@ -790,25 +795,47 @@
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz",
"integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
"requires": {
"glob": "7.1.2",
"interpret": "1.0.3",
"rechoir": "0.6.2"
"glob": "^7.0.0",
"interpret": "^1.0.0",
"rechoir": "^0.6.2"
}
},
"simple-plist": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.1.4.tgz",
"integrity": "sha1-EOtRtH4zxVbrjsRtXuZNZOcX210=",
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.2.1.tgz",
"integrity": "sha1-cXZts1IyaSjPOoByQrp2IyJjZyM=",
"requires": {
"bplist-creator": "0.0.4",
"bplist-parser": "0.0.6",
"plist": "1.2.0"
"bplist-creator": "0.0.7",
"bplist-parser": "0.1.1",
"plist": "2.0.1"
},
"dependencies": {
"base64-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.1.2.tgz",
"integrity": "sha1-1kAMrBxMZgl22Q0HoENR2JOV9eg="
},
"plist": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/plist/-/plist-2.0.1.tgz",
"integrity": "sha1-CjLKlIGxw2TpLhjcVch23p0B2os=",
"requires": {
"base64-js": "1.1.2",
"xmlbuilder": "8.2.2",
"xmldom": "0.1.x"
}
},
"xmlbuilder": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
"integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M="
}
}
},
"stream-buffers": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-0.2.6.tgz",
"integrity": "sha1-GBwI1bs2kARfaUAbmuanoM8zE/w="
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
"integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ="
},
"tostr": {
"version": "0.1.0",
@ -820,19 +847,40 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"uuid": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
"integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"xcode": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/xcode/-/xcode-0.8.9.tgz",
"integrity": "sha1-7Gdl9w6dzMzJ9umlubTn6BS0zzU=",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/xcode/-/xcode-1.0.0.tgz",
"integrity": "sha1-4fWxRDJF3tOMGAeW3xoQ/e2ghOw=",
"requires": {
"node-uuid": "1.4.7",
"pegjs": "0.9.0",
"simple-plist": "0.1.4"
"pegjs": "^0.10.0",
"simple-plist": "^0.2.1",
"uuid": "3.0.1"
}
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
},
"dependencies": {
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
}
}
},
"xmlbuilder": {
@ -840,7 +888,7 @@
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz",
"integrity": "sha1-mLj2UcowqmJANvEn0RzGbce5B6M=",
"requires": {
"lodash": "3.10.1"
"lodash": "^3.5.0"
},
"dependencies": {
"lodash": {

View File

@ -4,7 +4,6 @@
"displayName": "Padlock",
"cordova": {
"platforms": [
"android",
"ios"
],
"plugins": {
@ -13,15 +12,8 @@
"cordova-plugin-customurlscheme": {
"URL_SCHEME": "padlock"
},
"cordova-plugin-inappbrowser": {},
"cordova-plugin-privacyscreen": {},
"cordova-plugin-whitelist": {},
"ionic-plugin-keyboard": {},
"cordova-plugin-file": {},
"cordova-plugin-splashscreen": {},
"cordova-plugin-migrate-localstorage": {},
"cordova-custom-config": {},
"cordova-plugin-pbkdf2": {},
"cordova-plugin-device": {},
"cordova-plugin-compat": {},
"cordova-plugin-crosswalk-webview": {
@ -31,16 +23,25 @@
"XWALK_MODE": "embedded",
"XWALK_MULTIPLEAPK": "true"
},
"cordova-clipboard": {},
"cordova-plugin-wkwebview-engine": {},
"cordova-plugin-file": {},
"cordova-plugin-statusbar": {},
"cordova-plugin-splashscreen": {},
"cordova-plugin-privacyscreen": {},
"cordova-plugin-wkwebview-inputfocusfix": {},
"cordova-plugin-wkwebview-engine": {}
"cordova-clipboard": {},
"cordova-custom-config": {},
"cordova-android-support-gradle-release": {
"ANDROID_SUPPORT_VERSION": "27.+"
},
"cordova-plugin-inappbrowser": {}
}
},
"dependencies": {
"cordova-android": "^6.2.3",
"cordova-clipboard": "^1.1.1",
"cordova-custom-config": "^4.0.2",
"cordova-android": "^7.0.0",
"cordova-android-support-gradle-release": "^1.4.3",
"cordova-clipboard": "^1.2.1",
"cordova-custom-config": "^5.0.2",
"cordova-ios": "^4.5.4",
"cordova-plugin-app-version": "~0.1.9",
"cordova-plugin-backbutton": "~0.3.0",
@ -48,16 +49,14 @@
"cordova-plugin-crosswalk-webview": "^2.4.0",
"cordova-plugin-customurlscheme": "~4.2.0",
"cordova-plugin-device": "^1.1.6",
"cordova-plugin-file": "^4.3.3",
"cordova-plugin-inappbrowser": "~1.4.0",
"cordova-plugin-migrate-localstorage": "0.0.2",
"cordova-plugin-pbkdf2": "^0.2.0",
"cordova-plugin-privacyscreen": "~0.3.1",
"cordova-plugin-splashscreen": "^4.0.3",
"cordova-plugin-statusbar": "^2.4.1",
"cordova-plugin-file": "^6.0.1",
"cordova-plugin-inappbrowser": "^3.0.0",
"cordova-plugin-privacyscreen": "^0.4.0",
"cordova-plugin-splashscreen": "^5.0.2",
"cordova-plugin-statusbar": "^2.4.2",
"cordova-plugin-whitelist": "~1.2.2",
"cordova-plugin-wkwebview-engine": "^1.1.4",
"cordova-plugin-wkwebview-inputfocusfix": "^1.0.2",
"ionic-plugin-keyboard": "~2.2.1"
}
}
}

View File

@ -0,0 +1,79 @@
{
"prepare_queue": {
"installed": [],
"uninstalled": []
},
"config_munge": {
"files": {}
},
"installed_plugins": {
"cordova-android-support-gradle-release": {
"ANDROID_SUPPORT_VERSION": "27.+",
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-clipboard": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-custom-config": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-app-version": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-backbutton": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-crosswalk-webview": {
"XWALK_VERSION": "23+",
"XWALK_LITEVERSION": "xwalk_core_library_canary:17+",
"XWALK_COMMANDLINE": "--disable-pull-to-refresh-effect",
"XWALK_MODE": "embedded",
"XWALK_MULTIPLEAPK": "true",
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-customurlscheme": {
"URL_SCHEME": "padlock",
"ANDROID_SCHEME": " ",
"ANDROID_HOST": " ",
"ANDROID_PATHPREFIX": "/",
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-device": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-file": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-inappbrowser": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-migrate-localstorage": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-pbkdf2": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-privacyscreen": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-splashscreen": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-statusbar": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-whitelist": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-wkwebview-engine": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"cordova-plugin-wkwebview-inputfocusfix": {
"PACKAGE_NAME": "com.maklesoft.padlock"
},
"ionic-plugin-keyboard": {
"PACKAGE_NAME": "com.maklesoft.padlock"
}
},
"dependent_plugins": {}
}

View File

@ -0,0 +1,172 @@
cordova-android-support-gradle-release
======================================
This Cordova/Phonegap plugin for Android aligns various versions of the Android Support libraries specified by other plugins to a specific version.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Purpose](#purpose)
- [Installation](#installation)
- [Library versions](#library-versions)
- [Default version](#default-version)
- [Other versions](#other-versions)
- [Example cases](#example-cases)
- [Example 1](#example-1)
- [Example 2](#example-2)
- [Credits](#credits)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Purpose
**TL;DR**: To prevent build failures caused by including different versions of the support libraries.
Some Cordova plugins include [Android Support Libraries](https://developer.android.com/topic/libraries/support-library/index.html) to faciliate them.
Most commonly, these are now included into the Cordova project by specifying them as Gradle dependencies (see the [Cordova plugin spec documentation](https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework)).
Example plugins:
- [cordova-diagnostic-plugin](https://github.com/dpa99c/cordova-diagnostic-plugin)
- [Telerik ImagePicker plugin](https://github.com/Telerik-Verified-Plugins/ImagePicker)
- [cordova-plugin-local-notifications](https://github.com/katzer/cordova-plugin-local-notifications/)
- [cordova-plugin-facebook4](https://github.com/jeduan/cordova-plugin-facebook4)
The problem arises when these plugins specify different versions of the support libraries. This can cause build failures to occur, which are not easy to resolve without changes by the plugin authors to align the specified versions. See these issues:
- [phonegap-plugin-barcodescanner#480](https://github.com/phonegap/phonegap-plugin-barcodescanner/issues/480)
- [cordova-plugin-facebook4#507](https://github.com/jeduan/cordova-plugin-facebook4/issues/507)
- [cordova-plugin-local-notifications#1322](https://github.com/katzer/cordova-plugin-local-notifications/issues/1322)
- [cordova-diagnostic-plugin#211](https://github.com/dpa99c/cordova-diagnostic-plugin/issues/211)
To resolve these version collisions, this plugin injects a Gradle configuration file into the native Android platform project, which overrides any versions specified by other plugins, and forces them to the version specified in its Gradle file.
If you're encountering similar problems with the Play Services and/or Firebase libraries, checkout the sister plugins:
- [cordova-android-play-services-gradle-release](https://github.com/dpa99c/cordova-android-play-services-gradle-release)
- [cordova-android-firebase-gradle-release](https://github.com/dpa99c/cordova-android-firebase-gradle-release)
# Installation
$ cordova plugin add cordova-android-support-gradle-release
$ cordova plugin add cordova-android-support-gradle-release --variable ANDROID_SUPPORT_VERSION={required version}
The plugin needs to be installed with the [`cordova-fetch`](https://cordova.apache.org/news/2016/05/24/tools-release.html) mechanism in order to satisfy its [package dependencies](https://github.com/dpa99c/cordova-android-support-gradle-release/blob/master/package.json#L8) by installing it via npm.
Therefore if you're installing with `cordova@6`, you'll need to explicitly specify the `--fetch` option:
$ cordova plugin add cordova-android-support-gradle-release --fetch
# Library versions
## Default version
By default, this plugin will use the major version of the most recent release of the support libraries - [see here](https://developer.android.com/topic/libraries/support-library/revisions.html) for a list recent versions. "Most recent release" means the highest major version that will not result in an Alpha or Beta version being included.
$ cordova plugin add cordova-android-support-gradle-release
For example, if the most recent versions are:
- `26.0.0 Beta 2`
- `25.4.0`
Then this plugin will default to `25.+` because `26` is still in Beta.
## Other versions
In some cases, you may want to specify a different version of the support libraries. For example, [Telerik ImagePicker plugin v2.1.7](https://github.com/Telerik-Verified-Plugins/ImagePicker/tree/2.1.7) specifies `v23` because it contains code that is incompatible with `v24+`.
In this case, including the default version of this plugin will still result in a build error. So this plugin enables you to specify other versions of the support library using the `ANDROID_SUPPORT_VERSION` plugin variable.
In the above case, you'd want to install v23 of the support library. To so, you'd specify the version via the variable:
cordova plugin add cordova-android-support-gradle-release --variable ANDROID_SUPPORT_VERSION=23.+
# Example cases
## Example 1
Uses v25 of the support libraries to fix the build issue.
1. `cordova create test1 && cd test1/`
2. `cordova platform add android@latest`
3. `cordova plugin add cordova-plugin-facebook4@1.9.1 --save --variable APP_ID="123456789" --variable APP_NAME="myApplication"`
4. `cordova compile`
Observe the build succeeds and in the console output is `v25.3.1` of Android Support Library:
:prepareComAndroidSupportSupportV42531Library
5. `cordova plugin add de.appplant.cordova.plugin.local-notification@0.8.5`
6. `cordova compile`
Observe the build failed and in the console output is higher than `v25.3.1` (e.g `v26.0.0-alpha1`) of Android Support Library:
:prepareComAndroidSupportSupportV42600Alpha1Library
7. `cordova plugin add cordova-android-support-gradle-release --variable ANDROID_SUPPORT_VERSION=25.+`
8. `cordova prepare && cordova compile`
Observe the build succeeds and in the console output is `v25` of Android Support Library.
## Example 2
Uses v23 of the support libraries to fix the build issue, because v2.1.7 of the ImagePicker only works with v23.
1. `cordova create test2 && cd test2/`
2. `cordova platform add android@latest`
3. `cordova plugin add https://github.com/Telerik-Verified-Plugins/ImagePicker.git#2.1.7`
4. `cordova compile`
Observe the build succeeds and in the console output is `v23.4.0` of Android Support Library:
:prepareComAndroidSupportSupportV42340Library
5. `cordova plugin add cordova.plugins.diagnostic@3.6.5`
Observe the build failed and in the console output is higher than `v23.4.0` (e.g `v26.0.0-alpha1`) of Android Support Library:
:prepareComAndroidSupportSupportV42600Alpha1Library
7. `cordova plugin add cordova-android-support-gradle-release`
8. `cordova compile`
Observe the build still failed and in the console output is still higher than `v23.4.0` (e.g `v25.3.1`) of Android Support Library:
:prepareComAndroidSupportSupportV42531Library
9. `cordova plugin rm cordova-android-support-gradle-release`
10. `cordova plugin add cordova-android-support-gradle-release --variable ANDROID_SUPPORT_VERSION=23.+`
11. `cordova prepare && cordova compile`
Observe the build succeeds and in the console output is v23 of Android Support Library.
# Credits
Thanks to [**Chris Scott, Transistor Software**](https://github.com/christocracy) for his idea of extending the initial implementation to support dynamic specification of the library version via a plugin variable in [cordova-google-api-version](https://github.com/transistorsoft/cordova-google-api-version)
License
================
The MIT License
Copyright (c) 2017 Dave Alden / Working Edge Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,31 @@
repositories{
// Google APIs are now hosted at Maven
maven {
url 'https://maven.google.com'
}
}
def PLUGIN_NAME = "cordova-android-support-gradle-release"
// Fetch ANDROID_SUPPORT_VERSION var from properties.gradle
apply from: PLUGIN_NAME + '/properties.gradle'
// List of libs to search for.
def LIBS = [
'com.android.support'
]
def IGNORED = [
'multidex',
'multidex-instrumentation'
]
println("+-----------------------------------------------------------------");
println("| " + PLUGIN_NAME + ": " + ANDROID_SUPPORT_VERSION);
println("+-----------------------------------------------------------------");
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group in LIBS && !(details.requested.name in IGNORED)) { details.useVersion ANDROID_SUPPORT_VERSION }
}
}

View File

@ -0,0 +1,39 @@
{
"_from": "cordova-android-support-gradle-release",
"_id": "cordova-android-support-gradle-release@1.4.3",
"_inBundle": false,
"_integrity": "sha512-b72Ne+nfrda8kidwTRu6LToWvkT/18eXIfLrmVC1vrdO9nJpFNo+sCdKYrBg+u33U3c1QP9wAejB0zLD9pNx7Q==",
"_location": "/cordova-android-support-gradle-release",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "cordova-android-support-gradle-release",
"name": "cordova-android-support-gradle-release",
"escapedName": "cordova-android-support-gradle-release",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/cordova-android-support-gradle-release/-/cordova-android-support-gradle-release-1.4.3.tgz",
"_shasum": "54d4ecc83bc4cc92d45237b6be9184b665e80645",
"_spec": "cordova-android-support-gradle-release",
"_where": "/Users/martin/workspace/padlock/cordova",
"author": {
"name": "Dave Alden"
},
"bundleDependencies": false,
"dependencies": {
"xml2js": "~0.4.19"
},
"deprecated": false,
"description": "Cordova/Phonegap plugin to align various versions of the Android Support libraries specified by other plugins to a specific version",
"devDependencies": {},
"license": "MIT",
"name": "cordova-android-support-gradle-release",
"version": "1.4.3"
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-android-support-gradle-release"
version="1.4.3">
<name>cordova-android-support-gradle-release</name>
<description>Cordova/Phonegap plugin to align various versions of the Android Support libraries specified by other plugins to a specific version</description>
<author>Dave Alden</author>
<engines>
<engine name="cordova" version=">=6.2.0" />
<engine name="cordova-android" version=">=6.0.0" />
</engines>
<platform name="android">
<hook type="after_prepare" src="scripts/apply-changes.js" />
<hook type="before_build" src="scripts/apply-changes.js" />
<hook type="after_plugin_install" src="scripts/apply-changes.js" />
<preference name="ANDROID_SUPPORT_VERSION" default="27.+" />
<framework src="cordova-android-support-gradle-release.gradle" custom="true" type="gradleReference" />
<source-file src="properties.gradle" target-dir="cordova-android-support-gradle-release" /> <!-- cordova-android@6-->
<source-file src="properties.gradle" target-dir="app/cordova-android-support-gradle-release" /> <!-- cordova-android@7-->
</platform>
</plugin>

View File

@ -0,0 +1 @@
ext {ANDROID_SUPPORT_VERSION = "27.+"}

View File

@ -0,0 +1,90 @@
const PLUGIN_NAME = "cordova-android-support-gradle-release";
const V6 = "cordova-android@6";
const V7 = "cordova-android@7";
const PACKAGE_PATTERN = /(compile "com.android.support:[^:]+:)([^"]+)"/;
const PROPERTIES_TEMPLATE = 'ext {ANDROID_SUPPORT_VERSION = "<VERSION>"}';
var FILE_PATHS = {};
FILE_PATHS[V6] = {
"build.gradle": "platforms/android/build.gradle",
"properties.gradle": "platforms/android/"+PLUGIN_NAME+"/properties.gradle"
};
FILE_PATHS[V7] = {
"build.gradle": "platforms/android/app/build.gradle",
"properties.gradle": "platforms/android/app/"+PLUGIN_NAME+"/properties.gradle"
};
var deferral, fs, path, parser, platformVersion;
function log(message) {
console.log(PLUGIN_NAME + ": " + message);
}
function onError(error) {
log("ERROR: " + error);
deferral.resolve();
}
function getCordovaAndroidVersion(){
var cordovaVersion = require(path.resolve(process.cwd(),'platforms/android/cordova/version'));
return parseInt(cordovaVersion.version) === 7 ? V7 : V6;
}
function run() {
try {
fs = require('fs');
path = require('path');
parser = require('xml2js');
} catch (e) {
throw("Failed to load dependencies. If using cordova@6 CLI, ensure this plugin is installed with the --fetch option: " + e.toString());
}
platformVersion = getCordovaAndroidVersion();
log("Android platform: " + platformVersion);
var data = fs.readFileSync(path.resolve(process.cwd(), 'config.xml'));
parser.parseString(data, attempt(function (err, result) {
if (err) throw err;
var version, plugins = result.widget.plugin;
for (var n = 0, len = plugins.length; n < len; n++) {
var plugin = plugins[n];
if (plugin.$.name === PLUGIN_NAME && plugin.variable && plugin.variable.length > 0) {
version = plugin.variable.pop().$.value;
break;
}
}
if (version) {
// build.gradle
var buildGradlePath = path.resolve(process.cwd(), FILE_PATHS[platformVersion]["build.gradle"]);
var contents = fs.readFileSync(buildGradlePath).toString();
fs.writeFileSync(buildGradlePath, contents.replace(PACKAGE_PATTERN, "$1" + version + '"'), 'utf8');
log("Wrote custom version '" + version + "' to " + buildGradlePath);
// properties.gradle
var propertiesGradlePath = path.resolve(process.cwd(), FILE_PATHS[platformVersion]["properties.gradle"]);
fs.writeFileSync(propertiesGradlePath, PROPERTIES_TEMPLATE.replace(/<VERSION>/, version), 'utf8');
log("Wrote custom version '" + version + "' to " + propertiesGradlePath);
} else {
log("No custom version found in config.xml - using plugin default");
}
deferral.resolve();
}));
}
function attempt(fn) {
return function () {
try {
fn.apply(this, arguments);
} catch (e) {
onError("EXCEPTION: " + e.toString());
}
}
}
module.exports = function (ctx) {
deferral = ctx.requireCordovaModule('q').defer();
attempt(run)();
return deferral.promise;
};

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Verso Solutions LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,64 @@
Clipboard
=========
Clipboard management plugin for Cordova/PhoneGap that supports iOS, Android, and Windows Phone 8.
## Usage
```
cordova plugin add cordova-clipboard
```
The plugin creates the object `cordova.plugins.clipboard` with the methods `copy(text, onSuccess, onError)`, `paste(onSuccess, onError)` and `clear(onSuccess, onError)`
Example:
var text = "Hello World!";
cordova.plugins.clipboard.copy(text);
cordova.plugins.clipboard.paste(function (text) { alert(text); });
cordova.plugins.clipboard.clear();
## Notes
### All platforms
- The plugin only works with text content.
### Windows Phone
- The Windows Phone platform doesn't allow applications to read the content of the clipboard. Using the `paste` method will return an error.
### Android
- The minimum supported API Level is 11. Make sure that `minSdkVersion` is larger or equal to 11 in `AndroidManifest.xml`.
## Acknowledgements
This plugin was inspired by [ClipboardManagerPlugin](https://github.com/jacob/ClipboardManagerPlugin) (Android) and [ClipboardPlugin](https://github.com/phonegap/phonegap-plugins/tree/master/iPhone/ClipboardPlugin) (iOS).
## License
The MIT License (MIT)
Copyright (c) 2013 Verso Solutions LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,58 @@
{
"_from": "cordova-clipboard",
"_id": "cordova-clipboard@1.2.1",
"_inBundle": false,
"_integrity": "sha512-WTGxyQJYsgmll8wDEo0u4XevZDUH1ZH1VPoOwwNkQ8YOtCNQS8gRIIVtZ70Kan+Vo+CiUMV0oJXdNAdARb8JwQ==",
"_location": "/cordova-clipboard",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "cordova-clipboard",
"name": "cordova-clipboard",
"escapedName": "cordova-clipboard",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/cordova-clipboard/-/cordova-clipboard-1.2.1.tgz",
"_shasum": "130624356583f749abed23b380724e1d273051c8",
"_spec": "cordova-clipboard",
"_where": "/Users/martin/workspace/padlock/cordova",
"author": {
"name": "Ibrahim Hadeed"
},
"bugs": {
"url": "https://github.com/ihadeed/cordova-clipboard/issues"
},
"bundleDependencies": false,
"cordova": {
"id": "cordova-clipboard",
"platforms": [
"android",
"ios",
"wp8"
]
},
"deprecated": false,
"description": "Clipboard management plugin for Cordova/PhoneGap that supports iOS, Android, and WP8",
"homepage": "https://github.com/ihadeed/cordova-clipboard#readme",
"keywords": [
"cordova",
"plugin",
"clipboard",
"phonegap",
"ionic"
],
"license": "MIT",
"name": "cordova-clipboard",
"repository": {
"type": "git",
"url": "git+https://github.com/ihadeed/cordova-clipboard.git"
},
"version": "1.2.1"
}

View File

@ -0,0 +1,65 @@
<?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-clipboard"
version="1.2.1">
<engines>
<engine name="cordova" version=">=4.0.0" />
</engines>
<name>Clipboard</name>
<description>Clipboard management plugin for Cordova/PhoneGap</description>
<author>Verso Solutions LLC</author>
<keywords>clipboard,copy,paste,clear</keywords>
<license>MIT</license>
<js-module src="www/clipboard.js" name="Clipboard">
<clobbers target="cordova.plugins.clipboard" />
</js-module>
<!-- iOS -->
<platform name="ios">
<config-file target="config.xml" parent="/*">
<feature name="Clipboard">
<param name="ios-package" value="CDVClipboard" />
</feature>
</config-file>
<header-file src="src/ios/CDVClipboard.h" />
<source-file src="src/ios/CDVClipboard.m" />
</platform>
<!-- Android -->
<platform name="android">
<source-file src="src/android/Clipboard.java" target-dir="src/com/verso/cordova/clipboard" />
<config-file target="res/xml/config.xml" parent="/*">
<feature name="Clipboard">
<param name="android-package" value="com.verso.cordova.clipboard.Clipboard" />
</feature>
</config-file>
</platform>
<!-- Windows Phone 8 -->
<platform name="wp8">
<config-file target="config.xml" parent="/*">
<feature name="Clipboard">
<param name="wp-package" value="Clipboard"/>
</feature>
</config-file>
<source-file src="src/wp8/Clipboard.cs" />
</platform>
<!-- Windows uwp -->
<platform name="windows">
<js-module src="src/windows/ClipboardProxy.js" name="ClipboardProxy">
<merges target="" />
</js-module>
</platform>
</plugin>

View File

@ -0,0 +1,74 @@
package com.verso.cordova.clipboard;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.Context;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.ClipDescription;
public class Clipboard extends CordovaPlugin {
private static final String actionCopy = "copy";
private static final String actionPaste = "paste";
private static final String actionClear = "clear";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
ClipboardManager clipboard = (ClipboardManager) cordova.getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
if (action.equals(actionCopy)) {
try {
String text = args.getString(0);
ClipData clip = ClipData.newPlainText("Text", text);
clipboard.setPrimaryClip(clip);
callbackContext.success(text);
return true;
} catch (JSONException e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
} catch (Exception e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, e.toString()));
}
} else if (action.equals(actionPaste)) {
if (!clipboard.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.NO_RESULT));
}
try {
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
String text = item.getText().toString();
if (text == null) text = "";
callbackContext.success(text);
return true;
} catch (Exception e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, e.toString()));
}
} else if (action.equals(actionClear)) {
try {
ClipData clip = ClipData.newPlainText("", "");
clipboard.setPrimaryClip(clip);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
return true;
} catch (Exception e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, e.toString()));
}
}
return false;
}
}

View File

@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
#import <Cordova/CDVPlugin.h>
@interface CDVClipboard : CDVPlugin {}
- (void)copy:(CDVInvokedUrlCommand*)command;
- (void)paste:(CDVInvokedUrlCommand*)command;
- (void)clear:(CDVInvokedUrlCommand*)command;
@end

View File

@ -0,0 +1,40 @@
#import <Foundation/Foundation.h>
#import <Cordova/CDVPlugin.h>
#import <Cordova/CDVPluginResult.h>
#import "CDVClipboard.h"
@implementation CDVClipboard
- (void)copy:(CDVInvokedUrlCommand*)command {
[self.commandDelegate runInBackground:^{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSString *text = [command.arguments objectAtIndex:0];
pasteboard.string = text;
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:text];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
- (void)paste:(CDVInvokedUrlCommand*)command {
[self.commandDelegate runInBackground:^{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSString *text = [pasteboard valueForPasteboardType:@"public.text"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:text];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
- (void)clear:(CDVInvokedUrlCommand*)command {
[self.commandDelegate runInBackground:^{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setValue:@"" forPasteboardType:UIPasteboardNameGeneral];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:true];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@end

View File

@ -0,0 +1,67 @@
/*
*
* 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.
*
*/
/*jslint sloppy:true */
/*global Windows:true, require, document, window, module */
var cordova = require('cordova');
module.exports = {
copy: function (successCallback, errorCallback, args) {
try {
var text = args[0];
var dataPackage = new Windows.ApplicationModel.DataTransfer.DataPackage();
dataPackage.setText(text);
Windows.ApplicationModel.DataTransfer.Clipboard.setContent(dataPackage);
successCallback(text);
} catch (e) {
errorCallback(e);;
}
},
paste: function (successCallback, errorCallback, args) {
try {
var text = "";
var dataPackageView = Windows.ApplicationModel.DataTransfer.Clipboard.getContent();
if (dataPackageView.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.text)) {
dataPackageView.getTextAsync().then(function (value) {
text = value;
successCallback(text);
});
}
} catch (e) {
errorCallback(e);;
}
},
clear: function (successCallback, errorCallback, args) {
try {
if(Windows.ApplicationModel.DataTransfer.Clipboard.getContent()){
successCallback(true);
}
} catch (e) {
errorCallback(e);;
}
}
}; // exports
require("cordova/exec/proxy").add("Clipboard", module.exports);

View File

@ -0,0 +1,74 @@
using WPCordovaClassLib.Cordova;
using WPCordovaClassLib.Cordova.Commands;
using WPCordovaClassLib.Cordova.JSON;
namespace Cordova.Extension.Commands
{
public class Clipboard : BaseCommand
{
public void copy(string options)
{
string text = "";
try
{
text = JsonHelper.Deserialize<string[]>(options)[0];
}
catch
{
DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
return;
}
try
{
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
{
System.Windows.Clipboard.SetText(text);
});
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text));
}
catch
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
}
}
public void paste()
{
string text = "";
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
{
try
{
text = System.Windows.Clipboard.GetText();
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text));
}
catch
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
}
});
}
public void clear()
{
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
{
try
{
System.Windows.Clipboard.Clear();
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true));
}
catch
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
}
});
}
}
}

View File

@ -0,0 +1,44 @@
var cordova = require('cordova');
/**
* Clipboard plugin for Cordova
*
* @constructor
*/
function Clipboard () {}
/**
* Sets the clipboard content
*
* @param {String} text The content to copy to the clipboard
* @param {Function} onSuccess The function to call in case of success (takes the copied text as argument)
* @param {Function} onFail The function to call in case of error
*/
Clipboard.prototype.copy = function (text, onSuccess, onFail) {
if (typeof text === "undefined" || text === null) text = "";
cordova.exec(onSuccess, onFail, "Clipboard", "copy", [text]);
};
/**
* Gets the clipboard content
*
* @param {Function} onSuccess The function to call in case of success
* @param {Function} onFail The function to call in case of error
*/
Clipboard.prototype.paste = function (onSuccess, onFail) {
cordova.exec(onSuccess, onFail, "Clipboard", "paste", []);
};
/**
* Clear the clipboard content
*
* @param {Function} onSuccess The function to call in case of success
* @param {Function} onFail The function to call in case of error
*/
Clipboard.prototype.clear = function (onSuccess, onFail) {
cordova.exec(onSuccess, onFail, "Clipboard", "clear", []);
};
// Register the plugin
var clipboard = new Clipboard();
module.exports = clipboard;

View File

@ -0,0 +1,200 @@
# CHANGELOG
**v5.0.2**
* Improve handling of errors caused by missing dependencies or during script running.
**v5.0.1**
* Update `plist` and `xcode` dependencies to resolve issues caused by PR #119. Resolves #136.
**v5.0.0** Major update for `cordova-android@7`
* Support the new Android project structure introduced with the [release of cordova@7.0.0](http://cordova.apache.org/announcements/2017/12/04/cordova-android-7.0.0.html) . Resolves #135.
* Expect custom config elements to be prefixed with `<custom-` to avoid build issues now `cordova-android@7` attempts to parse `<config-file>` blocks, but continue to support unprefixed elements by default for `cordova-android@6`.
**v4.0.2**
* Fix iOS bug where a `<config-file>` block with `mode=delete` causes an error if the plist doesn't contain the specified parent key.
**v4.0.0**
* Remove manual dependency resolution logic and require cordova-fetch for installation.
**v3.3.0**
* Enable deleting of existing iOS plist entries.
**v3.2.0**
* Add support for iOS asset catalogs as image resources.
**v3.1.4**
* Add missing before_prepare and before_compile plugin hooks. Fixes #110.
**v3.1.3**
* Wait for async processing of project.pbxproj to finish before resolving exported promise. Addresses #108.
* Initial documentation regarding precompile headers
* Support for precompile headers (*-Prefix.pch) on iOS
**v3.1.2**
* Fix relative paths in xcode-func preferences
**v3.1.1**
* Remove engines restriction of npm version to see if it affects #94.
**v3.1.0**
* Add cordova-fetch as preferred install method
* Add support for mode attribute on config-file blocks
* Dump out datastructures if --dump CLI arg is specified
* Update Phonegap Build issue for no hooks support
* Fix merging of plist array values.
* Prevent insertion of multiple duplicate elements from config-file blocks if no top-level attributes.
* When removing preferences, if element is not found under parent element, search from root as well.
* Update jshint rules to allow ES6 syntax
* Fix missing ; for jshint
* Specify npm@>=3.0.0 via engines in package.json. Resolves #76 while hopefully not breaking #79 and #80 again.
* Merge plist arrays instead of overriding them
* If loading dependencies fails, try to resolve them again. Addresses #89.
* Prevent problem with NSMainNibFile. Change as suggested in #90.
* Declare globals in example project build trigger script to prevent issues with typescript compilers. Fixes #88.
* Add documentation for xcodefunc blocks
* ios preference to apply node-xcode functions to modify project.pbxproj
* Added logic for updating Entitlements-Release.plist and Entitlements-Debug.plist files that were added with cordova-ios 4.3
* Note the addition for support of <edit-config> in config.xml in cordova@6.4.0. Resolves #81.
**v3.0.14**
* Remove npm version check due to quoting problems between Windows vs OSX. Fixes #80 (and #79).
**v3.0.13**
* Use double quotes (instead of single quotes) around version in preinstall version check to avoid problems in Windows env. Fixes #79.
**v3.0.12**
* Add preinstall hook to check npm version is >= 3 and fail installation if not.
* Clarify npm requirements
* Add Travis config and hook script to trigger build of example project on commit
* Add build, version and downloads badges
* Add note regarding npm version requirements. Fixes #76.
**v3.0.11**
* Locally implement _.keyBy to avoid issues where local version of lodash < 2.0.0
**v3.0.10**
* Add backward compatibility for lodash < 4.0.0
**v3.0.9**
* Eliminate race condition when resolving npm dependencies
**v3.0.8**
* Add jshint validation
**v3.0.7**
* Only attempt to remove an element from AndroidManifest.xml if it actually exists
* Dump error details when exception is raised applying custom config
**v3.0.6**
* Revise the plugin installation process to make it more robust
**v3.0.5**
* Add relative copy
**v3.0.1 to v3.0.4**
* Various bug fixes
**v3.0.0**
* Change dependency resolution to rely on cordova-fetch (cordova@6.2.0+).
* Require latest xcode@0.8.9 to resolve security issue. Fixes #74
* Fix jshint errors
* When <preference delete="true">, ensure the child is deleted rather than the parent node. Fixes #65.
* Add attribute for config-file
* Multiple meta-data inside application
* android: create parent path if not exist
**v2.0.3**
* Remove useless platform parameter.
* Rationalise and fix the handling of multiple sibling homogeneous elements in Android manifest. Fixes #64.
* Add debug tools to logger
* Rename logger.debug() to logger.verbose()
**v2.0.2**
* Fix bug introduced by pull request #59. Resolves #61.
**v2.0.1**
* Add shelljs as dependency
* adding ability to remove nodes from AndroidManifest.xml
* Only apply config to hook context platforms
* fix: instead of going through all the prepared platforms use hook context to find out which config update to apply
* Update docs to relect non-support of Phonegap Build / Intel XDK
* Update Android examples to use actual existing default theme
* Add a preference to allow control of which hook the plugin uses to apply custom config
* Added ability for multiple intent-filter blocks
* Previously intent-filter blocks where duplicated in the AndroidManifest when added to a config.xml config-block. Now checks for intent-filter by label.
* Fixed indexOf syntax error in updateAndroidManifest()
* Patched weird behaviour: "shell" is undefined if android platform has been removed and added with a new package id but ios stayed the same. Patched by checking if shell is defined and using logger.error if it isn't
**v2.0.0**
* Remove deprecated pre-defined preferences for Android.
* Make auto-restore OFF by default - resolves #42.
* Merge pull request #41 from hilkeheremans/master. Fix for iOS issue with some types of keys.
**v1.2.6**
* Fix typo causing item value to always be quoted in XC build config blocks. Fixes #40
**v1.2.5**
* During backup/restore and apply custom config, only attempt to resolve dependencies if an error occurs while trying to load them.
* Add MIT license.
* Support for all root-level tags that can appear multiple times. Fixes #34.
**v1.2.4**
* Remove lodash compatibility hack now version numbers is package.json dependencies are respected.
* Fix non-resolution of promise if config is set to skip auto-backup/restore. Fixes #32.
**v1.2.3**
* fix removal of project-level package.json during prepare operation due to concurrency in dependency resolution script by ensuring synchronisation of restoreBackups.js and applyCustomConfig.js using deferred promises
**v1.2.2**
* properly fix dependency resolution using promises to defer async progression of hook scripts. Fixes #23 and fixes #29.
**v1.2.1**
* Rework dependency resolution to eliminate race conditions.
**v1.2.0**
* Enable preference quote attribute to control quoting in project.pbxproj
* Don't quote keys and values in .xcconfig files
* Only replace settings in xcconfig files that are of the same build type as specified in config.
* Add special case handling of Debug CODE_SIGNING_IDENTITY for which Cordova defaults reside in build.xcconfig (not build-debug.xcconfig).
* Enable forced addition of settings (if they don't already exist) to relevant xcconfig file using xcconfigEnforce attribute.
**v1.1.11**
* Run dependency resolution script on 'after_platform_add` hook to avoid issues if plugin is added to a project with no platforms then platforms are subsequently added.
**v1.1.10**
* Document necessity of android namespace attribute in config.xml. Fixes #24.
* wp8 config-file append mode
* Added quote attribute for iOS prefs
* Update to fix indexBy() which is renamed keyBy() in lodash@4.0.0. Fixes #20 and #21
**v1.1.8**
* Update with details of XPath preferences
* Add support for xpath-style Android manifest preferences
* Add warning message regarding deprecation of pre-defined preferences
* Change verbosity of log messages when successfully resolved dependencies to debug so it only show up with --verbose
* Add colours to log messages
**v1.1.7**
* Overwrite build settings if they are present in Cordova's xcconfig files. Fixes #6.
* Don't try to resolve dependencies when plugin is being removed
* Rework dependency resolution to eliminate race conditions. Fixes #15. Fixes #11.
* Also rework and homogenise hook scripts.
**v1.1.6**
* If error occurs while processing a platform, log the error and proceed with other platforms/cordova operations by default unless cordova-custom-config-stoponerror preference is set. Fixes #12.
* Remove read-package-json from list of modules to install. Fixes #10.
**v1.1.5**
* Document preference to prevent auto-restoring of backups
* Add support for preference to disable auto-restore of config file backups
* escape values from preferences (closes #4)
**v1.1.4**
* Add missing license field. Fixes #3.
* Replace 'after_plugin_add' and 'before_plugin_rm' hooks with 'after_plugin_install' and 'before_plugin_uninstall'. Fixes #2.
**v1.1.2**
* Replace the hard-coded old manifest name in windowSoftInputMode parent. Fixes #1
**v1.1.1**
* Add support for new Cordova activity name on Android

View File

@ -0,0 +1,724 @@
cordova-custom-config plugin [![Build Status](https://travis-ci.org/dpa99c/cordova-custom-config-example.svg?branch=master)](https://travis-ci.org/dpa99c/cordova-custom-config-example/branches) [![Latest Stable Version](https://img.shields.io/npm/v/cordova-custom-config.svg)](https://www.npmjs.com/package/cordova-custom-config) [![Total Downloads](https://img.shields.io/npm/dt/cordova-custom-config.svg)](https://npm-stat.com/charts.html?package=cordova-custom-config)
============================
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Overview](#overview)
- [Do I need it?](#do-i-need-it)
- [Important notes](#important-notes)
- [Changes in `cordova-custom-config@5`](#changes-in-cordova-custom-config5)
- [Remote build environments](#remote-build-environments)
- [Installation](#installation)
- [Usage](#usage)
- [Removable preferences via backup/restore](#removable-preferences-via-backuprestore)
- [Preferences](#preferences)
- [Config blocks](#config-blocks)
- [Android](#android)
- [Android preferences](#android-preferences)
- [Android config blocks](#android-config-blocks)
- [Android example](#android-example)
- [iOS](#ios)
- [iOS preferences](#ios-preferences)
- [iOS config blocks](#ios-config-blocks)
- [iOS image resources](#ios-image-resources)
- [iOS example](#ios-example)
- [Plugin preferences](#plugin-preferences)
- [Log output](#log-output)
- [Example project](#example-project)
- [TODO](#todo)
- [Credits](#credits)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Overview
The purpose of this plugin is to enable manipulation of native platform configuration files that are not supported out-of-the-box by Cordova/Phonegap CLI.
The plugin uses hook scripts to update iOS and Android platform configuration files based on custom data defined in `config.xml`.
<!-- DONATE -->
[![donate](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG_global.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZRD3W47HQ3EMJ)
I dedicate a considerable amount of my free time to developing and maintaining this Cordova plugin, along with my other Open Source software.
To help ensure this plugin is kept updated, new features are added and bugfixes are implemented quickly, please donate a couple of dollars (or a little more if you can stretch) as this will help me to afford to dedicate time to its maintenance. Please consider donating if you're using this plugin in an app that makes you money, if you're being paid to make the app, if you're asking for new features or priority bug fixes.
<!-- END DONATE -->
## Do I need it?
**Manual editing** of the platform configuration files in the `platforms/` directory is one solution to setting of custom platform configuration.
But this is not maintainable across multiple development machines or a CI environment where subsequent build operations may overwrite your changes.
This plugin reads custom preferences from `config.xml`, which can be committed to version control and therefore applied across multiple development machines, CI environments,
and maintained between builds, even if a platform is removed and re-added.
**However:** recent versions of the Cordova/Phonegap CLI have added official support for [`<edit-config>`](https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#edit-config) and [`<config-file>`](https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#config-file) blocks in the `config.xml` (previously they only worked in `plugin.xml`).
So if all you want to do is insert a block of native config or change a native preference, you probably don't need this plugin at all.
I hope that eventually the Cordova/Phonegap CLI will support all the functionality that this plugin provides and it can be retired.
**Until then:** there are still some operations that can be performed by this plugin which are not supported by the latest Cordova/Phonegap CLI versions. These include:
- Overriding default platform preferences set during the `cordova prepare` operation.
- Deletion of existing elements/attributes in `AndroidManifest.xml`
- Manipulation of build settings in the native iOS Xcode project file `project.pbxproj` via [XCBuildConfiguration](#xcbuildconfiguration) blocks.
- Manipulation of iOS Precompile header files via [iOS Precompile Header config blocks](#ios-precompile-header-config-blocks)
- Advanced manipulation of iOS Xcode project using [xcodefunc](#xcodefunc).
# Important notes
## Changes in `cordova-custom-config@5`
The recent [release of cordova@7.0.0](http://cordova.apache.org/announcements/2017/12/04/cordova-android-7.0.0.html) has introduced backwardly-incompatible breaking changes to the structure of Android projects generated by Cordova.
Therefore a new major version of this plugin (v5) has been released to support these changes. Here are things to be aware of:
- The location of `AndroidManifest.xml` has changed in `cordova-android@7` but `cordova-custom-config@5` should detect which version of `cordova-android` platform is present in the project and use the correct path.
- All custom config elements supported by this plugin in `config.xml` should now be prefixed with `custom-` for use with `cordova-custom-config@5`
- `<config-file>` => `<custom-config-file>`
- `<preference>` => `<custom-preference>`
- `<resource>` => `<custom-resource>`
- This is because `cordova-android@7` now attempts to parse `<config-file>` blocks in the `config.xml`, so `<config-file>` blocks intended for this plugin to parse will be picked up by Cordova and can cause build errors (see [#135](https://github.com/dpa99c/cordova-custom-config/issues/135)) for an example.
- [Plugin preferences](#plugin-preferences) should still however be specified as `<preference>`
- e.g. `<preference name="cordova-custom-config-autorestore" value="true" />`
- The plugin will detect if the platform project is `cordova-android@7` or `cordova-android@6` (or below)
- If `cordova-android@6`, by default the plugin will support non-prefixed custom config elements
- If `cordova-android@7`, by default the plugin will NOT support non-prefixed custom config elements
- This can be overridden by explicitly setting the `parse_unprefixed` preference
- `<preference name="cordova-custom-config-parse_unprefixed" value="true" />`
## Remote build environments
This plugin is intended for the automated application of custom configuration to native platform projects in a **local build environment**.
This plugin **WILL NOT WORK** with remote ("Cloud") build environments that do not support the execution of this plugin's [hook scripts](https://cordova.apache.org/docs/en/latest/guide/appdev/hooks/). This includes:
- [Phonegap Build](https://github.com/phonegap/build/issues/425)
- [Intel XDK](https://software.intel.com/en-us/xdk/docs/add-manage-project-plugins)
- [Telerik Appbuilder](http://docs.telerik.com/platform/appbuilder/cordova/using-plugins/using-custom-plugins/using-custom-plugins)
- [Ionic Cloud](https://docs.ionic.io/services/package/#hooks)
If you are using another cloud-based Cordova/Phonegap build service and find this plugin doesn't work, the reason is probably also the same.
FWIW: if you are professionally developing Cordova/Phonegap apps, you are eventually going to find it preferable to build locally.
# Installation
The plugin is registered as `cordova-custom-config` on [npm](https://www.npmjs.com/package/cordova-custom-config) (requires Cordova CLI 5.0.0+)
`cordova-custom-config@4+` requires the plugin to be installed via the [`cordova-fetch`](https://cordova.apache.org/news/2016/05/24/tools-release.html) mechanism in order to satisfy its package dependencies by installing it via npm.
Therefore a Cordova CLI version of `cordova@7+` is required to install the plugin:
$ cordova plugin add cordova-custom-config
Or `cordova@6.2+` if the `--fetch` option is specified explicitly:
$ cordova plugin add cordova-custom-config --fetch
# Usage
The hook scripts included in this plugin are run after each platform `prepare` operation and apply preferences dictated by custom keys in the project `config.xml` file to the relevant platform config files.
As such, all you need to do to "use" this plugin is include the relevant keys in your `config.xml` and the scripts will take care of the rest when you build your project.
**IMPORTANT**: As of `cordova-custom-config@5`, this plugin expects that custom configuration keys will be prefixed with `<custom-` in order to distinguish them from the Cordova configuration keys. For example, for a preference intended for this plugin to parse you should use `<custom-preference>` but for a preference intended for Cordova to parse you should use `<preference>`
NOTE: There are no run-time source files included in this plugin - it is simply a convenient package of hook scripts.
## Removable preferences via backup/restore
By default, any changes made by this plugin to platform config files are irreversible - i.e. if you want to undo changes made by the plugin, you'll need to remove then re-add the Cordova platform, for example:
cordova platform rm android && cordova platform add android
However, if you want the changes made to be reversible, you can enable auto-backup/restore functionality by adding the following preference inside the top-level `<widget>` element of your `config.xml`:
<preference name="cordova-custom-config-autorestore" value="true" />
When the first `prepare` operation runs after the plugin is installed, it will make backup copies of the original configuration files before it makes any modifications.
These backup copies are stored in `plugins/cordova-custom-config/backup/` and will be restored before each `prepare` operation, allowing Cordova to make modifications and then the plugin to make further modifications after the `prepare`.
This means changes made by the plugin are reversible, so removing a custom element from the `config.xml` will remove it from the platform configuration file on the next `prepare` operation and uninstalling the plugin will restore the configuration files to their original state (before the plugin made any modifications).
Consequently, any manual changes made to the platform configuration files in `platforms/` **after** installing the plugin will be overwritten by the plugin on the next `prepare` operation.
To prevent auto-restoring of backups and make manual changes to platform configuration files persist, remove the `autorestore` preference from the `config.xml`
## Preferences
Preferences are set by defining a `<custom-preference>` element in the config.xml, e.g. `<custom-preference name="android-launchMode" value="singleTop" />`
1. Preferences defined outside of the platform element will apply to all platforms
2. Preferences defined inside a platform element will apply only to the specified platform
3. Platform preferences take precedence over common preferences
4. Platform-specific preferences must be prefixed with the platform name (e.g. `name="ios-somepref"`) and be defined inside a platform element.
## Config blocks
`<custom-config-file>` blocks allow platform-specific chunks of config to be defined as an XML subtree in the `config.xml`, which is then applied to the appropriate platform configuration file by the plugin.
1. config-file elements MUST be defined inside a platform element, otherwise they will be ignored.
2. config-file target attributes specify the target file to update. (AndroidManifest.xml or *-Info.plist)
3. config-file parent attributes specify the parent element (AndroidManifest.xml) or parent key (*-Info.plist) that the child data will replace or be appended to.
4. config-file elements are uniquely indexed by target AND parent for each platform.
5. If there are multiple config-file's defined with the same target AND parent, the last config-file will be used
6. Elements defined WITHIN a config-file will replace or be appended to the same elements relative to the parent element
7. If a unique config-file contains multiples of the same elements (other than uses-permission elements which are selected by by the uses-permission name attribute), the last defined element will be retrieved.
## Android
The plugin currently supports setting of custom config only in `platforms/android/AndroidManifest.xml`.
For a list of possible manifest values see [http://developer.android.com/guide/topics/manifest/manifest-intro.html](http://developer.android.com/guide/topics/manifest/manifest-intro.html)
### Android preferences
Note: [cordova@6.4.0](https://cordova.apache.org/news/2016/10/25/tools-release.html) adds support for [`<edit-config>`](http://cordova.apache.org/docs/en/6.x/plugin_ref/spec.html#edit-config) blocks in `config.xml`, which enables you to achieve similar manipulation of Android preferences without needing this plugin.
- `<custom-preference>` elements in `config.xml` are used to set set attributes on existing elements in the `AndroidManifest.xml`.
- e.g. `<custom-preference name="android-manifest/@android:hardwareAccelerated" value="false" />`
- will result in `AndroidManifest.xml`: `<manifest android:hardwareAccelerated="false">`
- Sometimes there plugins set some defaults in AndroidManifest.xml that you may not want.
It is also possible to delete nodes using the preferences and the `delete="true"` attribute.
- e.g. `<custom-preference name="android-manifest/uses-permission/[@android:name='android.permission.WRITE_CONTACTS']" delete="true" />`
- will delete the existing node `<uses-permission android:name="android.permission.WRITE_CONTACTS" />`
#### Android namespace attribute
__Important:__ In order to user the `android:` namespace in preferences within your `config.xml`, you must include the android namespace attribute on the root `<widget>` element.
The namespace attribute fragment is:
xmlns:android="http://schemas.android.com/apk/res/android"
so your `<widget>` element should look something like:
<widget
id="com.my.app"
version="0.0.1"
xmlns="http://www.w3.org/ns/widgets"
xmlns:cdv="http://cordova.apache.org/ns/1.0"
xmlns:android="http://schemas.android.com/apk/res/android">
#### XPath preferences
Android manifest preferences are set by using XPaths in the preference name to define which element attribute the value should be applied to.
The preference name should be prefixed with `android-manifest` then follow with an XPath which specifies the element and attribute to apple the value to.
For example:
<custom-preference name="android-manifest/application/activity/@android:launchMode" value="singleTask" />
This preference specifies that the `launchMode` attribute should be given a value of `singleTask`:
<activity android:launchMode="singleTask">
If your manifest contains other activities, you should specify the activity name in the XPath. Note that the activity name for Cordova 4.2.0 and below was "CordovaApp" whereas Cordova 4.3.0 and above is "MainActivity". For example:
<custom-preference name="android-manifest/application/activity[@android:name='MainActivity']/@android:launchMode" value="singleTask" />
If the attribute you are setting is on the root `<manifest>` element, just omit the element name and specify the attribute. For example:
<custom-preference name="android-manifest/@android:installLocation" value="auto" />
### Android config blocks
- `<custom-config-file>` blocks are use to define chunks of config an XML subtree, to be inserted into `AndroidManifest.xml`
- the `target` attribute must be set to `AndroidManifest.xml`: `<custom-config-file target="AndroidManifest.xml">`
- the `parent` attribute defines an Xpath to the parent element in the `AndroidManifest.xml` under which the XML subtree block should be inserted
- to insert a block under the root `<manifest>` element, use `parent="/*"`
- to insert a block under a descendant of `<manifest>`, use an Xpath prefixed with `./`
e.g `parent="./application/activity"` will insert the block under `/manifest/application/activity`
- the child elements inside the `<custom-config-file>` block will be inserted under the parent element.
For example:
<custom-config-file target="AndroidManifest.xml" parent="./application">
<some-element />
</custom-config-file>
will result in `AndroidManifest.xml` with:
<manifest ...>
<application ...>
<some-element />
</application>
</manifest>
**NOTE:** By default, if the specified parent element contains an existing child element of the same name as that defined in the XML subtree, the existing element will be overwritten.
For example:
<custom-config-file target="AndroidManifest.xml">
<application android:name="MyApp" />
</custom-config-file>
will replace the existing `<application>` element(s).
To force the preservation (rather than replacement) of existing child elements, you can use the `mode="add"` attribute.
So for the example above:
<custom-config-file target="AndroidManifest.xml" mode="add">
<application android:name="MyApp" />
</custom-config-file>
will preserve the existing `<application>` element(s).
### Android example
config.xml:
<platform name="android">
<!-- custom preferences examples -->
<custom-preference name="android-manifest/application/activity/@android:windowSoftInputMode" value="stateVisible" />
<custom-preference name="android-manifest/@android:installLocation" value="auto" />
<custom-preference name="android-manifest/application/@android:hardwareAccelerated" value="false" />
<custom-preference name="android-manifest/@android:hardwareAccelerated" value="false" />
<custom-preference name="android-manifest/application/activity/@android:configChanges" value="orientation" />
<custom-preference name="android-manifest/application/activity/@android:theme" value="@android:style/Theme.Material" />
<!-- specify activity name -->
<custom-preference name="android-manifest/application/activity[@android:name='MainActivity']/@android:launchMode" value="singleTask" />
<!-- Delete an element -->
<custom-preference name="android-manifest/application/activity[@android:name='DeleteMe']" delete="true" />
<!-- These preferences are actually available in Cordova by default although not currently documented -->
<custom-preference name="android-minSdkVersion" value="10" />
<custom-preference name="android-maxSdkVersion" value="22" />
<custom-preference name="android-targetSdkVersion" value="21" />
<!-- Or you can use a custom-config-file element for them -->
<custom-config-file target="AndroidManifest.xml" parent="/*">
<uses-sdk android:maxSdkVersion="22" android:minSdkVersion="10" android:targetSdkVersion="21" />
</custom-config-file>
<!-- custom config example -->
<custom-config-file target="AndroidManifest.xml" parent="/*">
<supports-screens
android:xlargeScreens="false"
android:largeScreens="false"
android:smallScreens="false" />
<uses-permission android:name="android.permission.READ_CONTACTS" android:maxSdkVersion="15" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
</custom-config-file>
<!-- Add (rather than overwrite) a custom-config-file block -->
<custom-config-file target="AndroidManifest.xml" parent="./" mode="add">
<application android:name="customApplication"></application>
</custom-config-file>
</platform>
## iOS
- The plugin currently supports custom configuration of:
- the project plist (`*-Info.plist`) using `<custom-config-file>` blocks
- build settings using `<custom-preference>` elements
- image asset catalogs using `<custom-resource>` elements
- All iOS-specific config should be placed inside the `<platform name="ios">` in `config.xml`.
### iOS preferences
- Preferences for iOS can be used to define build configuration settings
- The plugin currently supports:
- setting of `XCBuildConfiguration` block keys in the `project.pbxproj` file
- `xcodefunc` as an interface to apply functions from [node-xcode](https://github.com/alunny/node-xcode)
#### XCBuildConfiguration
- XCBuildConfiguration `<custom-preference>` elements are used to set preferences in the project settings file `platforms/ios/{PROJECT_NAME}/{PROJECT_NAME}.xcodeproj/project.pbxproj`
- Currently, `XCBuildConfiguration` is the only supported block type.
- However, there is no constraint on the list of keys for which values may be set.
- If an entry already exists in an `XCBuildConfiguration` block for the specified key, the existing value will be overwritten with the specified value.
- If no entry exists in any `XCBuildConfiguration` block for the specified key, a new key entry will be created in each `XCBuildConfiguration` block with the specified value.
- By default, values will be applied to both "Release" and "Debug" `XCBuildConfiguration` blocks.
- However, the block type can be specified by adding a `buildType` attribute to the `<custom-preference>` element in the config.xml: value is either `debug` or `release`
- e.g `<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" />`
- By default, both the key (preference name) and value will be quote-escaped when inserted into the `XCBuildConfiguration` block.
- e.g. `<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" />`
- will appear in `project.pbxproj` as: `"IPHONEOS_DEPLOYMENT_TARGET" = "7.0";`
- The default quoting can be override by setting the `quote` attribute on the `<custom-preference>` element.
- Valid values are:
- "none" - don't quote key or value
- "key" - quote key but not value
- "value" - quote value but not key
- "both" - quote both key and value
- e.g. `<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" quote="none" />`
- will appear in `project.pbxproj` as: `IPHONEOS_DEPLOYMENT_TARGET = 7.0;`
- Preferences should be defined in the format `<custom-preference name="ios-SOME_BLOCK_TYPE-SOME_KEY" value="SOME_VALUE" />`
- Therefore, the preference name should be prefixed with `ios-XCBuildConfiguration`, for example:
<custom-preference name="ios-XCBuildConfiguration-ENABLE_BITCODE" value="YES" />
##### .xcconfig files
- Cordova uses `.xcconfig` files in `/platforms/ios/cordova/` to override Xcode project settings in `project.pbxproj` with build-type specific values.
- `build.xcconfig` is overriden by settings in `build-debug.xcconfig` and `build-release.xcconfig` for the corresponding build type.
- When applying a custom preference, the plugin will look for an existing entry in the `.xcconfig` file that corresponds to the buildType attribute.
- If buildType attribute is "debug" or "release", the plugin will look in `build-debug.xcconfig` or `build-release.xcconfig` respectively.
- If buildType is not specified or set to "none", the plugin will look in `build.xcconfig`.
- By default, if an entry is found in the `.xcconfig` file which corresponds to the custom preference name in the `config.xml`, the value in the `.xcconfig` file will be overwritten with the value in the `config.xml`.
- To prevent the plugin from overwriting the value of a specific preference in the corresponding `.xcconfig` file, set the preference attribute `xcconfigEnforce="false"`.
- e.g `<custom-preference name="ios-XCBuildConfiguration-SOME_PREFERENCE" value="Some value" buildType="debug" xcconfigEnforce="false" />`
- If a preference value doesn't already exist in the corresponding `.xcconfig` file, you can force its addition by setting the preference attribute `xcconfigEnforce="true"`.
This will append it to the corresponding .xcconfig` file.
- e.g `<custom-preference name="ios-XCBuildConfiguration-SOME_PREFERENCE" value="Some value" buildType="debug" xcconfigEnforce="true" />`
- A backup copy of any modified `.xcconfig` file will be made in 'plugins/cordova-custom-config/backup/ios'. By default, these backups will be restored prior to the next `prepare` operation.
- Auto-restore of the backups can be disabled by setting `<custom-preference name="cordova-custom-config-autorestore" value="false" />` in the `config.xml`.
- Preference names and values will not be quote-escaped in `.xcconfig` files, so the `quote` attribute has no effect on them.
##### CODE\_SIGN\_IDENTITY preferences
- Cordova places its default CODE\_SIGN\_IDENTITY for Release builds in `build-release.xcconfig` but for Debug builds in `build.xcconfig.
- If you set a CODE\_SIGN\_IDENTITY preference in the `config.xml` with `buildType="release"`, the plugin will overwrite the defaults in `build-release.xcconfig`.
- e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Release Profile (A1B2C3D4)" buildType="release" />`
- If you set a CODE\_SIGN\_IDENTITY preference in the `config.xml` with `buildType="debug"`, the plugin will overwrite the defaults in `build.xcconfig`.
- e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Debug Profile (A1B2C3D4)" buildType="debug" />`
- You can prevent the CODE\_SIGN\_IDENTITY preferences being overwritten by setting `xcconfigEnforce="false"`.
- e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Release Profile (A1B2C3D4)" buildType="release" xcconfigEnforce="false" />`
- You can force the plugin to add a new entry for CODE\_SIGN\_IDENTITY preference with `buildType="debug"` to `build-debug.xcconfig`, rather than overwriting the defaults in `build.xcconfig` by setting `xcconfigEnforce="true"`.
This will still override the defaults in `build.xcconfig`, because `build-debug.xcconfig` overrides `build.xcconfig`.
- e.g. `<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: My Debug Profile (A1B2C3D4)" buildType="debug" xcconfigEnforce="true" />`
#### xcodefunc
- `xcode-func` preferences enable functions within [node-xcode](https://github.com/alunny/node-xcode) to be called to edit different block types (such as Sources, Resources or Frameworks) in the `project.pbxproj`.
- The preference name should be `ios-xcodefunc`, i.e. `name="ios-xcodefunc"`
- The function to call in [node-xcode](https://github.com/alunny/node-xcode) should be specified using the `func` attribute, e.g. `func="addResourceFile"`
- Function arguments should be specified using `<arg />` child elements. It supports the following attributes:
- `value`- the value of the argument, e.g. `value="src/content/image.png"`
- `type` - the type of the value, e.g. `type="String"`.
- Supported types are:
- `Null` - evaluates to `null`
- `Undefined` - evaluates to `undefined`
- `Object` - a stringified JSON object that will be parsed back into its object form
- `Number` - a Javascript Number
- `String` - a Javascript String
- `Symbol` - a Javascript Symbol
- If `type` is not specified, the argument value will be passed exactly as defined in the `value` attribute
- `flag` - a modifier for specific types of argument, e.g. `flag="path"`
- Currently, the only supported value is `path` which forces the path to be resolved either as an absolute path or relative to the project root.
For example:
<custom-preference name="ios-xcodefunc" func="addResourceFile">
<arg type="String" value="src/content/image.png" flag="path" />
</custom-preference>
will add resource `image.png` from `./src/content` (i.e. `../../src/content/image.png` relative to `./platforms/ios/`)
### iOS config blocks
- `<custom-config-file>` elements are currently used to:
- set preferences in the project .plist file (`platforms/ios/{PROJECT_NAME}/{PROJECT_NAME}-Info.plist`).
- add to Precompiled Headers file (`platforms/ios/{PROJECT_NAME}/{PROJECT_NAME}-Prefix.pch`).
- all `<custom-config-file>` elements should have the `platform` attribute set to `ios`: `<custom-config-file platform="ios">`
#### iOS project plist config blocks
- the `target` attribute of the `<custom-config-file>` should be set to `*-Info.plist`: `<custom-config-file platform="ios" target="*-Info.plist">`
- the `parent` attribute is used to determine which key name to use for the custom preference
- e.g. `<custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationAlwaysUsageDescription">`
- will appear in `{PROJECT_NAME}-Info.plist` as `<key>NSLocationAlwaysUsageDescription</key>` under `/plist/dict`
- the value of the preference is set by the child elements of the `<custom-config-file>` element. These will appear directly below the preference `<key>` in the .plist file.
- For example:
`<custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationAlwaysUsageDescription">
<string>This app requires constant access to your location in order to track your position, even when the screen is off.</string>
</custom-config-file>`
- will appear in the plist file as:
`<key>NSLocationAlwaysUsageDescription</key>
<string>This app requires constant access to your location in order to track your position, even when the screen is off.</string>`
- if the .plist value type is an array, by default the values in the `<custom-config-file>` block will be merged with any existing values.
- For example, if the plist already contains:
`<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-api</string>
</array>`
- Then adding the `<custom-config-file>` block:
`<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist">
<array>
<string>myapp</string>
<string>myapp2</string>
</array>
</custom-config-file>`
- will result in the plist file as:
`<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-api</string>
<string>myapp</string>
<string>myapp2</string>
</array>`
- this behaviour can also be explicitly specified by adding `mode="merge"` to the `<custom-config-file>` block:
- For example, the `<custom-config-file>` block:
`<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist" mode="replace">
<array>
<string>myapp</string>
<string>myapp2</string>
</array>
</custom-config-file>`
- will also result in the plist file as:
`<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-api</string>
<string>myapp</string>
<string>myapp2</string>
</array>`
- to replace existing values with those in the `<custom-config-file>` block, use the attribute `mode="replace"`:
- For example, if the plist already contains:
`<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-api</string>
</array>`
- Then adding the `<custom-config-file>` block:
`<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist" mode="replace">
<array>
<string>myapp</string>
<string>myapp2</string>
</array>
</custom-config-file>`
- will result in the plist file as:
`<key>LSApplicationQueriesSchemes</key>
<array>
<string>myapp</string>
<string>myapp2</string>
</array>`
- to delete existing values in the plist, specify the key to delete as the parent and use the attribute `mode="delete"`:
- For example, if the plist already contains:
`<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-api</string>
</array>`
- Then adding the `<custom-config-file>` block:
`<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist" mode="delete"/>`
- will result in the existing block being removed from the plist
#### iOS Precompile Header config blocks
- the `target` attribute of the `<custom-config-file>` should be set to `*-Prefix.pch`: `<custom-config-file platform="ios" target="*-Prefix.pch">`
### iOS image resources
Purpose:
- Sometimes it can be necessary to create custom iOS image asset catalogs in Cordova-based iOS apps.
- For example, some plugins require that custom images be present in a custom asset catalog in order to make use of them:
- [cordova-plugin-themeablebrowser](https://github.com/initialxy/cordova-plugin-themeablebrowser)
- [cordova-plugin-3dtouch](https://github.com/EddyVerbruggen/cordova-plugin-3dtouch)
- This could be done manually by editing the platform project in XCode, but this is fragile since platform projects are volatile.
- i.e. can be removed when removing/updating the platform via Cordova CLI.
- So this plugin provides a mechanism to automate the generation custom asset catalogs.
Usage:
- Image [asset catalogs](https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/) can be defined using `<custom-resource>` elements
- The `<custom-resource>` elements must be places inside of the `<platform name="ios">` element
- The `<custom-resource>` elements must have the attribute `type="image"`: `<custom-resource type="image" />`
- The `src` attribute (required) should specify the relative local path to the image file.
- The relative root is the Cordova project root
- e.g. `<custom-resource src="resources/custom-catalog/back@1x.png" />`
- where the image file is location in `/path/to/project/root/resources/custom-catalog/back@1x.png`
- The `catalog` attribute (required) specifies the name of the catalog to add the image to
- e.g. `<custom-resource catalog="custom-catalog"/>`
- The `scale` attribute (required) specifies the scale factor of the image
- Valid values are: `1x`, `2x`, `3x`
- e.g. `<custom-resource scale="1x"/>`
- The `idiom` attribute (optional) specifies the target device family
- Valid values are:
- `universal` - all devices
- `iphone` - iPhones only
- `ipad` - iPads only
- `watch` - Apple Watch only
- If not specified, defaults to `universal`
- e.g. `<custom-resource idiom="iphone"/>`
- Full example:
`<custom-resource type="image" src="resources/custom-catalog/back@1x.png" catalog="custom-catalog" scale="1x" idiom="iphone" />`
### iOS example
config.xml:
<platform name="ios">
<!-- Set ENABLE_BITCODE to YES in XCode project file override NO value in /ios/cordova/build.xcconfig -->
<custom-preference name="ios-XCBuildConfiguration-ENABLE_BITCODE" value="YES" />
<!-- Set deploy target SDKs for release and debug builds -->
<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="9.1" buildType="debug" quote="none" />
<custom-preference name="ios-XCBuildConfiguration-IPHONEOS_DEPLOYMENT_TARGET" value="7.0" buildType="release" />
<!-- Custom code signing profiles (overriding those in /ios/cordova/*.xcconfig -->
<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Developer: Dave Alden (8VUQ6DYDLL)" buildType="debug" xcconfigEnforce="true" />
<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos*]" value="iPhone Developer: Dave Alden (8VUQ6DYDLL)" buildType="debug" />
<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos9.1]" value="iPhone Developer: Dave Alden (8VUQ6DYDLL)" buildType="debug" />
<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY" value="iPhone Distribution: Working Edge Ltd (556F3DRHUD)" buildType="release" xcconfigEnforce="false" />
<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos*]" value="iPhone Distribution: Working Edge Ltd (556F3DRHUD)" buildType="release" />
<custom-preference name="ios-XCBuildConfiguration-CODE\_SIGN\_IDENTITY[sdk=iphoneos9.1]" value="iPhone Distribution: Working Edge Ltd (556F3DRHUD)" buildType="release" />
<!-- Add resource file by relative path -->
<custom-preference name="ios-xcodefunc" func="addResourceFile">
<arg type="String" value="src/content/image.png" flag="path" />
</custom-preference>
<!-- By default, merge with existing array values -->
<custom-config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist">
<array>
<string>myapp</string>
<string>myapp2</string>
<string>myapp3</string>
</array>
</custom-config-file>
<!-- Explicitly merge with existing array values -->
<custom-config-file platform="ios" target="*-Info.plist" parent="UISupportedInterfaceOrientations" mode="merge" >
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</custom-config-file>
<!-- Replace existing values -->
<custom-config-file platform="ios" target="*-Info.plist" parent="UISupportedInterfaceOrientations~ipad" mode="replace">
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</custom-config-file>
<!-- Set background location mode -->
<custom-config-file platform="ios" target="*-Info.plist" parent="UIBackgroundModes">
<array>
<string>location</string>
</array>
</custom-config-file>
<!-- Set message displayed when app requests constant location updates -->
<custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationAlwaysUsageDescription">
<string>This app requires constant access to your location in order to track your position, even when the screen is off.</string>
</custom-config-file>
<!-- Set message displayed when app requests foreground location updates -->
<custom-config-file platform="ios" target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
<string>This app will now only track your location when the screen is on and the app is displayed.</string>
</custom-config-file>
<!-- Allow arbitrary loading of resources over HTTP on iOS9 -->
<custom-config-file platform="ios" target="*-Info.plist" parent="NSAppTransportSecurity">
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</custom-config-file>
<!-- Custom image asset catalog -->
<custom-resource type="image" catalog="custom" src="resources/ios/custom-icons/back@1x.png" scale="1x" idiom="universal" />
<custom-resource type="image" catalog="custom" src="resources/ios/custom-icons/back@2x.png" scale="2x" idiom="universal" />
<custom-resource type="image" catalog="custom" src="resources/ios/custom-icons/back@3x.png" scale="3x" idiom="universal" />
</platform>
## Plugin preferences
The plugin supports some preferences which are used to customise the behaviour of the plugin.
These preferences should be placed at the top level (inside `<widget>`) rather than inside individual `<platform>` elements.
Each preference name is prefixed with `cordova-custom-config` to avoid name clashes, for example:
<preference name="cordova-custom-config-autorestore" value="true" />
The following preferences are currently supported:
- `cordova-custom-config-autorestore` - if true, the plugin will restore a backup of platform configuration files taken at plugin installation time.
See the [Removable preferences](#removable-preferences-via-backuprestore) section for details. Defaults to `false`.
- `cordova-custom-config-stoponerror` - if true and an error occurs while updating config for a given platform during a `prepare` operation, the error will cause the `prepare` operation to fail.
If false, the plugin will log the error but will proceed and attempt to update any other platforms, before allowing the `prepare` operation to continue.
Defaults to `false`.
- `cordova-custom-config-hook` - determines which Cordova hook operation to use to run the plugin and apply custom config.
Defaults to `after_prepare` if not specified.
You may wish to change this to apply custom config earlier or later, for example if config applied by this plugin is clashing with other plugins.
Possible values are: `before_prepare`, `after_prepare`, `before_compile`.
See the [Cordova hooks documentation](https://cordova.apache.org/docs/en/latest/guide/appdev/hooks/) for more on the Cordova build process.
- `cordova-custom-config-parse_unprefixed` - determines whether the plugin should attempt to parse unprefixed custom config elements in `config.xml`
- If not explicitly specified, the plugin will set a default by detecting whether the platform project is `cordova-android@7` or `cordova-android@6` (or below)
- If `cordova-android@6`, defaults to `true`
- If `cordova-android@7`, defaults to `false`
## Log output
If you run the prepare operation with the `--verbose` command-line option, the plugin will output detail about the operations it's performing. Console messages are prefixed with `cordova-custom-config: `. For example:
cordova prepare ios --verbose
# Example project
An example project illustrating use of this plugin can be found here: [https://github.com/dpa99c/cordova-custom-config-example](https://github.com/dpa99c/cordova-custom-config-example)
# TODO
See the [TODO list](https://github.com/dpa99c/cordova-custom-config/wiki/TODO) for planned features/improvements.
# Credits
Config update hook based on [this hook](https://github.com/diegonetto/generator-ionic/blob/master/templates/hooks/after_prepare/update_platform_config.js) by [Diego Netto](https://github.com/diegonetto)
# License
================
The MIT License
Copyright (c) 2016 Working Edge Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>Padlock</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIcons</key>
<dict/>
<key>CFBundleIcons~ipad</key>
<dict/>
<key>CFBundleIdentifier</key>
<string>com.maklesoft.padlock</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>3.0.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSMainNibFile</key>
<string/>
<key>NSMainNibFile~ipad</key>
<string/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIRequiresFullScreen</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>stripe.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>stripe.network</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>padlock</string>
</array>
</dict>
</array>
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UILaunchStoryboardName</key>
<string>CDVLaunchScreen</string>
</dict>
</plist>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
#!/usr/bin/env node
/**********
* Globals
**********/
var fs,
path,
_,
et,
tostr;
/**
* Provides files utilities
*/
var fileUtils = (function(){
/**********************
* Internal properties
*********************/
var fileUtils = {}, context, configXmlData, settings;
/************
* Public API
************/
// Parses a given file into an elementtree object
fileUtils.parseElementtreeSync = function(filename) {
var contents = fs.readFileSync(filename, 'utf-8');
if(contents) {
//Windows is the BOM. Skip the Byte Order Mark.
contents = contents.substring(contents.indexOf('<'));
}
return new et.ElementTree(et.XML(contents));
};
// Parses the config.xml into an elementtree object and stores in the config object
fileUtils.getConfigXml = function() {
if(!configXmlData) {
configXmlData = fileUtils.parseElementtreeSync(path.join(context.opts.projectRoot, 'config.xml'));
}
return configXmlData;
};
// Returns plugin settings from config.xml
fileUtils.getSettings = function (){
if(!settings){
settings = {};
var name, preferences = fileUtils.getConfigXml().findall("preference");
_.each(preferences, function (preference) {
name = preference.attrib.name;
if(name.match("cordova-custom-config")){
settings[name.split('-').pop()] = preference.attrib.value;
}
});
}
return settings;
};
// Returns project name from config.xml
fileUtils.getProjectName = function(){
if(!configXmlData) {
fileUtils.getConfigXml();
}
return configXmlData.findtext('name');
};
fileUtils.fileExists = function(filePath){
try {
return fs.statSync(filePath).isFile();
}
catch (err) {
return false;
}
};
fileUtils.directoryExists = function(dirPath){
try {
return fs.statSync(dirPath).isDirectory();
}
catch (err) {
return false;
}
};
fileUtils.createDirectory = function (dirPath){
return fs.mkdirSync(dirPath);
};
fileUtils.copySync = function (sourcePath, targetPath){
var contents = fs.readFileSync(sourcePath);
fs.writeFileSync(targetPath, contents);
};
fileUtils.copySyncRelative = function (sourcePath, targetPath){
fileUtils.copySync(path.resolve(sourcePath), path.resolve(targetPath));
};
fileUtils.init = function(ctx){
context = ctx;
// Load modules
fs = require('fs');
path = require('path');
_ = require('lodash');
et = require('elementtree');
tostr = require('tostr');
};
return fileUtils;
})();
module.exports = function(ctx){
fileUtils.init(ctx);
return fileUtils;
};

View File

@ -0,0 +1,87 @@
#!/usr/bin/env node
var logger = (function(){
/**********************
* Internal properties
*********************/
var logger, context, hasColors = true;
try{
require('colors');
}catch(e){
hasColors = false;
}
function prefixMsg(msg){
return context.opts.plugin.id+": "+msg;
}
/************
* Public API
************/
logger = {
init: function(ctx){
context = ctx;
},
dump: function (obj){
if(context.cmdLine.match("--debug") || context.cmdLine.match("--dump")) {
console.log("DUMP: "+require('util').inspect(obj));
}
},
debug: function(msg){
if(context.cmdLine.match("--debug")){
msg = "DEBUG: " + msg;
console.log(msg);
}
},
verbose: function(msg){
if(context.opts.verbose || context.cmdLine.match("--verbose") || context.cmdLine.match("--debug")){
msg = prefixMsg(msg);
if(hasColors){
console.log(msg.green);
}else{
console.log(msg);
}
}
},
log: function(msg){
msg = prefixMsg(msg);
if(hasColors){
console.log(msg.white);
}else{
console.log(msg);
}
},
info: function(msg){
msg = prefixMsg(msg);
if(hasColors){
console.log(msg.blue);
}else{
console.info(msg);
}
},
warn: function(msg){
msg = prefixMsg(msg);
if(hasColors){
console.log(msg.yellow);
}else{
console.warn(msg);
}
},
error: function(msg){
msg = prefixMsg(msg);
if(hasColors){
console.log(msg.red);
}else{
console.error(msg);
}
}
};
return logger;
})();
module.exports = function(ctx){
logger.init(ctx);
return logger;
};

View File

@ -0,0 +1,147 @@
#!/usr/bin/env node
/**********
* Globals
**********/
var TAG = "cordova-custom-config";
var SCRIPT_NAME = "restoreBackups.js";
// Pre-existing Cordova npm modules
var deferral, path, cwd;
// Npm dependencies
var logger,
fs,
_,
fileUtils;
// Other globals
var hooksPath;
var restoreBackups = (function(){
/**********************
* Internal properties
*********************/
var restoreBackups = {}, context, projectName, logFn, settings;
var PLATFORM_CONFIG_FILES = {
"ios":{
"{projectName}-Info.plist": "{projectName}/{projectName}-Info.plist",
"project.pbxproj": "{projectName}.xcodeproj/project.pbxproj",
"build.xcconfig": "cordova/build.xcconfig",
"build-extras.xcconfig": "cordova/build-extras.xcconfig",
"build-debug.xcconfig": "cordova/build-debug.xcconfig",
"build-release.xcconfig": "cordova/build-release.xcconfig",
"Entitlements-Release.plist": "{projectName}/Entitlements-Release.plist",
"Entitlements-Debug.plist": "{projectName}/Entitlements-Debug.plist"
},
"android":{
"AndroidManifest.xml": "AndroidManifest.xml"
}
};
/*********************
* Internal functions
*********************/
function restorePlatformBackups(platform){
var configFiles = PLATFORM_CONFIG_FILES[platform],
backupFile, backupFileName, backupFilePath, backupFileExists, targetFilePath;
logger.verbose("Checking to see if there are backups to restore...");
for(backupFile in configFiles){
backupFileName = parseProjectName(backupFile);
backupFilePath = path.join(cwd, 'plugins', context.opts.plugin.id, "backup", platform, backupFileName);
backupFileExists = fileUtils.fileExists(backupFilePath);
if(backupFileExists){
targetFilePath = path.join(cwd, 'platforms', platform, parseProjectName(configFiles[backupFile]));
fileUtils.copySync(backupFilePath, targetFilePath);
logFn("Restored backup of '"+backupFileName+"' to :"+targetFilePath);
}
}
}
function parseProjectName(fileName){
return fileName.replace(/{(projectName)}/g, projectName);
}
// Script operations are complete, so resolve deferred promises
function complete(){
deferral.resolve();
}
/*************
* Public API
*************/
restoreBackups.loadDependencies = function(ctx){
fs = require('fs'),
_ = require('lodash'),
fileUtils = require(path.resolve(hooksPath, "fileUtils.js"))(ctx);
logger.verbose("Loaded module dependencies");
};
restoreBackups.init = function(ctx){
context = ctx;
projectName = fileUtils.getProjectName();
logFn = context.hook === "before_plugin_uninstall" ? logger.log : logger.verbose;
settings = fileUtils.getSettings();
if(typeof(settings.autorestore) === "undefined" || settings.autorestore === "false"){
logger.log("Skipping auto-restore of config file backup(s)");
complete();
return;
}
// go through each of the platform directories
var platforms = _.filter(fs.readdirSync('platforms'), function (file) {
return fs.statSync(path.resolve('platforms', file)).isDirectory();
});
_.each(platforms, function (platform, index) {
platform = platform.trim().toLowerCase();
try{
restorePlatformBackups(platform);
if(index === platforms.length - 1){
logger.verbose("Finished restoring backups");
complete();
}
}catch(e){
var msg = "Error restoring backups for platform '"+platform+"': "+ e.message;
logger.error(msg);
if(settings.stoponerror){
deferral.reject(TAG + ": " +msg);
}
}
});
};
return restoreBackups;
})();
module.exports = function(ctx) {
try{
deferral = ctx.requireCordovaModule('q').defer();
path = ctx.requireCordovaModule('path');
cwd = path.resolve();
hooksPath = path.resolve(ctx.opts.projectRoot, "plugins", ctx.opts.plugin.id, "hooks");
logger = require(path.resolve(hooksPath, "logger.js"))(ctx);
restoreBackups.loadDependencies(ctx);
}catch(e){
var msg = TAG + ": Error loading dependencies for "+SCRIPT_NAME+" - ensure the plugin has been installed via cordova-fetch or run 'npm install cordova-custom-config': "+e.message;
deferral.reject(msg);
return deferral.promise;
}
try{
logger.verbose("Running " + SCRIPT_NAME);
restoreBackups.init(ctx);
}catch(e){
var msg = TAG + ": Error running "+SCRIPT_NAME+": "+e.message;
deferral.reject(msg);
}
return deferral.promise;
};

View File

@ -0,0 +1,49 @@
#!/usr/bin/env node
"use strict";
var shell = require('shelljs');
var path = require('path');
var got = require('got');
var targetRepo = 'dpa99c/cordova-custom-config-example';
console.log("Fetching Git commit hash...");
var gitCommitRet = shell.exec('git rev-parse HEAD', {
cwd: path.join(__dirname, '..')
});
if (0 !== gitCommitRet.code) {
console.error('Error getting git commit hash');
process.exit(-1);
}
var gitCommitHash = gitCommitRet.stdout.trim();
console.log("Git commit: "+gitCommitHash);
console.log('Calling Travis...');
got.post("https://api.travis-ci.org/repo/"+encodeURIComponent(targetRepo)+"/requests", {
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"Travis-API-Version": "3",
"Authorization": "token "+process.env.TRAVIS_API_TOKEN
},
body: JSON.stringify({
request: {
message: "Trigger build at "+targetRepo+" commit: "+gitCommitHash,
branch: 'master'
}
})
})
.then(function(){
console.log("Triggered build of "+targetRepo);
})
.catch(function(err){
console.error(err);
process.exit(-1);
});

View File

@ -0,0 +1,79 @@
{
"_from": "cordova-custom-config",
"_id": "cordova-custom-config@5.0.2",
"_inBundle": false,
"_integrity": "sha512-BOlDpmll+CIL+pSFfYrp0ederNCDKvB3X8tDY2EacEvcar/kB2MKs9M5Htv4VTrkfdIbjJtoePD2vaH3apY1Tg==",
"_location": "/cordova-custom-config",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "cordova-custom-config",
"name": "cordova-custom-config",
"escapedName": "cordova-custom-config",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/cordova-custom-config/-/cordova-custom-config-5.0.2.tgz",
"_shasum": "43e751d0c9d20845f57ace227c82c26668662eff",
"_spec": "cordova-custom-config",
"_where": "/Users/martin/workspace/padlock/cordova",
"author": {
"name": "Dave Alden"
},
"bugs": {
"url": "https://github.com/dpa99c/cordova-custom-config/issues"
},
"bundleDependencies": false,
"cordova": {
"id": "cordova-custom-config",
"platforms": [
"android",
"ios"
]
},
"cordova_name": "cordova-custom-config",
"dependencies": {
"colors": "^1.1.2",
"elementtree": "^0.1.6",
"lodash": "^4.3.0",
"plist": "github:xiangpingmeng/plist.js",
"shelljs": "^0.7.0",
"tostr": "^0.1.0",
"xcode": "^1.0.0"
},
"deprecated": false,
"description": "Cordova/Phonegap plugin to update platform configuration files based on preferences and config-file data defined in config.xml.",
"devDependencies": {
"got": "^6.5.0",
"jshint": "^2.6.0"
},
"homepage": "https://github.com/dpa99c/cordova-custom-config#readme",
"issue": "https://github.com/dpa99c/cordova-custom-config/issues",
"keywords": [
"ecosystem:cordova",
"cordova",
"cordova-android",
"cordova-ios",
"phonegap",
"config",
"configuration",
"plist",
"manifest"
],
"license": "MIT",
"name": "cordova-custom-config",
"repository": {
"type": "git",
"url": "git+https://github.com/dpa99c/cordova-custom-config.git"
},
"scripts": {
"test": "jshint hooks"
},
"version": "5.0.2"
}

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-custom-config"
version="5.0.2">
<name>cordova-custom-config</name>
<description>Cordova/Phonegap plugin to update platform configuration files based on preferences and config-file data defined in config.xml</description>
<author>Dave Alden</author>
<engines>
<engine name="cordova" version=">=3.0.0" />
</engines>
<repo>https://github.com/dpa99c/cordova-custom-config.git</repo>
<issue>https://github.com/dpa99c/cordova-custom-config/issues</issue>
<keywords>ecosystem:cordova,cordova,phonegap,ios,android,config,configuration,plist,manifest</keywords>
<license>MIT</license>
<hook src="hooks/restoreBackups.js" type="before_prepare" />
<hook src="hooks/restoreBackups.js" type="before_plugin_uninstall" />
<hook src="hooks/applyCustomConfig.js" type="after_prepare" />
<hook src="hooks/applyCustomConfig.js" type="before_prepare" />
<hook src="hooks/applyCustomConfig.js" type="before_compile" />
</plugin>

View File

@ -0,0 +1,7 @@
{
"images" : [],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,42 @@
### 0.1.9
* Renamed Windows8 platform to Windows
### 0.1.7
* Add getPackageName feature (thanks to @gprasanth)
* Add getAppName feature (thanks to @mirko77)
* Fix for windows 8 (thanks to @deliriousrhino)
* Fix version number in plugin.xml file
### 0.1.6
* Split into two functions getAppVersion.getVersionNumber() and getAppVersion.getVersionCode() to return build number
* Fix a deprecation warning in iOS version
### 0.1.5
* iOS: Return version number but log and fall back to build number if it is nil (thanks to [Eddy Verbruggen](https://github.com/EddyVerbruggen))
### 0.1.4
* Return version number, not build number on iOS (thanks to http://www.humancopy.net)
* Support for Windows phone 8 (thanks to Cristi Badila / Gediminas Šaltenis)
* Support for AngularJS as well as jQuery (thanks to Matias Singers, [Red Ape Solutions](http://www.redapesolutions.com/))
### 0.1.3
* Fixes to Android for Corova 3 and above (thanks to AxoInsanit)
### 0.1.2
* Updated for Cordova 3 and above (thanks to Russell Keith-Magee [freakboy3742](https://github.com/freakboy3742)
### 0.1.1
* Improved README
* Bug fix for non-jQuery use
* Tidy plugin.xml
### 0.1.0
* First release

View File

@ -0,0 +1,20 @@
Copyright (c) 2013 White October
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,71 @@
# Cordova AppVersion plugin
Reads the version of your app from the target build settings.
## Installation
### With cordova-cli
If you are using [cordova-cli](https://github.com/apache/cordova-cli), install
with:
cordova plugin add cordova-plugin-app-version
### With plugman
With a plain [plugman](https://github.com/apache/cordova-plugman), you should be
able to install with something like:
plugman --platform <ios|android> --project <directory> --plugin https://github.com/whiteoctober/cordova-plugin-app-version.git
### Manually in iOS
TODO: Write these instructions
### Manually in Android
TODO: Write these instructions
## Use from Javascript
If you are using jQuery, AngularJS, WinJS or any Promise/A library (Bluebird), promise style is supported. Use something like:
cordova.getAppVersion.getVersionNumber().then(function (version) {
$('.version').text(version);
});
If not, pass a callback function:
cordova.getAppVersion.getVersionNumber(function (version) {
alert(version);
});
In addition to the version number you can also retrieve other details about your application:
### getAppName
Returns the name of the app. E.g. "My Awesome App"
### getPackageName
Returns the package name of the app - the reversed domain name app identifier like com.example.myawesomeapp
### getVersionCode
Returns the build identifier of the app
### getVersionNumber
Returns the version number of the app
## Credits
Written by [Robert (Jamie) Munro](http://twitter.com/rjmunro) at
[White October](http://whiteoctober.co.uk/)
Various others have contributed fixes and new features. See the CHANGELOG.md for details.
Original code based on the following Stack Overflow posts:
* [iOS](http://stackoverflow.com/a/14713364/3408)
* [Android](http://stackoverflow.com/a/3637686/3408)

View File

@ -0,0 +1 @@
0.1.9

View File

@ -0,0 +1,26 @@
{
"name": "cordova-plugin-app-version",
"version": "0.1.9",
"description": "Cordova plugin to return the version number of the current app",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/whiteoctober/cordova-plugin-app-version.git"
},
"keywords": [
"cordova",
"ecosystem:cordova",
"app",
"version",
"appversion",
"plugin"
],
"author": "whiteoctober",
"license": "MIT",
"bugs": {
"url": "https://github.com/whiteoctober/cordova-plugin-app-version/issues"
},
"homepage": "https://github.com/whiteoctober/cordova-plugin-app-version#readme"
}

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-plugin-app-version"
version="0.1.9">
<name>AppVersion</name>
<description>
This plugin will return the version of your App that you have set in
packaging it. I.e. it will always match the version in the app store.
</description>
<license>MIT</license>
<engines>
<!--
Cordova 2.8.0 is all I have tested on - it should work fine with earlier versions.
Please modify the below line, test, and submit a PR if it works for you.
-->
<engine name="cordova" version=">=3.0.0" />
</engines>
<js-module src="www/AppVersionPlugin.js">
<clobbers target="cordova.getAppVersion" />
</js-module>
<!-- android -->
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<feature name="AppVersion">
<param name="android-package" value="uk.co.whiteoctober.cordova.AppVersion"/>
</feature>
</config-file>
<source-file src="src/android/AppVersion.java" target-dir="src/uk/co/whiteoctober/cordova" />
</platform>
<!-- blackberry10 -->
<platform name="blackberry10">
<dependency id="cordova-plugin-bb-app" />
<config-file target="www/config.xml" parent="/widget">
<feature name="AppVersion" value="AppVersion" />
</config-file>
<js-module src="www/blackberry10/AppVersionProxy.js" name="AppVersionProxy.js" >
<runs />
</js-module>
</platform>
<!-- ios -->
<platform name="ios">
<plugins-plist key="AppVersion" string="AppVersion" />
<config-file target="config.xml" parent="/*">
<feature name="AppVersion">
<param name="ios-package" value="AppVersion" />
</feature>
</config-file>
<header-file src="src/ios/AppVersion.h" />
<source-file src="src/ios/AppVersion.m" />
</platform>
<!-- windows8 -->
<platform name="windows">
<js-module src="src/windows/AppVersionProxy.js" name="AppVersionProxy">
<merges target=""/>
</js-module>
</platform>
<!-- wp8 -->
<platform name="wp8">
<config-file target="config.xml" parent="/*">
<feature name="AppVersion">
<param name="wp-package" value="AppVersion"/>
</feature>
</config-file>
<source-file src="src/wp8/AppVersion.cs" />
</platform>
</plugin>

View File

@ -0,0 +1,45 @@
package uk.co.whiteoctober.cordova;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager;
public class AppVersion extends CordovaPlugin {
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
try {
if (action.equals("getAppName")) {
PackageManager packageManager = this.cordova.getActivity().getPackageManager();
ApplicationInfo app = packageManager.getApplicationInfo(this.cordova.getActivity().getPackageName(), 0);
callbackContext.success((String)packageManager.getApplicationLabel(app));
return true;
}
if (action.equals("getPackageName")) {
callbackContext.success(this.cordova.getActivity().getPackageName());
return true;
}
if (action.equals("getVersionNumber")) {
PackageManager packageManager = this.cordova.getActivity().getPackageManager();
callbackContext.success(packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), 0).versionName);
return true;
}
if (action.equals("getVersionCode")) {
PackageManager packageManager = this.cordova.getActivity().getPackageManager();
callbackContext.success(packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), 0).versionCode);
return true;
}
return false;
} catch (NameNotFoundException e) {
callbackContext.success("N/A");
return true;
}
}
}

View File

@ -0,0 +1,13 @@
#import <Cordova/CDVPlugin.h>
@interface AppVersion : CDVPlugin
- (void)getAppName:(CDVInvokedUrlCommand*)command;
- (void)getPackageName:(CDVInvokedUrlCommand*)command;
- (void)getVersionNumber:(CDVInvokedUrlCommand*)command;
- (void)getVersionCode:(CDVInvokedUrlCommand*)command;
@end

View File

@ -0,0 +1,47 @@
#import "AppVersion.h"
#import <Cordova/CDVPluginResult.h>
@implementation AppVersion
- (void)getAppName : (CDVInvokedUrlCommand *)command
{
NSString * callbackId = command.callbackId;
NSString * version =[[[NSBundle mainBundle]infoDictionary]objectForKey :@"CFBundleDisplayName"];
CDVPluginResult * pluginResult =[CDVPluginResult resultWithStatus : CDVCommandStatus_OK messageAsString : version];
[self.commandDelegate sendPluginResult : pluginResult callbackId : callbackId];
}
- (void)getPackageName:(CDVInvokedUrlCommand*)command
{
NSString* callbackId = command.callbackId;
NSString* packageName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:packageName];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
- (void)getVersionNumber:(CDVInvokedUrlCommand*)command
{
NSString* callbackId = command.callbackId;
NSString* version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
if (version == nil) {
NSLog(@"CFBundleShortVersionString was nil, attempting CFBundleVersion");
version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
if (version == nil) {
NSLog(@"CFBundleVersion was also nil, giving up");
// not calling error callback here to maintain backward compatibility
}
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:version];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
- (void)getVersionCode:(CDVInvokedUrlCommand*)command
{
NSString* callbackId = command.callbackId;
NSString* version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:version];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
@end

View File

@ -0,0 +1,33 @@
AppVersionProxy = {
getVersionNumber: function (successCallback, failCallback, args) {
var version = Windows.ApplicationModel.Package.current.id.version;
successCallback([version.major, version.minor, version.build, version.revision].join('.'));
},
getAppName: function (successCallback, failCallback, args) {
if(Windows.ApplicationModel.Package.current && Windows.ApplicationModel.Package.current.displayName){
var name = Windows.ApplicationModel.Package.current.displayName;
successCallback(name);
} else {
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync("AppxManifest.xml").then(function (file) {
Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file).then(function (xdoc) {
var displayName = xdoc.getElementsByTagName("DisplayName");
if (displayName && displayName.length === 1) {
var name = displayName[0].innerText;
successCallback(name);
} else {
(failCallback || function(){})({ code: -1, message: "ERR_DISPLAY_NAME_NOT_FOUND" });
}
}, (failCallback || function(){}));
}, (failCallback || function(){}));
}
},
getPackageName: function (successCallback, failCallback, args) {
var name = Windows.ApplicationModel.Package.current.id.name;
successCallback(name);
},
getVersionCode: function (successCallback, failCallback, args) {
var build = Windows.ApplicationModel.Package.current.id.version.build;
successCallback(build);
}
};
cordova.commandProxy.add("AppVersion", AppVersionProxy);

View File

@ -0,0 +1,53 @@
using System;
using System.Reflection;
using System.Xml.Linq;
using Windows.ApplicationModel;
using WPCordovaClassLib.Cordova;
using WPCordovaClassLib.Cordova.Commands;
namespace Cordova.Extension.Commands
{
public class AppVersion : BaseCommand
{
public void getVersionNumber(string empty)
{
string version;
if (Environment.OSVersion.Version.Major <= 8)
{
// Package.Current.Id is NOT working in Windows Phone 8
// Workaround based on http://stackoverflow.com/questions/14371275/how-can-i-get-my-windows-store-apps-title-and-version-info
version = XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Version").Value;
}
else
{
version = Package.Current.Id.Version.ToString();
}
this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK, version));
}
public void getAppName(string empty)
{
string name;
if (Environment.OSVersion.Version.Major <= 8)
{
//Windows.ApplicationModel.Package.Current.Id is NOT working in Windows Phone 8
//Workaround based on http://stackoverflow.com/questions/14371275/how-can-i-get-my-windows-store-apps-title-and-version-info
name = XDocument.Load("WMAppManifest.xml").Root.Element("App").Attribute("Title").Value;
}
else
{
name = Package.Current.Id.Name;
}
this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK, name));
}
public void getPackageName(string empty)
{
string package = Assembly.GetExecutingAssembly().GetName().Name;
this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK, package));
}
}
}

View File

@ -0,0 +1,65 @@
/*jslint indent: 2 */
/*global window, jQuery, angular, cordova */
"use strict";
// Returns a jQuery or AngularJS deferred object, or pass a success and fail callbacks if you don't want to use jQuery or AngularJS
var getPromisedCordovaExec = function (command, success, fail) {
var toReturn, deferred, injector, $q;
if (success === undefined) {
if (window.jQuery) {
deferred = jQuery.Deferred();
success = deferred.resolve;
fail = deferred.reject;
toReturn = deferred;
} else if (window.angular) {
injector = angular.injector(["ng"]);
$q = injector.get("$q");
deferred = $q.defer();
success = deferred.resolve;
fail = deferred.reject;
toReturn = deferred.promise;
} else if (window.when && window.when.promise) {
deferred = when.defer();
success = deferred.resolve;
fail = deferred.reject;
toReturn = deferred.promise;
} else if (window.Promise) {
toReturn = new Promise(function(c, e) {
success = c;
fail = e;
});
} else if (window.WinJS && window.WinJS.Promise) {
toReturn = new WinJS.Promise(function(c, e) {
success = c;
fail = e;
});
} else {
return console.error('AppVersion either needs a success callback, or jQuery/AngularJS/Promise/WinJS.Promise defined for using promises');
}
}
// 5th param is NOT optional. must be at least empty array
cordova.exec(success, fail, "AppVersion", command, []);
return toReturn;
};
var getAppVersion = function (success, fail) {
return getPromisedCordovaExec('getVersionNumber', success, fail);
};
getAppVersion.getAppName = function (success, fail) {
return getPromisedCordovaExec('getAppName', success, fail);
};
getAppVersion.getPackageName = function (success, fail) {
return getPromisedCordovaExec('getPackageName', success, fail);
};
getAppVersion.getVersionNumber = function (success, fail) {
return getPromisedCordovaExec('getVersionNumber', success, fail);
};
getAppVersion.getVersionCode = function (success, fail) {
return getPromisedCordovaExec('getVersionCode', success, fail);
};
module.exports = getAppVersion;

View File

@ -0,0 +1,18 @@
module.exports = {
getVersionNumber: function( success, fail ) {
if( !blackberry || !blackberry.app || !blackberry.app.version ) {
if( fail ) {
return fail();
} else {
return "";
}
}
if( success ) {
return success( blackberry.app.version );
}
return blackberry.app.version;
}
};
require("cordova/exec/proxy").add("AppVersion", module.exports);

View File

@ -0,0 +1,64 @@
Backbutton plugin for Cordova / PhoneGap
======================================================
This Plugin put app in background for android Devices.
## Usage
Example Usage:
```js
navigator.Backbutton.goHome(function() {
console.log('success')
}, function() {
console.log('fail')
});
```
Or just jump to previous app if possible:
```js
navigator.Backbutton.goBack(function() {
console.log('success')
}, function() {
console.log('fail')
});
```
## Installation
for Cordova >= 3.0.0
phonegap local plugin add https://github.com/mohamed-salah/phonegap-backbutton-plugin.git
cordova plugin add https://github.com/mohamed-salah/phonegap-backbutton-plugin.git
for Cordova >= 5.0.0
cordova plugin add cordova-plugin-backbutton
This has been successfully tested on Cordova 3.0 to 3.1.
## MIT Licence
Copyright 2013 Monday Consulting GmbH
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,22 @@
{
"name": "cordova-plugin-backbutton",
"version": "0.3.0",
"description": "Cordova Backbutton Plugin",
"cordova": {
"id": "cordova-plugin-backbutton",
"platforms": [
"android"
]
},
"repository": {
"type": "git",
"url": "https://github.com/mohamed-salah/phonegap-backbutton-plugin"
},
"keywords": [
"cordova",
"backbutton",
"ecosystem:cordova",
"cordova-android"
],
"license": "MIT"
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-plugin-backbutton"
version="0.3.0">
<name>Backbutton</name>
<description>Cordova Backbutton Plugin</description>
<license>Apache 2.0</license>
<keywords>cordova,Backbutton</keywords>
<js-module src="www/Backbutton.js" name="Backbutton">
<clobbers target="navigator.Backbutton" />
</js-module>
<!-- android -->
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<feature name="BackbuttonPlugin">
<param name="android-package" value="com.badrit.Backbutton.BackbuttonPlugin"/>
</feature>
</config-file>
<source-file src="src/android/BackbuttonPlugin.java" target-dir="src/com/badrit/Backbutton/" />
</platform>
</plugin>

View File

@ -0,0 +1,59 @@
package com.badrit.Backbutton;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.Intent;
import android.util.Log;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
public class BackbuttonPlugin extends CordovaPlugin {
private static final String LOG_TAG = "HomePlugin";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if ("goHome".equals(action)) {
try {
Intent i = new Intent(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_HOME);
this.cordova.getActivity().startActivity(i);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception occurred: ".concat(e.getMessage()));
return false;
}
callbackContext.success();
return true;
}
if ("goBack".equals(action)) {
try {
// try to send it back and back to previous app
boolean sentAppToBackground = this.cordova.getActivity().moveTaskToBack(true);
// if not possible jump to home
if(!sentAppToBackground){
Intent i = new Intent(Intent.ACTION_MAIN);
i.setAction(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_HOME);
this.cordova.getActivity().startActivity(i);
}
} catch (Exception e) {
Log.e(LOG_TAG, "Exception occurred: ".concat(e.getMessage()));
return false;
}
callbackContext.success();
return true;
}
Log.e(LOG_TAG, "Called invalid action: "+action);
return false;
}
}

View File

@ -0,0 +1,13 @@
var Backbutton = {
goHome: function(successCallback, failureCallback) {
cordova.exec(successCallback, failureCallback, 'BackbuttonPlugin',
'goHome', []);
},
goBack: function(successCallback, failureCallback) {
cordova.exec(successCallback, failureCallback, 'BackbuttonPlugin',
'goBack', []);
}
};
module.exports = Backbutton;

View File

@ -0,0 +1,35 @@
<!--
#
# 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-compat
------------------------
This repo is for remaining backwards compatible with previous versions of Cordova.
## Deprecated
> This plugin is no longer being worked on as the functionality provided by this plugin is now included in cordova-android 6.3.0. You should upgrade your application to use version 1.2.0 of this plugin. It will detect whether or not the plugin is required based on the version of cordova-android your app uses.
## USAGE
Your plugin can depend on this plugin and use it to handle the new run time permissions Android 6.0.0 (cordova-android 5.0.0) introduced.
View [this commit](https://github.com/apache/cordova-plugin-camera/commit/a9c18710f23e86f5b7f8918dfab7c87a85064870) to see how to depend on `cordova-plugin-compat`. View [this file](https://github.com/apache/cordova-plugin-camera/blob/master/src/android/CameraLauncher.java) to see how `PermissionHelper` is being used to request and store permissions. Read more about Android permissions at http://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html#android-permissions.

View File

@ -0,0 +1,33 @@
<!--
#
# 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.2.0 (Sep 18, 2017)
* [CB-12730](https://issues.apache.org/jira/browse/CB-12730) Integrate this plugin into `cordova-android@6.3.0` and deprecate this plugin as it is no longer needed.
* [CB-12730](https://issues.apache.org/jira/browse/CB-12730) Prevent plugin from installing with `cordova-android >= 6.3.0`
### 1.1.0 (Nov 02, 2016)
* [CB-11625](https://issues.apache.org/jira/browse/CB-11625) Adding the `BuildConfig` fetching code as a backup to using a new preference
* Add github pull request template
### 1.0.0 (Apr 15, 2016)
* Initial release
* Moved `PermissionHelper.java` into `src`

View File

@ -0,0 +1,65 @@
{
"_from": "cordova-plugin-compat@1.2.0",
"_id": "cordova-plugin-compat@1.2.0",
"_inBundle": false,
"_integrity": "sha1-C8ZXVyduvZIMASzpIOJ0F3V2Nz4=",
"_location": "/cordova-plugin-compat",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "cordova-plugin-compat@1.2.0",
"name": "cordova-plugin-compat",
"escapedName": "cordova-plugin-compat",
"rawSpec": "1.2.0",
"saveSpec": null,
"fetchSpec": "1.2.0"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/cordova-plugin-compat/-/cordova-plugin-compat-1.2.0.tgz",
"_shasum": "0bc65757276ebd920c012ce920e274177576373e",
"_spec": "cordova-plugin-compat@1.2.0",
"_where": "/Users/martin/workspace/padlock/cordova",
"author": {
"name": "Apache Software Foundation"
},
"bugs": {
"url": "https://github.com/apache/cordova-plugin-compat/issues"
},
"bundleDependencies": false,
"cordova": {
"id": "cordova-plugin-compat",
"platforms": [
"android"
]
},
"deprecated": false,
"description": "[DEPRECATED] This repo is for remaining backwards compatible with previous versions of Cordova.",
"engines": {
"cordovaDependencies": {
"<1.2.0": {
"cordova": ">=5.0.0"
},
">=1.2.0": {
"cordova": ">=5.0.0",
"cordova-android": "<6.3.0"
}
}
},
"homepage": "http://github.com/apache/cordova-plugin-compat#readme",
"keywords": [
"ecosystem:cordova",
"ecosystem:phonegap",
"cordova-android"
],
"license": "Apache-2.0",
"name": "cordova-plugin-compat",
"repository": {
"type": "git",
"url": "git://github.com/apache/cordova-plugin-compat.git"
},
"version": "1.2.0"
}

View File

@ -0,0 +1,37 @@
<?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-compat" version="1.2.0">
<name>Compat</name>
<description>[DEPRECATED] Cordova Compatibility Plugin</description>
<license>Apache 2.0</license>
<keywords>cordova,compat</keywords>
<repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-compat.git</repo>
<engines>
<engine name="cordova" version=">=5.0.0"/>
<engine name="cordova-android" version="
<6.3.0"/>
</engines>
<!-- android -->
<platform name="android">
<source-file src="src/android/PermissionHelper.java" target-dir="src/org/apache/cordova" />
<source-file src="src/android/BuildHelper.java" target-dir="src/org/apache/cordova" />
</platform>
</plugin>

View File

@ -0,0 +1,70 @@
/*
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;
/*
* This is a utility class that allows us to get the BuildConfig variable, which is required
* for the use of different providers. This is not guaranteed to work, and it's better for this
* to be set in the build step in config.xml
*
*/
import android.app.Activity;
import android.content.Context;
import java.lang.reflect.Field;
public class BuildHelper {
private static String TAG="BuildHelper";
/*
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
* that read the Build Configuration.
*
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
* StackOverflow. This is annoying as hell! However, this method does not work with
* ProGuard, and you should use the config.xml to define the application_id
*
*/
public static Object getBuildConfigValue(Context ctx, String key)
{
try
{
Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
Field field = clazz.getField(key);
return field.get(null);
} catch (ClassNotFoundException e) {
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
e.printStackTrace();
} catch (NoSuchFieldException e) {
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
} catch (IllegalAccessException e) {
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,138 @@
/*
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;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.LOG;
import android.content.pm.PackageManager;
/**
* This class provides reflective methods for permission requesting and checking so that plugins
* written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
*/
public class PermissionHelper {
private static final String LOG_TAG = "CordovaPermissionHelper";
/**
* Requests a "dangerous" permission for the application at runtime. This is a helper method
* alternative to cordovaInterface.requestPermission() that does not require the project to be
* built with cordova-android 5.0.0+
*
* @param plugin The plugin the permission is being requested for
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
* along with the result of the permission request
* @param permission The permission to be requested
*/
public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
}
/**
* Requests "dangerous" permissions for the application at runtime. This is a helper method
* alternative to cordovaInterface.requestPermissions() that does not require the project to be
* built with cordova-android 5.0.0+
*
* @param plugin The plugin the permissions are being requested for
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
* along with the result of the permissions request
* @param permissions The permissions to be requested
*/
public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
try {
Method requestPermission = CordovaInterface.class.getDeclaredMethod(
"requestPermissions", CordovaPlugin.class, int.class, String[].class);
// If there is no exception, then this is cordova-android 5.0.0+
requestPermission.invoke(plugin.cordova, plugin, requestCode, permissions);
} catch (NoSuchMethodException noSuchMethodException) {
// cordova-android version is less than 5.0.0, so permission is implicitly granted
LOG.d(LOG_TAG, "No need to request permissions " + Arrays.toString(permissions));
// Notify the plugin that all were granted by using more reflection
deliverPermissionResult(plugin, requestCode, permissions);
} catch (IllegalAccessException illegalAccessException) {
// Should never be caught; this is a public method
LOG.e(LOG_TAG, "IllegalAccessException when requesting permissions " + Arrays.toString(permissions), illegalAccessException);
} catch(InvocationTargetException invocationTargetException) {
// This method does not throw any exceptions, so this should never be caught
LOG.e(LOG_TAG, "invocationTargetException when requesting permissions " + Arrays.toString(permissions), invocationTargetException);
}
}
/**
* Checks at runtime to see if the application has been granted a permission. This is a helper
* method alternative to cordovaInterface.hasPermission() that does not require the project to
* be built with cordova-android 5.0.0+
*
* @param plugin The plugin the permission is being checked against
* @param permission The permission to be checked
*
* @return True if the permission has already been granted and false otherwise
*/
public static boolean hasPermission(CordovaPlugin plugin, String permission) {
try {
Method hasPermission = CordovaInterface.class.getDeclaredMethod("hasPermission", String.class);
// If there is no exception, then this is cordova-android 5.0.0+
return (Boolean) hasPermission.invoke(plugin.cordova, permission);
} catch (NoSuchMethodException noSuchMethodException) {
// cordova-android version is less than 5.0.0, so permission is implicitly granted
LOG.d(LOG_TAG, "No need to check for permission " + permission);
return true;
} catch (IllegalAccessException illegalAccessException) {
// Should never be caught; this is a public method
LOG.e(LOG_TAG, "IllegalAccessException when checking permission " + permission, illegalAccessException);
} catch(InvocationTargetException invocationTargetException) {
// This method does not throw any exceptions, so this should never be caught
LOG.e(LOG_TAG, "invocationTargetException when checking permission " + permission, invocationTargetException);
}
return false;
}
private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
// Generate the request results
int[] requestResults = new int[permissions.length];
Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
try {
Method onRequestPermissionResult = CordovaPlugin.class.getDeclaredMethod(
"onRequestPermissionResult", int.class, String[].class, int[].class);
onRequestPermissionResult.invoke(plugin, requestCode, permissions, requestResults);
} catch (NoSuchMethodException noSuchMethodException) {
// Should never be caught since the plugin must be written for cordova-android 5.0.0+ if it
// made it to this point
LOG.e(LOG_TAG, "NoSuchMethodException when delivering permissions results", noSuchMethodException);
} catch (IllegalAccessException illegalAccessException) {
// Should never be caught; this is a public method
LOG.e(LOG_TAG, "IllegalAccessException when delivering permissions results", illegalAccessException);
} catch(InvocationTargetException invocationTargetException) {
// This method may throw a JSONException. We are just duplicating cordova-android's
// exception handling behavior here; all it does is log the exception in CordovaActivity,
// print the stacktrace, and ignore it
LOG.e(LOG_TAG, "InvocationTargetException when delivering permissions results", invocationTargetException);
}
}
}

View File

@ -0,0 +1,235 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed 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.
/************************************************/
This product bundles CordovaXWalkCoreExtensionBridge.java as well
as the XWalk.pak and the Crosswalk JSApi which is available under a
"3-clause BSD" license. For details, see below:
Copyright (c) 2013 Intel Corporation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,8 @@
Apache Cordova
Copyright 2014 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org)
This software includes software developed at Intel Corporation.
Copyright 2014 Intel Corporation

View File

@ -0,0 +1,30 @@
### Directions for Non-CLI Android-Only cordova project
* Pull down the Cordova Android
```
$ git clone https://github.com/apache/cordova-android.git
```
* Generate a project, e.g creating HelloWorld
```
$ /path/to/cordova-android/bin/create hello com.example.hello HelloWorld
```
* Navigate to the project folder
```
$ cd hello
```
* Install Crosswalk engine plugin by plugman (version >= 0.22.17)
```
$ plugman install --platform android --plugin https://github.com/MobileChromeApps/cordova-crosswalk-engine.git --project .
```
* Build
```
$ ./cordova/build
```
The build script will automatically fetch the Crosswalk WebView libraries from Crosswalk project download site (https://download.01.org/crosswalk/releases/crosswalk/android/) and build for both X86 and ARM architectures.
For example, building HelloWorld generates:
```
/path/to/hello/build/outputs/apk/hello-x86-debug.apk
/path/to/hello/build/outputs/apk/hello-armv7-debug.apk
```

View File

@ -0,0 +1,171 @@
# cordova-plugin-crosswalk-webview
Makes your Cordova application use the [Crosswalk WebView](https://crosswalk-project.org/)
instead of the System WebView. Requires cordova-android 4.0 or greater.
### Benefits
* WebView doesn't change depending on Android version
* Capabilities: such as WebRTC, WebAudio, Web Components
* Performance improvements (compared to older system webviews)
### Drawbacks
* Increased memory footprint
* An overhead of ~30MB (as reported by the RSS column of ps)
* Increased APK size (about 17MB)
* Increased size on disk when installed (about 50MB)
* Crosswalk WebView stores data (IndexedDB, LocalStorage, etc) separately from System WebView
* You'll need to manually migrate local data when switching between the two (note: this is fixed in Crosswalk 15)
### Install
The following directions are for cordova-cli (most people). Alternatively you can use the [Android platform scripts workflow](PlatformScriptsWorkflow.md).
* Open an existing cordova project, with cordova-android 4.0.0+, and using the latest CLI. Crosswalk variables can be configured as an option when installing the plugin
* Add this plugin
```
$ cordova plugin add cordova-plugin-crosswalk-webview
```
* Build
```
$ cordova build android
```
The build script will automatically fetch the Crosswalk WebView libraries from Crosswalk project download site (https://download.01.org/crosswalk/releases/crosswalk/android/maven2/) and build for both X86 and ARM architectures.
For example, building android with Crosswalk generates:
```
/path/to/hello/platforms/android/build/outputs/apk/hello-x86-debug.apk
/path/to/hello/platforms/android/build/outputs/apk/hello-armv7-debug.apk
```
Note that you might have to run `cordova clean` before building, if you previously built the app without cordova-plugin-crosswalk-webview. Also, manually uninstall the app from the device/emulator before attempting to install the crosswalk-enabled version.
Also note that it is also possible to publish a multi-APK application on the Play Store that uses Crosswalk for Pre-L devices, and the (updatable) system webview for L+:
To build Crosswalk-enabled apks, add this plugin and run:
$ cordova build --release
To build System-webview apk, remove this plugin and run:
$ cordova build --release -- --minSdkVersion=21
### Configure
You can try out a different Crosswalk version by specifying certain variables while installing the plugin, or by changing the value of `xwalkVersion` in your `config.xml` after installing the plugin. Some examples:
<!-- These are all equivalent -->
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_core_library:14+"
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="xwalk_core_library:14+"
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="14+"
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="14"
<preference name="xwalkVersion" value="org.xwalk:xwalk_core_library:14+" />
<preference name="xwalkVersion" value="xwalk_core_library:14+" />
<preference name="xwalkVersion" value="14+" />
<preference name="xwalkVersion" value="14" />
You can also use a Crosswalk beta version. Some examples:
<!-- These are all equivalent -->
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_core_library_beta:14+"
<preference name="xwalkVersion" value="org.xwalk:xwalk_core_library_beta:14+" />
You can set [command-line flags](http://peter.sh/experiments/chromium-command-line-switches/) as well:
<!-- This is the default -->
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_COMMANDLINE="--disable-pull-to-refresh-effect"
<preference name="xwalkCommandLine" value="--disable-pull-to-refresh-effect" />
You can use the Crosswalk [shared mode](https://crosswalk-project.org/documentation/shared_mode.html) which allows multiple Crosswalk applications to share one Crosswalk runtime downloaded from the Play Store.
<!-- These are all equivalent -->
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_MODE="shared"
<preference name="xwalkMode" value="shared" />
You can also use a Crosswalk beta version on shared mode, e.g.:
<!-- Using a Crosswalk shared mode beta version -->
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_VERSION="org.xwalk:xwalk_shared_library_beta:14+"
You can use the Crosswalk [lite mode](https://crosswalk-project.org/documentation/crosswalk_lite.html) which is the Crosswalk runtime designed to be as small as possible by removing less common libraries and features and compressing the APK.
<!-- These are all equivalent -->
cordova plugin add cordova-plugin-crosswalk-webview --variable XWALK_MODE="lite"
<preference name="xwalkMode" value="lite" />
You can set background color with the preference of BackgroundColor.
<!-- Set red background color -->
<preference name="BackgroundColor" value="0xFFFF0000" />
You can also set user agent with the preference of xwalkUserAgent.
<preference name="xwalkUserAgent" value="customer UA" />
### Release Notes
#### 2.4.0
* Keep compatibility with cordova-android 7.0 project structure
#### 2.3.0 (January 21, 2017)
* Uses the latest Crosswalk 23 stable version by default
#### 2.2.0 (November 4, 2016)
* Uses the latest Crosswalk 22 stable version by default
* Keep compatible for Cordova-android 6.0 with evaluating Javascript bridge
* This version requires cordova-android 6.0.0 or newer
#### 2.1.0 (September 9, 2016)
* Uses the latest Crosswalk 21 stable version by default
#### 2.0.0 (August 17, 2016)
* Uses the latest Crosswalk 20 stable version by default
* Discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20
#### 1.8.0 (June 30, 2016)
* Uses the latest Crosswalk 19 stable version by default
#### 1.7.0 (May 4, 2016)
* Uses the latest Crosswalk 18 stable version by default
* Support to use [Crosswalk Lite](https://crosswalk-project.org/documentation/crosswalk_lite.html), It's possible to specify lite value with the variable of XWALK_MODE at install plugin time.
* [Cordova screenshot plugin](https://github.com/gitawego/cordova-screenshot.git) can capture the visible content of web page with Crosswalk library.
* Doesn't work with Crosswalk 17 and earlier
#### 1.6.0 (March 11, 2016)
* Uses the latest Crosswalk 17 stable version by default
* Support to [package apps for 64-bit devices](https://crosswalk-project.org/documentation/android/android_64bit.html), it's possible to specify 64-bit targets using the `--xwalk64bit` option in the build command:
cordova build android --xwalk64bit
#### 1.5.0 (January 18, 2016)
* Uses the latest Crosswalk 16 stable version by default
* The message of xwalk's ready can be listened
#### 1.4.0 (November 5, 2015)
* Uses the latest Crosswalk 15 stable version by default
* Support User Agent and Background Color configuration preferences
* Compatible with the newest Cordova version 5.3.4
#### 1.3.0 (August 28, 2015)
* Crosswalk variables can be configured as an option via CLI
* Support for [Crosswalk's shared mode](https://crosswalk-project.org/documentation/shared_mode.html) via the XWALK_MODE install variable or xwalkMode preference
* Uses the latest Crosswalk 14 stable version by default
* The ANIMATABLE_XWALK_VIEW preference is false by default
* Doesn't work with Crosswalk 14.43.343.17 and earlier
#### 1.2.0 (April 22, 2015)
* Made Crosswalk command-line configurable via `<preference name="xwalkCommandLine" value="..." />`
* Disabled pull-down-to-refresh by default
#### 1.1.0 (April 21, 2015)
* Based on Crosswalk v13
* Made Crosswalk version configurable via `<preference name="xwalkVersion" value="..." />`
#### 1.0.0 (Mar 25, 2015)
* Initial release
* Based on Crosswalk v11

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
module.exports = function(context) {
/** @external */
var deferral = context.requireCordovaModule('q').defer(),
UpdateConfig = require('./../update_config.js'),
updateConfig = new UpdateConfig(context);
/** Main method */
var main = function() {
// Remove the xwalk variables
updateConfig.afterBuild64bit();
deferral.resolve();
};
main();
return deferral.promise;
};

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
module.exports = function(context) {
/** @external */
var deferral = context.requireCordovaModule('q').defer(),
UpdateConfig = require('./../update_config.js'),
updateConfig = new UpdateConfig(context);
/** Main method */
var main = function() {
// Add xwalk preference to config.xml
updateConfig.addPreferences();
deferral.resolve();
};
main();
return deferral.promise;
};

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
module.exports = function(context) {
/** @external */
var deferral = context.requireCordovaModule('q').defer(),
UpdateConfig = require('./../update_config.js'),
updateConfig = new UpdateConfig(context);
/** Main method */
var main = function() {
// Remove the xwalk variables
updateConfig.beforeBuild64bit();
deferral.resolve();
};
main();
return deferral.promise;
};

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
module.exports = function(context) {
/** @external */
var deferral = context.requireCordovaModule('q').defer(),
UpdateConfig = require('./../update_config.js'),
updateConfig = new UpdateConfig(context);
/** Main method */
var main = function() {
// Remove the xwalk variables
updateConfig.removePreferences();
deferral.resolve();
};
main();
return deferral.promise;
};

View File

@ -0,0 +1,211 @@
#!/usr/bin/env node
module.exports = function(context) {
var ConfigParser, XmlHelpers;
try {
// cordova-lib >= 5.3.4 doesn't contain ConfigParser and xml-helpers anymore
ConfigParser = context.requireCordovaModule("cordova-common").ConfigParser;
XmlHelpers = context.requireCordovaModule("cordova-common").xmlHelpers;
} catch (e) {
ConfigParser = context.requireCordovaModule("cordova-lib/src/configparser/ConfigParser");
XmlHelpers = context.requireCordovaModule("cordova-lib/src/util/xml-helpers");
}
/** @external */
var fs = context.requireCordovaModule('fs'),
path = context.requireCordovaModule('path'),
et = context.requireCordovaModule('elementtree');
/** @defaults */
var xwalkVariables = {},
argumentsString = context.cmdLine,
pluginConfigurationFile = path.join(context.opts.plugin.dir, 'plugin.xml'),
androidPlatformDir = path.join(context.opts.projectRoot,
'platforms', 'android'),
projectConfigurationFile = path.join(context.opts.projectRoot,
'config.xml'),
platformConfigurationFile,
projectManifestFile = path.join(androidPlatformDir,
'AndroidManifest.xml'),
xwalk64bit = "xwalk64bit",
xwalkLiteVersion = "",
specificVersion = false;
var oldConfigXMLLocation = path.join(androidPlatformDir, 'res', 'xml', 'config.xml');
var newConfigXMLLocation = path.join(androidPlatformDir, 'app', 'src', 'main', 'res', 'xml', 'config.xml');
if (fs.existsSync(newConfigXMLLocation)) {
// cordova-android >= 7.0.0
platformConfigurationFile = newConfigXMLLocation;
} else {
// cordova-android < 7.0.0
platformConfigurationFile = oldConfigXMLLocation;
}
/** Init */
var CordovaConfig = new ConfigParser(platformConfigurationFile);
var addPermission = function() {
var projectManifestXmlRoot = XmlHelpers.parseElementtreeSync(projectManifestFile);
var child = et.XML('<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />');
XmlHelpers.graftXML(projectManifestXmlRoot, [child], '/manifest');
fs.writeFileSync(projectManifestFile, projectManifestXmlRoot.write({indent: 4}), 'utf-8');
}
var removePermission = function() {
var projectManifestXmlRoot = XmlHelpers.parseElementtreeSync(projectManifestFile);
var child = et.XML('<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />');
XmlHelpers.pruneXML(projectManifestXmlRoot, [child], '/manifest');
fs.writeFileSync(projectManifestFile, projectManifestXmlRoot.write({indent: 4}), 'utf-8');
}
var defaultPreferences = function() {
var pluginPreferences = {};
var pluginXmlRoot = XmlHelpers.parseElementtreeSync(pluginConfigurationFile),
tagName = "preference",
containerName = "config-file",
targetPlatform = 'android',
targetPlatformTag = pluginXmlRoot.find('./platform[@name="' + targetPlatform + '"]');
var tagsInRoot = pluginXmlRoot.findall(tagName) || [],
tagsInPlatform = targetPlatformTag ? targetPlatformTag.findall(tagName) : [],
tagsInContainer = targetPlatformTag ? targetPlatformTag.findall(containerName) : [],
tagsList = tagsInRoot.concat(tagsInContainer);
// Parses <preference> tags within <config-file>-blocks
tagsList.map(function(prefTag) {
prefTag.getchildren().forEach(function(element) {
if ((element.tag == 'preference') && (element.attrib['name']) && element.attrib['default']) {
// Don't add xwalkLiteVersion in the app/config.xml
if (element.attrib['name'] == "xwalkLiteVersion") {
xwalkLiteVersion = element.attrib['default'];
} else {
pluginPreferences[element.attrib['name']] = element.attrib['default'];
}
}
});
});
return pluginPreferences;
}
/** The style of name align with config.xml */
var setConfigPreference = function(name, value) {
var trimName = name.replace('_', '');
for (var localName in xwalkVariables) {
if (localName.toUpperCase() == trimName.toUpperCase()) {
xwalkVariables[localName] = value;
if (localName == 'xwalkVersion') {
specificVersion = true;
}
}
}
}
/** Pase the cli command to get the specific preference*/
var parseCliPreference = function() {
var commandlineVariablesList = argumentsString.split('--variable');
if (commandlineVariablesList) {
commandlineVariablesList.forEach(function(element) {
element = element.trim();
if(element && element.indexOf('XWALK') == 0) {
var preference = element.split('=');
if (preference && preference.length == 2) {
setConfigPreference(preference[0], preference[1]);
}
}
});
}
}
/** Add preference */
this.addPreferences = function() {
// Pick the xwalk variables with the cli preferences
// parseCliPreference();
// Add the permission of writing external storage when using shared mode
if (CordovaConfig.getGlobalPreference('xwalkMode') == 'shared') {
addPermission();
}
// Configure the final value in the config.xml
// var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
// var preferenceUpdated = false;
// for (var name in xwalkVariables) {
// var child = configXmlRoot.find('./preference[@name="' + name + '"]');
// if(!child) {
// preferenceUpdated = true;
// child = et.XML('<preference name="' + name + '" value="' + xwalkVariables[name] + '" />');
// XmlHelpers.graftXML(configXmlRoot, [child], '/*');
// }
// }
// if(preferenceUpdated) {
// fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
// }
}
/** Remove preference*/
this.removePreferences = function() {
if (CordovaConfig.getGlobalPreference('xwalkMode') == 'shared') {
// Add the permission of write_external_storage in shared mode
removePermission();
}
// var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
// for (var name in xwalkVariables) {
// var child = configXmlRoot.find('./preference[@name="' + name + '"]');
// if (child) {
// XmlHelpers.pruneXML(configXmlRoot, [child], '/*');
// }
// }
// fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
}
var build64bit = function() {
var build64bit = false;
var commandlineVariablesList = argumentsString.split('--');
if (commandlineVariablesList) {
commandlineVariablesList.forEach(function(element) {
element = element.trim();
if(element && element.indexOf(xwalk64bit) == 0) {
build64bit = true;
}
});
}
return build64bit;
}
this.beforeBuild64bit = function() {
if(build64bit()) {
var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
var child = configXmlRoot.find('./preference[@name="' + xwalk64bit + '"]');
if(!child) {
child = et.XML('<preference name="' + xwalk64bit + '" value="' + xwalk64bit + '" />');
XmlHelpers.graftXML(configXmlRoot, [child], '/*');
fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
}
}
}
this.afterBuild64bit = function() {
if(build64bit()) {
var configXmlRoot = XmlHelpers.parseElementtreeSync(projectConfigurationFile);
var child = configXmlRoot.find('./preference[@name="' + xwalk64bit + '"]');
if (child) {
XmlHelpers.pruneXML(configXmlRoot, [child], '/*');
fs.writeFileSync(projectConfigurationFile, configXmlRoot.write({indent: 4}), 'utf-8');
}
}
console.log("Crosswalk info:");
console.log(" After much discussion and analysis of the market,");
console.log(" we have decided to discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20,");
console.log(" so the minSdkVersion of Cordova project is configured to 16 by default. \n");
}
xwalkVariables = defaultPreferences();
};

View File

@ -0,0 +1,75 @@
{
"_from": "cordova-plugin-crosswalk-webview@2.4.0",
"_id": "cordova-plugin-crosswalk-webview@2.4.0",
"_inBundle": false,
"_integrity": "sha512-XoUBv7RHvOWMKtI5mwSUzZDgStTaFfRlmZeC1cgicel6cX88IFMr+qqECix5V/xlutiLgEv34rxP4jR3Br19Cg==",
"_location": "/cordova-plugin-crosswalk-webview",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "cordova-plugin-crosswalk-webview@2.4.0",
"name": "cordova-plugin-crosswalk-webview",
"escapedName": "cordova-plugin-crosswalk-webview",
"rawSpec": "2.4.0",
"saveSpec": null,
"fetchSpec": "2.4.0"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/cordova-plugin-crosswalk-webview/-/cordova-plugin-crosswalk-webview-2.4.0.tgz",
"_shasum": "90717bc4315c92d3182782fcba6b7ad89e532311",
"_spec": "cordova-plugin-crosswalk-webview@2.4.0",
"_where": "/Users/martin/workspace/padlock/cordova",
"author": "",
"bugs": {
"url": "https://crosswalk-project.org/jira"
},
"bundleDependencies": false,
"cordova": {
"id": "cordova-plugin-crosswalk-webview",
"platforms": [
"android"
]
},
"deprecated": false,
"description": "Changes the default WebView to CrossWalk",
"engines": {
"cordovaDependencies": {
"2.0.0": {
"cordova": ">=5.2.0",
"cordova-android": "4 - 5"
},
"2.1.0": {
"cordova": ">=5.2.0",
"cordova-android": "4 - 5"
},
"2.2.0": {
"cordova": ">=5.2.0",
"cordova-android": ">=6"
},
"3.0.0": {
"cordova": ">100"
}
}
},
"homepage": "https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview",
"keywords": [
"cordova",
"chromium",
"crosswalk",
"webview",
"engine",
"ecosystem:cordova",
"cordova-android"
],
"license": "Apache 2.0",
"name": "cordova-plugin-crosswalk-webview",
"repository": {
"type": "git",
"url": "git+https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview.git"
},
"version": "2.4.0"
}

View File

@ -0,0 +1,90 @@
/*
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.crosswalk.engine;
import org.apache.cordova.ICordovaClientCertRequest;
import org.xwalk.core.ClientCertRequest;
import java.security.cert.X509Certificate;
import java.security.Principal;
import java.security.PrivateKey;
import java.util.Arrays;
public class XWalkCordovaClientCertRequest implements ICordovaClientCertRequest {
private final ClientCertRequest request;
public XWalkCordovaClientCertRequest(ClientCertRequest request) {
this.request = request;
}
/**
* Cancel this request
*/
public void cancel() {
request.cancel();
}
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost() {
return request.getHost();
}
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes() {
return request.getKeyTypes();
}
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort() {
return request.getPort();
}
/*
* Returns the acceptable certificate issuers for the certificate matching the private
* key (can be null).
*/
public Principal[] getPrincipals() {
return request.getPrincipals();
}
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore() {
request.ignore();
}
/*
* Proceed with the specified private key and client certificate chain. Remember the user's
* positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain) {
request.proceed(privateKey, Arrays.asList(chain));
}
}

View File

@ -0,0 +1,53 @@
/*
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.crosswalk.engine;
import org.apache.cordova.ICordovaCookieManager;
import org.xwalk.core.XWalkCookieManager;
class XWalkCordovaCookieManager implements ICordovaCookieManager {
protected XWalkCookieManager cookieManager = null;
public XWalkCordovaCookieManager() {
cookieManager = new XWalkCookieManager();
}
public void setCookiesEnabled(boolean accept) {
cookieManager.setAcceptCookie(accept);
}
public void setCookie(final String url, final String value) {
cookieManager.setCookie(url, value);
}
public String getCookie(final String url) {
return cookieManager.getCookie(url);
}
public void clearCookies() {
cookieManager.removeAllCookie();
}
public void flush() {
cookieManager.flushCookieStore();
}
};

View File

@ -0,0 +1,52 @@
/*
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.crosswalk.engine;
import org.apache.cordova.ICordovaHttpAuthHandler;
import org.xwalk.core.XWalkHttpAuthHandler;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public class XWalkCordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
private final XWalkHttpAuthHandler handler;
public XWalkCordovaHttpAuthHandler(XWalkHttpAuthHandler handler) {
this.handler = handler;
}
/**
* Instructs the XWalkView to cancel the authentication request.
*/
public void cancel() {
handler.cancel();
}
/**
* Instructs the XWalkView to proceed with the authentication with the given credentials.
*
* @param username
* @param password
*/
public void proceed(String username, String password) {
handler.proceed(username, password);
}
}

View File

@ -0,0 +1,159 @@
/*
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.crosswalk.engine;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.net.http.SslError;
import android.webkit.ValueCallback;
import android.webkit.WebResourceResponse;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
import org.xwalk.core.ClientCertRequest;
import org.xwalk.core.XWalkHttpAuthHandler;
import org.xwalk.core.XWalkResourceClient;
import org.xwalk.core.XWalkView;
import java.io.FileNotFoundException;
import java.io.IOException;
public class XWalkCordovaResourceClient extends XWalkResourceClient {
private static final String TAG = "XWalkCordovaResourceClient";
protected XWalkWebViewEngine parentEngine;
public XWalkCordovaResourceClient(XWalkWebViewEngine parentEngine) {
super(parentEngine.webView);
this.parentEngine = parentEngine;
}
/**
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
*/
@Override
public void onReceivedLoadError(XWalkView view, int errorCode, String description,
String failingUrl) {
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
parentEngine.client.onReceivedError(errorCode, description, failingUrl);
}
@Override
public WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
try {
// Check the against the white-list.
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
CordovaResourceApi resourceApi = parentEngine.resourceApi;
Uri origUri = Uri.parse(url);
// Allow plugins to intercept WebView requests.
Uri remappedUri = resourceApi.remapUri(origUri);
if (!origUri.equals(remappedUri)) {
OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
}
// If we don't need to special-case the request, let the browser load it.
return null;
} catch (IOException e) {
if (!(e instanceof FileNotFoundException)) {
LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
}
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
}
@Override
public boolean shouldOverrideUrlLoading(XWalkView view, String url) {
return parentEngine.client.onNavigationAttempt(url);
}
/**
* Notify the host application that an SSL error occurred while loading a
* resource. The host application must call either callback.onReceiveValue(true)
* or callback.onReceiveValue(false). Note that the decision may be
* retained for use in response to future SSL errors. The default behavior
* is to pop up a dialog.
*/
@Override
public void onReceivedSslError(XWalkView view, ValueCallback<Boolean> callback, SslError error) {
final String packageName = parentEngine.cordova.getActivity().getPackageName();
final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// debug = true
callback.onReceiveValue(true);
} else {
// debug = false
callback.onReceiveValue(false);
}
} catch (PackageManager.NameNotFoundException e) {
// When it doubt, lock it out!
callback.onReceiveValue(false);
}
}
@Override
public void onReceivedHttpAuthRequest(XWalkView view, XWalkHttpAuthHandler handler,
String host, String realm) {
// Check if there is some plugin which can resolve this auth challenge
PluginManager pluginManager = parentEngine.pluginManager;
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(
parentEngine.parentWebView,
new XWalkCordovaHttpAuthHandler(handler), host, realm)) {
parentEngine.client.clearLoadTimeoutTimer();
return;
}
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
@Override
public void onReceivedClientCertRequest(XWalkView view, ClientCertRequest request) {
// Check if there is some plugin which can resolve this certificate request
PluginManager pluginManager = parentEngine.pluginManager;
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(
parentEngine.parentWebView, new XWalkCordovaClientCertRequest(request))) {
parentEngine.client.clearLoadTimeoutTimer();
return;
}
super.onReceivedClientCertRequest(view, request);
}
}

View File

@ -0,0 +1,208 @@
/*
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.crosswalk.engine;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.webkit.ValueCallback;
import org.apache.cordova.CordovaDialogsHelper;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.LOG;
import org.xwalk.core.XWalkJavascriptResult;
import org.xwalk.core.XWalkUIClient;
import org.xwalk.core.XWalkView;
import org.crosswalk.engine.XWalkWebViewEngine.PermissionRequestListener;
public class XWalkCordovaUiClient extends XWalkUIClient {
private static final String TAG = "XWalkCordovaUiClient";
protected final CordovaDialogsHelper dialogsHelper;
protected final XWalkWebViewEngine parentEngine;
private XWalkFileChooser mFileChooser;
private CordovaPlugin mFileChooserResultPlugin;
private static final int FILECHOOSER_RESULTCODE = 5173;
public XWalkCordovaUiClient(XWalkWebViewEngine parentEngine) {
super(parentEngine.webView);
this.parentEngine = parentEngine;
dialogsHelper = new CordovaDialogsHelper(parentEngine.webView.getContext());
}
@Override
public boolean onJavascriptModalDialog(XWalkView view, JavascriptMessageType type, String url,
String message, String defaultValue, XWalkJavascriptResult result) {
switch (type) {
case JAVASCRIPT_ALERT:
return onJsAlert(view, url, message, result);
case JAVASCRIPT_CONFIRM:
return onJsConfirm(view, url, message, result);
case JAVASCRIPT_PROMPT:
return onJsPrompt(view, url, message, defaultValue, result);
case JAVASCRIPT_BEFOREUNLOAD:
// Reuse onJsConfirm to show the dialog.
return onJsConfirm(view, url, message, result);
default:
break;
}
assert (false);
return false;
}
/**
* Tell the client to display a javascript alert dialog.
*/
public boolean onJsAlert(XWalkView view, String url, String message,
final XWalkJavascriptResult result) {
dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirm();
} else {
result.cancel();
}
}
});
return true;
}
/**
* Tell the client to display a confirm dialog to the user.
*/
public boolean onJsConfirm(XWalkView view, String url, String message,
final XWalkJavascriptResult result) {
dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirm();
} else {
result.cancel();
}
}
});
return true;
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
* <p/>
* Since we are hacking prompts for our own purposes, we should not be using them for
* this purpose, perhaps we should hack console.log to do this instead!
*/
public boolean onJsPrompt(XWalkView view, String origin, String message, String defaultValue,
final XWalkJavascriptResult result) {
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
if (handledRet != null) {
result.confirmWithResult(handledRet);
} else {
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirmWithResult(value);
} else {
result.cancel();
}
}
});
}
return true;
}
/**
* Notify the host application that a page has started loading.
* This method is called once for each main frame load so a page with iframes or framesets will call onPageLoadStarted
* one time for the main frame. This also means that onPageLoadStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
*
* @param view The webView initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageLoadStarted(XWalkView view, String url) {
LOG.d(TAG, "onPageLoadStarted(" + url + ")");
if (view.getUrl() != null) {
// Flush stale messages.
parentEngine.client.onPageStarted(url);
parentEngine.bridge.reset();
}
}
/**
* Notify the host application that a page has stopped loading.
* This method is called only for main frame. When onPageLoadStopped() is called, the rendering picture may not be updated yet.
*
* @param view The webView initiating the callback.
* @param url The url of the page.
* @param status The load status of the webView, can be FINISHED, CANCELLED or FAILED.
*/
@Override
public void onPageLoadStopped(XWalkView view, String url, LoadStatus status) {
LOG.d(TAG, "onPageLoadStopped(" + url + ")");
if (status == LoadStatus.FINISHED) {
parentEngine.client.onPageFinishedLoading(url);
} else if (status == LoadStatus.FAILED) {
// TODO: Should this call parentEngine.client.onReceivedError()?
// Right now we call this from ResourceClient, but maybe that is just for sub-resources?
}
}
// File Chooser
@Override
public void openFileChooser(XWalkView view, final ValueCallback<Uri> uploadFile,
final String acceptType, final String capture) {
if (mFileChooser == null) {
mFileChooser = new XWalkFileChooser(parentEngine.cordova.getActivity());
mFileChooserResultPlugin = new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
mFileChooser.onActivityResult(requestCode, resultCode, intent);
}
};
}
PermissionRequestListener listener = new PermissionRequestListener() {
@Override
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) {
for (int i = 0; i < permissions.length; ++i) {
Log.d(TAG, "permission:" + permissions[i] + " result:" + grantResults[i]);
}
parentEngine.cordova.setActivityResultCallback(mFileChooserResultPlugin);
mFileChooser.showFileChooser(uploadFile, acceptType, capture);
}
};
if (!parentEngine.requestPermissionsForFileChooser(listener)) {
parentEngine.cordova.setActivityResultCallback(mFileChooserResultPlugin);
mFileChooser.showFileChooser(uploadFile, acceptType, capture);
}
}
}

View File

@ -0,0 +1,135 @@
package org.crosswalk.engine;
import org.apache.cordova.CordovaPreferences;
import org.xwalk.core.XWalkPreferences;
import org.xwalk.core.XWalkResourceClient;
import org.xwalk.core.XWalkUIClient;
import org.xwalk.core.XWalkView;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
public class XWalkCordovaView extends XWalkView implements CordovaWebViewEngine.EngineView {
public static final String TAG = "XWalkCordovaView";
protected XWalkCordovaResourceClient resourceClient;
protected XWalkCordovaUiClient uiClient;
protected XWalkWebViewEngine parentEngine;
private static boolean hasSetStaticPref;
// This needs to run before the super's constructor.
private static Context setGlobalPrefs(Context context, CordovaPreferences preferences) {
if (!hasSetStaticPref) {
hasSetStaticPref = true;
ApplicationInfo ai = null;
try {
ai = context.getPackageManager().getApplicationInfo(context.getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
boolean prefAnimatable = preferences == null ? false : preferences.getBoolean("CrosswalkAnimatable", false);
boolean manifestAnimatable = ai.metaData == null ? false : ai.metaData.getBoolean("CrosswalkAnimatable");
// Selects between a TextureView (obeys framework transforms applied to view) or a SurfaceView (better performance).
XWalkPreferences.setValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW, prefAnimatable || manifestAnimatable);
if ((ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
XWalkPreferences.setValue(XWalkPreferences.REMOTE_DEBUGGING, true);
}
XWalkPreferences.setValue(XWalkPreferences.JAVASCRIPT_CAN_OPEN_WINDOW, true);
XWalkPreferences.setValue(XWalkPreferences.ALLOW_UNIVERSAL_ACCESS_FROM_FILE, true);
}
return context;
}
public XWalkCordovaView(Context context, CordovaPreferences preferences) {
super(setGlobalPrefs(context, preferences), (AttributeSet)null);
}
public XWalkCordovaView(Context context, AttributeSet attrs) {
super(setGlobalPrefs(context, null), attrs);
}
void init(XWalkWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
if (resourceClient == null) {
setResourceClient(new XWalkCordovaResourceClient(parentEngine));
}
if (uiClient == null) {
setUIClient(new XWalkCordovaUiClient(parentEngine));
}
}
@Override
public void setResourceClient(XWalkResourceClient client) {
// XWalk calls this method from its constructor.
if (client instanceof XWalkCordovaResourceClient) {
this.resourceClient = (XWalkCordovaResourceClient)client;
}
super.setResourceClient(client);
}
@Override
public void setUIClient(XWalkUIClient client) {
// XWalk calls this method from its constructor.
if (client instanceof XWalkCordovaUiClient) {
this.uiClient = (XWalkCordovaUiClient)client;
}
super.setUIClient(client);
}
// Call CordovaInterface to start activity for result to make sure
// onActivityResult() callback will be triggered from CordovaActivity correctly.
// Todo(leonhsl) How to handle |options|?
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
// Route to XWalkView.
Log.i(TAG, "Route onActivityResult() to XWalkView");
XWalkCordovaView.this.onActivityResult(requestCode, resultCode, intent);
}
}, intent, requestCode);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
if (ret != null) {
return ret.booleanValue();
}
return super.dispatchKeyEvent(event);
}
@Override
public void pauseTimers() {
// This is called by XWalkViewInternal.onActivityStateChange().
// We don't want them paused by default though.
}
public void pauseTimersForReal() {
super.pauseTimers();
}
@Override
public CordovaWebView getCordovaWebView() {
return parentEngine == null ? null : parentEngine.getCordovaWebView();
}
@Override
public void setBackgroundColor(int color) {
if (parentEngine != null && parentEngine.isXWalkReady()) {
super.setBackgroundColor(color);
}
}
}

View File

@ -0,0 +1,52 @@
/*
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.crosswalk.engine;
import android.os.Looper;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.ExposedJsApi;
import org.json.JSONException;
import org.xwalk.core.JavascriptInterface;
class XWalkExposedJsApi implements ExposedJsApi {
private final CordovaBridge bridge;
XWalkExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
if (Looper.myLooper() == null) {
Looper.prepare();
}
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
}

View File

@ -0,0 +1,229 @@
/*
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.crosswalk.engine;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.Manifest;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.ValueCallback;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
public class XWalkFileChooser {
private static final String IMAGE_TYPE = "image/";
private static final String VIDEO_TYPE = "video/";
private static final String AUDIO_TYPE = "audio/";
private static final String ALL_IMAGE_TYPES = IMAGE_TYPE + "*";
private static final String ALL_VIDEO_TYPES = VIDEO_TYPE + "*";
private static final String ALL_AUDIO_TYPES = AUDIO_TYPE + "*";
private static final String ANY_TYPES = "*/*";
private static final String SPLIT_EXPRESSION = ",";
private static final String PATH_PREFIX = "file:";
private static final String WRITE_EXTERNAL_STORAGE= "android.permission.WRITE_EXTERNAL_STORAGE";
public static final int INPUT_FILE_REQUEST_CODE = 1;
private static final String TAG = "XWalkFileChooser";
private Activity mActivity;
private ValueCallback<Uri> mFilePathCallback;
private String mCameraPhotoPath;
public XWalkFileChooser(Activity activity) {
mActivity = activity;
}
public boolean showFileChooser(ValueCallback<Uri> uploadFile, String acceptType,
String capture) {
mFilePathCallback = uploadFile;
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(mActivity.getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = createImageFile();
// Continue only if the File was successfully created
if (photoFile != null) {
mCameraPhotoPath = PATH_PREFIX + photoFile.getAbsolutePath();
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
} else {
takePictureIntent = null;
}
}
Intent camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
Intent soundRecorder = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
ArrayList<Intent> extraIntents = new ArrayList<Intent>();
// A single mime type.
if (!(acceptType.contains(SPLIT_EXPRESSION) || acceptType.contains(ANY_TYPES))) {
if (capture.equals("true")) {
if (acceptType.startsWith(IMAGE_TYPE)) {
if (takePictureIntent != null) {
mActivity.startActivityForResult(takePictureIntent, INPUT_FILE_REQUEST_CODE);
Log.d(TAG, "Started taking picture");
return true;
}
} else if (acceptType.startsWith(VIDEO_TYPE)) {
mActivity.startActivityForResult(camcorder, INPUT_FILE_REQUEST_CODE);
Log.d(TAG, "Started camcorder");
return true;
} else if (acceptType.startsWith(AUDIO_TYPE)) {
mActivity.startActivityForResult(soundRecorder, INPUT_FILE_REQUEST_CODE);
Log.d(TAG, "Started sound recorder");
return true;
}
} else {
if (acceptType.startsWith(IMAGE_TYPE)) {
if (takePictureIntent != null) {
extraIntents.add(takePictureIntent);
}
contentSelectionIntent.setType(ALL_IMAGE_TYPES);
} else if (acceptType.startsWith(VIDEO_TYPE)) {
extraIntents.add(camcorder);
contentSelectionIntent.setType(ALL_VIDEO_TYPES);
} else if (acceptType.startsWith(AUDIO_TYPE)) {
extraIntents.add(soundRecorder);
contentSelectionIntent.setType(ALL_AUDIO_TYPES);
}
}
}
// Couldn't resolve an accept type.
if (extraIntents.isEmpty() && canWriteExternalStorage()) {
if (takePictureIntent != null) {
extraIntents.add(takePictureIntent);
}
extraIntents.add(camcorder);
extraIntents.add(soundRecorder);
contentSelectionIntent.setType(ANY_TYPES);
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
if (!extraIntents.isEmpty()) {
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
extraIntents.toArray(new Intent[] { }));
}
mActivity.startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
Log.d(TAG, "Started chooser");
return true;
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == INPUT_FILE_REQUEST_CODE && mFilePathCallback != null) {
Log.d(TAG, "Activity result: " + resultCode);
Uri results = null;
// Check that the response is a good one
if(Activity.RESULT_OK == resultCode) {
// In Android M, camera results return an empty Intent rather than null.
if(data == null || (data.getAction() == null && data.getData() == null)) {
// If there is not data, then we may have taken a photo
if(mCameraPhotoPath != null) {
results = Uri.parse(mCameraPhotoPath);
}
} else {
String dataString = data.getDataString();
if (dataString != null) {
results = Uri.parse(dataString);
}
deleteImageFile();
}
} else if (Activity.RESULT_CANCELED == resultCode) {
deleteImageFile();
}
if (results != null) {
Log.d(TAG, "Received file: " + results.toString());
}
mFilePathCallback.onReceiveValue(results);
mFilePathCallback = null;
}
}
private boolean canWriteExternalStorage() {
try {
PackageManager packageManager = mActivity.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(
mActivity.getPackageName(), PackageManager.GET_PERMISSIONS);
return Arrays.asList(packageInfo.requestedPermissions).contains(WRITE_EXTERNAL_STORAGE);
} catch (NameNotFoundException e) {
return false;
} catch (NullPointerException e) {
return false;
}
}
private File createImageFile() {
// FIXME: If the external storage state is not "MEDIA_MOUNTED", we need to get
// other volume paths by "getVolumePaths()" when it was exposed.
String state = Environment.getExternalStorageState();
if (!state.equals(Environment.MEDIA_MOUNTED)) {
Log.e(TAG, "External storage is not mounted.");
return null;
}
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
if (!storageDir.exists()) {
storageDir.mkdirs();
}
try {
File file = File.createTempFile(imageFileName, ".jpg", storageDir);
Log.d(TAG, "Created image file: " + file.getAbsolutePath());
return file;
} catch (IOException e) {
// Error occurred while creating the File
Log.e(TAG, "Unable to create Image File, " +
"please make sure permission 'WRITE_EXTERNAL_STORAGE' was added.");
return null;
}
}
private boolean deleteImageFile() {
if (mCameraPhotoPath == null || !mCameraPhotoPath.contains(PATH_PREFIX)) {
return false;
}
String filePath = mCameraPhotoPath.split(PATH_PREFIX)[1];
boolean result = new File(filePath).delete();
Log.d(TAG, "Delete image file: " + filePath + " result: " + result);
return result;
}
}

View File

@ -0,0 +1,366 @@
/*
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.crosswalk.engine;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.Manifest;
import android.util.Log;
import android.view.View;
import android.webkit.ValueCallback;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaPreferences;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
import org.apache.cordova.ICordovaCookieManager;
import org.apache.cordova.NativeToJsMessageQueue;
import org.apache.cordova.PluginEntry;
import org.apache.cordova.PluginManager;
import org.xwalk.core.XWalkActivityDelegate;
import org.xwalk.core.XWalkNavigationHistory;
import org.xwalk.core.XWalkView;
import org.xwalk.core.XWalkGetBitmapCallback;
/**
* Glue class between CordovaWebView (main Cordova logic) and XWalkCordovaView (the actual View).
*/
public class XWalkWebViewEngine implements CordovaWebViewEngine {
public static final String TAG = "XWalkWebViewEngine";
public static final String XWALK_USER_AGENT = "xwalkUserAgent";
public static final String XWALK_Z_ORDER_ON_TOP = "xwalkZOrderOnTop";
private static final String XWALK_EXTENSIONS_FOLDER = "xwalk-extensions";
private static final int PERMISSION_REQUEST_CODE = 100;
protected final XWalkCordovaView webView;
protected XWalkCordovaCookieManager cookieManager;
protected CordovaBridge bridge;
protected CordovaWebViewEngine.Client client;
protected CordovaWebView parentWebView;
protected CordovaInterface cordova;
protected PluginManager pluginManager;
protected CordovaResourceApi resourceApi;
protected NativeToJsMessageQueue nativeToJsMessageQueue;
protected XWalkActivityDelegate activityDelegate;
protected String startUrl;
protected CordovaPreferences preferences;
/** Used when created via reflection. */
public XWalkWebViewEngine(Context context, CordovaPreferences preferences) {
this.preferences = preferences;
Runnable cancelCommand = new Runnable() {
@Override
public void run() {
cordova.getActivity().finish();
}
};
Runnable completeCommand = new Runnable() {
@Override
public void run() {
cookieManager = new XWalkCordovaCookieManager();
initWebViewSettings();
exposeJsInterface(webView, bridge);
loadExtensions();
CordovaPlugin notifPlugin = new CordovaPlugin() {
@Override
public void onNewIntent(Intent intent) {
Log.i(TAG, "notifPlugin route onNewIntent() to XWalkView: " + intent.toString());
XWalkWebViewEngine.this.webView.onNewIntent(intent);
}
@Override
public Object onMessage(String id, Object data) {
if (id.equals("captureXWalkBitmap")) {
// Capture bitmap on UI thread.
XWalkWebViewEngine.this.cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
XWalkWebViewEngine.this.webView.captureBitmapAsync(
new XWalkGetBitmapCallback() {
@Override
public void onFinishGetBitmap(Bitmap bitmap,
int response) {
pluginManager.postMessage(
"onGotXWalkBitmap", bitmap);
}
});
}
});
}
return null;
}
};
pluginManager.addService(new PluginEntry("XWalkNotif", notifPlugin));
// Send the massage of xwalk's ready to plugin.
if (pluginManager != null) {
pluginManager.postMessage("onXWalkReady", this);
}
if (startUrl != null) {
webView.load(startUrl, null);
}
}
};
activityDelegate = new XWalkActivityDelegate((Activity) context, cancelCommand, completeCommand);
webView = new XWalkCordovaView(context, preferences);
}
// Use two-phase init so that the control will work with XML layouts.
@Override
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
if (this.cordova != null) {
throw new IllegalStateException();
}
this.parentWebView = parentWebView;
this.cordova = cordova;
this.client = client;
this.resourceApi = resourceApi;
this.pluginManager = pluginManager;
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
CordovaPlugin activityDelegatePlugin = new CordovaPlugin() {
@Override
public void onResume(boolean multitasking) {
activityDelegate.onResume();
}
};
pluginManager.addService(new PluginEntry("XWalkActivityDelegate", activityDelegatePlugin));
webView.init(this);
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(
new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
@Override
public void setNetworkAvailable(boolean value) {
webView.setNetworkAvailable(value);
}
@Override
public void runOnUiThread(Runnable r) {
XWalkWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
}
}));
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
}
@Override
public CordovaWebView getCordovaWebView() {
return parentWebView;
}
@Override
public View getView() {
return webView;
}
private void initWebViewSettings() {
webView.setVerticalScrollBarEnabled(false);
boolean zOrderOnTop = preferences == null ? false : preferences.getBoolean(XWALK_Z_ORDER_ON_TOP, false);
webView.setZOrderOnTop(zOrderOnTop);
// Set xwalk webview settings by Cordova preferences.
String xwalkUserAgent = preferences == null ? "" : preferences.getString(XWALK_USER_AGENT, "");
if (!xwalkUserAgent.isEmpty()) {
webView.setUserAgentString(xwalkUserAgent);
}
String appendUserAgent = preferences.getString("AppendUserAgent", "");
if (!appendUserAgent.isEmpty()) {
webView.setUserAgentString(webView.getUserAgentString() + " " + appendUserAgent);
}
if (preferences.contains("BackgroundColor")) {
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
webView.setBackgroundColor(backgroundColor);
}
}
private static void exposeJsInterface(XWalkView webView, CordovaBridge bridge) {
XWalkExposedJsApi exposedJsApi = new XWalkExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
private void loadExtensions() {
AssetManager assetManager = cordova.getActivity().getAssets();
String[] extList;
try {
Log.i(TAG, "Iterate assets/xwalk-extensions folder");
extList = assetManager.list(XWALK_EXTENSIONS_FOLDER);
} catch (IOException e) {
Log.w(TAG, "Failed to iterate assets/xwalk-extensions folder");
return;
}
for (String path : extList) {
// Load the extension.
Log.i(TAG, "Start to load extension: " + path);
webView.getExtensionManager().loadExtension(XWALK_EXTENSIONS_FOLDER + File.separator + path);
}
}
@Override
public boolean canGoBack() {
if (!activityDelegate.isXWalkReady()) return false;
return this.webView.getNavigationHistory().canGoBack();
}
@Override
public boolean goBack() {
if (this.webView.getNavigationHistory().canGoBack()) {
this.webView.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.BACKWARD, 1);
return true;
}
return false;
}
@Override
public void setPaused(boolean value) {
if (!activityDelegate.isXWalkReady()) return;
if (value) {
// TODO: I think this has been fixed upstream and we don't need to override pauseTimers() anymore.
webView.pauseTimersForReal();
} else {
webView.resumeTimers();
}
}
@Override
public void destroy() {
if (!activityDelegate.isXWalkReady()) return;
webView.onDestroy();
}
@Override
public void clearHistory() {
if (!activityDelegate.isXWalkReady()) return;
this.webView.getNavigationHistory().clear();
}
@Override
public void stopLoading() {
if (!activityDelegate.isXWalkReady()) return;
this.webView.stopLoading();
}
@Override
public void clearCache() {
if (!activityDelegate.isXWalkReady()) return;
webView.clearCache(true);
}
@Override
public String getUrl() {
if (!activityDelegate.isXWalkReady()) return null;
return this.webView.getUrl();
}
@Override
public ICordovaCookieManager getCookieManager() {
return cookieManager;
}
@Override
public void loadUrl(String url, boolean clearNavigationStack) {
if (!activityDelegate.isXWalkReady()) {
startUrl = url;
return;
}
webView.load(url, null);
}
/**
* This API is used in Cordova-Android 6.0.0 override from
*
* CordovaWebViewEngine.java
* @since Cordova 6.0
*/
public void evaluateJavascript(String js, ValueCallback<String> callback) {
webView.evaluateJavascript(js, callback);
}
public boolean isXWalkReady() {
return activityDelegate.isXWalkReady();
}
public interface PermissionRequestListener {
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults);
}
public boolean requestPermissionsForFileChooser(final PermissionRequestListener listener) {
ArrayList<String> dangerous_permissions = new ArrayList<String>();
try {
PackageManager packageManager = cordova.getActivity().getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(
cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS);
for (String permission : packageInfo.requestedPermissions) {
if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|| permission.equals(Manifest.permission.CAMERA)) {
dangerous_permissions.add(permission);
}
}
} catch (NameNotFoundException e) {
}
if (dangerous_permissions.isEmpty()) {
return false;
}
CordovaPlugin permissionRequestPlugin = new CordovaPlugin() {
@Override
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode != PERMISSION_REQUEST_CODE) return;
listener.onRequestPermissionResult(requestCode, permissions, grantResults);
}
};
try {
cordova.requestPermissions(permissionRequestPlugin, PERMISSION_REQUEST_CODE,
dangerous_permissions.toArray(new String[dangerous_permissions.size()]));
} catch (NoSuchMethodError e) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,180 @@
/*
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.
*/
def EMBEDDED_MODE = "embedded"
def SHARED_MODE = "shared"
def LITE_MODE = "lite"
def DEFAULT_GROUP_ID = "org.xwalk:"
def SHARED_ARTIFACT_ID = "xwalk_shared_library:"
def EMBEDD_ARTIFACT_ID = "xwalk_core_library:"
def CANARY_ARTIFACT_ID = "xwalk_core_library_canary:"
def BIT_64 = ":64bit@aar"
def DEFAULT_MIN_SDK_VERSION = 14
def getConfigPreference(name) {
name = name.toLowerCase()
def xml
if (file("src/main/res/xml/config.xml").exists()) {
// cordova-android >= 7.0.0
xml = file("src/main/res/xml/config.xml").getText()
} else {
// cordova-android < 7.0.0
xml = file("res/xml/config.xml").getText()
}
// Disable namespace awareness since Cordova doesn't use them properly
def root = new XmlParser(false, false).parseText(xml)
def ret, defaultValue
root.preference.each { it ->
def attrName = it.attribute("name")
if (attrName && attrName.toLowerCase() == name) {
if (it.attribute('default') != null) {
defaultValue = it.attribute('default');
} else {
ret = it.attribute("value")
}
}
}
return ret ? ret : defaultValue
}
if (!project.hasProperty('xwalk64bit')) {
ext.xwalk64bit = getConfigPreference("xwalk64bit");
println xwalk64bit
}
if (cdvBuildMultipleApks == null) {
ext.xwalkMultipleApk = getConfigPreference("xwalkMultipleApk").toBoolean();
} else {
ext.xwalkMultipleApk = cdvBuildMultipleApks.toBoolean();
}
def minSdk = getConfigPreference("android-minSdkVersion");
if (cdvMinSdkVersion == null) {
ext.cdvMinSdkVersion = minSdk && Integer.parseInt(minSdk) > DEFAULT_MIN_SDK_VERSION ? minSdk : DEFAULT_MIN_SDK_VERSION;
} else if (Integer.parseInt('' + cdvMinSdkVersion) < Integer.parseInt(minSdk)) {
ext.cdvMinSdkVersion = minSdk;
}
if (!project.hasProperty('xwalkMode')) {
ext.xwalkMode = getConfigPreference("xwalkMode");
}
if (ext.xwalkMode == SHARED_MODE) {
// Build one apk at shared mode because the value of
// ext.cdvBuildMultipleApks is false by default.
xwalk64bit = null;
} else if (xwalk64bit == null) {
// Build embedded 32 bit crosswalk will generate two apks by default.
ext.cdvBuildMultipleApks = xwalkMultipleApk;
}
// Set defaults before project's build-extras.gradle
if (!project.hasProperty('xwalkVersion')) {
ext.xwalkVersion = getConfigPreference("xwalkVersion")
}
// Set defaults before project's build-extras.gradle
if (!project.hasProperty('xwalkLiteVersion')) {
ext.xwalkLiteVersion = getConfigPreference("xwalkLiteVersion")
}
if (!project.hasProperty('xwalkCommandLine')) {
ext.xwalkCommandLine = getConfigPreference("xwalkCommandLine")
}
// Apply values after project's build-extras.gradle
cdvPluginPostBuildExtras.add({
def xwalkMavenRepo = 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2';
if (xwalkMode == LITE_MODE) {
xwalkMavenRepo = 'https://download.01.org/crosswalk/releases/crosswalk-lite/android/maven2';
}
repositories {
maven {
url xwalkMavenRepo
}
}
android {
if (xwalk64bit != null) {
productFlavors {
x86_64 {
versionCode defaultConfig.versionCode + 6
ndk {
abiFilters "x86_64", ""
}
}
arm64 {
versionCode defaultConfig.versionCode + 9
ndk {
abiFilters "arm64-v8a", ""
}
}
}
}
}
def xwalkSpec = xwalkVersion
if (ext.xwalkMode == LITE_MODE) {
xwalkSpec = xwalkLiteVersion;
}
if ((xwalkSpec =~ /:/).count == 1) {
xwalkSpec = DEFAULT_GROUP_ID + xwalkSpec
} else if ((xwalkSpec =~ /:/).count == 0) {
if (xwalkSpec ==~ /\d+/) {
xwalkSpec = "${xwalkSpec}+"
}
def artifactid = EMBEDD_ARTIFACT_ID;
if (ext.xwalkMode == SHARED_MODE) {
artifactid = SHARED_ARTIFACT_ID;
} else if (ext.xwalkMode == LITE_MODE) {
artifactid = CANARY_ARTIFACT_ID;
}
xwalkSpec = DEFAULT_GROUP_ID + artifactid + xwalkSpec
}
if (xwalk64bit != null) {
xwalkSpec = xwalkSpec + BIT_64
}
println xwalkSpec
dependencies {
compile xwalkSpec
}
if (file('assets/xwalk-command-line').exists()) {
println('Not writing assets/xwalk-command-line since file already exists.')
return
}
android.applicationVariants.all { variant ->
def variantName = variant.name.capitalize()
def mergeTask = tasks["merge${variantName}Assets"]
def processTask = tasks["process${variantName}Resources"]
def outFile = new File (mergeTask.outputDir, "xwalk-command-line")
def newTask = project.task("createXwalkCommandLineFile${variantName}") << {
mergeTask.outputDir.mkdirs()
outFile.write("xwalk ${xwalkCommandLine}\n")
}
newTask.dependsOn(mergeTask)
processTask.dependsOn(newTask)
}
})

View File

@ -0,0 +1,65 @@
<?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-crosswalk-webview"
version="2.4.0">
<name>Crosswalk WebView Engine</name>
<description>Changes the default WebView to CrossWalk</description>
<license>Apache 2.0</license>
<keywords>cordova,chromium,crosswalk,webview</keywords>
<repo>https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview</repo>
<issue>https://crosswalk-project.org/jira</issue>
<engines>
<engine name="cordova-android" version=">=6"/>
<engine name="cordova-plugman" version=">=5.2.0"/><!-- needed for gradleReference support -->
</engines>
<!-- android -->
<platform name="android">
<preference name="XWALK_VERSION" default="23+"/>
<preference name="XWALK_LITEVERSION" default="xwalk_core_library_canary:17+"/>
<preference name="XWALK_COMMANDLINE" default="--disable-pull-to-refresh-effect"/>
<preference name="XWALK_MODE" default="embedded" />
<preference name="XWALK_MULTIPLEAPK" default="true" />
<config-file target="res/xml/config.xml" parent="/*">
<preference name="webView" value="org.crosswalk.engine.XWalkWebViewEngine"/>
<preference name="xwalkVersion" value="$XWALK_VERSION"/>
<preference name="xwalkLiteVersion" value="$XWALK_LITEVERSION"/>
<preference name="xwalkCommandLine" value="$XWALK_COMMANDLINE"/>
<preference name="xwalkMode" value="$XWALK_MODE" />
<preference name="xwalkMultipleApk" value="$XWALK_MULTIPLEAPK" />
<preference name="android-minSdkVersion" value="16" />
</config-file>
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
</config-file>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkWebViewEngine.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkFileChooser.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkExposedJsApi.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkCordovaResourceClient.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkCordovaUiClient.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkCordovaView.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkCordovaCookieManager.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkCordovaClientCertRequest.java" target-dir="src/org/crosswalk/engine"/>
<source-file src="platforms/android/src/org/crosswalk/engine/XWalkCordovaHttpAuthHandler.java" target-dir="src/org/crosswalk/engine"/>
<framework src="platforms/android/xwalk.gradle" custom="true" type="gradleReference"/>
<hook type="after_plugin_install" src="hooks/after_plugin_install/000-shared_mode_special.js"/>
<hook type="before_plugin_uninstall" src="hooks/before_plugin_uninstall/000-shared_mode_special.js"/>
<hook type="after_build" src="hooks/after_build/000-build_64_bit.js"/>
<hook type="before_build" src="hooks/before_build/000-build_64_bit.js"/>
</platform>
<info>
After much discussion and analysis of the market, we have decided to discontinue support for Android 4.0 (ICS) in Crosswalk starting with version 20.
So the minSdkVersion of Cordova project is configured to 16 by default.
</info>
</plugin>

View File

@ -0,0 +1,238 @@
# Custom URL scheme PhoneGap Plugin
#### launch your app by a link like this: `mycoolapp://`
for iOS, Android and WP, by [Eddy Verbruggen](http://www.x-services.nl)
- This repo is for PhoneGap 3.0.0 and up
- For PhoneGap 2.9.0 and lower, [switch to the phonegap-2.9.0-and-lower branch](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin/tree/phonegap-2.9.0-and-lower)
1. [Description](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#1-description)
2. [Installation](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#2-installation)
2. [Automatically (CLI / Plugman)](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#automatically-cli--plugman)
2. [Manually](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#manually)
2. [PhoneGap Build](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#phonegap-build)
3. [Usage](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#3-usage)
2. [iOS](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#ios-usage)
2. [Meteor](https://github.com/EddyVerbruggen/Custom-URL-scheme#meteor--getlastintent-android-only)
4. [URL Scheme hints](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#4-url-scheme-hints)
5. [License](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#5-license)
### BEWARE:
### - [This Apache Cordova issue](https://issues.apache.org/jira/browse/CB-7606) causes problems with Cordova-iOS 3.7.0: the `handleOpenURL` function is not invoked upon cold start. Use a higher or lower version than 3.7.0.
### - As of iOS 9.2, the dialog `Open in "mycoolapp"?` no longer blocks JS, so if you have a short timeout that opens the app store, the user will be taken to the store before they have a chance to see and answer the dialog. [See below](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#ios-usage) for available solutions.
## 1. Description
This plugin allows you to start your app by calling it with a URL like `mycoolapp://path?foo=bar`
* Compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman)
* Submitted and waiting for approval at PhoneGap Build ([more information](https://build.phonegap.com/plugins))
### iOS specifics
* Forget about [using config.xml to define a URL scheme](https://build.phonegap.com/docs/config-xml#url_schemes). This plugin adds 2 essential enhancements:
- Uniform URL scheme with Android (for which there is no option to define a URL scheme via PhoneGap configuration at all).
- You still need to wire up the Javascript to handle incoming events. This plugin assists you with that.
* Tested on iOS 5.1, 6 and 7.
### Android specifics
* Unlike iOS, there is no way to use config.xml to define a scheme for your app. Now there is.
* Tested on Android 4.3, will most likely work with 2.2 and up.
* If you're trying to launch your app from an In-App Browser it opened previously, then [use this In-App Browser plugin fork](https://github.com/Innovation-District/cordova-plugin-inappbrowser) which allows that.
* In case you have a multi-page app (multiple HTML files, and all implementing handleOpenURL), set the preference `CustomURLSchemePluginClearsAndroidIntent` to `true` in `config.xml` so the function won't be triggered multiple times. Note that this may interfere with other plugins requiring the intent data.
## 2. Installation
### Automatically (CLI / Plugman)
LaunchMyApp is compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman).
Replace `mycoolapp` by a nice scheme you want to have your app listen to:
Latest release on npm:
```
$ cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=mycoolapp
```
Bleeding edge master version from Github:
```
$ cordova plugin add https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin.git --variable URL_SCHEME=mycoolapp
```
(Note that the Phonegap CLI didn't support `--variable` before version 3.6.3, so please use the Cordova CLI as shown above in case you're on an older version)
The LaunchMyApp.js file is brought in automatically.
Note for iOS: there was a bug in CLI which caused an error in your `*-Info.plist`.
Please manually remove the blank line and whitespace (if any) from `NSMainNibFile` and `NSMainNibFile~ipad` (or your app won't start at all).
### Manually
Don't shoot yourself in the foot - use the CLI! That being said, here goes:
#### iOS
1\. `Copy www/ios/LaunchMyApp.js` to `www/js/plugins/LaunchMyApp.js` and reference it in your `index.html`:
```html
<script type="text/javascript" src="js/plugins/LaunchMyApp.js"></script>
```
2\. Add this to your `*-Info.plist` (replace `URL_SCHEME` by a nice scheme you want to have your app listen to, like `mycoolapp`):
```xml
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>URL_SCHEME</string>
</array>
</dict>
</array>
```
#### Android
1\. Copy www/android/LaunchMyApp.js to www/js/plugins/LaunchMyApp.js and reference it in your `index.html`:
```html
<script type="text/javascript" src="js/plugins/LaunchMyApp.js"></script>
```
2\. Add the following xml to your `config.xml` to always use the latest version of this plugin:
```xml
<plugin name="LaunchMyApp" value="nl.xservices.plugins.LaunchMyApp"/>
```
3\. Copy `LaunchMyApp.java` to `platforms/android/src/nl/xservices/plugins` (create the folders)
4\. Add the following to your `AndroidManifest.xml` inside the `/manifest/application/activity` node (replace `URL_SCHEME` by a nice scheme you want to have your app listen to, like `mycoolapp`):
```xml
<intent-filter>
<data android:scheme="URL_SCHEME"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
```
5\. In `AndroidManifest.xml` set the launchMode to singleTask to avoid issues like [#24]. `<activity android:launchMode="singleTask" ..`
### PhoneGap Build
Using LaunchMyApp with PhoneGap Build requires you to add the following xml to your `config.xml` to use the latest version of this plugin (replace `mycoolapp` by a nice scheme you want to have your app listen to):
```xml
<gap:plugin name="cordova-plugin-customurlscheme" source="npm">
<param name="URL_SCHEME" value="mycoolapp" />
</gap:plugin>
```
The LaunchMyApp.js file is brought in automatically.
NOTE: When Hydration is enabled at PGB, this plugin may not work.
### Restoring cordova plugin settings on plugin add or update
In order to be able to restore the plugin settings on `cordova plugin add`, one need to add the following feature into config.xml. Note that if you added the plugin with the `--save` param you will find this in your `config.xml` already, except for the `variable` tag which is likely a `param` tag. [Change that.](https://github.com/EddyVerbruggen/Custom-URL-scheme/issues/76)
```xml
<feature name="Custom URL scheme">
<param name="id" value="cordova-plugin-customurlscheme" />
<param name="url" value="https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin.git" />
<variable name="URL_SCHEME" value="mycoolapp" /><!-- change as appropriate -->
</feature>
```
Please notice that URL_SCHEME is saved as `variable`, not as `prop`. However if you do `cordova plugin add` with a --save option, cordova will write the URL_SCHEME as a `prop`, you need to change the tag name from `param` to `variable` in this case.
These plugin restore instructions are tested on:
cordova-cli 4.3.+ and cordova-android 3.7.1+
## 3. Usage
1a\. Your app can be launced by linking to it like this from a website or an email for example (all of these will work):
```html
<a href="mycoolapp://">Open my app</a>
<a href="mycoolapp://somepath">Open my app</a>
<a href="mycoolapp://somepath?foo=bar">Open my app</a>
<a href="mycoolapp://?foo=bar">Open my app</a>
```
`mycoolapp` is the value of URL_SCHEME you used while installing this plugin.
1b\. If you're trying to open your app from another PhoneGap app, use the InAppBrowser plugin and launch the receiving app like this, to avoid a 'protocol not supported' error:
```html
<button onclick="window.open('mycoolapp://', '_system')">Open the other app</button>
```
2\. When your app is launched by a URL, you probably want to do something based on the path and parameters in the URL. For that, you could implement the (optional) `handleOpenURL(url)` method, which receives the URL that was used to launch your app.
```javascript
function handleOpenURL(url) {
console.log("received url: " + url);
}
```
If you want to alert the URL for testing the plugin, at least on iOS you need to wrap it in a timeout like this:
```javascript
function handleOpenURL(url) {
setTimeout(function() {
alert("received url: " + url);
}, 0);
}
```
A more useful implementation would mean parsing the URL, saving any params to sessionStorage and redirecting the app to the correct page inside your app.
All this happens before the first page is loaded.
### iOS Usage
A common method of deeplinking is to give the user the URL of a webpage (for instance http://linker.myapp.com/pathfoo) that opens the app if installed or the app store if not. This can be done in the following ways, depending on the desired UX:
1. The page content has a button that says "Install app" and when clicked opens the app store by doing `location.href = 'itms-apps://itunes.apple.com/us/app/mycoolapp/idfoo'`. On page load, do `location.href = 'mycoolapp://pathfoo'`. If the user has the app, they will see a dialog that says `Open in "mycoolapp"? [Cancel] [Open]`. If the user does not have the app, they will see an alert that says `Cannot Open Page: Safari cannot open the page because the address is invalid`. Once they dismiss the alert, they see the button that opens the app store, and they tap it.
2. The page has two buttons: one that opens the app, and one that opens the app store.
3. On page load, open a Universal Link using [cordova-universal-links-plugin](https://github.com/nordnet/cordova-universal-links-plugin). (A Universal Link either opens the app or the app store.) Then fall back to one of the above methods if Univeral Links is not supported.
You can also use a service that provides pages that do #3 for you, such as [Branch](https://branch.io/).
### CSP - or: `handleOpenURL` doesn't work
The Whitelist plugin will prevent inline JS from executing, unless you whitelist the url scheme. Please see [this SO issue](http://stackoverflow.com/questions/34257097/using-handleopenurl-with-custom-url-scheme-in-cordova/34281420#34281420) for details.
### Meteor / getLastIntent (Android only)
When running a [meteor](meteor.com) app in the cordova environment, `handleOpenURL` doesn't get called after a cold start, because cordova resets the javascript world during startup and our timer waiting for `handleOpenURL` gets vanished (see [#98](https://github.com/EddyVerbruggen/Custom-URL-scheme/issues/98)). To get the intent by which the app was started in a meteor cordova app you need to ask for it from the meteor side with `getLastIntent` like this.
```javascript
Meteor.startup(function() {
if (Meteor.isCordova) {
window.plugins.launchmyapp.getLastIntent(function(url) {
if (intent.indexOf('mycoolapp://' > -1)) {
console.log("received url: " + url);
} else {
return console.log("ignore intent: " + url);
}
}, function(error) {
return console.log("no intent received");
});
return;
}
});
```
## 4. URL Scheme hints
Please choose a URL_SCHEME which which complies to these restrictions:
- Don't use an already registered scheme (like `fb`, `twitter`, `comgooglemaps`, etc).
- Use only lowercase characters.
- Don't use a dash `-` because on Android it will become underscore `_`.
- Use only 1 word (no spaces).
TIP: test your scheme by installing the app on a device or simulator and typing yourscheme:// in the browser URL bar, or create a test HTML page with a link to your app to impress your buddies.
## 5. License
[The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,72 @@
{
"_from": "cordova-plugin-customurlscheme@~4.2.0",
"_id": "cordova-plugin-customurlscheme@4.2.0",
"_inBundle": false,
"_integrity": "sha1-t6Zu5fwmrGN0iDVTdgZHFjo30Hs=",
"_location": "/cordova-plugin-customurlscheme",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "cordova-plugin-customurlscheme@~4.2.0",
"name": "cordova-plugin-customurlscheme",
"escapedName": "cordova-plugin-customurlscheme",
"rawSpec": "~4.2.0",
"saveSpec": null,
"fetchSpec": "~4.2.0"
},
"_requiredBy": [
"#USER"
],
"_resolved": "https://registry.npmjs.org/cordova-plugin-customurlscheme/-/cordova-plugin-customurlscheme-4.2.0.tgz",
"_shasum": "b7a66ee5fc26ac6374883553760647163a37d07b",
"_spec": "cordova-plugin-customurlscheme@~4.2.0",
"_where": "/Users/martin/workspace/padlock/cordova",
"author": {
"name": "Eddy Verbruggen",
"email": "eddyverbruggen@gmail.com",
"url": "https://github.com/EddyVerbruggen"
},
"bugs": {
"url": "https://github.com/EddyVerbruggen/Custom-URL-scheme/issues"
},
"bundleDependencies": false,
"cordova": {
"id": "cordova-plugin-customurlscheme",
"platforms": [
"ios",
"android",
"windows8",
"windows"
]
},
"deprecated": false,
"description": "Launch your app by using this URL: mycoolapp://, you can add a path and even pass params like this: mycoolerapp://somepath?foo=bar",
"engines": [
{
"name": "cordova",
"version": ">=3.0.0"
}
],
"homepage": "https://github.com/EddyVerbruggen/Custom-URL-scheme#readme",
"keywords": [
"Custom URL Scheme",
"URLscheme",
"URL scheme",
"Custom URL",
"Launch My App",
"Launch App",
"ecosystem:cordova",
"cordova-ios",
"cordova-android",
"cordova-windows",
"cordova-windows8"
],
"license": "MIT",
"name": "cordova-plugin-customurlscheme",
"repository": {
"type": "git",
"url": "git+https://github.com/EddyVerbruggen/Custom-URL-scheme.git"
},
"version": "4.2.0"
}

View File

@ -0,0 +1,149 @@
<?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-customurlscheme"
version="4.2.0">
<name>Custom URL scheme</name>
<description>
Launch your app by using this URL: mycoolapp://
You can add a path and even pass params like this: mycoolerapp://somepath?foo=bar
</description>
<author>Eddy Verbruggen</author>
<license>MIT</license>
<keywords>Custom URL Scheme, URLscheme, URL scheme, Custom URL, Launch My App, Launch App</keywords>
<repo>https://github.com/EddyVerbruggen/Custom-URL-scheme.git</repo>
<issue>https://github.com/EddyVerbruggen/Custom-URL-scheme/issues</issue>
<preference name="URL_SCHEME" />
<engines>
<engine name="cordova" version=">=3.0.0"/>
</engines>
<!-- ios -->
<platform name="ios">
<js-module src="www/ios/LaunchMyApp.js" name="LaunchMyApp">
<clobbers target="window.plugins.launchmyapp" />
</js-module>
<config-file target="*-Info.plist" parent="CFBundleURLTypes">
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>$URL_SCHEME</string>
</array>
</dict>
</array>
</config-file>
</platform>
<!-- android -->
<platform name="android">
<preference name="ANDROID_SCHEME" default=" " />
<preference name="ANDROID_HOST" default=" " />
<preference name="ANDROID_PATHPREFIX" default="/" />
<js-module src="www/android/LaunchMyApp.js" name="LaunchMyApp">
<clobbers target="window.plugins.launchmyapp" />
</js-module>
<config-file target="res/xml/config.xml" parent="/*">
<feature name="LaunchMyApp">
<param name="android-package" value="nl.xservices.plugins.LaunchMyApp"/>
</feature>
</config-file>
<source-file src="src/android/nl/xservices/plugins/LaunchMyApp.java" target-dir="src/nl/xservices/plugins"/>
<config-file target="AndroidManifest.xml" parent="/manifest/application/activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="$URL_SCHEME"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="$ANDROID_SCHEME"
android:host="$ANDROID_HOST"
android:pathPrefix="$ANDROID_PATHPREFIX" />
</intent-filter>
</config-file>
</platform>
<!-- windows8 -->
<platform name="windows8">
<config-file target="package.appxmanifest" parent="/Package/Applications/Application/Extensions">
<Extension Category="windows.protocol" StartPage="www/index.html">
<Protocol Name="$URL_SCHEME" />
</Extension>
</config-file>
<js-module src="www/windows/LaunchMyApp.js" name="LaunchMyApp">
<clobbers target="window.plugins.launchmyapp" />
</js-module>
</platform>
<!-- windows -->
<platform name="windows">
<hook type="before_prepare" src="src/windows/hooks/prepare-manifest.js" />
<config-file target="package.windows10.appxmanifest" parent="/Package/Applications/Application/Extensions">
<uap:Extension Category="windows.protocol" StartPage="www/index.html">
<uap:Protocol Name="$URL_SCHEME" />
</uap:Extension>
</config-file>
<config-file target="package.windows.appxmanifest" parent="/Package/Applications/Application/Extensions">
<Extension Category="windows.protocol" StartPage="www/index.html">
<Protocol Name="$URL_SCHEME" />
</Extension>
</config-file>
<config-file target="package.windows80.appxmanifest" parent="/Package/Applications/Application/Extensions">
<Extension Category="windows.protocol" StartPage="www/index.html">
<Protocol Name="$URL_SCHEME" />
</Extension>
</config-file>
<config-file target="package.phone.appxmanifest" parent="/Package/Applications/Application/Extensions">
<Extension Category="windows.protocol" StartPage="www/index.html">
<Protocol Name="$URL_SCHEME" />
</Extension>
</config-file>
<js-module src="www/windows/LaunchMyApp.js" name="LaunchMyApp">
<clobbers target="window.plugins.launchmyapp" />
</js-module>
</platform>
<!-- wp8 -->
<platform name="wp8">
<config-file target="config.xml" parent="/*">
<feature name="CustomUriMapperCommand">
<param name="wp-package" value="CustomUriMapperCommand"/>
<param name="onload" value="true" />
</feature>
</config-file>
<config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App" after="Tokens">
<Extensions>
<Protocol Name="$URL_SCHEME" NavUriFragment="encodedLaunchUri=%s" TaskID="_default" />
</Extensions>
</config-file>
<source-file src="src/wp8/CompositeUriMapper.cs" />
<source-file src="src/wp8/ICustomUriMapper.cs" />
<source-file src="src/wp8/CustomUriMapperCommand.cs" />
<js-module src="www/wp8/LaunchMyApp.js" name="LaunchMyApp">
<clobbers target="window.plugins.launchmyapp" />
</js-module>
<hook type="after_plugin_install" src="src/wp8/hooks/add-uri-mapper.js" />
</platform>
</plugin>

View File

@ -0,0 +1,173 @@
package nl.xservices.plugins;
import android.content.Intent;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaActivity;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
public class LaunchMyApp extends CordovaPlugin {
private static final String ACTION_CHECKINTENT = "checkIntent";
private static final String ACTION_CLEARINTENT = "clearIntent";
private static final String ACTION_GETLASTINTENT = "getLastIntent";
private String lastIntentString = null;
/**
* We don't want to interfere with other plugins requiring the intent data,
* but in case of a multi-page app your app may receive the same intent data
* multiple times, that's why you'll get an option to reset it (null it).
*
* Add this to config.xml to enable that behaviour (default false):
* <preference name="CustomURLSchemePluginClearsAndroidIntent" value="true"/>
*/
private boolean resetIntent;
@Override
public void initialize(final CordovaInterface cordova, CordovaWebView webView){
this.resetIntent = preferences.getBoolean("resetIntent", false) ||
preferences.getBoolean("CustomURLSchemePluginClearsAndroidIntent", false);
}
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (ACTION_CLEARINTENT.equalsIgnoreCase(action)) {
final Intent intent = ((CordovaActivity) this.webView.getContext()).getIntent();
if (resetIntent){
intent.setData(null);
}
return true;
} else if (ACTION_CHECKINTENT.equalsIgnoreCase(action)) {
final Intent intent = ((CordovaActivity) this.webView.getContext()).getIntent();
final String intentString = intent.getDataString();
if (intentString != null && intent.getScheme() != null) {
lastIntentString = intentString;
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, intent.getDataString()));
} else {
callbackContext.error("App was not started via the launchmyapp URL scheme. Ignoring this errorcallback is the best approach.");
}
return true;
} else if (ACTION_GETLASTINTENT.equalsIgnoreCase(action)) {
if(lastIntentString != null) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, lastIntentString));
} else {
callbackContext.error("No intent received so far.");
}
return true;
} else {
callbackContext.error("This plugin only responds to the " + ACTION_CHECKINTENT + " action.");
return false;
}
}
@Override
public void onNewIntent(Intent intent) {
final String intentString = intent.getDataString();
if (intentString != null && intent.getScheme() != null) {
if (resetIntent){
intent.setData(null);
}
try {
StringWriter writer = new StringWriter(intentString.length() * 2);
escapeJavaStyleString(writer, intentString, true, false);
webView.loadUrl("javascript:handleOpenURL('" + writer.toString() + "');");
} catch (IOException ignore) {
}
}
}
// Taken from commons StringEscapeUtils
private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote,
boolean escapeForwardSlash) throws IOException {
if (out == null) {
throw new IllegalArgumentException("The Writer must not be null");
}
if (str == null) {
return;
}
int sz;
sz = str.length();
for (int i = 0; i < sz; i++) {
char ch = str.charAt(i);
// handle unicode
if (ch > 0xfff) {
out.write("\\u" + hex(ch));
} else if (ch > 0xff) {
out.write("\\u0" + hex(ch));
} else if (ch > 0x7f) {
out.write("\\u00" + hex(ch));
} else if (ch < 32) {
switch (ch) {
case '\b':
out.write('\\');
out.write('b');
break;
case '\n':
out.write('\\');
out.write('n');
break;
case '\t':
out.write('\\');
out.write('t');
break;
case '\f':
out.write('\\');
out.write('f');
break;
case '\r':
out.write('\\');
out.write('r');
break;
default:
if (ch > 0xf) {
out.write("\\u00" + hex(ch));
} else {
out.write("\\u000" + hex(ch));
}
break;
}
} else {
switch (ch) {
case '\'':
if (escapeSingleQuote) {
out.write('\\');
}
out.write('\'');
break;
case '"':
out.write('\\');
out.write('"');
break;
case '\\':
out.write('\\');
out.write('\\');
break;
case '/':
if (escapeForwardSlash) {
out.write('\\');
}
out.write('/');
break;
default:
out.write(ch);
break;
}
}
}
}
private static String hex(char ch) {
return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
}
}

View File

@ -0,0 +1,30 @@
module.exports = function(context) {
var fs = context.requireCordovaModule('fs'),
et = context.requireCordovaModule('elementtree'),
path = context.requireCordovaModule('path'),
xml= context.requireCordovaModule('cordova-common').xmlHelpers,
projectRoot = path.join(context.opts.projectRoot, "platforms", "windows");
var MANIFEST_WINDOWS = 'package.windows.appxmanifest',
MANIFEST_PHONE = 'package.phone.appxmanifest',
MANIFEST_WINDOWS10 = 'package.windows10.appxmanifest',
MANIFEST_WINDOWS80 = 'package.windows80.appxmanifest';
function updateManifestFile(manifestPath) {
var doc = xml.parseElementtreeSync(manifestPath);
var root = doc.getroot();
var app = root.find('./Applications/Application');
if (!app) {
throw new Error(manifestPath + ' has incorrect XML structure.');
}
if (!app.find('./Extensions')) {
app.append(new et.Element('Extensions'));
}
fs.writeFileSync(manifestPath, doc.write({indent: 4}), 'utf-8');
}
[MANIFEST_PHONE, MANIFEST_WINDOWS80, MANIFEST_WINDOWS, MANIFEST_WINDOWS10]
.forEach(function(manifestFile) {
updateManifestFile(path.join(projectRoot, manifestFile));
});
}

Some files were not shown because too many files have changed in this diff Show More