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
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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, "");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
580
core/crypto.ts
|
@ -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!;
|
||||
// }
|
||||
// }
|
125
core/jose.ts
|
@ -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[];
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
60
package.json
|
@ -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/*"]
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 782 B After Width: | Height: | Size: 782 B |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
@ -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
|
||||
}
|
|
@ -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>
|
|
@ -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": {
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": {}
|
||||
}
|
|
@ -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.
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
ext {ANDROID_SUPPORT_VERSION = "27.+"}
|
90
packages/cordova/plugins/cordova-android-support-gradle-release/scripts/apply-changes.js
vendored
Normal 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;
|
||||
};
|
|
@ -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.
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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
|
|
@ -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.
|
|
@ -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>
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
49
packages/cordova/plugins/cordova-custom-config/hooks/triggerExampleProjBuild.js
vendored
Normal 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);
|
||||
});
|
||||
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"images" : [],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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.
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
0.1.9
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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
|
33
packages/cordova/plugins/cordova-plugin-app-version/src/windows/AppVersionProxy.js
vendored
Normal 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);
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
18
packages/cordova/plugins/cordova-plugin-app-version/www/blackberry10/AppVersionProxy.js
vendored
Normal 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);
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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.
|
|
@ -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`
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
```
|
|
@ -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
|
22
packages/cordova/plugins/cordova-plugin-crosswalk-webview/hooks/after_build/000-build_64_bit.js
vendored
Normal 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;
|
||||
|
||||
};
|
|
@ -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;
|
||||
|
||||
};
|
22
packages/cordova/plugins/cordova-plugin-crosswalk-webview/hooks/before_build/000-build_64_bit.js
vendored
Normal 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;
|
||||
|
||||
};
|
|
@ -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;
|
||||
|
||||
};
|
211
packages/cordova/plugins/cordova-plugin-crosswalk-webview/hooks/update_config.js
vendored
Normal 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();
|
||||
|
||||
};
|
|
@ -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"
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
|
@ -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>
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
30
packages/cordova/plugins/cordova-plugin-customurlscheme/src/windows/hooks/prepare-manifest.js
vendored
Normal 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));
|
||||
});
|
||||
}
|