@qbix/q 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +678 -0
- package/dist/Metrics.js +2873 -0
- package/dist/Metrics.min.js +95 -0
- package/dist/Q.js +15677 -0
- package/dist/Q.min.js +385 -0
- package/dist/Q.minimal.js +11594 -0
- package/dist/Q.minimal.min.js +286 -0
- package/dist/handlebars-v4.0.10.min.js +29 -0
- package/dist/handlebars.minimal.min.js +1 -0
- package/dist/img/hints/rotate-left.gif +0 -0
- package/dist/img/hints/swipe-down.gif +0 -0
- package/dist/img/hints/swipe-up.gif +0 -0
- package/dist/img/hints/tap.gif +0 -0
- package/dist/img/throbbers/loading.gif +0 -0
- package/dist/jquery.minimal.min.js +18 -0
- package/dist/methods/Q/Audio/load.js +29 -0
- package/dist/methods/Q/Audio/loadVoices.js +36 -0
- package/dist/methods/Q/Audio/play.js +47 -0
- package/dist/methods/Q/Audio/speak.js +149 -0
- package/dist/methods/Q/Crypto/delegate.js +186 -0
- package/dist/methods/Q/Crypto/internalKeypair.js +170 -0
- package/dist/methods/Q/Crypto/sign.js +200 -0
- package/dist/methods/Q/Crypto/verify.js +212 -0
- package/dist/methods/Q/Crypto/verifyDelegated.js +214 -0
- package/dist/methods/Q/Data/Bloom/_internal.js +163 -0
- package/dist/methods/Q/Data/Bloom/create.js +23 -0
- package/dist/methods/Q/Data/Bloom/fromBase64.js +21 -0
- package/dist/methods/Q/Data/Bloom/fromBytes.js +15 -0
- package/dist/methods/Q/Data/Bloom/fromElements.js +33 -0
- package/dist/methods/Q/Data/Merkle/_internal.js +68 -0
- package/dist/methods/Q/Data/Merkle/build.js +29 -0
- package/dist/methods/Q/Data/Merkle/proof.js +50 -0
- package/dist/methods/Q/Data/Merkle/verify.js +32 -0
- package/dist/methods/Q/Data/Prolly/_internal.js +190 -0
- package/dist/methods/Q/Data/Prolly/build.js +28 -0
- package/dist/methods/Q/Data/Prolly/delete.js +28 -0
- package/dist/methods/Q/Data/Prolly/diff.js +70 -0
- package/dist/methods/Q/Data/Prolly/get.js +38 -0
- package/dist/methods/Q/Data/Prolly/set.js +32 -0
- package/dist/methods/Q/Data/compress.js +45 -0
- package/dist/methods/Q/Data/decompress.js +35 -0
- package/dist/methods/Q/Data/decrypt.js +55 -0
- package/dist/methods/Q/Data/derive.js +76 -0
- package/dist/methods/Q/Data/digest.js +29 -0
- package/dist/methods/Q/Data/encrypt.js +61 -0
- package/dist/methods/Q/Data/generateKey.js +40 -0
- package/dist/methods/Q/Data/hkdf.js +44 -0
- package/dist/methods/Q/Data/importKey.js +34 -0
- package/dist/methods/Q/Data/sign.js +42 -0
- package/dist/methods/Q/Data/verify.js +45 -0
- package/dist/methods/Q/Onboarding/handle.js +50 -0
- package/dist/methods/Q/Onboarding/start.js +165 -0
- package/dist/methods/Q/Onboarding/stop.js +21 -0
- package/dist/methods/Q/Sandbox/run.js +392 -0
- package/dist/methods/Q/Tool/define/component.js +218 -0
- package/dist/methods/Q/globalMemoryWalk.js +82 -0
- package/dist/methods/Q/leaves.js +34 -0
- package/dist/methods/Q/registerWebComponent.js +0 -0
- package/dist/methods/Q/sanitize.js +142 -0
- package/dist/test.html +6 -0
- package/dist/tools/Q/lazyload.js +433 -0
- package/package.json +26 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decrypts AES-256-GCM ciphertext.
|
|
5
|
+
*
|
|
6
|
+
* @module Q
|
|
7
|
+
* @class Q.Data
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @static
|
|
12
|
+
* @method decrypt
|
|
13
|
+
* @param {CryptoKey} key AES-GCM key
|
|
14
|
+
* @param {String} ivBase64 Base64-encoded IV (12 bytes)
|
|
15
|
+
* @param {String} ciphertextBase64 Base64-encoded ciphertext + tag
|
|
16
|
+
* @param {Object} [options]
|
|
17
|
+
* @param {Uint8Array} [options.additional] Additional authenticated data (AAD).
|
|
18
|
+
* Must match exactly what was passed to encrypt(), otherwise decryption fails.
|
|
19
|
+
* @param {String} [options.tag] Base64-encoded authentication tag (16 bytes).
|
|
20
|
+
* If provided, the tag is appended to ciphertext before decryption,
|
|
21
|
+
* matching the WebCrypto AES-GCM expectation of ciphertext || tag.
|
|
22
|
+
* If omitted, ciphertextBase64 is assumed to already include the tag.
|
|
23
|
+
* @return {Q.Promise} Resolves Uint8Array plaintext
|
|
24
|
+
*/
|
|
25
|
+
return function Q_Data_decrypt(key, ivBase64, ciphertextBase64, options) {
|
|
26
|
+
options = options || {};
|
|
27
|
+
|
|
28
|
+
var iv = Q.Data.fromBase64(ivBase64);
|
|
29
|
+
var ciphertext = Q.Data.fromBase64(ciphertextBase64);
|
|
30
|
+
|
|
31
|
+
// If a separate tag is provided, concatenate ciphertext || tag
|
|
32
|
+
// to match the WebCrypto AES-GCM wire format
|
|
33
|
+
if (options.tag) {
|
|
34
|
+
var tag = Q.Data.fromBase64(options.tag);
|
|
35
|
+
var combined = new Uint8Array(ciphertext.length + tag.length);
|
|
36
|
+
combined.set(ciphertext, 0);
|
|
37
|
+
combined.set(tag, ciphertext.length);
|
|
38
|
+
ciphertext = combined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var params = {
|
|
42
|
+
name: 'AES-GCM',
|
|
43
|
+
iv: iv
|
|
44
|
+
};
|
|
45
|
+
if (options.additional) {
|
|
46
|
+
params.additionalData = options.additional;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return crypto.subtle.decrypt(params, key, ciphertext)
|
|
50
|
+
.then(function (plaintext) {
|
|
51
|
+
return new Uint8Array(plaintext);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deterministically derives key material from a seed
|
|
5
|
+
* using HKDF with explicit domain separation.
|
|
6
|
+
*
|
|
7
|
+
* PURE FUNCTION:
|
|
8
|
+
* - no randomness
|
|
9
|
+
* - no storage
|
|
10
|
+
* - no side effects
|
|
11
|
+
*
|
|
12
|
+
* Seed must be binary — pass Uint8Array, ArrayBuffer, or a raw
|
|
13
|
+
* binary-compatible type handled by Q.Data.toUint8Array().
|
|
14
|
+
* If you have a hex or base64 string, decode it first with
|
|
15
|
+
* Q.Data.fromHex() or Q.Data.fromBase64() before calling derive().
|
|
16
|
+
*
|
|
17
|
+
* @module Q
|
|
18
|
+
* @class Q.Data
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @static
|
|
23
|
+
* @method derive
|
|
24
|
+
*
|
|
25
|
+
* @param {Uint8Array|ArrayBuffer} seed
|
|
26
|
+
* Raw binary seed material. Must be a typed binary type —
|
|
27
|
+
* not a string. Decode hex/base64 strings before calling.
|
|
28
|
+
* @param {String} label
|
|
29
|
+
* Domain separation label (used as HKDF info). Must be non-empty UTF-8.
|
|
30
|
+
* @param {Object} [options]
|
|
31
|
+
* @param {Number} [options.size=32] Output length in bytes.
|
|
32
|
+
* @param {String} [options.context=""] Context string used to derive salt
|
|
33
|
+
* via SHA-256(context). Empty string is valid and produces a fixed salt.
|
|
34
|
+
*
|
|
35
|
+
* @return {Q.Promise<Uint8Array>} Resolves derived key material.
|
|
36
|
+
*/
|
|
37
|
+
return function Q_Data_derive(seed, label, options) {
|
|
38
|
+
|
|
39
|
+
options = options || {};
|
|
40
|
+
var size = options.size || 32;
|
|
41
|
+
var context = options.context || "";
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------
|
|
44
|
+
// Normalize seed — strict binary types only
|
|
45
|
+
// ---------------------------------------------
|
|
46
|
+
var seedBytes;
|
|
47
|
+
try {
|
|
48
|
+
seedBytes = Q.Data.toUint8Array(seed);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return Q.reject(new Error(
|
|
51
|
+
"derive: seed must be a binary type (Uint8Array, ArrayBuffer). " +
|
|
52
|
+
"Decode hex or base64 strings before calling derive(). " +
|
|
53
|
+
"Original error: " + e.message
|
|
54
|
+
));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!label || typeof label !== "string") {
|
|
58
|
+
return Q.reject(new Error("derive: label must be a non-empty string"));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------
|
|
62
|
+
// salt = SHA-256(context)
|
|
63
|
+
// ---------------------------------------------
|
|
64
|
+
var contextBytes = new TextEncoder().encode(context);
|
|
65
|
+
|
|
66
|
+
return Q.Data.digest("SHA-256", contextBytes)
|
|
67
|
+
.then(function (salt) {
|
|
68
|
+
return Q.Data.hkdf(
|
|
69
|
+
seedBytes, // IKM
|
|
70
|
+
salt, // salt = SHA-256(context)
|
|
71
|
+
label, // info = label
|
|
72
|
+
size
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
/**
|
|
3
|
+
* Q plugin's front end code
|
|
4
|
+
*
|
|
5
|
+
* @module Q
|
|
6
|
+
* @class Q.Data
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a digest
|
|
11
|
+
* @static
|
|
12
|
+
* @method digest
|
|
13
|
+
* @param {String} algorithm
|
|
14
|
+
* @param {String} payload
|
|
15
|
+
* @param {Function} callback receives (err, result)
|
|
16
|
+
* @return {Q.Promise} Returns a Uint8Array
|
|
17
|
+
*/
|
|
18
|
+
return function Q_Data_digest(algorithm, payload, callback) {
|
|
19
|
+
var encoded = new TextEncoder().encode(payload);
|
|
20
|
+
|
|
21
|
+
return crypto.subtle.digest(algorithm, encoded)
|
|
22
|
+
.then(function (buffer) {
|
|
23
|
+
var result = new Uint8Array(buffer);
|
|
24
|
+
callback && callback(null, result);
|
|
25
|
+
return result;
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encrypts plaintext using AES-256-GCM.
|
|
5
|
+
*
|
|
6
|
+
* @module Q
|
|
7
|
+
* @class Q.Data
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @static
|
|
12
|
+
* @method encrypt
|
|
13
|
+
* @param {CryptoKey} key AES-GCM key
|
|
14
|
+
* @param {Uint8Array} plaintext
|
|
15
|
+
* @param {Object} [options]
|
|
16
|
+
* @param {Uint8Array|String} [options.iv] Optional IV (12 bytes).
|
|
17
|
+
* If omitted, a random IV is generated via crypto.getRandomValues.
|
|
18
|
+
* Pass a derived IV (e.g. from Q.Data.derive) for deterministic /
|
|
19
|
+
* convergent encryption, such as Safecloud chunk encryption where
|
|
20
|
+
* the same content should always produce the same ciphertext and CID.
|
|
21
|
+
* WARNING: never reuse the same (key, iv) pair for different plaintexts.
|
|
22
|
+
* @param {Uint8Array} [options.additional] Additional authenticated data (AAD).
|
|
23
|
+
* Authenticated but not encrypted. Pass e.g. a chunk index or stream ID
|
|
24
|
+
* to bind the ciphertext to its position and prevent reordering attacks.
|
|
25
|
+
* @return {Q.Promise} Resolves { iv: String, ciphertext: String, tag: String }
|
|
26
|
+
* All values are base64-encoded.
|
|
27
|
+
*/
|
|
28
|
+
return function Q_Data_encrypt(key, plaintext, options) {
|
|
29
|
+
options = options || {};
|
|
30
|
+
|
|
31
|
+
var iv;
|
|
32
|
+
if (options.iv) {
|
|
33
|
+
iv = (typeof options.iv === 'string')
|
|
34
|
+
? Q.Data.fromBase64(options.iv)
|
|
35
|
+
: Q.Data.toUint8Array(options.iv);
|
|
36
|
+
} else {
|
|
37
|
+
iv = crypto.getRandomValues(new Uint8Array(12));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var params = {
|
|
41
|
+
name: 'AES-GCM',
|
|
42
|
+
iv: iv
|
|
43
|
+
};
|
|
44
|
+
if (options.additional) {
|
|
45
|
+
params.additionalData = options.additional;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return crypto.subtle.encrypt(params, key, plaintext)
|
|
49
|
+
.then(function (ciphertext) {
|
|
50
|
+
var ct = new Uint8Array(ciphertext);
|
|
51
|
+
var tag = ct.slice(ct.length - 16);
|
|
52
|
+
var data = ct.slice(0, ct.length - 16);
|
|
53
|
+
return {
|
|
54
|
+
iv: Q.Data.toBase64(iv),
|
|
55
|
+
ciphertext: Q.Data.toBase64(data),
|
|
56
|
+
tag: Q.Data.toBase64(tag)
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
/**
|
|
3
|
+
* Generates a public/private key pair and exports them as base64 strings.
|
|
4
|
+
* Compatible with Q.Data.verify and Q.Data.sign using SubtleCrypto.
|
|
5
|
+
*
|
|
6
|
+
* @module Q
|
|
7
|
+
* @class Q.Data
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @static
|
|
12
|
+
* @method generateKey
|
|
13
|
+
* @param {Object} [algo] optional algorithm override
|
|
14
|
+
* @param {String} [algo.name="ECDSA"]
|
|
15
|
+
* @param {String} [algo.namedCurve="P-256"]
|
|
16
|
+
* @param {String} [algo.hash="SHA-256"]
|
|
17
|
+
* @return {Q.Promise} Resolves with { publicKey, privateKey, algorithm },
|
|
18
|
+
* where each key is a base64 string and algorithm is the resolved object.
|
|
19
|
+
*/
|
|
20
|
+
return function Q_Data_generateKey(algo) {
|
|
21
|
+
algo = Q.extend({
|
|
22
|
+
name: 'ECDSA',
|
|
23
|
+
namedCurve: 'P-256',
|
|
24
|
+
hash: { name: "SHA-256" }
|
|
25
|
+
}, algo);
|
|
26
|
+
|
|
27
|
+
return crypto.subtle.generateKey(algo, true, ['sign', 'verify']).then(function (keyPair) {
|
|
28
|
+
return Q.Promise.all([
|
|
29
|
+
crypto.subtle.exportKey('raw', keyPair.publicKey),
|
|
30
|
+
crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
|
|
31
|
+
]).then(function ([pubKeyBuf, privKeyBuf]) {
|
|
32
|
+
return {
|
|
33
|
+
publicKey: Q.Data.toBase64(pubKeyBuf),
|
|
34
|
+
privateKey: Q.Data.toBase64(privKeyBuf),
|
|
35
|
+
algorithm: algo
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HKDF using SHA-256 (RFC 5869).
|
|
5
|
+
*
|
|
6
|
+
* @module Q
|
|
7
|
+
* @class Q.Data
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @static
|
|
12
|
+
* @method hkdf
|
|
13
|
+
* @param {Uint8Array} ikm Input key material
|
|
14
|
+
* @param {Uint8Array} salt Salt
|
|
15
|
+
* @param {String} info Context string
|
|
16
|
+
* @param {Number} [length=32] Output length in bytes
|
|
17
|
+
* @return {Q.Promise} Resolves Uint8Array
|
|
18
|
+
*/
|
|
19
|
+
return function Q_Data_hkdf(ikm, salt, info, length) {
|
|
20
|
+
length = length || 32;
|
|
21
|
+
info = info || "";
|
|
22
|
+
|
|
23
|
+
return crypto.subtle.importKey(
|
|
24
|
+
"raw",
|
|
25
|
+
ikm,
|
|
26
|
+
{ name: "HKDF" },
|
|
27
|
+
false,
|
|
28
|
+
["deriveBits"]
|
|
29
|
+
).then(function (key) {
|
|
30
|
+
return crypto.subtle.deriveBits(
|
|
31
|
+
{
|
|
32
|
+
name: "HKDF",
|
|
33
|
+
hash: "SHA-256",
|
|
34
|
+
salt: salt,
|
|
35
|
+
info: new TextEncoder().encode(info)
|
|
36
|
+
},
|
|
37
|
+
key,
|
|
38
|
+
length * 8
|
|
39
|
+
);
|
|
40
|
+
}).then(function (bits) {
|
|
41
|
+
return new Uint8Array(bits);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module Q
|
|
5
|
+
* @class Q.Data
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Imports raw key bytes into a CryptoKey for use with SubtleCrypto.
|
|
10
|
+
* @static
|
|
11
|
+
* @method importKey
|
|
12
|
+
* @param {Uint8Array} keyBytes Raw key material
|
|
13
|
+
* @param {Object} [algo] optional algorithm override
|
|
14
|
+
* @param {String} [algo.name="AES-GCM"]
|
|
15
|
+
* @param {Number} [algo.length=256]
|
|
16
|
+
* @param {Array} [algo.usages=["encrypt", "decrypt"]]
|
|
17
|
+
* @return {Q.Promise} Resolves CryptoKey
|
|
18
|
+
*/
|
|
19
|
+
return function Q_Data_importKey(keyBytes, algo) {
|
|
20
|
+
algo = Q.extend({
|
|
21
|
+
name: "AES-GCM",
|
|
22
|
+
length: 256,
|
|
23
|
+
usages: ["encrypt", "decrypt"]
|
|
24
|
+
}, algo);
|
|
25
|
+
|
|
26
|
+
return crypto.subtle.importKey(
|
|
27
|
+
"raw",
|
|
28
|
+
keyBytes,
|
|
29
|
+
{ name: algo.name, length: algo.length },
|
|
30
|
+
false,
|
|
31
|
+
algo.usages
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
/**
|
|
3
|
+
* Q plugin's front end code
|
|
4
|
+
*
|
|
5
|
+
* @module Q
|
|
6
|
+
* @class Q.Data
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Encodes text data with TextEncoder, then signs it with
|
|
11
|
+
* each of the private keys. Returns a promise that is rejected
|
|
12
|
+
* if any of the keys fail to be imported.
|
|
13
|
+
* @static
|
|
14
|
+
* @method sign
|
|
15
|
+
* @param {String} data the data to sign
|
|
16
|
+
* @param {Array} privateKeyPKCS8Strings array of strings containing
|
|
17
|
+
* PKCS8 encodings of private keys
|
|
18
|
+
* @param {Object} [algo] you can specify a different algorithm
|
|
19
|
+
* @param {String} [algo.name="ECDSA"]
|
|
20
|
+
* @param {String} [algo.namedCurve="P-256"]
|
|
21
|
+
* @param {String} [algo.hash="SHA-256"]
|
|
22
|
+
* @param {Function} callback receives (err, result)
|
|
23
|
+
* @return {Q.Promise} Resolves an array of ArrayBuffers containing
|
|
24
|
+
* data signed with each of the corresponding public keys.
|
|
25
|
+
* (You can use Q.Data.toBase64 to convert them into strings).
|
|
26
|
+
* Or rejects if any of the keys fail to be imported.
|
|
27
|
+
*/
|
|
28
|
+
return function Q_Data_sign(data, privateKeyPKCS8Strings, algo) {
|
|
29
|
+
algo = Q.extend({
|
|
30
|
+
name: 'ECDSA',
|
|
31
|
+
namedCurve: 'P-256',
|
|
32
|
+
hash: { name: "SHA-256" }
|
|
33
|
+
}, algo);
|
|
34
|
+
return Q.Promise.all(
|
|
35
|
+
privateKeyPKCS8Strings.map(pks =>
|
|
36
|
+
crypto.subtle.importKey('pkcs8', Q.Data.fromBase64(pks), algo, false, ['sign'])
|
|
37
|
+
.then(privateKey => crypto.subtle.sign(algo, privateKey, new TextEncoder().encode(data)))
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
/**
|
|
3
|
+
* Q plugin's front end code
|
|
4
|
+
*
|
|
5
|
+
* @module Q
|
|
6
|
+
* @class Q.Data
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Encodes text data with TextEncoder, then verifies it with
|
|
11
|
+
* each of the public keys.
|
|
12
|
+
* @static
|
|
13
|
+
* @method verify
|
|
14
|
+
* @param {String} data the signed payload to verify
|
|
15
|
+
* @param {Array} publicKeyRawStrings array of exported public keys
|
|
16
|
+
* @param {Array} signatures array of ArrayBuffer signatures
|
|
17
|
+
* previously generated with the corresponding public keys.
|
|
18
|
+
* @param {Object} [algo] you can specify a different algorithm
|
|
19
|
+
* @param {String} [algo.name="ECDSA"]
|
|
20
|
+
* @param {String} [algo.namedCurve="P-256"]
|
|
21
|
+
* @param {String} [algo.hash="SHA-256"]
|
|
22
|
+
* @param {Function} callback receives (err, result)
|
|
23
|
+
* @return {Q.Promise} Resolves with array of booleans corresponding
|
|
24
|
+
* to each public key, indicating whether the verifications succeeded.
|
|
25
|
+
* Or rejects if any of the keys fail to be imported.
|
|
26
|
+
*/
|
|
27
|
+
return function Q_Data_verify(data, publicKeyRawStrings, signatures, algo) {
|
|
28
|
+
const sigs = [];
|
|
29
|
+
for (var sig of signatures) {
|
|
30
|
+
sigs.push(typeof sig === 'string' ? Q.Data.fromBase64(sig) : sig);
|
|
31
|
+
}
|
|
32
|
+
algo = Q.extend({
|
|
33
|
+
name: 'ECDSA',
|
|
34
|
+
namedCurve: 'P-256',
|
|
35
|
+
hash: { name: "SHA-256" }
|
|
36
|
+
}, algo);
|
|
37
|
+
return Q.Promise.all(
|
|
38
|
+
publicKeyRawStrings.map((pks, i) =>
|
|
39
|
+
crypto.subtle.importKey('raw', Q.Data.fromBase64(pks), algo, false, ['verify']
|
|
40
|
+
).then(publicKey => crypto.subtle.verify(algo, publicKey, sigs[i], new TextEncoder().encode(data)))
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
/**
|
|
3
|
+
* Q plugin's front end code
|
|
4
|
+
*
|
|
5
|
+
* @module Q
|
|
6
|
+
* @class Q.Onboarding
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handles a specific hint event, if it hasn't already occurred.
|
|
11
|
+
* Ignores the "after" instruction of the event, if any.
|
|
12
|
+
* But checks whether elements are visible, otherwise returns false.
|
|
13
|
+
* @static
|
|
14
|
+
* @method handle
|
|
15
|
+
* @param {String} key
|
|
16
|
+
* @param {Function} [callback]
|
|
17
|
+
*/
|
|
18
|
+
return function _Q_Onboarding_handle(key, callback) {
|
|
19
|
+
// Added: support returning a Promise in addition to callback
|
|
20
|
+
var promise = new Promise(function (resolve) {
|
|
21
|
+
var lsk = Q.Onboarding.prefix + key;
|
|
22
|
+
var event = Q.Onboarding.events[key];
|
|
23
|
+
if (!localStorage[lsk] || !event || event.occurred || event.stopped) {
|
|
24
|
+
if (callback) callback(false);
|
|
25
|
+
return resolve(false);
|
|
26
|
+
}
|
|
27
|
+
var appeared = [];
|
|
28
|
+
var visible = false;
|
|
29
|
+
var selectorToAppear = Q.Onboarding.selectors[key].appear;
|
|
30
|
+
var elementsToAppear = document.querySelectorAll(selectorToAppear);
|
|
31
|
+
for (var i=0; i<elementsToAppear.length; ++i) {
|
|
32
|
+
var r = elementsToAppear[i].getBoundingClientRect();
|
|
33
|
+
if (r.width && r.height) {
|
|
34
|
+
visible = true;
|
|
35
|
+
appeared.push(elementsToAppear[i]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!visible) {
|
|
39
|
+
if (callback) callback(false);
|
|
40
|
+
return resolve(false);
|
|
41
|
+
}
|
|
42
|
+
Q.Onboarding.waitToDisappear[key] = false;
|
|
43
|
+
Q.handle(Q.Onboarding.events[key], {}, [appeared, key]);
|
|
44
|
+
if (callback) callback(true);
|
|
45
|
+
resolve(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return promise;
|
|
49
|
+
};
|
|
50
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
/**
|
|
3
|
+
* Q plugin's front end code
|
|
4
|
+
*
|
|
5
|
+
* @module Q
|
|
6
|
+
* @class Q.Onboarding
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Starts an onboarding process. You can also trigger
|
|
11
|
+
* Q.Onboarding.event manually with Q.handle().
|
|
12
|
+
* @static
|
|
13
|
+
* @method start
|
|
14
|
+
* @param {Object} [instructions] An object of key: instructionObject pairs
|
|
15
|
+
* where instructionObject has at least "appear" key, with value = CSS selector.
|
|
16
|
+
* It can also have optional:
|
|
17
|
+
* "targets" (selector to override what to hint),
|
|
18
|
+
* "disappear" (selector to override what should disappear before hint event stops)
|
|
19
|
+
* "after" (key of a hint event that has to stop before this hint starts)
|
|
20
|
+
* "options" (for options passed to Q.Visual.hint, merged over Q.Onboarding.options.hint)
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
return function _Q_Onboarding_start(instructions) {
|
|
24
|
+
// Added: track whether onboarding actually started
|
|
25
|
+
var started = false;
|
|
26
|
+
var resolveStarted;
|
|
27
|
+
var startedPromise = new Promise(function (resolve) {
|
|
28
|
+
resolveStarted = resolve;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!instructions) {
|
|
32
|
+
resolveStarted(false);
|
|
33
|
+
return startedPromise;
|
|
34
|
+
}
|
|
35
|
+
if (typeof Q.Onboarding.textName === 'string') {
|
|
36
|
+
Q.Text.get(Q.Onboarding.textName).then(function (text) {
|
|
37
|
+
Q.Onboarding.text = text;
|
|
38
|
+
if (!Q.isEmpty(Q.Onboarding.textPath)) {
|
|
39
|
+
Q.Onboarding.text = Q.getObject(
|
|
40
|
+
Q.Onboarding.textPath, Q.Onboarding.text
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
var selectors = Q.Onboarding.selectors;
|
|
46
|
+
var events = Q.Onboarding.events;
|
|
47
|
+
var waitToDisappear = Q.Onboarding.waitToDisappear = {};
|
|
48
|
+
Q.each(instructions, function (k) {
|
|
49
|
+
var instr = instructions[k];
|
|
50
|
+
events[k] = new Q.Event(function (targets, k) {
|
|
51
|
+
var hintTargets = (selectors[k].targets)
|
|
52
|
+
? document.querySelectorAll(selectors[k].targets)
|
|
53
|
+
: targets;
|
|
54
|
+
var lsk = Q.Onboarding.prefix + k;
|
|
55
|
+
if (localStorage[lsk]) {
|
|
56
|
+
events[k].stop(); // it already occurred before
|
|
57
|
+
} else {
|
|
58
|
+
var o = Q.extend({}, Q.Onboarding.options.hint, instr.options);
|
|
59
|
+
if (Q.Onboarding.text[k]) {
|
|
60
|
+
o.tooltip = Q.extend({
|
|
61
|
+
text: Q.Onboarding.text && Q.Onboarding.text[k]
|
|
62
|
+
}, o.tooltip);
|
|
63
|
+
}
|
|
64
|
+
o.onHide = Q.extend(o.onHide, {
|
|
65
|
+
'Q.Onboarding': function () {
|
|
66
|
+
setTimeout(function () {
|
|
67
|
+
if (!waitToDisappear[k]) {
|
|
68
|
+
// They never appeared, or weren't specified
|
|
69
|
+
// So go to the next hint.
|
|
70
|
+
localStorage[Q.Onboarding.prefix + k] = 'stopped';
|
|
71
|
+
events[k].stop();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}, Q.Onboarding.interval.afterHintHide || 3000);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
Q.Visual.stopHints(); // stop previous hints
|
|
78
|
+
Q.Visual.hint(hintTargets, o);
|
|
79
|
+
localStorage[lsk] = 'occurred';
|
|
80
|
+
}
|
|
81
|
+
}, 'Q.hint');
|
|
82
|
+
selectors[k] = { appear: instr.appear };
|
|
83
|
+
Q.take(instr, ['targets', 'disappear'], selectors[k]);
|
|
84
|
+
});
|
|
85
|
+
Q.Onboarding.intervalId && clearInterval(Q.Onboarding.intervalId);
|
|
86
|
+
Q.Onboarding.intervalId = setInterval(function () {
|
|
87
|
+
for (var k in selectors) {
|
|
88
|
+
var selectorToAppear = selectors[k].appear;
|
|
89
|
+
var selectorToDisappear = selectors[k].disappear || selectors[k].appear;
|
|
90
|
+
var elementsToAppear = document.querySelectorAll(selectorToAppear);
|
|
91
|
+
var appeared = [];
|
|
92
|
+
var visible = false;
|
|
93
|
+
for (var i=0; i<elementsToAppear.length; ++i) {
|
|
94
|
+
var r = elementsToAppear[i].getBoundingClientRect();
|
|
95
|
+
var s = Q.Onboarding.treatAsVisible;
|
|
96
|
+
var elements = Array.prototype.slice.call(document.querySelectorAll(s));
|
|
97
|
+
var treatAsVisible = elements.includes(elementsToAppear[i]);
|
|
98
|
+
if (treatAsVisible || (r.width && r.height)) {
|
|
99
|
+
visible = true;
|
|
100
|
+
appeared.push(elementsToAppear[i]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!events[k].occurred && visible) {
|
|
104
|
+
var after = instructions[k]?.after;
|
|
105
|
+
if (typeof after === 'string') {
|
|
106
|
+
after = [after];
|
|
107
|
+
}
|
|
108
|
+
var proceed = false;
|
|
109
|
+
Q.each(after, function (i, s) {
|
|
110
|
+
if (Q.Onboarding.events[s]?.stopped
|
|
111
|
+
|| localStorage[Q.Onboarding.prefix + s] === 'stopped') {
|
|
112
|
+
proceed = true;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
if (!after || proceed) {
|
|
117
|
+
Q.Onboarding.waitToDisappear[k] = false;
|
|
118
|
+
Q.handle(events[k], instructions, [appeared, k]);
|
|
119
|
+
// Added: resolve when onboarding first starts
|
|
120
|
+
if (!started) {
|
|
121
|
+
started = true;
|
|
122
|
+
resolveStarted(true);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!events[k].occurred || events[k].stopped) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (selectorToDisappear === selectorToAppear) {
|
|
130
|
+
// event will stop onHide of the hints
|
|
131
|
+
continue; // nothing to wait for
|
|
132
|
+
}
|
|
133
|
+
visible = false;
|
|
134
|
+
var elementsToDisappear = document.querySelectorAll(selectorToDisappear);
|
|
135
|
+
for (var i=0; i<elementsToDisappear.length; ++i) {
|
|
136
|
+
var r = elementsToDisappear[i].getBoundingClientRect();
|
|
137
|
+
var s = Q.Onboarding.treatAsVisible;
|
|
138
|
+
var elements = Array.prototype.slice.call(document.querySelectorAll(s));
|
|
139
|
+
var treatAsVisible = elements.includes(elementsToDisappear[i]);
|
|
140
|
+
if (treatAsVisible || (r.width && r.height)) {
|
|
141
|
+
visible = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (visible) {
|
|
146
|
+
waitToDisappear[k] = true;
|
|
147
|
+
}
|
|
148
|
+
if (waitToDisappear[k] && !visible) {
|
|
149
|
+
localStorage[Q.Onboarding.prefix + k] = 'stopped';
|
|
150
|
+
events[k].stop();
|
|
151
|
+
waitToDisappear[k] = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}, 100);
|
|
155
|
+
|
|
156
|
+
// Added: if nothing ever started after a grace period, resolve false
|
|
157
|
+
setTimeout(function () {
|
|
158
|
+
if (!started) {
|
|
159
|
+
resolveStarted(false);
|
|
160
|
+
}
|
|
161
|
+
}, 1000);
|
|
162
|
+
|
|
163
|
+
return startedPromise;
|
|
164
|
+
};
|
|
165
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Q.exports(function (Q) {
|
|
2
|
+
/**
|
|
3
|
+
* Q plugin's front end code
|
|
4
|
+
*
|
|
5
|
+
* @module Q
|
|
6
|
+
* @class Q.Onboarding
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Starts an onboarding process
|
|
11
|
+
* @static
|
|
12
|
+
* @method start
|
|
13
|
+
* @param {Object} [options={}] Can be one of the following options
|
|
14
|
+
*/
|
|
15
|
+
return function _Q_Onboarding_stop(key) {
|
|
16
|
+
var instructions = Q.Onboarding.processes[key];
|
|
17
|
+
if (instructions) {
|
|
18
|
+
clearInterval(instructions.intervalId);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
});
|