Bug 1919799 - Add QR code generation for QR export. r=mkmelin,dandarnell
Differential Revision: https://phabricator.services.mozilla.com/D224327 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
f608ce48a6
Коммит
1d0e68af39
|
@ -1335,3 +1335,37 @@ 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.
|
||||
</pre>
|
||||
|
||||
<h1><a id="tb-qrcodejs"></a>qrcodejs License</h1>
|
||||
<p>This license applies to the following files:</p>
|
||||
<ul>
|
||||
<li><code>third_party/qrcode</code></li>
|
||||
</ul>
|
||||
|
||||
<pre>
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2020, Dan Jackson
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
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 HOLDER 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.
|
||||
</pre>
|
||||
|
|
|
@ -32,4 +32,5 @@
|
|||
</li>
|
||||
<li><a href="about:license#tb-sdp-transform">sdp-transform License</a></li>
|
||||
<li><a href="about:license#tb-jwt-decode">jwt-decode License</a></li>
|
||||
<li><a href="about:license#tb-qrcodejs">qrcodejs License</a></li>
|
||||
</ul>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -22,7 +22,7 @@ const { FeedUtils } = ChromeUtils.importESModule(
|
|||
* @returns {nsIMsgAccount} Created account with associated default identity and
|
||||
* servers.
|
||||
*/
|
||||
function createMailAccount(name, emailLocalPart, protocol) {
|
||||
async function createMailAccount(name, emailLocalPart, protocol) {
|
||||
const server = MailServices.accounts.createIncomingServer(
|
||||
name,
|
||||
"foo.invalid",
|
||||
|
@ -31,15 +31,35 @@ function createMailAccount(name, emailLocalPart, protocol) {
|
|||
server.password = "password";
|
||||
const identity = MailServices.accounts.createIdentity();
|
||||
identity.email = `${emailLocalPart}@foo.invalid`;
|
||||
identity.fullName = name;
|
||||
const account = MailServices.accounts.createAccount();
|
||||
account.incomingServer = server;
|
||||
account.addIdentity(identity);
|
||||
const outgoing = MailServices.outgoingServer.createServer("smtp");
|
||||
outgoing.QueryInterface(Ci.nsISmtpServer);
|
||||
outgoing.username = name;
|
||||
outgoing.hostname = "foo.invalid";
|
||||
outgoing.port = 587;
|
||||
identity.smtpServerKey = outgoing.key;
|
||||
const login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
|
||||
Ci.nsILoginInfo
|
||||
);
|
||||
login.init(
|
||||
"smtp://foo.invalid",
|
||||
null,
|
||||
"smtp://foo.invalid",
|
||||
name,
|
||||
"smtppass",
|
||||
"",
|
||||
""
|
||||
);
|
||||
await Services.logins.addLoginAsync(login);
|
||||
// Setting password last since setting other things might clear it.
|
||||
outgoing.password = "smtppass";
|
||||
return account;
|
||||
}
|
||||
|
||||
add_task(function test_getEligibleAccounts() {
|
||||
add_task(async function test_getEligibleAccounts() {
|
||||
const emptyEligibleAccounts = QRExport.getEligibleAccounts();
|
||||
|
||||
Assert.deepEqual(
|
||||
|
@ -49,11 +69,15 @@ add_task(function test_getEligibleAccounts() {
|
|||
);
|
||||
|
||||
// Eligible accounts
|
||||
const imapAccount = createMailAccount("imap@foo.invalid", "imap", "imap");
|
||||
const popAccount = createMailAccount("pop", "tinderbox", "pop3");
|
||||
const imapAccount = await createMailAccount(
|
||||
"imap@foo.invalid",
|
||||
"imap",
|
||||
"imap"
|
||||
);
|
||||
const popAccount = await createMailAccount("pop", "tinderbox", "pop3");
|
||||
|
||||
// Ineligible accounts
|
||||
const unsupportedIncomingAuthAccount = createMailAccount(
|
||||
const unsupportedIncomingAuthAccount = await createMailAccount(
|
||||
"incomingauth",
|
||||
"incomingauth",
|
||||
"imap"
|
||||
|
@ -61,7 +85,7 @@ add_task(function test_getEligibleAccounts() {
|
|||
unsupportedIncomingAuthAccount.incomingServer.authMethod =
|
||||
Ci.nsMsgAuthMethod.GSSAPI;
|
||||
|
||||
const unsupportedOutgoingAuthAccount = createMailAccount(
|
||||
const unsupportedOutgoingAuthAccount = await createMailAccount(
|
||||
"outgoingauth",
|
||||
"outgoingauth",
|
||||
"imap"
|
||||
|
@ -71,7 +95,7 @@ add_task(function test_getEligibleAccounts() {
|
|||
);
|
||||
unspoortedAuthOutgoingServer.authMethod = Ci.nsMsgAuthMethod.GSSAPI;
|
||||
|
||||
const noOutgoingServerAccount = createMailAccount(
|
||||
const noOutgoingServerAccount = await createMailAccount(
|
||||
"nooutgoing",
|
||||
"nooutgoing",
|
||||
"imap"
|
||||
|
@ -83,7 +107,7 @@ add_task(function test_getEligibleAccounts() {
|
|||
unsupportedOutgoingAuthAccount,
|
||||
noOutgoingServerAccount,
|
||||
// Non-ASCII email
|
||||
createMailAccount("nonascii", "unüblich", "imap"),
|
||||
await createMailAccount("nonascii", "unüblich", "imap"),
|
||||
// Different account types
|
||||
MailServices.accounts.createLocalMailAccount(),
|
||||
FeedUtils.createRssAccount("qrExport"),
|
||||
|
@ -107,3 +131,157 @@ add_task(function test_getEligibleAccounts() {
|
|||
MailServices.accounts.removeAccount(account, false);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_getAccountData() {
|
||||
const account = await createMailAccount("encode", "encode", "imap");
|
||||
|
||||
const dataWithoutPasswords = QRExport.getAccountData(account.key, false);
|
||||
|
||||
Assert.deepEqual(
|
||||
dataWithoutPasswords,
|
||||
[
|
||||
[
|
||||
0,
|
||||
"foo.invalid",
|
||||
143,
|
||||
0,
|
||||
1,
|
||||
"encode",
|
||||
"Mail for encode@foo.invalid",
|
||||
"",
|
||||
],
|
||||
[
|
||||
[
|
||||
[0, "foo.invalid", 587, 0, 1, "encode", ""],
|
||||
["encode@foo.invalid", "encode"],
|
||||
],
|
||||
],
|
||||
],
|
||||
"Should contain expected account data without passwords"
|
||||
);
|
||||
|
||||
const dataWithPasswords = QRExport.getAccountData(account.key, true);
|
||||
|
||||
Assert.deepEqual(
|
||||
dataWithPasswords,
|
||||
[
|
||||
[
|
||||
0,
|
||||
"foo.invalid",
|
||||
143,
|
||||
0,
|
||||
1,
|
||||
"encode",
|
||||
"Mail for encode@foo.invalid",
|
||||
"password",
|
||||
],
|
||||
[
|
||||
[
|
||||
[0, "foo.invalid", 587, 0, 1, "encode", "smtppass"],
|
||||
["encode@foo.invalid", "encode"],
|
||||
],
|
||||
],
|
||||
],
|
||||
"Should contain expected account data with passwords"
|
||||
);
|
||||
|
||||
MailServices.accounts.removeAccount(account, false);
|
||||
});
|
||||
|
||||
add_task(function test_getQRData() {
|
||||
const chunk = QRExport.getQRData(["foo", "bar"], 3, 9);
|
||||
|
||||
Assert.deepEqual(
|
||||
chunk,
|
||||
[1, [3, 9], "foo", "bar"],
|
||||
"Should match data format v1"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_renderQR() {
|
||||
const loremQR = QRExport.renderQR("lorem ipsum");
|
||||
|
||||
// Using Assert.ok for these assertions, so we don't log 7KB+ of data URI for
|
||||
// every assertion.
|
||||
|
||||
Assert.ok(
|
||||
/^data:image\/svg\+xml,[a-zA-Z0-9%-.]+$/.test(loremQR),
|
||||
"Result should be a data URI for an SVG"
|
||||
);
|
||||
Assert.ok(
|
||||
QRExport.renderQR("foo bar") != loremQR,
|
||||
"Result should vary by input"
|
||||
);
|
||||
Assert.ok(
|
||||
QRExport.renderQR("lorem ipsum") == loremQR,
|
||||
"Should return consistent result"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_getQRCode_single() {
|
||||
const account = await createMailAccount("getcode", "getcode", "imap");
|
||||
|
||||
const qrCodes = QRExport.getQRCodes([account.key], true);
|
||||
|
||||
Assert.ok(Array.isArray(qrCodes), "Should get an array");
|
||||
Assert.equal(
|
||||
qrCodes.length,
|
||||
1,
|
||||
"Should only get a single chunk for one account"
|
||||
);
|
||||
Assert.ok(
|
||||
/^data:image\/svg\+xml,[a-zA-Z0-9%-.]+$/.test(qrCodes[0]),
|
||||
"QR code should be a data URI for an SVG"
|
||||
);
|
||||
|
||||
MailServices.accounts.removeAccount(account, false);
|
||||
});
|
||||
|
||||
add_task(async function test_getQRCode_multipleChunks() {
|
||||
const accounts = [
|
||||
await createMailAccount("accountone", "firstaccount", "imap"),
|
||||
await createMailAccount("accounttwo", "secondaccount", "pop3"),
|
||||
await createMailAccount(
|
||||
"accountthree@foo.invalid",
|
||||
"thirdaccountbutthisonewithalongmail",
|
||||
"imap"
|
||||
),
|
||||
await createMailAccount("accountfour", "fourthaccount", "imap"),
|
||||
];
|
||||
|
||||
const qrCodes = QRExport.getQRCodes(
|
||||
accounts.map(account => account.key),
|
||||
true
|
||||
);
|
||||
|
||||
Assert.ok(Array.isArray(qrCodes), "Should get an array");
|
||||
Assert.equal(qrCodes.length, 2, "Should get two QR codes for four accounts");
|
||||
Assert.ok(
|
||||
/^data:image\/svg\+xml,[a-zA-Z0-9%-.]+$/.test(qrCodes[0]),
|
||||
"QR code 1 should be a data URI for an SVG"
|
||||
);
|
||||
Assert.ok(
|
||||
/^data:image\/svg\+xml,[a-zA-Z0-9%-.]+$/.test(qrCodes[1]),
|
||||
"QR code 2 should be a data URI for an SVG"
|
||||
);
|
||||
|
||||
// Snapshot test to ensure the data format encoded in the QR code is
|
||||
// consistent, since we can't check the full encoded JSON blob without
|
||||
// decoding the QR code. Instead we assume that someone tested the current
|
||||
// implementation and accepted it as correct.
|
||||
|
||||
// In case the snapshot needs to be updated, uncomment this line for a test
|
||||
// run.
|
||||
// await IOUtils.writeJSON(do_get_file("resources/qrdata.txt").path, qrCodes);
|
||||
const qrCodeSnapshot = await IOUtils.readUTF8(
|
||||
do_get_file("resources/qrdata.txt").path
|
||||
);
|
||||
Assert.ok(
|
||||
JSON.stringify(qrCodes) === qrCodeSnapshot,
|
||||
"Snapshot should match generated QR codes"
|
||||
);
|
||||
|
||||
for (const account of accounts) {
|
||||
MailServices.accounts.removeAccount(account, false);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,6 +4,17 @@
|
|||
|
||||
import { MailServices } from "resource:///modules/MailServices.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
default: "resource:///modules/qrcode.mjs",
|
||||
});
|
||||
ChromeUtils.defineLazyGetter(lazy, "console", () =>
|
||||
console.createInstance({
|
||||
prefix: "QRExport",
|
||||
maxLogLevel: "Warn",
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Incoming server types supported by the Android app.
|
||||
*/
|
||||
|
@ -18,6 +29,37 @@ const UNSUPPORTED_AUTH_METHODS = new Set([
|
|||
Ci.nsMsgAuthMethod.External,
|
||||
]);
|
||||
|
||||
// QR Code data content constants
|
||||
// These should match with https://github.com/thunderbird/thunderbird-android/blob/4ebcca8d893bc57df9ef293fdf7d6d8fe46becad/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/AccountData.kt#L48-L105
|
||||
|
||||
const QR_DATA_FORMAT_VERSION = 1;
|
||||
|
||||
const INCOMING_PROTOCOL = new Map([
|
||||
["imap", 0],
|
||||
["pop3", 1],
|
||||
]);
|
||||
const SOCKET_TYPES = new Map([
|
||||
[Ci.nsMsgSocketType.plain, 0],
|
||||
[Ci.nsMsgSocketType.alwaysSTARTTLS, 2],
|
||||
[Ci.nsMsgSocketType.SSL, 3],
|
||||
]);
|
||||
const AUTH_METHODS = new Map([
|
||||
[Ci.nsMsgAuthMethod.none, 0],
|
||||
// [Ci.nsMsgAuthMethod.old, "old"],
|
||||
[Ci.nsMsgAuthMethod.passwordCleartext, 1],
|
||||
[Ci.nsMsgAuthMethod.passwordEncrypted, 2],
|
||||
[Ci.nsMsgAuthMethod.GSSAPI, 3],
|
||||
[Ci.nsMsgAuthMethod.NTLM, 4],
|
||||
[Ci.nsMsgAuthMethod.External, 5],
|
||||
// [Ci.nsMsgAuthMethod.secure, "secure"],
|
||||
// [Ci.nsMsgAuthMethod.anything, "anything"],
|
||||
[Ci.nsMsgAuthMethod.OAuth2, 6],
|
||||
]);
|
||||
const OUTGOING_PROTOCOL_SMTP = 0;
|
||||
|
||||
const ACCOUNTS_PER_QR_CODE = 3;
|
||||
const MAX_CHUNK_LENGTH = 800;
|
||||
|
||||
export const QRExport = {
|
||||
/**
|
||||
* Eligible accounts fulfill:
|
||||
|
@ -46,8 +88,8 @@ export const QRExport = {
|
|||
if (!/^[\x00-\x7F]+$/.test(identity.email)) {
|
||||
return false;
|
||||
}
|
||||
const outgoingServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.key == identity.smtpServerKey
|
||||
const outgoingServer = MailServices.outgoingServer.getServerByKey(
|
||||
identity.smtpServerKey
|
||||
);
|
||||
return (
|
||||
outgoingServer instanceof Ci.nsISmtpServer &&
|
||||
|
@ -55,4 +97,120 @@ export const QRExport = {
|
|||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate the QR codes to export the selected accounts. Splits the accounts
|
||||
* into chunks of 3 accounts per QR code.
|
||||
*
|
||||
* @param {string[]} accountKeys - Accounts that should be exported.
|
||||
* @param {boolean} includePasswords - If passwords should be included in the export data.
|
||||
* @returns {string[]} Returns an array of SVG URLs, each representing a QR code.
|
||||
*/
|
||||
getQRCodes(accountKeys, includePasswords) {
|
||||
const accounts = accountKeys.map(key =>
|
||||
this.getAccountData(key, includePasswords)
|
||||
);
|
||||
// For practical purposes each QR code should hold no more than 1000
|
||||
// characters, optimally 800 characters maximum.
|
||||
const chunkCount = Math.ceil(accounts.length / ACCOUNTS_PER_QR_CODE);
|
||||
const qrCodes = [];
|
||||
for (let i = 0; i < chunkCount; i++) {
|
||||
const chunkOffset = i * ACCOUNTS_PER_QR_CODE;
|
||||
const chunk = accounts
|
||||
.slice(chunkOffset, chunkOffset + ACCOUNTS_PER_QR_CODE)
|
||||
.flat();
|
||||
const chunkPart = i + 1; // 1-indexed
|
||||
const chunkData = this.getQRData(chunk, chunkPart, chunkCount);
|
||||
const serializedChunk = JSON.stringify(chunkData);
|
||||
if (serializedChunk.length > MAX_CHUNK_LENGTH) {
|
||||
lazy.console.warn(
|
||||
"Data for QR code",
|
||||
chunkPart,
|
||||
"is longer than expected, result might be hard to read"
|
||||
);
|
||||
}
|
||||
qrCodes.push(this.renderQR(serializedChunk));
|
||||
}
|
||||
return qrCodes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate the data for a QR code with a chunk of account data.
|
||||
*
|
||||
* @param {Array} data - Account data contained in this chunk.
|
||||
* @param {number} part - 1-based index of this chunk.
|
||||
* @param {number} count - Total number of QR codes.
|
||||
* @returns {Array}
|
||||
*/
|
||||
getQRData(data, part, count) {
|
||||
return [QR_DATA_FORMAT_VERSION, [part, count], ...data];
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a minimal account description for serialization to JSON.
|
||||
*
|
||||
* @param {string} accountKey - Key of the account to get the data for.
|
||||
* @param {boolean} includePasswords - If the result should include passwords.
|
||||
* @returns {Array} Array structure with account data to serialize to JSON.
|
||||
* @see https://docs.google.com/document/d/1siSwPzNPkwq4BL5G3z2K4zzRJuL9N9zbPodMOggXbdA/edit for format
|
||||
*/
|
||||
getAccountData(accountKey, includePasswords) {
|
||||
const account = MailServices.accounts.getAccount(accountKey);
|
||||
const incomingServer = account.incomingServer;
|
||||
const defaultSmtpServerKey = account.defaultIdentity.smtpServerKey;
|
||||
const outgoingServer =
|
||||
MailServices.outgoingServer.getServerByKey(defaultSmtpServerKey);
|
||||
outgoingServer.QueryInterface(Ci.nsISmtpServer);
|
||||
const identites = account.identities.filter(
|
||||
identity =>
|
||||
!identity.smtpServerKey ||
|
||||
identity.smtpServerKey == defaultSmtpServerKey
|
||||
);
|
||||
return [
|
||||
[
|
||||
INCOMING_PROTOCOL.get(incomingServer.type),
|
||||
incomingServer.hostName,
|
||||
incomingServer.port,
|
||||
SOCKET_TYPES.get(incomingServer.socketType),
|
||||
AUTH_METHODS.get(incomingServer.authMethod),
|
||||
incomingServer.username,
|
||||
incomingServer.prettyName,
|
||||
(includePasswords &&
|
||||
!incomingServer.passwordPromptRequired &&
|
||||
incomingServer.password) ||
|
||||
"",
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
OUTGOING_PROTOCOL_SMTP,
|
||||
outgoingServer.hostname,
|
||||
outgoingServer.port,
|
||||
SOCKET_TYPES.get(outgoingServer.socketType),
|
||||
AUTH_METHODS.get(outgoingServer.authMethod),
|
||||
outgoingServer.username,
|
||||
(includePasswords &&
|
||||
(outgoingServer.password ||
|
||||
outgoingServer.wrappedJSObject._getPasswordWithoutUI())) ||
|
||||
"",
|
||||
],
|
||||
...identites.map(identity => [identity.email, identity.fullName]),
|
||||
],
|
||||
],
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the given data into a QR code with L error correction.
|
||||
*
|
||||
* @param {string} data - Data to encode in the QR code.
|
||||
* @returns {string} QR code rendered as SVG URI.
|
||||
*/
|
||||
renderQR(data) {
|
||||
const qrOptions = {
|
||||
errorCorrectionLevel: lazy.default.ErrorCorrectionLevel.L,
|
||||
};
|
||||
const matrix = lazy.default.generate(data, qrOptions);
|
||||
return lazy.default.render("svg-uri", matrix);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,6 +7,10 @@ DIRS += [
|
|||
"asn1js",
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"qrcode/qrcode.mjs",
|
||||
]
|
||||
|
||||
if CONFIG["TB_LIBOTR_PREBUILT"]:
|
||||
DEFINES["TB_LIBOTR_PREBUILT"] = CONFIG["TB_LIBOTR_PREBUILT"]
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2020, Dan Jackson
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
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 HOLDER 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,120 @@
|
|||
# QR Code JS
|
||||
|
||||
Javascript QR Code generator. Derived from my C version: [qrcode](https://github.com/danielgjackson/qrcode).
|
||||
|
||||
|
||||
## Demo Site
|
||||
|
||||
Generate your own SVG QR Code:
|
||||
|
||||
* [danielgjackson.github.io/qrcodejs](https://danielgjackson.github.io/qrcodejs)
|
||||
|
||||
|
||||
## QR Codes in you terminal
|
||||
|
||||
If you have [Deno](https://deno.land/) installed, you can generate a QR Code in your terminal:
|
||||
|
||||
```bash
|
||||
deno run https://danielgjackson.github.io/qrcodejs/qrcli.mjs 'Hello, World!'
|
||||
```
|
||||
|
||||
...or write out a QR Code to a file:
|
||||
|
||||
```bash
|
||||
deno run --allow-write https://danielgjackson.github.io/qrcodejs/qrcli.mjs --output:svg --file hello.svg 'Hello, World!'
|
||||
```
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
### Example usage
|
||||
|
||||
Install (if using `npm`):
|
||||
|
||||
```bash
|
||||
npm i -S https://github.com/danielgjackson/qrcodejs
|
||||
```
|
||||
|
||||
<!--
|
||||
|
||||
Quick test (also works from a non-module):
|
||||
|
||||
```javascript
|
||||
(async() => {
|
||||
const { default: QrCode } = await import('qrcodejs');
|
||||
console.log(QrCode.render('medium', QrCode.generate('Hello, World!')));
|
||||
})();
|
||||
```
|
||||
|
||||
-->
|
||||
|
||||
Example usage from an ECMAScript module (`.mjs` file):
|
||||
|
||||
```javascript
|
||||
import QrCode from 'qrcodejs';
|
||||
|
||||
const data = 'Hello, World!';
|
||||
const matrix = QrCode.generate(data);
|
||||
const text = QrCode.render('medium', matrix);
|
||||
console.log(text);
|
||||
```
|
||||
|
||||
### Example web page usage
|
||||
|
||||
Example usage in a web page:
|
||||
|
||||
```html
|
||||
<img>
|
||||
<script type="module">
|
||||
import QrCode from 'https://danielgjackson.github.io/qrcodejs/qrcode.mjs';
|
||||
|
||||
const data = 'Hello, World!';
|
||||
const matrix = QrCode.generate(data);
|
||||
const uri = QrCode.render('svg-uri', matrix);
|
||||
document.querySelector('img').src = uri;
|
||||
</script>
|
||||
```
|
||||
|
||||
### Browser without a server
|
||||
|
||||
If you would like to use this directly as part of a browser-based app over the `file:` protocol (which disallows modules), you can easily convert this to a non-module `.js` file:
|
||||
|
||||
* Download [`qrcode.mjs`](https://raw.githubusercontent.com/danielgjackson/qrcodejs/master/qrcode.mjs) renamed as `qrcode.js`.
|
||||
* Remove the last line from the file (`export default QrCode`).
|
||||
* Ensure there is no `type="module"` attribute in your `<script src="qrcode.js"></script>` tag.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### `QrCode.generate(data, options)`
|
||||
|
||||
* `data` - the text to encode in the QR Code.
|
||||
|
||||
* `options` - the configuration object for the QR Code (optional). Options include `errorCorrectionLevel` (0-3), `optimizeEcc` (boolean flag, default `true`, to maximize the error-correction level within the chosen output size), `minVersion`/`maxVersion` (1-40), `maskPattern` (0-7). Hints for the rendering stage are `invert` (boolean flag to invert the code, not as widely supported), and `quiet` (the size, in modules, of the quiet area around the code).
|
||||
|
||||
Returns a *matrix* that can be passed to the `render()` function.
|
||||
|
||||
|
||||
### `QrCode.render(mode, matrix, options)`
|
||||
|
||||
* `mode` - the rendering mode, one of:
|
||||
|
||||
* `large` - Generate block-character text, each module takes 2x1 character cells.
|
||||
* `medium` - Generate block-character text, fitting 1x2 modules in each character cell.
|
||||
* `compact` - Generate block-character text, fitting 2x2 modules in each character cell.
|
||||
* `svg` - Generate the contents for a scalable vector graphics file (`.svg`).
|
||||
* `bmp` - Generate the contents for a bitmap file (`.bmp`).
|
||||
* `svg-uri` - Generate a `data:` URI for an SVG file
|
||||
* `bmp-uri` - Generate a `data:` URI for a BMP file.
|
||||
|
||||
The `-uri` modes can be, for example, directly used as the `src` for an `<img>` tag, or `url()` image in CSS.
|
||||
|
||||
* `matrix` - the matrix to draw, as returned by the `generate()` function.
|
||||
|
||||
* `options` - the configuration object (optional), depends on the chosen rendering `mode`:
|
||||
|
||||
* `svg` / `svg-uri`: `moduleSize` the unit dimensions of each module, `white` (boolean) output the non-set modules (otherwise will be transparent background), `moduleRound` proportion of how rounded the modules are, `finderRound` to hide the standard finder modules and instead output a shape with the specified roundness, `alignmentRound` to hide the standard alignment modules and instead output a shape with the specified roundness.
|
||||
|
||||
* `bmp` / `bmp-uri`: `scale` for the size of a module, `alpha` (boolean) to use a transparent background, `width`/`height` can set a specific image size (rather than scaling the matrix dimensions).
|
||||
|
||||
Returns the text or binary output from the chosen `mode`.
|
|
@ -0,0 +1,41 @@
|
|||
# All fields are mandatory unless otherwise noted
|
||||
|
||||
schema: 1
|
||||
# Version of this schema
|
||||
|
||||
bugzilla:
|
||||
# Bugzilla product and component for this directory and subdirectories.
|
||||
product: Thunderbird
|
||||
component: Account Manager
|
||||
|
||||
origin:
|
||||
name: qrcodejs
|
||||
description: JavaScript QR Code Generator
|
||||
url: https://danielgjackson.github.io/qrcodejs
|
||||
|
||||
release: 86770ec12f0f9abee8728fc9018ab7bd0949f4bc
|
||||
# Human-readable identifier for this version/release
|
||||
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
||||
|
||||
revision: 86770ec12f0f9abee8728fc9018ab7bd0949f4bc
|
||||
# Revision to pull in
|
||||
# Must be a long or short commit SHA (long preferred)
|
||||
|
||||
license: BSD-2-Clause
|
||||
|
||||
vendoring:
|
||||
# Information needed to update the library automatically.
|
||||
|
||||
url: https://github.com/danielgjackson/qrcodejs
|
||||
source-hosting: github
|
||||
|
||||
skip-vendoring-steps:
|
||||
- hg-add
|
||||
- spurious-check
|
||||
- update-moz-build
|
||||
|
||||
exclude:
|
||||
- '.*'
|
||||
- '.vscode/settings.json'
|
||||
- qrcli.mjs
|
||||
- index.html
|
|
@ -0,0 +1,61 @@
|
|||
# Outline-only QR Code
|
||||
|
||||
For a vector QR code that only uses stroke lines (no fill), replace the `<defs>` section of your *.svg* file with:
|
||||
|
||||
```svg
|
||||
<style>
|
||||
line, polyline, path, rect {
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 0.1;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
|
||||
<!-- Filled data modules -->
|
||||
<g id="b">
|
||||
<!-- Multiply cross -->
|
||||
<line x1="-0.25" y1="-0.25" x2="0.25" y2="0.25" />
|
||||
<line x1="0.25" y1="-0.25" x2="-0.25" y2="0.25" />
|
||||
<!-- Addition cross -->
|
||||
<line x1="-0.354" y1="0" x2="0.354" y2="0" />
|
||||
<line x1="0" y1="-0.354" x2="0" y2="0.354" />
|
||||
</g>
|
||||
|
||||
<!-- Filled finder and alignment modules -->
|
||||
<g id="bb">
|
||||
<!-- Zig-zag fill (not used) -->
|
||||
<!--
|
||||
<polyline points="-0.5,-0.5 -0.25,-0.5 -0.5,-0.25 -0.5,0 0,-0.5 0.25,-0.5 -0.5,0.25 -0.5,0.5 0.5,-0.5 0.5,-0.25 -0.25,0.5 0,0.5 0.5,0 0.5,0.25 0.25,0.5 0.5,0.5" stroke="currentColor" stroke-width="0.25" stroke-linecap="round" fill="none" />
|
||||
-->
|
||||
|
||||
<!-- Forward slash hatch -->
|
||||
<line x1="-0.25" y1="-0.5" x2="-0.5" y2="-0.25" />
|
||||
<line x1="0" y1="-0.5" x2="-0.5" y2="0" />
|
||||
<line x1="0.25" y1="-0.5" x2="-0.5" y2="0.25" />
|
||||
<line x1="0.5" y1="-0.5" x2="-0.5" y2="0.5" />
|
||||
<line x1="0.5" y1="-0.25" x2="-0.25" y2="0.5" />
|
||||
<line x1="0.5" y1="0" x2="0" y2="0.5" />
|
||||
<line x1="0.5" y1="0.25" x2="0.25" y2="0.5" />
|
||||
|
||||
<!-- Backward slash hatch -->
|
||||
<line x1="0.25" y1="-0.5" x2="0.5" y2="-0.25" />
|
||||
<line x1="0" y1="-0.5" x2="0.5" y2="0" />
|
||||
<line x1="-0.25" y1="-0.5" x2="0.5" y2="0.25" />
|
||||
<line x1="-0.5" y1="-0.5" x2="0.5" y2="0.5" />
|
||||
<line x1="-0.5" y1="-0.25" x2="0.25" y2="0.5" />
|
||||
<line x1="-0.5" y1="0" x2="0" y2="0.5" />
|
||||
<line x1="-0.5" y1="0.25" x2="-0.25" y2="0.5" />
|
||||
</g>
|
||||
|
||||
<!-- Use the modules for finder/alignment -->
|
||||
<use id="f" xlink:href="#bb" />
|
||||
<use id="a" xlink:href="#bb" />
|
||||
|
||||
<!-- Do not use a particular shape for finder/alignment patterns -->
|
||||
<path id="fc" d="" visibility="hidden" />
|
||||
<path id="ac" d="" visibility="hidden" />
|
||||
|
||||
</defs>
|
||||
```
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "qrcodejs",
|
||||
"version": "0.0.0",
|
||||
"description": "Javascript QR Code generator.",
|
||||
"main": "qrcode.mjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node qrcli.mjs",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/danielgjackson/qrcodejs.git"
|
||||
},
|
||||
"keywords": [
|
||||
"QR",
|
||||
"Code",
|
||||
"javascript"
|
||||
],
|
||||
"author": "Dan Jackson",
|
||||
"license": "BSD-2-Clause",
|
||||
"bugs": {
|
||||
"url": "https://github.com/danielgjackson/qrcodejs/issues"
|
||||
},
|
||||
"homepage": "https://github.com/danielgjackson/qrcodejs#readme"
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -14,6 +14,7 @@ comm/third_party/libgpg-error
|
|||
comm/third_party/libotr
|
||||
comm/third_party/niwcompat
|
||||
comm/third_party/python
|
||||
comm/third_party/qrcode
|
||||
comm/third_party/rnp
|
||||
comm/third_party/rust
|
||||
comm/third_party/zlib
|
||||
|
|
Загрузка…
Ссылка в новой задаче